Compare commits

...

30 Commits

Author SHA1 Message Date
Kiril Kovachev
5a1e991350 Use Japanese by default 2024-10-14 17:31:54 +01:00
Kiril Kovachev
d1744e672a Add theme and language select functionality 2024-10-14 17:31:07 +01:00
Kiril Kovachev
152a9bc950 Add color theme customization support 2024-10-14 17:10:12 +01:00
Kiril Kovachev
5d6ededd57 Add kanji indices 2024-10-14 16:34:31 +01:00
Kiril Kovachev
3b09ea2dda Add link to about on main page 2024-10-14 16:25:09 +01:00
Kiril Kovachev
b03fd3f54a Temporarily pause language string assertions 2024-10-14 16:20:54 +01:00
Kiril Kovachev
6b92b23b4f Add about page 2024-10-14 16:20:28 +01:00
Kiril Kovachev
21f1035b66 Flash localized messages from auth 2024-10-14 12:52:07 +01:00
Kiril Kovachev
934f7ae676 Localize flashed messages in base.html 2024-10-14 12:48:26 +01:00
Kiril Kovachev
bfc42bba19 Make the KankenOnline text clickable and route to main page 2024-10-14 12:47:53 +01:00
Kiril Kovachev
2100a324df Localize search 2024-10-14 12:46:16 +01:00
Kiril Kovachev
45c263602b Expand search to handle results, search submission 2024-10-14 12:33:37 +01:00
Kiril Kovachev
7b0cc42775 Use correct search template path 2024-10-14 12:17:52 +01:00
Kiril Kovachev
67810f78d4 Move search.html into own diretory 2024-10-14 12:17:02 +01:00
Kiril Kovachev
eb3f685d75 Separate search into blueprint 2024-10-14 12:15:47 +01:00
Kiril Kovachev
469bf8dfd7 Add search view 2024-10-14 12:13:26 +01:00
Kiril Kovachev
f4f6e624ed Add password string 2024-10-14 12:10:37 +01:00
Kiril Kovachev
b63c2a5f10 Restore post schema 2024-10-14 12:05:20 +01:00
Kiril Kovachev
04c52d20c3 Finish localizing all current strings 2024-10-14 12:02:11 +01:00
Kiril Kovachev
362699b82e Add forum HTML page 2024-10-14 11:54:43 +01:00
Kiril Kovachev
9af97e017d Add kanji + forum strings 2024-10-14 11:54:26 +01:00
Kiril Kovachev
06b7c8eb8e Add forum stub 2024-10-14 11:54:07 +01:00
Kiril Kovachev
2d5749007a Add localization support 2024-10-14 11:49:16 +01:00
Kiril Kovachev
6614d6344a Add kotoba reading section to view 2024-10-14 11:35:19 +01:00
Kiril Kovachev
7800d4b05d Use kanji on-click-copying script 2024-10-14 11:34:59 +01:00
Kiril Kovachev
c36da85953 Use blocks to allow special scripts or styles per page 2024-10-14 11:34:39 +01:00
Kiril Kovachev
2a4c724af5 Add kotoba view 2024-10-14 11:28:53 +01:00
Kiril Kovachev
21d14b086b Add kanji HTML page 2024-10-14 11:26:11 +01:00
Kiril Kovachev
2c07a48313 Add script to copy kanji on click 2024-10-14 11:22:40 +01:00
Kiril Kovachev
447f8d4770 Add kanji view 2024-10-14 11:07:47 +01:00
25 changed files with 384 additions and 31 deletions

View File

@ -1,5 +1,5 @@
import os
from flask import Flask, render_template
from flask import Flask, redirect, render_template, g, request, session
from pathlib import Path
from .auth import login_required
@ -28,16 +28,67 @@ def create_app(test_config=None):
def index():
return render_template("index.html")
@app.route("/options")
@app.route("/about")
def about_page():
return render_template("about.html")
def update_settings(form):
session["language"] = form["language"]
session["theme"] = form["theme"]
return redirect("/options")
@app.route("/options", methods=["GET", "POST"])
@login_required
def options():
return "options"
if request.method == "GET":
return render_template("options.html")
else:
return update_settings(request.form)
@app.route("/kanji/<kanji>")
def kanji_page(kanji: str):
# TODO use database to get 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 = "会意。广(げん)(いえ)と、𣏟(はい)(あさ)とから成り、屋下であさの繊維をはぎとる、ひいて「あさ」の意を表す。"
return render_template("kanji.html", kanji=kanji)
@app.route("/kotoba/<kotoba>")
def kotoba_page(kotoba: str):
return render_template("kotoba.html", kotoba=kotoba)
from . import database
database.initialize_app(app)
from . import auth, api
from . import auth, api, forum, search, indices
app.register_blueprint(auth.blueprint)
app.register_blueprint(api.blueprint)
app.register_blueprint(forum.blueprint)
app.register_blueprint(search.blueprint)
app.register_blueprint(indices.blueprint)
from . import lang
# def use_english(text_id: str):
# return lang.localize(text_id, lang.JAPANESE)
app.jinja_env.globals.update(localize=lang.localize)
return app

View File

@ -14,9 +14,9 @@ def register():
error = None
if not username:
error = "Username is required."
error = "username_required"
elif not password:
error = "Password is required."
error = "password_required"
if error is None:
try:
@ -46,9 +46,9 @@ def login():
).fetchone()
if user is None:
error = "Incorrect username."
error = "incorrect_username"
elif not check_password_hash(user["password"], password):
error = "Incorrect password."
error = "incorrect_password"
if error is None:
session.clear()

8
kanken_online/forum.py Normal file
View File

@ -0,0 +1,8 @@
from flask import Blueprint, render_template
blueprint = Blueprint("forum", __name__, url_prefix="/forum")
@blueprint.route("/")
def index():
return render_template("forum/index.html")

19
kanken_online/indices.py Normal file
View File

@ -0,0 +1,19 @@
from flask import Blueprint, render_template
blueprint = Blueprint("indices", __name__, url_prefix="/indices")
@blueprint.route("/")
def indices_page():
return render_template("indices/indices.html")
@blueprint.route("/indivisible")
def indivisible_kanji():
return render_template("indices/indivisible.html")
@blueprint.route("/radicals")
def kanji_radicals():
return render_template("indices/radicals.html")
@blueprint.route("/phonetic_series")
def phonetic_series():
return render_template("indices/phonetic_series.html")

103
kanken_online/lang.py Normal file
View File

@ -0,0 +1,103 @@
from flask import session
EXISTING_STRINGS = {
"kanken_online",
"options",
"log_in",
"register",
"log_out",
"kanji",
"forum",
"main_page",
"username",
"password",
"search",
"search_placeholder",
"include_kanji",
"include_kotoba",
"username_required",
"password_required",
"incorrect_username",
"incorrect_password"
"about",
"about-para",
"indices"
}
ENGLISH = {
"kanken_online": "KankenOnline",
"options": "Options",
"log_in": "Log in",
"register": "Register",
"log_out": "Log out",
"kanji": "Kanji",
"forum": "Forum",
"main_page": "Main Page",
"username": "Username",
"password": "Password",
"search": "Search",
"search_placeholder": "Enter kanji or word",
"include_kanji": "Include kanji",
"include_kotoba": "Include kotoba",
"username_required": "Username required.",
"password_required": "Password required.",
"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",
"phonetic_series": "Phonetic series",
"dark_theme": "Dark theme",
"light_theme": "Light theme",
"language": "Language",
"theme": "Color settings"
}
JAPANESE = {
"kanken_online": "漢検オンライン",
"options": "設定",
"log_in": "ログイン",
"register": "登録",
"log_out": "ログアウト",
"kanji": "漢字",
"forum": "掲示板",
"main_page": "ホームページ",
"username": "ユーザー名",
"password": "パスワード",
"search": "検索",
"search_placeholder": "漢字・言葉を入力",
"include_kanji": "漢字を含む",
"include_kotoba": "言葉を含む",
"username_required": "ユーザー名が必要です",
"password_required": "パスワードが必要です",
"incorrect_username": "ユーザー名が違います",
"incorrect_password": "パスワードが違います",
"about": "漢検オンラインとは",
"about-para": "漢検オンラインとは、漢検一級合格を目当てにした資料を供用しているサイトです。ここで漢検のやく字を検索でき、いろんな勉強材を自動的に作ることができます。たとえば、漢検一級模様の試験PDFを作ることができて、本物の漢検試験に大抵該当することが目的です。さらに、色々漢字についての情報を取り集めております。諧声域かいせいいき・音韻学・部首・不可分漢字・字源・語源などがその内です。",
"indices": "索引",
"radical_index": "部首索引",
"indivisible_index": "不可分漢字索引",
"phonetic_series": "諧声域索引",
"dark_theme": "ダークモード",
"light_theme": "ライトモード",
"language": "言語",
"theme": "色設定"
}
LANGUAGES = {
"en": ENGLISH,
"ja": JAPANESE
}
# assert all(all(key in lang for key in EXISTING_STRINGS) for lang in LANGUAGES) # Ensure all strings are mapped for all existing languages
# assert not [key for lang in LANGUAGES for key in lang if ((key in lang) and (key not in EXISTING_STRINGS))]
# assert not any((((key in lang) and (key not in EXISTING_STRINGS)) for key in lang) for lang in LANGUAGES) # Ensure no languages have strings not specified by the main index
def localize(text_id: str, language: dict[str, str] = None) -> str:
if language is None:
preference = session.get("language", "ja")
language = LANGUAGES.get(preference)
return language[text_id]

View File

@ -7,12 +7,11 @@ CREATE TABLE user (
password TEXT NOT NULL
);
-- CREATE TABLE post (
-- id INTEGER PRIMARY KEY AUTOINCREMENT,
-- author_id INTEGER NOT NULL,
-- created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- title TEXT NOT NULL,
-- body TEXT NOT NULL,
-- FOREIGN KEY (author_id) REFERENCES user (id)
-- );
CREATE TABLE post (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER NOT NULL,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
title TEXT NOT NULL,
body TEXT NOT NULL,
FOREIGN KEY (author_id) REFERENCES user (id)
);

16
kanken_online/search.py Normal file
View File

@ -0,0 +1,16 @@
from flask import Blueprint, render_template, request
blueprint = Blueprint("search", __name__, url_prefix="/search")
def search_results(args: dict):
class renderable:
def render(self):
return "ok"
results = [renderable(), renderable()] # Do something with args
return render_template("search/search_results.html", results=results, value=args["keywords"])
@blueprint.route("/")
def search_page():
if request.args:
return search_results(request.args)
return render_template("search/search.html")

View File

@ -0,0 +1,3 @@
body {
background: green;
}

View File

@ -0,0 +1,4 @@
const kanjiElem = document.getElementById("kanji");
kanjiElem.addEventListener("click", (_) => {
navigator.clipboard.writeText(kanjiElem.innerText);
});

View File

@ -0,0 +1,3 @@
body {
background-color: white;
}

View File

@ -0,0 +1,3 @@
@import url("/static/light_theme.css") (prefers-color-scheme: light);
@import url("/static/dark_theme.css") (prefers-color-scheme: no-preference);
@import url("/static/dark_theme.css") (prefers-color-scheme: dark);

View File

@ -0,0 +1,9 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("about") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
{{ localize("about-para") }}
{% endblock %}

View File

@ -1,15 +1,15 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Log In{% endblock %}</h1>
<h1>{% block title %}{{ localize("log_in") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<form method="post">
<label for="username">Username</label>
<label for="username">{{ localize("username") }}</label>
<input name="username" id="username" required autofocus>
<label for="password">Password</label>
<label for="password">{{ localize("password") }}</label>
<input type="password" name="password" id="password" required>
<input type="submit" value="Log In">
<input type="submit" value="{{ localize("log_in") }}">
</form>
{% endblock %}

View File

@ -2,21 +2,30 @@
<html>
<head>
<title>{% block title %}{% endblock %} - KankenOnline</title>
<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')}}">
{% else %}
<link rel="stylesheet" href="{{ url_for('static', filename='theme_select.css') }}"> <!-- Use CSS-based theme detection if no user login supplied -->
{% endif %}
{% block styles %}{% endblock %}
{% block scripts %}{% endblock %}
</head>
<body>
<nav>
<h1>KankenOnline</h1>
<h1><a href="/">{{ localize("kanken_online") }}</a></h1>
<ul>
{% if g.user %}
<li><span>{{ g.user['username'] }}</span>
<li><a href="{{ url_for('options') }}">Options</a>
<li><a href="{{ url_for('auth.logout') }}">Log Out</a>
<li><a href="{{ url_for('options') }}">{{ localize("options") }}</a>
<li><a href="{{ url_for('auth.logout') }}">{{ localize("log_out") }}</a>
{% else %}
<li><a href="{{ url_for('auth.register') }}">Register</a>
<li><a href="{{ url_for('auth.login') }}">Log In</a>
<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('about_page') }}">{{ localize("about") }}</a></li>
<li><a href="{{ url_for('indices.indices_page') }}">{{ localize("indices") }}</a></li>
</ul>
</nav>
<section class="content">
@ -24,7 +33,7 @@
{% block header %}{% endblock %}
</header>
{% for message in get_flashed_messages() %}
<div class="flash">{{ message }}</div>
<div class="flash">{{ localize(message) }}</div>
{% endfor %}
{% block content %}{% endblock %}
</section>

View File

@ -0,0 +1,9 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("forum") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
...
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Main Page{% endblock %}</h1>
<h1>{% block title %}{{ localize("main_page") }}{% endblock %}</h1>
{% endblock %}
{% block content %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("indices") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<ul id="index-list">
<li id="radicals"><a href="{{ url_for('indices.kanji_radicals') }}">{{ localize("radical_index") }}</a></li>
<li id="radicals"><a href="{{ url_for('indices.indivisible_kanji') }}">{{ localize("indivisible_index") }}</a></li>
<li id="radicals"><a href="{{ url_for('indices.phonetic_series') }}">{{ localize("phonetic_series") }}</a></li>
</ul>
{% endblock %}

View File

@ -0,0 +1,42 @@
{% extends 'base.html' %}
{% block scripts %}<script src="/static/kanji.js" defer></script>{%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>
<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="meaning-box">
<p id="meanings">{{ kanji.meanings }}</p>
</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 %}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Kotoba - {{ kotoba.word }}{% endblock %}</h1>
{% endblock %}
{% block content %}
<div id="headword-and-meaning">
<div id="kotoba-box">
<div id="headword-box">
<span id="kotoba">{{ kotoba.word }}</span>
<span id="yomi">{{ kotoba.reading }}</span>
</div>
</div>
<div id="meaning-box">
<p id="meanings">{{ kotoba.meanings }}</p>
</div>
</div>
{% endblock %}

View File

@ -1,9 +1,23 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}Options{% endblock %}</h1>
<h1>{% block title %}{{ localize("options") }}{% endblock %}</h1>
{% endblock %}
{% block content %}
Blahdy blah
<form method="post">
<label for="language-select">{{ localize("language") }}</label>
<select name="language" id="language-select">
<option value="ja">日本語</option>
<option value="en">English</option>
</select>
<label for="theme-select">{{ localize("theme") }}</label>
<select name="theme" id="theme-select">
<option value="dark">{{ localize("dark_theme") }}</option>
<option value="light">{{ localize("light_theme") }}</option>
</select>
<hr>
<button type="submit">Okay</button>
</form>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}{{ localize("search") }}{% endblock %}</h1>
{% endblock %}
{% 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>
{% block results %}
{% endblock %}
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'search/search.html' %}
{% block results %}
{% for result in results %}
<div class="result">
{{result.render()}}
</div>
{% endfor %}
{% endblock%}