Compare commits

...

29 Commits

Author SHA1 Message Date
Kiril Kovachev
6c1e0887b6 Create command for pruning excess profile picture files 2024-10-19 18:22:51 +01:00
Kiril Kovachev
477c61c4b6 Ensure PFP is not empty before uploading 2024-10-19 18:16:10 +01:00
Kiril Kovachev
197a5ab242 Expand search capabilities (WIP) 2024-10-19 18:12:43 +01:00
Kiril Kovachev
5d2cf0b280 Improve jinja2 template formatting in search.html 2024-10-19 17:23:11 +01:00
Kiril Kovachev
6a6a210d90 Add link to user's own page on navbar 2024-10-19 17:14:49 +01:00
Kiril Kovachev
003e1ae635 Add profile pictures 2024-10-19 17:13:31 +01:00
Kiril Kovachev
8148976913 Add string for user page 2024-10-18 16:36:20 +01:00
Kiril Kovachev
9eeb8bd873 Add user page stub 2024-10-18 16:36:03 +01:00
Kiril Kovachev
826d240489 Fix crash caused by unsert user ID in session 2024-10-18 16:29:16 +01:00
Kiril Kovachev
242fcfb823 Add GraphQL schema stub 2024-10-18 16:27:36 +01:00
Kiril Kovachev
3430bd2392 Wrap kanji entry in a Japanese-language div to ensure correct formatting 2024-10-16 16:08:01 +01:00
Kiril Kovachev
c66c097101 Specify the document language based on the session's language setting 2024-10-16 15:45:55 +01:00
Kiril Kovachev
49bc51d1e0 Always use UTF-8 encoding 2024-10-16 15:43:11 +01:00
Kiril Kovachev
ae7501bb2e Format non-okurigana part of kanji readings in bold 2024-10-16 15:34:03 +01:00
Kiril Kovachev
e6be4107f5 Do not show kanji reading types where no readings of that type exist 2024-10-16 15:21:16 +01:00
Kiril Kovachev
215e2618f7 Better separate and style kanji reading sections 2024-10-16 15:17:32 +01:00
Kiril Kovachev
1c777aa253 Use reading types as 'bullet points' for kanji display 2024-10-16 14:56:05 +01:00
Kiril Kovachev
ddf1d1933c Use more descriptive format to distinguish radical, stroke count, level etc. 2024-10-16 14:53:26 +01:00
Kiril Kovachev
5e8d64544b Add ability to fetch kanji from database 2024-10-16 14:52:47 +01:00
Kiril Kovachev
5e4f73d057 Complete basic forum functionality 2024-10-16 13:19:56 +01:00
Kiril Kovachev
2f8ab9ac96 Save user settings in database for cross-browser fetching 2024-10-16 12:35:06 +01:00
Kiril Kovachev
1ffbc1b6f0 Fix about-para string name to about_para 2024-10-16 12:34:27 +01:00
Kiril Kovachev
ed1f183a9c Make settings usable by logged-out users 2024-10-16 12:18:15 +01:00
Kiril Kovachev
9d3ce85cb4 Add data download 2024-10-16 12:09:03 +01:00
Kiril Kovachev
2ac903553c Add data download view (actual download WIP) 2024-10-16 11:56:44 +01:00
Kiril Kovachev
11689fb627 Use middle dot instead of hyphen-minus in page title 2024-10-16 11:55:18 +01:00
Kiril Kovachev
2348d7c425 Fix set-string command missing parameter 2024-10-16 11:52:31 +01:00
Kiril Kovachev
49d592bfca Add string renaming CLI 2024-10-16 11:37:52 +01:00
Kiril Kovachev
cbb334c7ea Rename 'about-para' string to 'about_para' 2024-10-16 11:37:20 +01:00
23 changed files with 48022 additions and 92 deletions

View File

@ -1,16 +1,24 @@
import json
import os
from flask import Flask, redirect, render_template, g, request, session, url_for
import uuid
from flask import Flask, redirect, render_template, g, request, send_file, session, url_for
from pathlib import Path
from kanken_online.api import get_kanji_by_character
from kanken_online.database import get_database
from .auth import login_required
DATABASE_NAME = "kanken_online.sqlite"
JITEN_DB_NAME = "kanken.db"
PFP_DIRECTORY_NAME = "pfp"
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
SECRET_KEY="dev",
DATABASE=str(Path(app.instance_path) / DATABASE_NAME)
DATABASE=str(Path(app.instance_path) / DATABASE_NAME),
JITEN_DB=str(Path(app.instance_path) / JITEN_DB_NAME),
PFP_STORE=str(Path(app.instance_path) / PFP_DIRECTORY_NAME)
)
if test_config is None:
@ -33,43 +41,92 @@ def create_app(test_config=None):
def about_page():
return render_template("about.html")
def update_settings(form):
def update_settings(form, files):
db = get_database()
if "user_id" in session:
# Set values in the database
settings = db.execute("SELECT * FROM user_settings WHERE user_id = ?",
(session["user_id"],)
).fetchone()
if settings:
db.execute("UPDATE user_settings SET lang = ?, theme = ? WHERE user_id = ?",
(form["language"], form["theme"], session["user_id"]))
else:
db.execute("INSERT INTO user_settings (user_id, lang, theme) VALUES (?, ?, ?)",
(session["user_id"], form["language"], form["theme"]))
if "pfp" in files and files["pfp"].filename != "":
stored_filename = str(uuid.uuid4())
pfp = files["pfp"]
pfp.save(os.path.join(app.config["PFP_STORE"], stored_filename))
db.execute("UPDATE user_settings SET pfp_filename = ?", (stored_filename,))
db.commit()
# Set values directly in the session
session["language"] = form["language"]
session["theme"] = form["theme"]
return redirect("/options")
@app.route("/options", methods=["GET", "POST"])
@login_required
def options():
if request.method == "GET":
return render_template("options.html")
else:
return update_settings(request.form)
return update_settings(request.form, request.files)
@app.get("/user/<int:user_id>")
def user_page(user_id: int):
db = get_database()
(username,pfp_filename) = db.execute("SELECT username, pfp_filename FROM user, user_settings WHERE user.id = ? AND user_settings.user_id = ?", (user_id,user_id)).fetchone()
return render_template("user_page.html", username=username, pfp_filename=os.path.join(app.config["PFP_STORE"], pfp_filename))
@app.get("/me")
@login_required
def my_page():
return user_page(session["user_id"])
def format_reading(reading: str) -> str:
"""Apply bold to the part of the reading which the kanji represents; for kun, this can be
e.g. : えら- --> <b>えら</b>. For reading strings which don't have any "-" character in them,
one is added to the end and the entire reading is emboldened.
Example: : かん --> <b>かん</b>
"""
if "-" not in reading:
reading += "-"
okurigana_position = reading.index("-")
emboldened_part = reading[:okurigana_position]
return f"<b>{emboldened_part}</b>{reading[okurigana_position+1:]}"
@app.route("/kanji/<kanji>")
def kanji_page(kanji: str):
# TODO use database to get kanji
kanji_obj = get_kanji_by_character(kanji)
class Kanji():
pass
# Highly tentative testing data
kanji = Kanji()
kanji.character = ""
kanji.is_joyo = "joyo kanji"
kanji.level = "pre-2"
kanji.strokes = 11
kanji.radical = ""
kanji.added_strokes = 0
kanji.goon = ""
kanji.kanon = ""
kanji.toon = ""
kanji.soon = ""
kanji.kanyoon = ""
kanji.kun = "あさ, しびれる"
kanji.meanings = "①あさ。クワ科の一年草。また、あさ類の総称。「亜麻」「乱麻」 ②しびれる。しびれ。「麻酔」「麻痺(マヒ)」類痲(マ)"
kanji.glyph_origin = "会意。广(げん)(いえ)と、𣏟(はい)(あさ)とから成り、屋下であさの繊維をはぎとる、ひいて「あさ」の意を表す。"
out = Kanji()
out.character = kanji_obj.character
out.is_joyo = "常用" if kanji_obj.level not in ["1", "準1"] else "表外"
out.level = kanji_obj.level
out.strokes = kanji_obj.stroke_count
out.radical = kanji_obj.radical
out.added_strokes = kanji_obj.radical_added_stroke_count
out.goon = [format_reading(obj.reading) for obj in kanji_obj.goon]
out.kanon = [format_reading(obj.reading) for obj in kanji_obj.kanon]
out.toon = [format_reading(obj.reading) for obj in kanji_obj.toon]
out.soon = [format_reading(obj.reading) for obj in kanji_obj.soon]
out.kanyoon = [format_reading(obj.reading) for obj in kanji_obj.kanyoon]
out.kun = [format_reading(obj.reading) for obj in kanji_obj.kun]
out.meanings = kanji_obj.meanings
out.glyph_origin = kanji_obj.glyph_origin
return render_template("kanji.html", kanji=kanji)
return render_template("kanji.html", kanji=out)
@app.route("/kotoba/<kotoba>")
def kotoba_page(kotoba: str):
@ -86,6 +143,7 @@ def create_app(test_config=None):
app.register_blueprint(indices.blueprint)
from . import lang
lang.add_translation_commands(app)
@app.route("/translations", methods=["GET", "POST"])
def strings_translation():
@ -109,6 +167,14 @@ def create_app(test_config=None):
lang.update_languages()
return redirect("/translations")
@app.get(app.config["PFP_STORE"] + "/<img>")
def get_pfp(img: str):
return send_file(os.path.join(app.config["PFP_STORE"], img))
@app.route("/data")
def data_page():
return render_template("data.html")
# def use_english(text_id: str):
# return lang.localize(text_id, lang.JAPANESE)
app.jinja_env.globals.update(localize=lang.localize)

View File

@ -4,7 +4,7 @@ from flask import Blueprint, jsonify
import jsonpickle
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from .database import get_database
from .database import get_database, get_jiten_db
blueprint = Blueprint("api", __name__, url_prefix="/api")
@ -125,24 +125,29 @@ class Kanji(Base):
@blueprint.route("/id/<int:kanji_id>")
def kanji_by_id(kanji_id: int):
engine = create_engine("sqlite:///kanken_online/kanken.db")
with Session(engine) as session:
query = select(Kanji).where(Kanji.id == kanji_id)
item = session.execute(query).first()
if item is None:
return "Invalid ID", 404
kanji = item[0]
return kanji.to_json()
session = get_jiten_db()
query = select(Kanji).where(Kanji.id == kanji_id)
item = session.execute(query).first()
if item is None:
return "Invalid ID", 404
kanji = item[0]
return kanji.to_json()
def get_kanji_by_character(kanji: str) -> Kanji:
session = get_jiten_db()
query = select(Kanji).where(Kanji.character == kanji)
item = session.execute(query).first()
if item is None:
return "Invalid kanji", 404
kanji_obj: Kanji = item[0]
return kanji_obj
@blueprint.route("/kanji/<kanji>")
def kanji_by_character(kanji: str):
engine = create_engine("sqlite:///kanken_online/kanken.db")
with Session(engine) as session:
query = select(Kanji).where(Kanji.character == kanji)
item = session.execute(query).first()
if item is None:
return "Invalid kanji", 404
kanji_obj = item[0]
return kanji_obj.to_json()
kanji_obj = get_kanji_by_character(kanji)
return kanji_obj.to_json()
def get_kanji_generic():
pass

View File

@ -53,6 +53,14 @@ def login():
if error is None:
session.clear()
session["user_id"] = user["id"]
settings = db.execute("SELECT * FROM user_settings WHERE user_id = ?",
(session["user_id"],)
).fetchone()
if settings:
session["language"] = settings["lang"]
session["theme"] = settings["theme"]
return redirect(url_for("index"))
flash(error)

View File

@ -2,6 +2,9 @@ import sqlite3
from typing import IO
import click
from flask import Flask, current_app, g
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
def get_database():
if "db" not in g:
@ -13,6 +16,14 @@ def get_database():
return g.db
def get_jiten_db():
if "jiten_db" not in g:
engine = create_engine(f"sqlite:///{current_app.config['JITEN_DB']}")
g.jiten_db = Session(engine)
print("Session is", g.jiten_db)
return g.jiten_db
def initialize_database():
db = get_database()
@ -35,6 +46,18 @@ def init_db_command():
else:
click.echo("Aborted.")
@click.command("prune-pfps")
def prune_pfps_command():
"""Removes excess profile pictures that aren't used in the database."""
import os
db = get_database()
used_pfps = {row[0] for row in db.execute("SELECT pfp_filename FROM user_settings").fetchall()}
stored_pfps = set(os.listdir(current_app.config["PFP_STORE"]))
unused_pfps = stored_pfps - used_pfps
for pfp in unused_pfps:
os.remove(os.path.join(current_app.config["PFP_STORE"], pfp))
def initialize_app(app: Flask):
app.teardown_appcontext(close_database)
app.cli.add_command(init_db_command)
app.cli.add_command(init_db_command)
app.cli.add_command(prune_pfps_command)

View File

@ -1,8 +1,37 @@
from flask import Blueprint, render_template
import datetime
from flask import Blueprint, flash, redirect, render_template, request, session, url_for
from kanken_online.auth import login_required
from kanken_online.database import get_database
blueprint = Blueprint("forum", __name__, url_prefix="/forum")
@blueprint.route("/")
def index():
return render_template("forum/index.html")
# Get forum posts
db = get_database()
posts = db.execute("SELECT post.*, user.username FROM post, user WHERE user.id = post.author_id ORDER BY post.created DESC LIMIT 50").fetchall()
# Pass to the template to render
return render_template("forum/index.html", posts=posts)
@blueprint.route("/new", methods=["GET", "POST"])
@login_required
def new_post():
if request.method == "GET":
return render_template("forum/new.html")
else:
title = request.form["title"]
body = request.form["body"]
author_id = session["user_id"]
created = datetime.datetime.now()
db = get_database()
db.execute("INSERT INTO post VALUES (NULL, ?, ?, ?, ?)",
(author_id, created, title, body))
db.commit()
flash("success_new_post")
return redirect(url_for("forum.index"))

View File

@ -0,0 +1,12 @@
schema {
kanji: Kanji,
kotoba: Kotoba
}
type Kanji {
}
type Kotoba {
}

View File

@ -1,6 +1,8 @@
import json
import os
from pathlib import Path
from flask import session
import click
from flask import Flask, session, current_app
LanguageMapping = dict[str, str]
@ -32,3 +34,46 @@ def localize(text_id: str, language: dict[str, str] = None) -> str:
preference = session.get("language", "ja")
language = LANGUAGES.get(preference)
return language[text_id]
@click.command("add-string")
@click.argument("string")
def add_string(string):
"""Add a new string to the translations."""
for path in Path("kanken_online/static/lang").glob("*.json"):
with open(path) as f:
data = json.load(f)
data[string] = ""
with open(path, mode="w") as f:
json.dump(data, f, ensure_ascii=False, indent=0)
@click.command("set-string")
@click.argument("lang")
@click.argument("string")
@click.argument("value")
def set_string(lang, string, value):
"""Set the value of a string for a particular language's translation file."""
path = Path("kanken_online/static/lang", f"{lang}.json")
with open(path) as f:
data = json.load(f)
data[string] = value
with open(path, mode="w") as f:
json.dump(data, f, ensure_ascii=False, indent=0)
@click.command("rename-string")
@click.argument("string")
@click.argument("new_name")
def rename_string(string, new_name):
"""Rename a string in the translations files."""
for path in Path("kanken_online/static/lang").glob("*.json"):
with open(path) as f:
data = json.load(f)
data[new_name] = data[string]
del data[string]
with open(path, mode="w") as f:
json.dump(data, f, ensure_ascii=False, indent=0)
def add_translation_commands(app: Flask):
app.cli.add_command(add_string)
app.cli.add_command(set_string)
app.cli.add_command(rename_string)

View File

@ -1,5 +1,6 @@
DROP TABLE IF EXISTS user;
-- DROP TABLE IF EXISTS post;
DROP TABLE IF EXISTS post;
DROP TABLE IF EXISTS user_settings;
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -15,3 +16,10 @@ CREATE TABLE post (
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id)
);
CREATE TABLE user_settings (
user_id INTEGER PRIMARY KEY AUTOINCREMENT REFERENCES user (id),
lang VARCHAR(3) NOT NULL, -- e.g. en/jp; may add support for other (possibly 3-long) codes later
theme VARCHAR(5) NOT NULL, -- "light" or "dark"
pfp_filename CHAR(36) -- 36-char UUID string
);

View File

@ -0,0 +1,10 @@
const kanjiTSVButton = document.getElementById("kanji-tsv-download-button");
const kotobaTSVButton = document.getElementById("kotoba-tsv-download-button");
const databaseButton = document.getElementById("database-download-button");
for (const button of [kanjiTSVButton, kotobaTSVButton, databaseButton]) {
button.addEventListener("click", () => {
const fileName = button.getAttribute("data-filename");
location.assign(`/static/download/${fileName}`);
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
ul#reading-list-list {
list-style-type: none;
}
#goon::before {
content: "呉";
}
#kanon::before {
content: "漢";
}
#kanyoon::before {
content: "慣";
}
#soon::before {
content: "宋";
}
#toon::before {
content: "唐";
}
#on::before {
content: "音";
}
#kun::before {
content: "訓";
}
ul.reading-list {
list-style-type: none;
display: inline-flex;
}
ul.reading-list > li::after {
content: " ・";
}
ul.reading-list > li:last-child::after {
content: none;
}

View File

@ -18,7 +18,6 @@
"incorrect_username": "Incorrect username.",
"incorrect_password": "Incorrect password.",
"about": "About KankenOnline",
"about-para": "KankenOnline is a website seeking to provide resources to pass the Kanji Kentei level 1 exam. You can search through the approximately 6,300 characters included in Kanken, as well as generate a number of study materials automatically. For example, you can generate a PDF-format exam resembling the Kanken level 1 exam, which has the goal of mirroring the real thing as closely as possible for your preparation. Additionally, a variety of information about kanji is provided, among which phonetic series, rimes/kanji phonology, radicals, inseparable kanji, character origins and etymologies.",
"indices": "Indices",
"radical_index": "Radical index",
"indivisible_index": "Indivisible kanji index",
@ -31,5 +30,18 @@
"en": "English",
"ja": "Japanese",
"add_string": "Add string",
"submit": "Submit"
"submit": "Submit",
"about_para": "KankenOnline is a website seeking to provide resources to pass the Kanji Kentei level 1 exam. You can search through the approximately 6,300 characters included in Kanken, as well as generate a number of study materials automatically. For example, you can generate a PDF-format exam resembling the Kanken level 1 exam, which has the goal of mirroring the real thing as closely as possible for your preparation. Additionally, a variety of information about kanji is provided, among which phonetic series, rimes/kanji phonology, radicals, inseparable kanji, character origins and etymologies.",
"data": "Data",
"kanji_tsv_download": "Download kanji TSV",
"kotoba_tsv_download": "Download kotoba TSV",
"ankipkg_download": "Download Anki package",
"database_download": "Download database",
"new_post": "New post",
"submit_post": "Submit post",
"title": "Title",
"post_body": "Post body",
"success_new_post": "Successfully created new post",
"user_introduction": "User",
"pfp": "Profile picture"
}

View File

@ -18,7 +18,6 @@
"incorrect_username": "ユーザー名が違います",
"incorrect_password": "パスワードが違います",
"about": "漢検オンラインとは",
"about-para": "漢検オンラインとは、漢検一級合格を目当てにした資料を供用しているサイトです。ここで漢検のやく字を検索でき、いろんな勉強材を自動的に作ることができます。たとえば、漢検一級模様の試験PDFを作ることができて、本物の漢検試験に大抵該当することが目的です。さらに、色々漢字についての情報を取り集めております。諧声域かいせいいき・音韻学・部首・不可分漢字・字源・語源などがその内です。",
"indices": "索引",
"radical_index": "部首索引",
"indivisible_index": "不可分漢字索引",
@ -31,5 +30,18 @@
"en": "英語",
"ja": "日本語",
"add_string": "翻訳語を追加",
"submit": "投入"
"submit": "投入",
"about_para": "漢検オンラインとは、漢検一級合格を目当てにした資料を供用しているサイトです。ここで漢検のやく字を検索でき、いろんな勉強材を自動的に作ることができます。たとえば、漢検一級模様の試験PDFを作ることができて、本物の漢検試験に大抵該当することが目的です。さらに、色々漢字についての情報を取り集めております。諧声域かいせいいき・音韻学・部首・不可分漢字・字源・語源などがその内です。",
"data": "データ",
"kanji_tsv_download": "漢字TSVをダウンロード",
"kotoba_tsv_download": "言葉TSVをダウンロード",
"ankipkg_download": "APKGをダウンロード",
"database_download": "データーベースをダウンロード",
"new_post": "新しい投稿",
"submit_post": "投稿する",
"title": "話題",
"post_body": "投稿内容",
"success_new_post": "無事に新しい投稿を作りました",
"user_introduction": "利用者の",
"pfp": "利用者アイコン"
}

View File

@ -5,5 +5,5 @@
{% endblock %}
{% block content %}
{{ localize("about-para") }}
{{ localize("about_para") }}
{% endblock %}

View File

@ -1,8 +1,9 @@
<!DOCTYPE html>
<html>
<html lang="{{session['language'] or 'ja'}}">
<head>
<title>{% block title %}{% endblock %} - {{ localize("kanken_online") }}</title>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %} ・ {{ localize("kanken_online") }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
{% if session %}
<link rel="stylesheet" href="{{ url_for('static', filename='dark_theme.css') if session['theme'] == 'dark' else url_for('static', filename='light_theme.css')}}">
@ -17,16 +18,18 @@
<h1><a href="/">{{ localize("kanken_online") }}</a></h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('options') }}">{{ localize("options") }}</a>
<li><a href="{{ url_for('my_page') }}">{{ g.user['username'] }}</a>
<li><a href="{{ url_for('auth.logout') }}">{{ localize("log_out") }}</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">{{ localize("register") }}</a>
<li><a href="{{ url_for('auth.login') }}">{{ localize("log_in") }}</a>
{% endif %}
<li><a href="{{ url_for('options') }}">{{ localize("options") }}</a>
<li><a href="{{ url_for('about_page') }}">{{ localize("about") }}</a></li>
<li><a href="{{ url_for('indices.indices_page') }}">{{ localize("indices") }}</a></li>
<li><a href="{{ url_for('search.search_page') }}">{{ localize("search") }}</a></li>
<li><a href="{{ url_for('forum.index') }}">{{ localize("forum") }}</a></li>
<li><a href="{{ url_for('data_page') }}">{{ localize("data") }}</a></li>
</ul>
</nav>
<section class="content">

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block scripts %}
<script src="/static/data.js" defer></script>
{% endblock %}
{% block header %}
<h1>{% block title %}{{ localize("data") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<button id="kanji-tsv-download-button" data-filename="kanji.tsv"> {{ localize("kanji_tsv_download") }}</button>
<button id="kotoba-tsv-download-button" data-filename="kotoba.tsv"> {{ localize("kotoba_tsv_download") }}</button>
<button id="ankipkg-download-button"> {{ localize("ankipkg_download") }}</button>
<button id="database-download-button" data-filename="kanken.db"> {{ localize("database_download") }}</button>
{% endblock %}

View File

@ -5,5 +5,19 @@
{% endblock %}
{% block content %}
...
<form action="{{ url_for('forum.new_post') }}">
<input id="new-post-button" type="submit" value="{{ localize('new_post') }}">
</form>
<hr>
<div id="forum-container">
{% for post in posts %}
<div class="post">
<h2 class="post-title">{{ post.title }}</h2>
<span class="post-author">{{ post.username }}</span>
<p>{{ post.body }} </p>
<br>
<span class="post-timestamp">{{ post.created }}</span>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("new_post") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<div id="new-post-area">
<form id="post" method="post">
<input type="text" name="title" id="post-title" placeholder="{{localize('title')}}" required>
<textarea id="post-body" name="body" placeholder="{{localize('post_body')}}" required></textarea>
<button type="submit"> {{ localize("submit_post") }}</button>
</form>
</div>
{% endblock %}

View File

@ -2,41 +2,45 @@
{% block scripts %}<script src="/static/kanji.js" defer></script>{%endblock %}
{% block styles %}
<link rel="stylesheet" href="{{ url_for('static', filename='kanji.css') }}">
{% endblock %}
{% block header %}
<h1>{% block title %}{{localize("kanji")}} - {{ kanji.character }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<div id="kanji-and-meaning">
<div id="kanji-box">
<div id="character-box">
<span id="kanji">{{ kanji.character }}</span>
<div id="entry" lang="ja">
<div id="kanji-and-meaning">
<div id="kanji-box">
<div id="character-box">
<span id="kanji">{{ kanji.character }}</span>
</div>
<div id="metadata-box">
<span id="joyo-class">{{ kanji.is_joyo }}</span>
<span id="level">{{ kanji.level }}級</span>
<span id="stroke-count">{{ kanji.strokes }}</span> (<span id="radical">{{ kanji.radical }}</span> + <span id="radical-added-strokes">{{ kanji.added_strokes }}</span>)
</div>
</div>
<div id="metadata-box">
<span id="joyo-class">{{ kanji.is_joyo }}</span>
<span id="level">{{ kanji.level }}</span>
<span id="stroke-count">{{ kanji.strokes }}</span>
<span id="radical">{{ kanji.radical }}</span>
<span id="radical-added-strokes">{{ kanji.added_strokes }}</span>
<div id="meaning-box">
<p id="meanings">{{ kanji.meanings }}</p>
</div>
</div>
<div id="meaning-box">
<p id="meanings">{{ kanji.meanings }}</p>
<div id="readings-and-glyph-origin">
<div id="reading-box">
<ul id="reading-list-list">
{% if kanji.goon %}<li id="goon"><ul class="reading-list">{% for reading in kanji.goon %}<li class="goon-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
{% if kanji.kanon %}<li id="kanon"><ul class="reading-list">{% for reading in kanji.kanon %}<li class="kanon-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
{% if kanji.kanyoon %}<li id="kanyoon"><ul class="reading-list">{% for reading in kanji.kanyoon %}<li class="kanyoon-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
{% if kanji.toon %}<li id="toon"><ul class="reading-list">{% for reading in kanji.toon %}<li class="toon-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
{% if kanji.soon %}<li id="soon"><ul class="reading-list">{% for reading in kanji.soon %}<li class="soon-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
{% if kanji.kun %}<li id="kun"><ul class="reading-list">{% for reading in kanji.kun %}<li class="kun-reading">{{reading|safe}}</li>{% endfor %}</ul></li>{% endif %}
</ul>
</div>
<div id="glyph-origin-box">
<p id="glyph-origin">{{ kanji.glyph_origin }}</p>
</div>
</div>
{% endblock %}
</div>
<div id="readings-and-glyph-origin">
<div id="reading-box">
<ul id="reading-list">
<li id="goon">{{ kanji.goon }}</li>
<li id="kanon">{{ kanji.kanon }}</li>
<li id="kanyoon">{{ kanji.kanyoon }}</li>
<li id="toon">{{ kanji.toon }}</li>
<li id="soon">{{ kanji.soon }}</li>
<li id="kun">{{ kanji.kun }}</li>
</ul>
</div>
<div id="glyph-origin-box">
<p id="glyph-origin">{{ kanji.glyph_origin }}</p>
</div>
</div>
{% endblock %}

View File

@ -5,7 +5,7 @@
{% endblock %}
{% block content %}
<form method="post">
<form method="post" enctype=multipart/form-data>
<label for="language-select">{{ localize("language") }}</label>
<select name="language" id="language-select">
<option value="ja">日本語</option>
@ -17,6 +17,10 @@
<option value="dark">{{ localize("dark_theme") }}</option>
<option value="light">{{ localize("light_theme") }}</option>
</select>
<label for="pfp">{{ localize("pfp") }}</label>
<input type="file" id="pfp" name="pfp">
<hr>
<button type="submit">Okay</button>
</form>

View File

@ -7,12 +7,40 @@
{% block content %}
<form method="get">
<label for="search_bar">{{ localize("search") }}</label>
<input type="text" id="search_bar" name="keywords" placeholder="{{ localize("search_placeholder") }}" value="{{value}}">
<label for="include_kanji">{{ localize("include_kanji") }}</label>
<input type="checkbox" id="include_kanji" checked>
<label for="include_kanji">{{ localize("include_kotoba") }}</label>
<input type="checkbox" id="include_kanji" checked>
</form>
<input type="text" id="search_bar" name="keywords" placeholder="{{ localize('search_placeholder') }}" value="{{ value }}" autofocus onfocus="this.select()">
<fieldset>
<label for="search_method_includes">Includes</label>
<input type="radio" id="search_method_includes" name="search_method" value="includes">
<label for="search_method_includes">Matches</label>
<input type="radio" id="search_method_matches" name="search_method" value="matches">
<label for="search_method_includes">Starts with</label>
<input type="radio" id="search_method_starts_with" name="search_method" value="starts_with">
<label for="search_method_includes">Ends with</label>
<input type="radio" id="search_method_ends_with" name="search_method" value="ends_with">
<label for="search_method_regex">Regular expression</label>
<input type="radio" id="search_method_regex" name="search_method" value="regex">
</fieldset>
<input type="text" id="onkun" name="onkun" placeholder="midashi onkun">
<input type="text" id="kanji_goon" name="goon" placeholder="goon">
<input type="text" id="kanji_kanon" name="kanon" placeholder="kanon">
<input type="text" id="kanji_soon" name="soon" placeholder="soon">
<input type="text" id="kanji_toon" name="toon" placeholder="toon">
<input type="text" id="kanji_kanyoon" name="kanyoon" placeholder="kanyoon">
<input type="text" id="kanji_kun" name="kun" placeholder="kun">
<input type="text" id="radical" name="radical" placeholder="radical">
<input type="text" id="stroke_min" name="stroke_min" placeholder="min stroke count">
<input type="text" id="stroke_max" name="stroke_max" placeholder="max stroke count">
<input type="text" id="level_min" name="level_min" placeholder="min level">
<input type="text" id="level_max" name="level_max" placeholder="max level">
<label for="kokuji">kokuji</label>
<select name="kokuji" id="kokuji">
<option value="include">include</option>
<option value="exclude">exclude</option>
</select>
</form>
{% block results %}
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("user_introduction") }} {{ username }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<img id="pfp" src="{{ pfp_filename }}" width=128 height=128>
<span id="username">{{ username }}</span>
{% endblock %}