Compare commits
29 Commits
9b932291d2
...
6c1e0887b6
Author | SHA1 | Date | |
---|---|---|---|
|
6c1e0887b6 | ||
|
477c61c4b6 | ||
|
197a5ab242 | ||
|
5d2cf0b280 | ||
|
6a6a210d90 | ||
|
003e1ae635 | ||
|
8148976913 | ||
|
9eeb8bd873 | ||
|
826d240489 | ||
|
242fcfb823 | ||
|
3430bd2392 | ||
|
c66c097101 | ||
|
49bc51d1e0 | ||
|
ae7501bb2e | ||
|
e6be4107f5 | ||
|
215e2618f7 | ||
|
1c777aa253 | ||
|
ddf1d1933c | ||
|
5e8d64544b | ||
|
5e4f73d057 | ||
|
2f8ab9ac96 | ||
|
1ffbc1b6f0 | ||
|
ed1f183a9c | ||
|
9d3ce85cb4 | ||
|
2ac903553c | ||
|
11689fb627 | ||
|
2348d7c425 | ||
|
49d592bfca | ||
|
cbb334c7ea |
@ -1,16 +1,24 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
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 pathlib import Path
|
||||||
|
|
||||||
|
from kanken_online.api import get_kanji_by_character
|
||||||
|
from kanken_online.database import get_database
|
||||||
from .auth import login_required
|
from .auth import login_required
|
||||||
|
|
||||||
|
|
||||||
DATABASE_NAME = "kanken_online.sqlite"
|
DATABASE_NAME = "kanken_online.sqlite"
|
||||||
|
JITEN_DB_NAME = "kanken.db"
|
||||||
|
PFP_DIRECTORY_NAME = "pfp"
|
||||||
def create_app(test_config=None):
|
def create_app(test_config=None):
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
app.config.from_mapping(
|
app.config.from_mapping(
|
||||||
SECRET_KEY="dev",
|
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:
|
if test_config is None:
|
||||||
@ -33,43 +41,92 @@ def create_app(test_config=None):
|
|||||||
def about_page():
|
def about_page():
|
||||||
return render_template("about.html")
|
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["language"] = form["language"]
|
||||||
session["theme"] = form["theme"]
|
session["theme"] = form["theme"]
|
||||||
|
|
||||||
return redirect("/options")
|
return redirect("/options")
|
||||||
|
|
||||||
@app.route("/options", methods=["GET", "POST"])
|
@app.route("/options", methods=["GET", "POST"])
|
||||||
@login_required
|
|
||||||
def options():
|
def options():
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
return render_template("options.html")
|
return render_template("options.html")
|
||||||
else:
|
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>")
|
@app.route("/kanji/<kanji>")
|
||||||
def kanji_page(kanji: str):
|
def kanji_page(kanji: str):
|
||||||
# TODO use database to get kanji
|
kanji_obj = get_kanji_by_character(kanji)
|
||||||
|
|
||||||
class Kanji():
|
class Kanji():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Highly tentative testing data
|
out = Kanji()
|
||||||
kanji = Kanji()
|
out.character = kanji_obj.character
|
||||||
kanji.character = "麻"
|
out.is_joyo = "常用" if kanji_obj.level not in ["1", "準1"] else "表外"
|
||||||
kanji.is_joyo = "joyo kanji"
|
out.level = kanji_obj.level
|
||||||
kanji.level = "pre-2"
|
out.strokes = kanji_obj.stroke_count
|
||||||
kanji.strokes = 11
|
out.radical = kanji_obj.radical
|
||||||
kanji.radical = "麻"
|
out.added_strokes = kanji_obj.radical_added_stroke_count
|
||||||
kanji.added_strokes = 0
|
out.goon = [format_reading(obj.reading) for obj in kanji_obj.goon]
|
||||||
kanji.goon = "マ"
|
out.kanon = [format_reading(obj.reading) for obj in kanji_obj.kanon]
|
||||||
kanji.kanon = "バ"
|
out.toon = [format_reading(obj.reading) for obj in kanji_obj.toon]
|
||||||
kanji.toon = ""
|
out.soon = [format_reading(obj.reading) for obj in kanji_obj.soon]
|
||||||
kanji.soon = ""
|
out.kanyoon = [format_reading(obj.reading) for obj in kanji_obj.kanyoon]
|
||||||
kanji.kanyoon = ""
|
out.kun = [format_reading(obj.reading) for obj in kanji_obj.kun]
|
||||||
kanji.kun = "あさ, しびれる"
|
out.meanings = kanji_obj.meanings
|
||||||
kanji.meanings = "①あさ。クワ科の一年草。また、あさ類の総称。「亜麻」「乱麻」 ②しびれる。しびれ。「麻酔」「麻痺(マヒ)」類痲(マ)"
|
out.glyph_origin = kanji_obj.glyph_origin
|
||||||
kanji.glyph_origin = "会意。广(げん)(いえ)と、𣏟(はい)(あさ)とから成り、屋下であさの繊維をはぎとる、ひいて「あさ」の意を表す。"
|
|
||||||
|
|
||||||
return render_template("kanji.html", kanji=kanji)
|
|
||||||
|
return render_template("kanji.html", kanji=out)
|
||||||
|
|
||||||
@app.route("/kotoba/<kotoba>")
|
@app.route("/kotoba/<kotoba>")
|
||||||
def kotoba_page(kotoba: str):
|
def kotoba_page(kotoba: str):
|
||||||
@ -86,6 +143,7 @@ def create_app(test_config=None):
|
|||||||
app.register_blueprint(indices.blueprint)
|
app.register_blueprint(indices.blueprint)
|
||||||
|
|
||||||
from . import lang
|
from . import lang
|
||||||
|
lang.add_translation_commands(app)
|
||||||
|
|
||||||
@app.route("/translations", methods=["GET", "POST"])
|
@app.route("/translations", methods=["GET", "POST"])
|
||||||
def strings_translation():
|
def strings_translation():
|
||||||
@ -109,6 +167,14 @@ def create_app(test_config=None):
|
|||||||
lang.update_languages()
|
lang.update_languages()
|
||||||
return redirect("/translations")
|
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):
|
# def use_english(text_id: str):
|
||||||
# return lang.localize(text_id, lang.JAPANESE)
|
# return lang.localize(text_id, lang.JAPANESE)
|
||||||
app.jinja_env.globals.update(localize=lang.localize)
|
app.jinja_env.globals.update(localize=lang.localize)
|
||||||
|
@ -4,7 +4,7 @@ from flask import Blueprint, jsonify
|
|||||||
import jsonpickle
|
import jsonpickle
|
||||||
from sqlalchemy import create_engine, select
|
from sqlalchemy import create_engine, select
|
||||||
from sqlalchemy.orm import Session
|
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")
|
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||||
|
|
||||||
@ -125,24 +125,29 @@ class Kanji(Base):
|
|||||||
|
|
||||||
@blueprint.route("/id/<int:kanji_id>")
|
@blueprint.route("/id/<int:kanji_id>")
|
||||||
def kanji_by_id(kanji_id: int):
|
def kanji_by_id(kanji_id: int):
|
||||||
engine = create_engine("sqlite:///kanken_online/kanken.db")
|
session = get_jiten_db()
|
||||||
with Session(engine) as session:
|
query = select(Kanji).where(Kanji.id == kanji_id)
|
||||||
query = select(Kanji).where(Kanji.id == kanji_id)
|
item = session.execute(query).first()
|
||||||
item = session.execute(query).first()
|
if item is None:
|
||||||
if item is None:
|
return "Invalid ID", 404
|
||||||
return "Invalid ID", 404
|
|
||||||
|
kanji = item[0]
|
||||||
kanji = item[0]
|
return kanji.to_json()
|
||||||
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>")
|
@blueprint.route("/kanji/<kanji>")
|
||||||
def kanji_by_character(kanji: str):
|
def kanji_by_character(kanji: str):
|
||||||
engine = create_engine("sqlite:///kanken_online/kanken.db")
|
kanji_obj = get_kanji_by_character(kanji)
|
||||||
with Session(engine) as session:
|
return kanji_obj.to_json()
|
||||||
query = select(Kanji).where(Kanji.character == kanji)
|
|
||||||
item = session.execute(query).first()
|
def get_kanji_generic():
|
||||||
if item is None:
|
pass
|
||||||
return "Invalid kanji", 404
|
|
||||||
|
|
||||||
kanji_obj = item[0]
|
|
||||||
return kanji_obj.to_json()
|
|
@ -53,6 +53,14 @@ def login():
|
|||||||
if error is None:
|
if error is None:
|
||||||
session.clear()
|
session.clear()
|
||||||
session["user_id"] = user["id"]
|
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"))
|
return redirect(url_for("index"))
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
|
@ -2,6 +2,9 @@ import sqlite3
|
|||||||
from typing import IO
|
from typing import IO
|
||||||
import click
|
import click
|
||||||
from flask import Flask, current_app, g
|
from flask import Flask, current_app, g
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
def get_database():
|
def get_database():
|
||||||
if "db" not in g:
|
if "db" not in g:
|
||||||
@ -13,6 +16,14 @@ def get_database():
|
|||||||
|
|
||||||
return g.db
|
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():
|
def initialize_database():
|
||||||
db = get_database()
|
db = get_database()
|
||||||
|
|
||||||
@ -35,6 +46,18 @@ def init_db_command():
|
|||||||
else:
|
else:
|
||||||
click.echo("Aborted.")
|
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):
|
def initialize_app(app: Flask):
|
||||||
app.teardown_appcontext(close_database)
|
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)
|
@ -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 = Blueprint("forum", __name__, url_prefix="/forum")
|
||||||
|
|
||||||
@blueprint.route("/")
|
@blueprint.route("/")
|
||||||
def index():
|
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"))
|
12
kanken_online/graphql/schema.graphql
Normal file
12
kanken_online/graphql/schema.graphql
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
schema {
|
||||||
|
kanji: Kanji,
|
||||||
|
kotoba: Kotoba
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kanji {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Kotoba {
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import session
|
import click
|
||||||
|
from flask import Flask, session, current_app
|
||||||
|
|
||||||
LanguageMapping = dict[str, str]
|
LanguageMapping = dict[str, str]
|
||||||
|
|
||||||
@ -32,3 +34,46 @@ def localize(text_id: str, language: dict[str, str] = None) -> str:
|
|||||||
preference = session.get("language", "ja")
|
preference = session.get("language", "ja")
|
||||||
language = LANGUAGES.get(preference)
|
language = LANGUAGES.get(preference)
|
||||||
return language[text_id]
|
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)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
DROP TABLE IF EXISTS user;
|
DROP TABLE IF EXISTS user;
|
||||||
-- DROP TABLE IF EXISTS post;
|
DROP TABLE IF EXISTS post;
|
||||||
|
DROP TABLE IF EXISTS user_settings;
|
||||||
|
|
||||||
CREATE TABLE user (
|
CREATE TABLE user (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@ -15,3 +16,10 @@ CREATE TABLE post (
|
|||||||
body TEXT NOT NULL,
|
body TEXT NOT NULL,
|
||||||
FOREIGN KEY (author_id) REFERENCES user (id)
|
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
|
||||||
|
);
|
10
kanken_online/static/data.js
Normal file
10
kanken_online/static/data.js
Normal 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}`);
|
||||||
|
});
|
||||||
|
}
|
6232
kanken_online/static/download/kanji.tsv
Normal file
6232
kanken_online/static/download/kanji.tsv
Normal file
File diff suppressed because it is too large
Load Diff
41330
kanken_online/static/download/kotoba.tsv
Normal file
41330
kanken_online/static/download/kotoba.tsv
Normal file
File diff suppressed because it is too large
Load Diff
44
kanken_online/static/kanji.css
Normal file
44
kanken_online/static/kanji.css
Normal 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;
|
||||||
|
}
|
@ -18,7 +18,6 @@
|
|||||||
"incorrect_username": "Incorrect username.",
|
"incorrect_username": "Incorrect username.",
|
||||||
"incorrect_password": "Incorrect password.",
|
"incorrect_password": "Incorrect password.",
|
||||||
"about": "About KankenOnline",
|
"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",
|
"indices": "Indices",
|
||||||
"radical_index": "Radical index",
|
"radical_index": "Radical index",
|
||||||
"indivisible_index": "Indivisible kanji index",
|
"indivisible_index": "Indivisible kanji index",
|
||||||
@ -31,5 +30,18 @@
|
|||||||
"en": "English",
|
"en": "English",
|
||||||
"ja": "Japanese",
|
"ja": "Japanese",
|
||||||
"add_string": "Add string",
|
"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"
|
||||||
}
|
}
|
@ -18,7 +18,6 @@
|
|||||||
"incorrect_username": "ユーザー名が違います",
|
"incorrect_username": "ユーザー名が違います",
|
||||||
"incorrect_password": "パスワードが違います",
|
"incorrect_password": "パスワードが違います",
|
||||||
"about": "漢検オンラインとは",
|
"about": "漢検オンラインとは",
|
||||||
"about-para": "漢検オンラインとは、漢検一級合格を目当てにした資料を供用しているサイトです。ここで漢検のやく6300字を検索でき、いろんな勉強材を自動的に作ることができます。たとえば、漢検一級模様の試験PDFを作ることができて、本物の漢検試験に大抵該当することが目的です。さらに、色々漢字についての情報を取り集めております。諧声域(かいせいいき)・音韻学・部首・不可分漢字・字源・語源などがその内です。",
|
|
||||||
"indices": "索引",
|
"indices": "索引",
|
||||||
"radical_index": "部首索引",
|
"radical_index": "部首索引",
|
||||||
"indivisible_index": "不可分漢字索引",
|
"indivisible_index": "不可分漢字索引",
|
||||||
@ -31,5 +30,18 @@
|
|||||||
"en": "英語",
|
"en": "英語",
|
||||||
"ja": "日本語",
|
"ja": "日本語",
|
||||||
"add_string": "翻訳語を追加",
|
"add_string": "翻訳語を追加",
|
||||||
"submit": "投入"
|
"submit": "投入",
|
||||||
|
"about_para": "漢検オンラインとは、漢検一級合格を目当てにした資料を供用しているサイトです。ここで漢検のやく6300字を検索でき、いろんな勉強材を自動的に作ることができます。たとえば、漢検一級模様の試験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": "利用者アイコン"
|
||||||
}
|
}
|
@ -5,5 +5,5 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{{ localize("about-para") }}
|
{{ localize("about_para") }}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -1,8 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="{{session['language'] or 'ja'}}">
|
||||||
|
|
||||||
<head>
|
<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') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
{% if session %}
|
{% 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')}}">
|
<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>
|
<h1><a href="/">{{ localize("kanken_online") }}</a></h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
<li><span>{{ g.user['username'] }}</span>
|
<li><a href="{{ url_for('my_page') }}">{{ g.user['username'] }}</a>
|
||||||
<li><a href="{{ url_for('options') }}">{{ localize("options") }}</a>
|
|
||||||
<li><a href="{{ url_for('auth.logout') }}">{{ localize("log_out") }}</a>
|
<li><a href="{{ url_for('auth.logout') }}">{{ localize("log_out") }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{{ url_for('auth.register') }}">{{ localize("register") }}</a>
|
<li><a href="{{ url_for('auth.register') }}">{{ localize("register") }}</a>
|
||||||
<li><a href="{{ url_for('auth.login') }}">{{ localize("log_in") }}</a>
|
<li><a href="{{ url_for('auth.login') }}">{{ localize("log_in") }}</a>
|
||||||
{% endif %}
|
{% 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('about_page') }}">{{ localize("about") }}</a></li>
|
||||||
<li><a href="{{ url_for('indices.indices_page') }}">{{ localize("indices") }}</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('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>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
|
16
kanken_online/templates/data.html
Normal file
16
kanken_online/templates/data.html
Normal 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 %}
|
@ -5,5 +5,19 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}
|
{% endblock %}
|
15
kanken_online/templates/forum/new.html
Normal file
15
kanken_online/templates/forum/new.html
Normal 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 %}
|
@ -2,41 +2,45 @@
|
|||||||
|
|
||||||
{% block scripts %}<script src="/static/kanji.js" defer></script>{%endblock %}
|
{% 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 %}
|
{% block header %}
|
||||||
<h1>{% block title %}{{localize("kanji")}} - {{ kanji.character }}{% endblock %}</h1>
|
<h1>{% block title %}{{localize("kanji")}} - {{ kanji.character }}{% endblock %}</h1>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="kanji-and-meaning">
|
<div id="entry" lang="ja">
|
||||||
<div id="kanji-box">
|
<div id="kanji-and-meaning">
|
||||||
<div id="character-box">
|
<div id="kanji-box">
|
||||||
<span id="kanji">{{ kanji.character }}</span>
|
<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>
|
||||||
<div id="metadata-box">
|
<div id="meaning-box">
|
||||||
<span id="joyo-class">{{ kanji.is_joyo }}</span>
|
<p id="meanings">{{ kanji.meanings }}</p>
|
||||||
<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>
|
</div>
|
||||||
<div id="meaning-box">
|
<div id="readings-and-glyph-origin">
|
||||||
<p id="meanings">{{ kanji.meanings }}</p>
|
<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>
|
</div>
|
||||||
|
{% endblock %}
|
||||||
</div>
|
</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 %}
|
|
@ -5,7 +5,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post" enctype=multipart/form-data>
|
||||||
<label for="language-select">{{ localize("language") }}</label>
|
<label for="language-select">{{ localize("language") }}</label>
|
||||||
<select name="language" id="language-select">
|
<select name="language" id="language-select">
|
||||||
<option value="ja">日本語</option>
|
<option value="ja">日本語</option>
|
||||||
@ -17,6 +17,10 @@
|
|||||||
<option value="dark">{{ localize("dark_theme") }}</option>
|
<option value="dark">{{ localize("dark_theme") }}</option>
|
||||||
<option value="light">{{ localize("light_theme") }}</option>
|
<option value="light">{{ localize("light_theme") }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<label for="pfp">{{ localize("pfp") }}</label>
|
||||||
|
<input type="file" id="pfp" name="pfp">
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<button type="submit">Okay</button>
|
<button type="submit">Okay</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -7,12 +7,40 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="get">
|
<form method="get">
|
||||||
<label for="search_bar">{{ localize("search") }}</label>
|
<label for="search_bar">{{ localize("search") }}</label>
|
||||||
<input type="text" id="search_bar" name="keywords" placeholder="{{ localize("search_placeholder") }}" value="{{value}}">
|
<input type="text" id="search_bar" name="keywords" placeholder="{{ localize('search_placeholder') }}" value="{{ value }}" autofocus onfocus="this.select()">
|
||||||
<label for="include_kanji">{{ localize("include_kanji") }}</label>
|
|
||||||
<input type="checkbox" id="include_kanji" checked>
|
<fieldset>
|
||||||
<label for="include_kanji">{{ localize("include_kotoba") }}</label>
|
<label for="search_method_includes">Includes</label>
|
||||||
<input type="checkbox" id="include_kanji" checked>
|
<input type="radio" id="search_method_includes" name="search_method" value="includes">
|
||||||
</form>
|
<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 %}
|
{% block results %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endblock %}
|
{% endblock %}
|
10
kanken_online/templates/user_page.html
Normal file
10
kanken_online/templates/user_page.html
Normal 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 %}
|
Loading…
Reference in New Issue
Block a user