From 6869e9fd3605a48dcc6fb4e4b47836d2b85f18cf Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 18:42:20 +0100 Subject: [PATCH 001/108] reviewer-bottom entrypoint --- build/configure/src/aqt.rs | 2 +- build/configure/src/web.rs | 12 ++++++++++++ qt/aqt/reviewer.py | 7 +------ ts/routes/reviewer-bottom/AnswerButton.svelte | 0 .../routes/reviewer-bottom/index.scss | 6 +++--- ts/routes/reviewer-bottom/index.svelte | 0 .../routes/reviewer-bottom/index.ts | 5 +++++ 7 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 ts/routes/reviewer-bottom/AnswerButton.svelte rename qt/aqt/data/web/css/reviewer-bottom.scss => ts/routes/reviewer-bottom/index.scss (89%) create mode 100644 ts/routes/reviewer-bottom/index.svelte rename qt/aqt/data/web/js/reviewer-bottom.ts => ts/routes/reviewer-bottom/index.ts (96%) diff --git a/build/configure/src/aqt.rs b/build/configure/src/aqt.rs index 83be77e91..183b7f0b3 100644 --- a/build/configure/src/aqt.rs +++ b/build/configure/src/aqt.rs @@ -170,7 +170,7 @@ fn build_imgs(build: &mut Build) -> Result<()> { } fn build_js(build: &mut Build) -> Result<()> { - for ts_file in &["deckbrowser", "webview", "toolbar", "reviewer-bottom"] { + for ts_file in &["deckbrowser", "webview", "toolbar"] { build.add_action( "qt:aqt:data:web:js", EsbuildScript { diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index ef2d268bb..9ee5d74b5 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -1,6 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +use anyhow::Ok; use anyhow::Result; use ninja_gen::action::BuildAction; use ninja_gen::copy::CopyFiles; @@ -228,6 +229,17 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> { ":sveltekit" ], )?; + build_page( + "reviewer-bottom", + true, + inputs![ + // + ":ts:lib", + ":ts:components", + ":sass", + ":sveltekit" + ], + )?; Ok(()) } diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index a8839c598..4d7f175ab 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -355,12 +355,7 @@ class Reviewer: self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") # show answer / ease buttons - self.bottom.web.stdHtml( - self._bottomHTML(), - css=["css/toolbar-bottom.css", "css/reviewer-bottom.css"], - js=["js/vendor/jquery.min.js", "js/reviewer-bottom.js"], - context=ReviewerBottomBar(self), - ) + self.bottom.web.load_ts_page("reviewer-bottom") # Showing the question ########################################################################## diff --git a/ts/routes/reviewer-bottom/AnswerButton.svelte b/ts/routes/reviewer-bottom/AnswerButton.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/qt/aqt/data/web/css/reviewer-bottom.scss b/ts/routes/reviewer-bottom/index.scss similarity index 89% rename from qt/aqt/data/web/css/reviewer-bottom.scss rename to ts/routes/reviewer-bottom/index.scss index 59098a5fb..fbe069779 100644 --- a/qt/aqt/data/web/css/reviewer-bottom.scss +++ b/ts/routes/reviewer-bottom/index.scss @@ -1,9 +1,9 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -@use "../../../../../ts/lib/sass/root-vars"; -@use "../../../../../ts/lib/sass/vars" as *; -@use "../../../../../ts/lib/sass/card-counts"; +@use "../../lib/sass/root-vars"; +@use "../../lib/sass/vars" as *; +@use "../../lib/sass/card-counts"; :root { --focus-color: #{palette-of(border-focus)}; diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/qt/aqt/data/web/js/reviewer-bottom.ts b/ts/routes/reviewer-bottom/index.ts similarity index 96% rename from qt/aqt/data/web/js/reviewer-bottom.ts rename to ts/routes/reviewer-bottom/index.ts index 4ebf34510..ecb93d309 100644 --- a/qt/aqt/data/web/js/reviewer-bottom.ts +++ b/ts/routes/reviewer-bottom/index.ts @@ -5,6 +5,10 @@ @typescript-eslint/no-unused-vars: "off", */ +import "./index.scss" + +console.log("Hello world") + let time: number; // set in python code let timerStopped = false; @@ -61,3 +65,4 @@ function selectedAnswerButton(): string { } return node.dataset.ease; } + From 244aade83630dfdf2d6678c02789a13b20681256 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 18:46:35 +0100 Subject: [PATCH 002/108] Fix: Showquestion is not defined --- qt/aqt/reviewer.py | 2 +- ts/routes/reviewer-bottom/index.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 4d7f175ab..2ae054184 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -847,7 +847,7 @@ timerStopped = false; maxTime = self.card.time_limit() / 1000 else: maxTime = 0 - self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) + self.bottom.web.eval("anki.showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) def _showEaseButtons(self) -> None: if not self._states_mutated: diff --git a/ts/routes/reviewer-bottom/index.ts b/ts/routes/reviewer-bottom/index.ts index ecb93d309..0f495bcdd 100644 --- a/ts/routes/reviewer-bottom/index.ts +++ b/ts/routes/reviewer-bottom/index.ts @@ -7,8 +7,6 @@ import "./index.scss" -console.log("Hello world") - let time: number; // set in python code let timerStopped = false; @@ -35,7 +33,7 @@ function updateTime(): void { let intervalId: number | undefined; -function showQuestion(txt: string, maxTime_: number): void { +export function showQuestion(txt: string, maxTime_: number): void { showAnswer(txt); time = 0; maxTime = maxTime_; From 34c1dfd8497a1ff9a8e16d57020ef8a5a6ff1ab3 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 20:34:26 +0100 Subject: [PATCH 003/108] Added: Svelte component --- ts/routes/reviewer-bottom/index.svelte | 3 +++ ts/routes/reviewer-bottom/index.ts | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte index e69de29bb..049eff39e 100644 --- a/ts/routes/reviewer-bottom/index.svelte +++ b/ts/routes/reviewer-bottom/index.svelte @@ -0,0 +1,3 @@ +
+ Hello from svelte +
\ No newline at end of file diff --git a/ts/routes/reviewer-bottom/index.ts b/ts/routes/reviewer-bottom/index.ts index 0f495bcdd..e4bec417a 100644 --- a/ts/routes/reviewer-bottom/index.ts +++ b/ts/routes/reviewer-bottom/index.ts @@ -5,7 +5,9 @@ @typescript-eslint/no-unused-vars: "off", */ +import { mount } from "svelte"; import "./index.scss" +import ReviewerBottom from "./index.svelte" let time: number; // set in python code let timerStopped = false; @@ -64,3 +66,7 @@ function selectedAnswerButton(): string { return node.dataset.ease; } +mount( + ReviewerBottom, + {target: document.body}, +) From 758cfa269360d8d75716c46265894af26d674d94 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 21:03:40 +0100 Subject: [PATCH 004/108] Buttons template --- qt/aqt/reviewer.py | 4 +++- ts/routes/reviewer-bottom/index.scss | 1 + ts/routes/reviewer-bottom/index.svelte | 25 ++++++++++++++++++++++--- ts/routes/reviewer-bottom/index.ts | 8 ++++---- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 2ae054184..b75f64fb3 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -847,7 +847,9 @@ timerStopped = false; maxTime = self.card.time_limit() / 1000 else: maxTime = 0 - self.bottom.web.eval("anki.showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) + self.bottom.web.eval( + "anki.showQuestion(%s,%d);" % (json.dumps(middle), maxTime) + ) def _showEaseButtons(self) -> None: if not self._states_mutated: diff --git a/ts/routes/reviewer-bottom/index.scss b/ts/routes/reviewer-bottom/index.scss index fbe069779..7c9985ba6 100644 --- a/ts/routes/reviewer-bottom/index.scss +++ b/ts/routes/reviewer-bottom/index.scss @@ -4,6 +4,7 @@ @use "../../lib/sass/root-vars"; @use "../../lib/sass/vars" as *; @use "../../lib/sass/card-counts"; +@use "../../lib/sass/buttons"; :root { --focus-color: #{palette-of(border-focus)}; diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte index 049eff39e..b4a9ba53b 100644 --- a/ts/routes/reviewer-bottom/index.svelte +++ b/ts/routes/reviewer-bottom/index.svelte @@ -1,3 +1,22 @@ -
- Hello from svelte -
\ No newline at end of file +
+
+
+ +
+
+ +
+
+ +
+
+
+ + diff --git a/ts/routes/reviewer-bottom/index.ts b/ts/routes/reviewer-bottom/index.ts index e4bec417a..d8b7a4c55 100644 --- a/ts/routes/reviewer-bottom/index.ts +++ b/ts/routes/reviewer-bottom/index.ts @@ -6,8 +6,8 @@ */ import { mount } from "svelte"; -import "./index.scss" -import ReviewerBottom from "./index.svelte" +import "./index.scss"; +import ReviewerBottom from "./index.svelte"; let time: number; // set in python code let timerStopped = false; @@ -68,5 +68,5 @@ function selectedAnswerButton(): string { mount( ReviewerBottom, - {target: document.body}, -) + { target: document.body }, +); From 7788aa7785524c7bd114451b7094bf9b0f51c0ba Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 21:55:21 +0100 Subject: [PATCH 005/108] Answer buttons --- qt/aqt/reviewer.py | 30 ++++++++----------- ts/routes/reviewer-bottom/AnswerButton.svelte | 9 ++++++ ts/routes/reviewer-bottom/index.svelte | 17 ++++++++++- ts/routes/reviewer-bottom/index.ts | 10 ++++--- ts/routes/reviewer-bottom/types.ts | 7 +++++ 5 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 ts/routes/reviewer-bottom/types.ts diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index b75f64fb3..ab51ff805 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -858,7 +858,7 @@ timerStopped = false; middle = self._answerButtons() conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id()) self.bottom.web.eval( - f"showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" + f"anki.showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" ) def _remaining(self) -> str: @@ -912,31 +912,25 @@ timerStopped = false; def but(i: int, label: str) -> str: if i == default: - extra = """id="defease" """ + id = "defease" else: - extra = "" + id = "" due = self._buttonTime(i, v3_labels=labels) key = ( tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i)) if aqt.mw.pm.get_answer_key(i) else "" ) - return """ -""" % ( - extra, - key, - i, - i, - label, - due, - ) + return { + "id": id, + "key": key, + "i": i, + "label": label, + "due": due, + } - buf = "
" - for ease, label in self._answerButtonList(): - buf += but(ease, label) - buf += "
" - return buf + return [but(ease, label) for ease, label in self._answerButtonList()] + def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str: if self.mw.col.conf["estTimes"]: diff --git a/ts/routes/reviewer-bottom/AnswerButton.svelte b/ts/routes/reviewer-bottom/AnswerButton.svelte index e69de29bb..2a04b7a20 100644 --- a/ts/routes/reviewer-bottom/AnswerButton.svelte +++ b/ts/routes/reviewer-bottom/AnswerButton.svelte @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte index b4a9ba53b..f2c8ff02d 100644 --- a/ts/routes/reviewer-bottom/index.svelte +++ b/ts/routes/reviewer-bottom/index.svelte @@ -1,10 +1,25 @@ + +
- + {#if $answerButtons.length} + {#each $answerButtons as answerButton} + + {/each} + {:else} + + {/if}
diff --git a/ts/routes/reviewer-bottom/index.ts b/ts/routes/reviewer-bottom/index.ts index d8b7a4c55..95ab43f00 100644 --- a/ts/routes/reviewer-bottom/index.ts +++ b/ts/routes/reviewer-bottom/index.ts @@ -8,6 +8,7 @@ import { mount } from "svelte"; import "./index.scss"; import ReviewerBottom from "./index.svelte"; +import { writable } from "svelte/store"; let time: number; // set in python code let timerStopped = false; @@ -34,9 +35,10 @@ function updateTime(): void { } let intervalId: number | undefined; +let answerButtons = writable([]) export function showQuestion(txt: string, maxTime_: number): void { - showAnswer(txt); + showAnswer([]); time = 0; maxTime = maxTime_; updateTime(); @@ -53,8 +55,8 @@ export function showQuestion(txt: string, maxTime_: number): void { }, 1000); } -function showAnswer(txt: string, stopTimer = false): void { - document.getElementById("middle").innerHTML = txt; +export function showAnswer(info: AnswerButtonInfo[], stopTimer = false): void { + answerButtons.set(info); timerStopped = stopTimer; } @@ -68,5 +70,5 @@ function selectedAnswerButton(): string { mount( ReviewerBottom, - { target: document.body }, + { target: document.body, props: {answerButtons} }, ); diff --git a/ts/routes/reviewer-bottom/types.ts b/ts/routes/reviewer-bottom/types.ts new file mode 100644 index 000000000..5a4ec8116 --- /dev/null +++ b/ts/routes/reviewer-bottom/types.ts @@ -0,0 +1,7 @@ +interface AnswerButtonInfo { + "extra": string, + "key": string, + "i": number, + "label": string, + "due": string, +} \ No newline at end of file From 5d536f2f8eb890924c95eb3a0e9e0a4c14e92f20 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 22:01:48 +0100 Subject: [PATCH 006/108] Added: edit button --- ts/routes/reviewer-bottom/index.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte index f2c8ff02d..468de32c4 100644 --- a/ts/routes/reviewer-bottom/index.svelte +++ b/ts/routes/reviewer-bottom/index.svelte @@ -2,6 +2,7 @@ import type { Writable } from "svelte/store"; import AnswerButton from "./AnswerButton.svelte"; import { bridgeCommand } from "@tslib/bridgecommand"; + import * as tr from "@generated/ftl" export let answerButtons: Writable $: console.log($answerButtons) @@ -10,7 +11,7 @@
- +
{#if $answerButtons.length} From 8c0d1d1720e30f7757535b5df8fb3968c44069ac Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 22:30:28 +0100 Subject: [PATCH 007/108] More i18n --- ts/routes/reviewer-bottom/index.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/index.svelte index 468de32c4..280f1c500 100644 --- a/ts/routes/reviewer-bottom/index.svelte +++ b/ts/routes/reviewer-bottom/index.svelte @@ -2,7 +2,7 @@ import type { Writable } from "svelte/store"; import AnswerButton from "./AnswerButton.svelte"; import { bridgeCommand } from "@tslib/bridgecommand"; - import * as tr from "@generated/ftl" + import * as tr from "@generated/ftl"; export let answerButtons: Writable $: console.log($answerButtons) @@ -19,11 +19,11 @@ {/each} {:else} - + {/if}
- +
From f4eb7e0ff9fd3d93f2cfb4a41aa91275ba88a71c Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 23:32:33 +0100 Subject: [PATCH 008/108] Use sveltekit --- build/configure/src/web.rs | 12 --- qt/aqt/mediasrv.py | 1 + qt/aqt/reviewer.py | 6 +- ts/routes/reviewer-bottom/+page.svelte | 69 +++++++++++++++++ .../{index.svelte => ReviewerBottom.svelte} | 0 ts/routes/reviewer-bottom/index.ts | 74 ------------------- 6 files changed, 73 insertions(+), 89 deletions(-) create mode 100644 ts/routes/reviewer-bottom/+page.svelte rename ts/routes/reviewer-bottom/{index.svelte => ReviewerBottom.svelte} (100%) delete mode 100644 ts/routes/reviewer-bottom/index.ts diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index 9ee5d74b5..56ef63bad 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -229,18 +229,6 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> { ":sveltekit" ], )?; - build_page( - "reviewer-bottom", - true, - inputs![ - // - ":ts:lib", - ":ts:components", - ":sass", - ":sveltekit" - ], - )?; - Ok(()) } diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 820e762d9..0a4fd0ea0 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -334,6 +334,7 @@ def is_sveltekit_page(path: str) -> bool: "import-csv", "import-page", "image-occlusion", + "reviewer-bottom" ] diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index ab51ff805..c6779063f 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -355,7 +355,7 @@ class Reviewer: self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") # show answer / ease buttons - self.bottom.web.load_ts_page("reviewer-bottom") + self.bottom.web.load_sveltekit_page("reviewer-bottom") # Showing the question ########################################################################## @@ -848,7 +848,7 @@ timerStopped = false; else: maxTime = 0 self.bottom.web.eval( - "anki.showQuestion(%s,%d);" % (json.dumps(middle), maxTime) + "_showQuestion(%s,%d);" % (json.dumps(middle), maxTime) ) def _showEaseButtons(self) -> None: @@ -858,7 +858,7 @@ timerStopped = false; middle = self._answerButtons() conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id()) self.bottom.web.eval( - f"anki.showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" + f"_showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" ) def _remaining(self) -> str: diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer-bottom/+page.svelte new file mode 100644 index 000000000..fa264b129 --- /dev/null +++ b/ts/routes/reviewer-bottom/+page.svelte @@ -0,0 +1,69 @@ + + + \ No newline at end of file diff --git a/ts/routes/reviewer-bottom/index.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte similarity index 100% rename from ts/routes/reviewer-bottom/index.svelte rename to ts/routes/reviewer-bottom/ReviewerBottom.svelte diff --git a/ts/routes/reviewer-bottom/index.ts b/ts/routes/reviewer-bottom/index.ts deleted file mode 100644 index 95ab43f00..000000000 --- a/ts/routes/reviewer-bottom/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright: Ankitects Pty Ltd and contributors - * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ - -/* eslint -@typescript-eslint/no-unused-vars: "off", -*/ - -import { mount } from "svelte"; -import "./index.scss"; -import ReviewerBottom from "./index.svelte"; -import { writable } from "svelte/store"; - -let time: number; // set in python code -let timerStopped = false; - -let maxTime = 0; - -function updateTime(): void { - const timeNode = document.getElementById("time"); - if (maxTime === 0) { - timeNode.textContent = ""; - return; - } - time = Math.min(maxTime, time); - const m = Math.floor(time / 60); - const s = time % 60; - const sStr = String(s).padStart(2, "0"); - const timeString = `${m}:${sStr}`; - - if (maxTime === time) { - timeNode.innerHTML = `${timeString}`; - } else { - timeNode.textContent = timeString; - } -} - -let intervalId: number | undefined; -let answerButtons = writable([]) - -export function showQuestion(txt: string, maxTime_: number): void { - showAnswer([]); - time = 0; - maxTime = maxTime_; - updateTime(); - - if (intervalId !== undefined) { - clearInterval(intervalId); - } - - intervalId = setInterval(function() { - if (!timerStopped) { - time += 1; - updateTime(); - } - }, 1000); -} - -export function showAnswer(info: AnswerButtonInfo[], stopTimer = false): void { - answerButtons.set(info); - timerStopped = stopTimer; -} - -function selectedAnswerButton(): string { - const node = document.activeElement as HTMLElement; - if (!node) { - return; - } - return node.dataset.ease; -} - -mount( - ReviewerBottom, - { target: document.body, props: {answerButtons} }, -); From 7e92c4016938be6964d3d85e89bdf8f4c19b873b Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 25 Aug 2025 23:35:22 +0100 Subject: [PATCH 009/108] Added: More bridge command --- ts/routes/reviewer-bottom/ReviewerBottom.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index 280f1c500..0345097ae 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -23,7 +23,7 @@ {/if}
- +
From 992c8ad73101b4e18534759329203242fda3b941 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 00:45:58 +0100 Subject: [PATCH 010/108] Added: Remaining --- qt/aqt/reviewer.py | 16 +-- ts/routes/reviewer-bottom/+page.svelte | 126 ++++++++++-------- .../reviewer-bottom/RemainingNumber.svelte | 12 ++ .../reviewer-bottom/ReviewerBottom.svelte | 35 +++-- 4 files changed, 114 insertions(+), 75 deletions(-) create mode 100644 ts/routes/reviewer-bottom/RemainingNumber.svelte diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index c6779063f..5ba90995f 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -259,8 +259,6 @@ class Reviewer: if self._reps is None: self._initWeb() - self._showQuestion() - def _get_next_v3_card(self) -> None: assert isinstance(self.mw.col.sched, V3Scheduler) output = self.mw.col.sched.get_queued_cards() @@ -676,6 +674,9 @@ class Reviewer: self.mw.onEditCurrent() elif url == "more": self.showContextMenu() + elif url == "bottomReady": + self._showQuestion() + self._remaining() elif url.startswith("play:"): play_clicked_audio(url, self.card) elif url.startswith("updateToolbar"): @@ -866,15 +867,8 @@ timerStopped = false; return "" counts: list[int | str] - idx, counts_ = self._v3.counts() - counts = cast(list[Union[int, str]], counts_) - counts[idx] = f"{counts[idx]}" - - return f""" -{counts[0]} + -{counts[1]} + -{counts[2]} -""" + idx, counts = self._v3.counts() + self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") def _defaultEase(self) -> Literal[2, 3]: return 3 diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer-bottom/+page.svelte index fa264b129..a5ea9fe09 100644 --- a/ts/routes/reviewer-bottom/+page.svelte +++ b/ts/routes/reviewer-bottom/+page.svelte @@ -1,69 +1,85 @@ - \ No newline at end of file + + \ No newline at end of file diff --git a/ts/routes/reviewer-bottom/RemainingNumber.svelte b/ts/routes/reviewer-bottom/RemainingNumber.svelte new file mode 100644 index 000000000..6192df946 --- /dev/null +++ b/ts/routes/reviewer-bottom/RemainingNumber.svelte @@ -0,0 +1,12 @@ + + + + {#if underlined} + + {:else} + + {/if} + diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index 0345097ae..dedf7f011 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -3,9 +3,13 @@ import AnswerButton from "./AnswerButton.svelte"; import { bridgeCommand } from "@tslib/bridgecommand"; import * as tr from "@generated/ftl"; + import RemainingNumber from "./RemainingNumber.svelte"; export let answerButtons: Writable - $: console.log($answerButtons) + export let remaining: Writable + export let remainingIndex: Writable + + $: console.log($remaining)
@@ -13,14 +17,21 @@
-
- {#if $answerButtons.length} - {#each $answerButtons as answerButton} - - {/each} - {:else} - - {/if} +
+ + {$remaining[0]} + + {$remaining[1]} + + {$remaining[2]} + +
+ {#if $answerButtons.length} + {#each $answerButtons as answerButton} + + {/each} + {:else} + + {/if} +
@@ -35,4 +46,10 @@ grid-template-columns: auto 1fr auto; justify-items: center; } + + .review-buttons { + display: flex; + flex-direction: column; + align-items: center + } From 8a57d1c5e10ca9208a13652dbe44762f2633da48 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 01:36:17 +0100 Subject: [PATCH 011/108] Fix: showQuestion issues --- qt/aqt/reviewer.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 5ba90995f..fd13584f4 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -259,6 +259,8 @@ class Reviewer: if self._reps is None: self._initWeb() + self._showQuestion() + def _get_next_v3_card(self) -> None: assert isinstance(self.mw.col.sched, V3Scheduler) output = self.mw.col.sched.get_queued_cards() @@ -675,7 +677,6 @@ class Reviewer: elif url == "more": self.showContextMenu() elif url == "bottomReady": - self._showQuestion() self._remaining() elif url.startswith("play:"): play_clicked_audio(url, self.card) @@ -833,23 +834,13 @@ timerStopped = false; ) def _showAnswerButton(self) -> None: - middle = """ -""".format( - tr.actions_shortcut_key(val=tr.studying_space()), - tr.studying_show_answer(), - self._remaining(), - ) # wrap it in a table so it has the same top margin as the ease buttons - middle = ( - "
%s
" - % middle - ) if self.card.should_show_timer(): maxTime = self.card.time_limit() / 1000 else: maxTime = 0 self.bottom.web.eval( - "_showQuestion(%s,%d);" % (json.dumps(middle), maxTime) + "_showQuestion(%s,%d);" % ("", maxTime) ) def _showEaseButtons(self) -> None: From 9dbb7abdbbebef24d21191089acbb3940724d2d6 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 01:46:27 +0100 Subject: [PATCH 012/108] Remove unneeded globals --- ts/routes/reviewer-bottom/+page.svelte | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer-bottom/+page.svelte index a5ea9fe09..2b801cf3a 100644 --- a/ts/routes/reviewer-bottom/+page.svelte +++ b/ts/routes/reviewer-bottom/+page.svelte @@ -5,9 +5,9 @@ import ReviewerBottom from "./ReviewerBottom.svelte"; import "./index.scss" - globalThis.answerButtons = writable([]) - globalThis.remaining = writable<[number, number, number]>([0, 0, 0]) - globalThis.remainingIndex = writable(-1) + let answerButtons = writable([]) + let remaining = writable<[number, number, number]>([0, 0, 0]) + let remainingIndex = writable(-1) onMount(() => { let timerStopped = false; @@ -56,13 +56,13 @@ function _showAnswer(info: AnswerButtonInfo[], stopTimer = false): void { console.log(info) - globalThis.answerButtons.set(info); + answerButtons.set(info); timerStopped = stopTimer; } function _updateRemaining(counts: [number, number, number], idx: number) { - globalThis.remaining.set(counts) - globalThis.remainingIndex.set(idx) + remaining.set(counts) + remainingIndex.set(idx) } globalThis._showQuestion = _showQuestion; @@ -82,4 +82,4 @@ - \ No newline at end of file + \ No newline at end of file From b256e88b1d62f9fd28467ba93974caf1ae15c11c Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 01:56:30 +0100 Subject: [PATCH 013/108] Fix: Large font size --- ts/routes/reviewer-bottom/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/routes/reviewer-bottom/index.scss b/ts/routes/reviewer-bottom/index.scss index 7c9985ba6..331f2de00 100644 --- a/ts/routes/reviewer-bottom/index.scss +++ b/ts/routes/reviewer-bottom/index.scss @@ -17,6 +17,7 @@ body { margin: 0; padding: 0; + font-size: 12px; } #middle td[align="center"] { From 28402c548db22b970e2085e5484c6225ab903b16 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 01:58:00 +0100 Subject: [PATCH 014/108] Fix: Id not class --- ts/routes/reviewer-bottom/ReviewerBottom.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index dedf7f011..bc1c7b88f 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -12,7 +12,7 @@ $: console.log($remaining) -
+
From a3653695626e41cd4e2160c9da859a04c66325ae Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 01:58:52 +0100 Subject: [PATCH 015/108] align items: center --- ts/routes/reviewer-bottom/ReviewerBottom.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index bc1c7b88f..31e3f82fd 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -45,6 +45,7 @@ display: grid; grid-template-columns: auto 1fr auto; justify-items: center; + align-items: center; } .review-buttons { From 6c540c89f10593122cc9a62e6169dd35b638c921 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 02:03:13 +0100 Subject: [PATCH 016/108] ./check --- qt/aqt/reviewer.py | 2 +- ts/routes/reviewer-bottom/+page.svelte | 24 +++++++++++-------- ts/routes/reviewer-bottom/AnswerButton.svelte | 1 + .../reviewer-bottom/ReviewerBottom.svelte | 1 + ts/routes/reviewer-bottom/types.ts | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index fd13584f4..b3232a9cf 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -10,7 +10,7 @@ from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from enum import Enum, auto from functools import partial -from typing import Any, Literal, Match, Union, cast +from typing import Any, Literal, Match, cast import aqt import aqt.browser diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer-bottom/+page.svelte index 2b801cf3a..566b287e6 100644 --- a/ts/routes/reviewer-bottom/+page.svelte +++ b/ts/routes/reviewer-bottom/+page.svelte @@ -3,13 +3,15 @@ import { writable } from "svelte/store"; import { bridgeCommand } from "@tslib/bridgecommand"; import ReviewerBottom from "./ReviewerBottom.svelte"; + import type {AnswerButtonInfo} from "./types" import "./index.scss" - let answerButtons = writable([]) - let remaining = writable<[number, number, number]>([0, 0, 0]) - let remainingIndex = writable(-1) + const answerButtons = writable([]) + const remaining = writable<[number, number, number]>([0, 0, 0]) + const remainingIndex = writable(-1) onMount(() => { + /* let timerStopped = false; let maxTime = 0; @@ -31,33 +33,34 @@ } else { timeNode.textContent = timeString; } - } + }*/ let intervalId: number | undefined; - function _showQuestion(txt: string, maxTime_: number): void { + function _showQuestion(_txt: string, _maxTime_: number): void { _showAnswer([]); globalThis.time = 0; - maxTime = maxTime_; + // maxTime = maxTime_; // updateTime(); if (intervalId !== undefined) { clearInterval(intervalId); } + /* intervalId = setInterval(function() { if (!timerStopped) { globalThis.time += 1; //updateTime(); } - }, 1000); + }, 1000);*/ } - function _showAnswer(info: AnswerButtonInfo[], stopTimer = false): void { + function _showAnswer(info: AnswerButtonInfo[], _stopTimer = false): void { console.log(info) answerButtons.set(info); - timerStopped = stopTimer; + // timerStopped = stopTimer; } function _updateRemaining(counts: [number, number, number], idx: number) { @@ -69,6 +72,7 @@ globalThis._showAnswer = _showAnswer; globalThis._updateRemaining = _updateRemaining; + /* function selectedAnswerButton(): string | undefined { const node = document.activeElement as HTMLElement; if (!node) { @@ -76,7 +80,7 @@ } return node.dataset.ease; } - + */ bridgeCommand("bottomReady"); }); diff --git a/ts/routes/reviewer-bottom/AnswerButton.svelte b/ts/routes/reviewer-bottom/AnswerButton.svelte index 2a04b7a20..645a4f4fe 100644 --- a/ts/routes/reviewer-bottom/AnswerButton.svelte +++ b/ts/routes/reviewer-bottom/AnswerButton.svelte @@ -1,5 +1,6 @@ diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index 31e3f82fd..0e2bded32 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -4,6 +4,7 @@ import { bridgeCommand } from "@tslib/bridgecommand"; import * as tr from "@generated/ftl"; import RemainingNumber from "./RemainingNumber.svelte"; + import type { AnswerButtonInfo } from "./types"; export let answerButtons: Writable export let remaining: Writable diff --git a/ts/routes/reviewer-bottom/types.ts b/ts/routes/reviewer-bottom/types.ts index 5a4ec8116..48fe4567d 100644 --- a/ts/routes/reviewer-bottom/types.ts +++ b/ts/routes/reviewer-bottom/types.ts @@ -1,4 +1,4 @@ -interface AnswerButtonInfo { +export interface AnswerButtonInfo { "extra": string, "key": string, "i": number, From 860a8b429539b81199e456ae1e0d712831840ee5 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 02:08:24 +0100 Subject: [PATCH 017/108] ./check --- qt/aqt/mediasrv.py | 2 +- qt/aqt/reviewer.py | 12 ++--- ts/routes/reviewer-bottom/+page.svelte | 24 +++++----- ts/routes/reviewer-bottom/AnswerButton.svelte | 10 +++-- .../reviewer-bottom/RemainingNumber.svelte | 12 +++-- .../reviewer-bottom/ReviewerBottom.svelte | 44 ++++++++++++++----- ts/routes/reviewer-bottom/types.ts | 14 +++--- 7 files changed, 74 insertions(+), 44 deletions(-) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 0a4fd0ea0..530b5d022 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -334,7 +334,7 @@ def is_sveltekit_page(path: str) -> bool: "import-csv", "import-page", "image-occlusion", - "reviewer-bottom" + "reviewer-bottom", ] diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index b3232a9cf..9acfd1254 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -839,9 +839,7 @@ timerStopped = false; maxTime = self.card.time_limit() / 1000 else: maxTime = 0 - self.bottom.web.eval( - "_showQuestion(%s,%d);" % ("", maxTime) - ) + self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime)) def _showEaseButtons(self) -> None: if not self._states_mutated: @@ -853,11 +851,10 @@ timerStopped = false; f"_showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" ) - def _remaining(self) -> str: + def _remaining(self): if not self.mw.col.conf["dueCounts"]: return "" - counts: list[int | str] idx, counts = self._v3.counts() self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") @@ -889,13 +886,13 @@ timerStopped = false; ) return buttons_tuple - def _answerButtons(self) -> str: + def _answerButtons(self): default = self._defaultEase() assert isinstance(self.mw.col.sched, V3Scheduler) labels = self.mw.col.sched.describe_next_states(self._v3.states) - def but(i: int, label: str) -> str: + def but(i: int, label: str): if i == default: id = "defease" else: @@ -915,7 +912,6 @@ timerStopped = false; } return [but(ease, label) for ease, label in self._answerButtonList()] - def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str: if self.mw.col.conf["estTimes"]: diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer-bottom/+page.svelte index 566b287e6..c92e56462 100644 --- a/ts/routes/reviewer-bottom/+page.svelte +++ b/ts/routes/reviewer-bottom/+page.svelte @@ -1,14 +1,18 @@ + - - \ No newline at end of file + diff --git a/ts/routes/reviewer-bottom/AnswerButton.svelte b/ts/routes/reviewer-bottom/AnswerButton.svelte index 645a4f4fe..96918be95 100644 --- a/ts/routes/reviewer-bottom/AnswerButton.svelte +++ b/ts/routes/reviewer-bottom/AnswerButton.svelte @@ -1,10 +1,14 @@ + - \ No newline at end of file + diff --git a/ts/routes/reviewer-bottom/RemainingNumber.svelte b/ts/routes/reviewer-bottom/RemainingNumber.svelte index 6192df946..4761dfdcd 100644 --- a/ts/routes/reviewer-bottom/RemainingNumber.svelte +++ b/ts/routes/reviewer-bottom/RemainingNumber.svelte @@ -1,12 +1,16 @@ + {#if underlined} - + {:else} - + {/if} diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer-bottom/ReviewerBottom.svelte index 0e2bded32..9dd6160b7 100644 --- a/ts/routes/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer-bottom/ReviewerBottom.svelte @@ -1,3 +1,7 @@ +
- +
- {$remaining[0]} + - {$remaining[1]} + - {$remaining[2]} + + {$remaining[0]} + + + + {$remaining[1]} + + + + {$remaining[2]} +
{#if $answerButtons.length} @@ -30,12 +45,19 @@ {/each} {:else} - + {/if}
- +
@@ -52,6 +74,6 @@ .review-buttons { display: flex; flex-direction: column; - align-items: center + align-items: center; } diff --git a/ts/routes/reviewer-bottom/types.ts b/ts/routes/reviewer-bottom/types.ts index 48fe4567d..4068ca1f9 100644 --- a/ts/routes/reviewer-bottom/types.ts +++ b/ts/routes/reviewer-bottom/types.ts @@ -1,7 +1,9 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export interface AnswerButtonInfo { - "extra": string, - "key": string, - "i": number, - "label": string, - "due": string, -} \ No newline at end of file + "extra": string; + "key": string; + "i": number; + "label": string; + "due": string; +} From d49f1eb430a4c5d860a07721b6a5f71468c54a2b Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 02:23:23 +0100 Subject: [PATCH 018/108] Fix: Update remaining on answer shown --- qt/aqt/reviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 9acfd1254..6edd73efc 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -839,6 +839,7 @@ timerStopped = false; maxTime = self.card.time_limit() / 1000 else: maxTime = 0 + self._remaining() self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime)) def _showEaseButtons(self) -> None: From fac5d64558c80b889486c8450d066c735a70d279 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 26 Aug 2025 16:11:45 +0100 Subject: [PATCH 019/108] Manually specify height --- ts/routes/reviewer-bottom/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/routes/reviewer-bottom/index.scss b/ts/routes/reviewer-bottom/index.scss index 331f2de00..9675a96a1 100644 --- a/ts/routes/reviewer-bottom/index.scss +++ b/ts/routes/reviewer-bottom/index.scss @@ -18,6 +18,7 @@ body { margin: 0; padding: 0; font-size: 12px; + height: 72px; } #middle td[align="center"] { From 4bf38ec2af5be2c8a435d144913e15e42e01bd75 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 18:40:37 +0100 Subject: [PATCH 020/108] Added: Reviewer entrypoint --- qt/aqt/mediasrv.py | 2 +- qt/aqt/reviewer.py | 13 ++----------- ts/routes/reviewer/+page.svelte | 5 +++++ ts/routes/reviewer/index.ts | 0 .../reviewer-bottom/AnswerButton.svelte | 0 .../reviewer-bottom/RemainingNumber.svelte | 0 .../reviewer-bottom/ReviewerBottom.svelte | 0 .../reviewer-bottom/ReviewerBottomOuter.svelte} | 0 ts/routes/{ => reviewer}/reviewer-bottom/index.scss | 8 ++++---- ts/routes/{ => reviewer}/reviewer-bottom/types.ts | 0 10 files changed, 12 insertions(+), 16 deletions(-) create mode 100644 ts/routes/reviewer/+page.svelte create mode 100644 ts/routes/reviewer/index.ts rename ts/routes/{ => reviewer}/reviewer-bottom/AnswerButton.svelte (100%) rename ts/routes/{ => reviewer}/reviewer-bottom/RemainingNumber.svelte (100%) rename ts/routes/{ => reviewer}/reviewer-bottom/ReviewerBottom.svelte (100%) rename ts/routes/{reviewer-bottom/+page.svelte => reviewer/reviewer-bottom/ReviewerBottomOuter.svelte} (100%) rename ts/routes/{ => reviewer}/reviewer-bottom/index.scss (89%) rename ts/routes/{ => reviewer}/reviewer-bottom/types.ts (100%) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 530b5d022..83f45c933 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -334,7 +334,7 @@ def is_sveltekit_page(path: str) -> bool: "import-csv", "import-page", "image-occlusion", - "reviewer-bottom", + "reviewer", ] diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 6edd73efc..535f87b22 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -341,21 +341,12 @@ class Reviewer: def _initWeb(self) -> None: self._reps = 0 # main window - self.web.stdHtml( - self.revHtml(), - css=["css/reviewer.css"], - js=[ - "js/mathjax.js", - "js/vendor/mathjax/tex-chtml-full.js", - "js/reviewer.js", - ], - context=self, - ) + self.web.load_sveltekit_page("reviewer") # block default drag & drop behavior while allowing drop events to be received by JS handlers self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") # show answer / ease buttons - self.bottom.web.load_sveltekit_page("reviewer-bottom") + self.bottom.web = self.web # Showing the question ########################################################################## diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte new file mode 100644 index 000000000..2299e944d --- /dev/null +++ b/ts/routes/reviewer/+page.svelte @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/ts/routes/reviewer/index.ts b/ts/routes/reviewer/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ts/routes/reviewer-bottom/AnswerButton.svelte b/ts/routes/reviewer/reviewer-bottom/AnswerButton.svelte similarity index 100% rename from ts/routes/reviewer-bottom/AnswerButton.svelte rename to ts/routes/reviewer/reviewer-bottom/AnswerButton.svelte diff --git a/ts/routes/reviewer-bottom/RemainingNumber.svelte b/ts/routes/reviewer/reviewer-bottom/RemainingNumber.svelte similarity index 100% rename from ts/routes/reviewer-bottom/RemainingNumber.svelte rename to ts/routes/reviewer/reviewer-bottom/RemainingNumber.svelte diff --git a/ts/routes/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte similarity index 100% rename from ts/routes/reviewer-bottom/ReviewerBottom.svelte rename to ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte diff --git a/ts/routes/reviewer-bottom/+page.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte similarity index 100% rename from ts/routes/reviewer-bottom/+page.svelte rename to ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte diff --git a/ts/routes/reviewer-bottom/index.scss b/ts/routes/reviewer/reviewer-bottom/index.scss similarity index 89% rename from ts/routes/reviewer-bottom/index.scss rename to ts/routes/reviewer/reviewer-bottom/index.scss index 9675a96a1..0f189d6b9 100644 --- a/ts/routes/reviewer-bottom/index.scss +++ b/ts/routes/reviewer/reviewer-bottom/index.scss @@ -1,10 +1,10 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -@use "../../lib/sass/root-vars"; -@use "../../lib/sass/vars" as *; -@use "../../lib/sass/card-counts"; -@use "../../lib/sass/buttons"; +@use "../../../lib/sass/root-vars"; +@use "../../../lib/sass/vars" as *; +@use "../../../lib/sass/card-counts"; +@use "../../../lib/sass/buttons"; :root { --focus-color: #{palette-of(border-focus)}; diff --git a/ts/routes/reviewer-bottom/types.ts b/ts/routes/reviewer/reviewer-bottom/types.ts similarity index 100% rename from ts/routes/reviewer-bottom/types.ts rename to ts/routes/reviewer/reviewer-bottom/types.ts From d81ec73205633b631d1273128e481c9414a30094 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 21:59:34 +0100 Subject: [PATCH 021/108] Use inheritance for reviewer --- qt/aqt/data/web/js/reviewer-bottom.ts | 63 ++++++++++++ qt/aqt/reviewer.py | 141 +++++++++++++++++++++----- 2 files changed, 181 insertions(+), 23 deletions(-) create mode 100644 qt/aqt/data/web/js/reviewer-bottom.ts diff --git a/qt/aqt/data/web/js/reviewer-bottom.ts b/qt/aqt/data/web/js/reviewer-bottom.ts new file mode 100644 index 000000000..c11fa2aa2 --- /dev/null +++ b/qt/aqt/data/web/js/reviewer-bottom.ts @@ -0,0 +1,63 @@ +/* Copyright: Ankitects Pty Ltd and contributors + * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ + +/* eslint +@typescript-eslint/no-unused-vars: "off", +*/ + +let time: number; // set in python code +let timerStopped = false; + +let maxTime = 0; + +function updateTime(): void { + const timeNode = document.getElementById("time"); + if (maxTime === 0) { + timeNode.textContent = ""; + return; + } + time = Math.min(maxTime, time); + const m = Math.floor(time / 60); + const s = time % 60; + const sStr = String(s).padStart(2, "0"); + const timeString = `${m}:${sStr}`; + + if (maxTime === time) { + timeNode.innerHTML = `${timeString}`; + } else { + timeNode.textContent = timeString; + } +} + +let intervalId: number | undefined; + +function showQuestion(txt: string, maxTime_: number): void { + showAnswer(txt); + time = 0; + maxTime = maxTime_; + updateTime(); + + if (intervalId !== undefined) { + clearInterval(intervalId); + } + + intervalId = setInterval(function() { + if (!timerStopped) { + time += 1; + updateTime(); + } + }, 1000); +} + +function showAnswer(txt: string, stopTimer = false): void { + document.getElementById("middle").innerHTML = txt; + timerStopped = stopTimer; +} + +function selectedAnswerButton(): string { + const node = document.activeElement as HTMLElement; + if (!node) { + return; + } + return node.dataset.ease; +} \ No newline at end of file diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 535f87b22..6bbc4f986 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -10,7 +10,7 @@ from collections.abc import Callable, Generator, Sequence from dataclasses import dataclass from enum import Enum, auto from functools import partial -from typing import Any, Literal, Match, cast +from typing import Any, Literal, Match, Union, cast import aqt import aqt.browser @@ -341,12 +341,26 @@ class Reviewer: def _initWeb(self) -> None: self._reps = 0 # main window - self.web.load_sveltekit_page("reviewer") + self.web.stdHtml( + self.revHtml(), + css=["css/reviewer.css"], + js=[ + "js/mathjax.js", + "js/vendor/mathjax/tex-chtml-full.js", + "js/reviewer.js", + ], + context=self, + ) # block default drag & drop behavior while allowing drop events to be received by JS handlers self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") # show answer / ease buttons - self.bottom.web = self.web + self.bottom.web.stdHtml( + self._bottomHTML(), + css=["css/toolbar-bottom.css", "css/reviewer-bottom.css"], + js=["js/vendor/jquery.min.js", "js/reviewer-bottom.js"], + context=ReviewerBottomBar(self), + ) # Showing the question ########################################################################## @@ -667,8 +681,6 @@ class Reviewer: self.mw.onEditCurrent() elif url == "more": self.showContextMenu() - elif url == "bottomReady": - self._remaining() elif url.startswith("play:"): play_clicked_audio(url, self.card) elif url.startswith("updateToolbar"): @@ -825,13 +837,22 @@ timerStopped = false; ) def _showAnswerButton(self) -> None: + middle = """ +""".format( + tr.actions_shortcut_key(val=tr.studying_space()), + tr.studying_show_answer(), + self._remaining(), + ) # wrap it in a table so it has the same top margin as the ease buttons + middle = ( + "
%s
" + % middle + ) if self.card.should_show_timer(): maxTime = self.card.time_limit() / 1000 else: maxTime = 0 - self._remaining() - self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime)) + self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) def _showEaseButtons(self) -> None: if not self._states_mutated: @@ -840,15 +861,23 @@ timerStopped = false; middle = self._answerButtons() conf = self.mw.col.decks.config_dict_for_deck_id(self.card.current_deck_id()) self.bottom.web.eval( - f"_showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" + f"showAnswer({json.dumps(middle)}, {json.dumps(conf['stopTimerOnAnswer'])});" ) - def _remaining(self): + def _remaining(self) -> str: if not self.mw.col.conf["dueCounts"]: return "" - idx, counts = self._v3.counts() - self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") + counts: list[int | str] + idx, counts_ = self._v3.counts() + counts = cast(list[Union[int, str]], counts_) + counts[idx] = f"{counts[idx]}" + + return f""" +{counts[0]} + +{counts[1]} + +{counts[2]} +""" def _defaultEase(self) -> Literal[2, 3]: return 3 @@ -878,32 +907,39 @@ timerStopped = false; ) return buttons_tuple - def _answerButtons(self): + def _answerButtons(self) -> str: default = self._defaultEase() assert isinstance(self.mw.col.sched, V3Scheduler) labels = self.mw.col.sched.describe_next_states(self._v3.states) - def but(i: int, label: str): + def but(i: int, label: str) -> str: if i == default: - id = "defease" + extra = """id="defease" """ else: - id = "" + extra = "" due = self._buttonTime(i, v3_labels=labels) key = ( tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i)) if aqt.mw.pm.get_answer_key(i) else "" ) - return { - "id": id, - "key": key, - "i": i, - "label": label, - "due": due, - } + return """ +""" % ( + extra, + key, + i, + i, + label, + due, + ) - return [but(ease, label) for ease, label in self._answerButtonList()] + buf = "
" + for ease, label in self._answerButtonList(): + buf += but(ease, label) + buf += "
" + return buf def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str: if self.mw.col.conf["estTimes"]: @@ -1191,6 +1227,65 @@ timerStopped = false; onMark = toggle_mark_on_current_note setFlag = set_flag_on_current_card +class SvelteReviewer(Reviewer): + def _answerButtons(self) -> str: + default = self._defaultEase() + + assert isinstance(self.mw.col.sched, V3Scheduler) + labels = self.mw.col.sched.describe_next_states(self._v3.states) + + def but(i: int, label: str): + if i == default: + id = "defease" + else: + id = "" + due = self._buttonTime(i, v3_labels=labels) + key = ( + tr.actions_shortcut_key(val=aqt.mw.pm.get_answer_key(i)) + if aqt.mw.pm.get_answer_key(i) + else "" + ) + return { + "id": id, + "key": key, + "i": i, + "label": label, + "due": due, + } + + return [but(ease, label) for ease, label in self._answerButtonList()] + + def _remaining(self) -> str: + if not self.mw.col.conf["dueCounts"]: + return "" + + idx, counts = self._v3.counts() + self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") + + def _showAnswerButton(self) -> None: + if self.card.should_show_timer(): + maxTime = self.card.time_limit() / 1000 + else: + maxTime = 0 + self._remaining() + self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime)) + + def _linkHandler(self, url: str) -> None: + if url == "bottomReady": + self._remaining() + return + super()._linkHandler(url) + + def _initWeb(self) -> None: + self._reps = 0 + # main window + self.web.load_sveltekit_page("reviewer") + # block default drag & drop behavior while allowing drop events to be received by JS handlers + self.web.allow_drops = True + self.web.eval("_blockDefaultDragDropBehavior();") + # ensure bottom web functions trigger + self.bottom.web = self.web + # if the last element is a comment, then the RUN_STATE_MUTATION code # breaks due to the comment wrongly commenting out python code. From 7cb8e622547900f3c9a42303bdfc2c31e3d6fb4c Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 21:59:46 +0100 Subject: [PATCH 022/108] revert random rust changes --- build/configure/src/web.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index 56ef63bad..3e695e01e 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -1,7 +1,6 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use anyhow::Ok; use anyhow::Result; use ninja_gen::action::BuildAction; use ninja_gen::copy::CopyFiles; @@ -229,6 +228,7 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> { ":sveltekit" ], )?; + Ok(()) } From f01e0f8d0b0133af8a22a95024c9ec05c802801a Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:01:41 +0100 Subject: [PATCH 023/108] toggle svelte reviewer on --- qt/aqt/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c707d1b2a..0c009a16c 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1075,9 +1075,9 @@ title="{}" {}>{}""".format( self.overview = Overview(self) def setupReviewer(self) -> None: - from aqt.reviewer import Reviewer + from aqt.reviewer import Reviewer, SvelteReviewer - self.reviewer = Reviewer(self) + self.reviewer = SvelteReviewer(self) if True else Reviewer(self) # Syncing ########################################################################## From e46d98e2e044ec45f286d3b30e9f968ab6fe5a88 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:05:35 +0100 Subject: [PATCH 024/108] ./check --- build/configure/src/web.rs | 2 +- qt/aqt/data/web/js/reviewer-bottom.ts | 2 +- qt/aqt/main.py | 2 +- qt/aqt/reviewer.py | 1 + ts/routes/reviewer/+page.svelte | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/configure/src/web.rs b/build/configure/src/web.rs index 3e695e01e..ef2d268bb 100644 --- a/build/configure/src/web.rs +++ b/build/configure/src/web.rs @@ -228,7 +228,7 @@ fn build_and_check_pages(build: &mut Build) -> Result<()> { ":sveltekit" ], )?; - + Ok(()) } diff --git a/qt/aqt/data/web/js/reviewer-bottom.ts b/qt/aqt/data/web/js/reviewer-bottom.ts index c11fa2aa2..4ebf34510 100644 --- a/qt/aqt/data/web/js/reviewer-bottom.ts +++ b/qt/aqt/data/web/js/reviewer-bottom.ts @@ -60,4 +60,4 @@ function selectedAnswerButton(): string { return; } return node.dataset.ease; -} \ No newline at end of file +} diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 0c009a16c..3affb3a2e 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1077,7 +1077,7 @@ title="{}" {}>{}""".format( def setupReviewer(self) -> None: from aqt.reviewer import Reviewer, SvelteReviewer - self.reviewer = SvelteReviewer(self) if True else Reviewer(self) + self.reviewer = SvelteReviewer(self) if True else Reviewer(self) # Syncing ########################################################################## diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 6bbc4f986..8cd02813d 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1227,6 +1227,7 @@ timerStopped = false; onMark = toggle_mark_on_current_note setFlag = set_flag_on_current_card + class SvelteReviewer(Reviewer): def _answerButtons(self) -> str: default = self._defaultEase() diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte index 2299e944d..8892f24c8 100644 --- a/ts/routes/reviewer/+page.svelte +++ b/ts/routes/reviewer/+page.svelte @@ -2,4 +2,4 @@ import ReviewerBottomOuter from "./reviewer-bottom/ReviewerBottomOuter.svelte"; - \ No newline at end of file + From 9d3451f97b92b127b392adb35c8734c47531662f Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:07:44 +0100 Subject: [PATCH 025/108] fix py issues --- qt/aqt/reviewer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 8cd02813d..1108b9e8f 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1254,7 +1254,7 @@ class SvelteReviewer(Reviewer): "due": due, } - return [but(ease, label) for ease, label in self._answerButtonList()] + return [but(ease, label) for ease, label in self._answerButtonList()] # type: ignore def _remaining(self) -> str: if not self.mw.col.conf["dueCounts"]: @@ -1262,6 +1262,7 @@ class SvelteReviewer(Reviewer): idx, counts = self._v3.counts() self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") + return "" def _showAnswerButton(self) -> None: if self.card.should_show_timer(): From c64dd6c9593ae2ae2ca20c38868b73eda13a5975 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:29:23 +0100 Subject: [PATCH 026/108] Neaten bottom code --- .../ReviewerBottomOuter.svelte | 83 ++---------------- .../reviewer/reviewer-bottom/reviewer.ts | 86 +++++++++++++++++++ 2 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 ts/routes/reviewer/reviewer-bottom/reviewer.ts diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte index c92e56462..2e95d1965 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte @@ -4,88 +4,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> - +{#if reviewerInfo} + +{/if} diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer.ts b/ts/routes/reviewer/reviewer-bottom/reviewer.ts new file mode 100644 index 000000000..ce25f719c --- /dev/null +++ b/ts/routes/reviewer/reviewer-bottom/reviewer.ts @@ -0,0 +1,86 @@ +import { writable } from "svelte/store"; +import type { AnswerButtonInfo } from "./types"; +import { bridgeCommand } from "@tslib/bridgecommand"; + +export function setupBottomBar() { + /* + let timerStopped = false; + + let maxTime = 0; + + function updateTime(): void { + const timeNode = document.getElementById("time"); + if (maxTime === 0) { + timeNode.textContent = ""; + return; + } + globalThis.time = Math.min(maxTime, globalThis.time); + const m = Math.floor(globalThis.time / 60); + const s = globalThis.time % 60; + const sStr = String(s).padStart(2, "0"); + const timeString = `${m}:${sStr}`; + + if (maxTime === time) { + timeNode.innerHTML = `${timeString}`; + } else { + timeNode.textContent = timeString; + } + }*/ + + const answerButtons = writable([]); + const remaining = writable<[number, number, number]>([0, 0, 0]); + const remainingIndex = writable(-1); + + let intervalId: number | undefined; + + function _showQuestion(_txt: string, _maxTime_: number): void { + _showAnswer([]); + globalThis.time = 0; + // maxTime = maxTime_; + // updateTime(); + + if (intervalId !== undefined) { + clearInterval(intervalId); + } + + /* + intervalId = setInterval(function() { + if (!timerStopped) { + globalThis.time += 1; + //updateTime(); + } + }, 1000);*/ + } + + function _showAnswer(info: AnswerButtonInfo[], _stopTimer = false): void { + console.log(info); + answerButtons.set(info); + // timerStopped = stopTimer; + } + + function _updateRemaining(counts: [number, number, number], idx: number) { + remaining.set(counts); + remainingIndex.set(idx); + } + + globalThis._showQuestion = _showQuestion; + globalThis._showAnswer = _showAnswer; + globalThis._updateRemaining = _updateRemaining; + + /* + function selectedAnswerButton(): string | undefined { + const node = document.activeElement as HTMLElement; + if (!node) { + return; + } + return node.dataset.ease; + } + */ + bridgeCommand("bottomReady"); + + return { + answerButtons, + remaining, + remainingIndex, + }; +} \ No newline at end of file From 7805b1b426bd53a28bb7f89dbd21fbde859f436f Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:34:26 +0100 Subject: [PATCH 027/108] Fix: Wrong function name --- ts/routes/reviewer/reviewer-bottom/reviewer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer.ts b/ts/routes/reviewer/reviewer-bottom/reviewer.ts index ce25f719c..1729f8c2c 100644 --- a/ts/routes/reviewer/reviewer-bottom/reviewer.ts +++ b/ts/routes/reviewer/reviewer-bottom/reviewer.ts @@ -64,7 +64,7 @@ export function setupBottomBar() { } globalThis._showQuestion = _showQuestion; - globalThis._showAnswer = _showAnswer; + globalThis.showAnswer = _showAnswer; globalThis._updateRemaining = _updateRemaining; /* From eac356139c4bdf0e019d32acb0e4e68b213df614 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:48:51 +0100 Subject: [PATCH 028/108] Added: Reviewer framework --- ts/routes/reviewer/+page.svelte | 14 ++++++++++++- .../ReviewerBottomOuter.svelte | 2 +- .../{reviewer.ts => reviewer-bottom.ts} | 0 ts/routes/reviewer/reviewer.svelte | 15 +++++++++++++ ts/routes/reviewer/reviewer.ts | 7 +++++++ ts/routes/reviewer/reviewerOuter.svelte | 21 +++++++++++++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) rename ts/routes/reviewer/reviewer-bottom/{reviewer.ts => reviewer-bottom.ts} (100%) create mode 100644 ts/routes/reviewer/reviewer.svelte create mode 100644 ts/routes/reviewer/reviewer.ts create mode 100644 ts/routes/reviewer/reviewerOuter.svelte diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte index 8892f24c8..2baa1b1f6 100644 --- a/ts/routes/reviewer/+page.svelte +++ b/ts/routes/reviewer/+page.svelte @@ -1,5 +1,17 @@ - +
+ + +
+ + diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte index 2e95d1965..5c66485e0 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte @@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { onMount } from "svelte"; import ReviewerBottom from "./ReviewerBottom.svelte"; import "./index.scss"; - import { setupBottomBar } from "./reviewer"; + import { setupBottomBar } from "./reviewer-bottom"; let reviewerInfo: null | ReturnType = null; diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer.ts b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts similarity index 100% rename from ts/routes/reviewer/reviewer-bottom/reviewer.ts rename to ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte new file mode 100644 index 000000000..85124fb0a --- /dev/null +++ b/ts/routes/reviewer/reviewer.svelte @@ -0,0 +1,15 @@ + + +
+ {@html $html} +
+ + diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts new file mode 100644 index 000000000..866e74d57 --- /dev/null +++ b/ts/routes/reviewer/reviewer.ts @@ -0,0 +1,7 @@ +import { writable } from "svelte/store" + +export function setupReviewer() { + const html = writable("") + + return {html} +} \ No newline at end of file diff --git a/ts/routes/reviewer/reviewerOuter.svelte b/ts/routes/reviewer/reviewerOuter.svelte new file mode 100644 index 000000000..71a818942 --- /dev/null +++ b/ts/routes/reviewer/reviewerOuter.svelte @@ -0,0 +1,21 @@ + + + +{#if reviewerInfo} + +{/if} From d0d1c519e6d08db52a447166734515b8ad963774 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 22:57:36 +0100 Subject: [PATCH 029/108] Naive reviewer --- ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts | 2 +- ts/routes/reviewer/reviewer.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts index 1729f8c2c..267531d2d 100644 --- a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts +++ b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts @@ -63,7 +63,7 @@ export function setupBottomBar() { remainingIndex.set(idx); } - globalThis._showQuestion = _showQuestion; + globalThis.showQuestion = _showQuestion; globalThis.showAnswer = _showAnswer; globalThis._updateRemaining = _updateRemaining; diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 866e74d57..d1f2f77f0 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,7 +1,16 @@ import { writable } from "svelte/store" +import { preloadAnswerImages } from "../../reviewer/images" export function setupReviewer() { const html = writable("") + function showQuestion(q, a, bodyclass) { + html.set(q) + preloadAnswerImages(a) + } + + globalThis._showAnswer = html.set + globalThis._showQuestion = showQuestion + return {html} } \ No newline at end of file From f35b2cf5d219ecc1d3413ea6ac874542f4f27442 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:07:04 +0100 Subject: [PATCH 030/108] bodyclass --- ts/routes/reviewer/reviewer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index d1f2f77f0..48d35f7ba 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -6,6 +6,7 @@ export function setupReviewer() { function showQuestion(q, a, bodyclass) { html.set(q) + document.body.className = bodyclass preloadAnswerImages(a) } From 0acc8d14e0227a8daf1fd016875385d910654177 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:14:19 +0100 Subject: [PATCH 031/108] Fix: Card style --- ts/routes/reviewer/reviewer.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte index 85124fb0a..d01486806 100644 --- a/ts/routes/reviewer/reviewer.svelte +++ b/ts/routes/reviewer/reviewer.svelte @@ -4,12 +4,12 @@ export let html: Writable -
+
{@html $html}
From a08bca267353cc78dbb3ed25be274b8fe2517fdc Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:24:45 +0100 Subject: [PATCH 032/108] dumb initial _showQuestion call --- qt/aqt/reviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 1108b9e8f..d51c7c32c 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1274,6 +1274,7 @@ class SvelteReviewer(Reviewer): def _linkHandler(self, url: str) -> None: if url == "bottomReady": + self._showQuestion() self._remaining() return super()._linkHandler(url) From 45793b3b64763b44522aca59562c9517818805a9 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:36:29 +0100 Subject: [PATCH 033/108] Added: Todo --- ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts index 267531d2d..bb44dad2b 100644 --- a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts +++ b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts @@ -76,6 +76,10 @@ export function setupBottomBar() { return node.dataset.ease; } */ + + // TODO This should probably be a "ready" command now that it is part of the actual reviewer, + // Currently this depends on this component mounting after the reviewer which it should but seems hacky. + // Maybe use a counter with a counter.subscribe($counter == 2 then call("ready")) bridgeCommand("bottomReady"); return { From 2e0a75ed83658ab464b0c2f7ba33d6c4422d9368 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:38:28 +0100 Subject: [PATCH 034/108] fix: "card" class included bottombar --- ts/routes/reviewer/reviewer.svelte | 3 ++- ts/routes/reviewer/reviewer.ts | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte index d01486806..f4fdd37af 100644 --- a/ts/routes/reviewer/reviewer.svelte +++ b/ts/routes/reviewer/reviewer.svelte @@ -2,9 +2,10 @@ import type { Writable } from "svelte/store"; export let html: Writable + export let cardClass: Writable -
+
{@html $html}
diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 48d35f7ba..d7d1fa35f 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -3,15 +3,16 @@ import { preloadAnswerImages } from "../../reviewer/images" export function setupReviewer() { const html = writable("") + const cardClass = writable("") - function showQuestion(q, a, bodyclass) { + function showQuestion(q, a, cc) { html.set(q) - document.body.className = bodyclass + cardClass.set(cc) preloadAnswerImages(a) } globalThis._showAnswer = html.set globalThis._showQuestion = showQuestion - return {html} + return {html, cardClass} } \ No newline at end of file From d775efcb0627db7e3c941ed87359231b059bacaa Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 3 Sep 2025 23:40:37 +0100 Subject: [PATCH 035/108] hide bottomweb --- qt/aqt/reviewer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index d51c7c32c..ccbb43773 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1288,6 +1288,7 @@ class SvelteReviewer(Reviewer): self.web.eval("_blockDefaultDragDropBehavior();") # ensure bottom web functions trigger self.bottom.web = self.web + self.mw.bottomWeb.hide() # if the last element is a comment, then the RUN_STATE_MUTATION code From c7fd7a096579f0d8e0996940cb158b6e2c7107d5 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 4 Sep 2025 00:25:22 +0100 Subject: [PATCH 036/108] re-add reviewer-bottom to build script --- build/configure/src/aqt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/configure/src/aqt.rs b/build/configure/src/aqt.rs index 183b7f0b3..83be77e91 100644 --- a/build/configure/src/aqt.rs +++ b/build/configure/src/aqt.rs @@ -170,7 +170,7 @@ fn build_imgs(build: &mut Build) -> Result<()> { } fn build_js(build: &mut Build) -> Result<()> { - for ts_file in &["deckbrowser", "webview", "toolbar"] { + for ts_file in &["deckbrowser", "webview", "toolbar", "reviewer-bottom"] { build.add_action( "qt:aqt:data:web:js", EsbuildScript { From 1e67a773c6e21dc9938d9e148f02452b94cbf8be Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 4 Sep 2025 00:26:10 +0100 Subject: [PATCH 037/108] ./check --- .../ReviewerBottomOuter.svelte | 2 +- .../reviewer-bottom/reviewer-bottom.ts | 6 ++--- ts/routes/reviewer/reviewer.svelte | 6 ++--- ts/routes/reviewer/reviewer.ts | 22 +++++++++---------- ts/routes/reviewer/reviewerOuter.svelte | 4 ++-- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte index 5c66485e0..59747cc5d 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottomOuter.svelte @@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let reviewerInfo: null | ReturnType = null; onMount(() => { - reviewerInfo = setupBottomBar() + reviewerInfo = setupBottomBar(); }); diff --git a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts index bb44dad2b..deedbd4bd 100644 --- a/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts +++ b/ts/routes/reviewer/reviewer-bottom/reviewer-bottom.ts @@ -1,6 +1,6 @@ +import { bridgeCommand } from "@tslib/bridgecommand"; import { writable } from "svelte/store"; import type { AnswerButtonInfo } from "./types"; -import { bridgeCommand } from "@tslib/bridgecommand"; export function setupBottomBar() { /* @@ -77,7 +77,7 @@ export function setupBottomBar() { } */ - // TODO This should probably be a "ready" command now that it is part of the actual reviewer, + // TODO This should probably be a "ready" command now that it is part of the actual reviewer, // Currently this depends on this component mounting after the reviewer which it should but seems hacky. // Maybe use a counter with a counter.subscribe($counter == 2 then call("ready")) bridgeCommand("bottomReady"); @@ -87,4 +87,4 @@ export function setupBottomBar() { remaining, remainingIndex, }; -} \ No newline at end of file +} diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte index f4fdd37af..aee74e922 100644 --- a/ts/routes/reviewer/reviewer.svelte +++ b/ts/routes/reviewer/reviewer.svelte @@ -1,8 +1,8 @@
@@ -11,6 +11,6 @@ diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index d7d1fa35f..c2f4ea781 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,18 +1,18 @@ -import { writable } from "svelte/store" -import { preloadAnswerImages } from "../../reviewer/images" +import { writable } from "svelte/store"; +import { preloadAnswerImages } from "../../reviewer/images"; export function setupReviewer() { - const html = writable("") - const cardClass = writable("") + const html = writable(""); + const cardClass = writable(""); function showQuestion(q, a, cc) { - html.set(q) - cardClass.set(cc) - preloadAnswerImages(a) + html.set(q); + cardClass.set(cc); + preloadAnswerImages(a); } - globalThis._showAnswer = html.set - globalThis._showQuestion = showQuestion + globalThis._showAnswer = html.set; + globalThis._showQuestion = showQuestion; - return {html, cardClass} -} \ No newline at end of file + return { html, cardClass }; +} diff --git a/ts/routes/reviewer/reviewerOuter.svelte b/ts/routes/reviewer/reviewerOuter.svelte index 71a818942..c261675f7 100644 --- a/ts/routes/reviewer/reviewerOuter.svelte +++ b/ts/routes/reviewer/reviewerOuter.svelte @@ -7,12 +7,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { setupReviewer } from "./reviewer"; import Reviewer from "./reviewer.svelte"; - import "../../reviewer/reviewer.scss" + import "../../reviewer/reviewer.scss"; let reviewerInfo: null | ReturnType = null; onMount(() => { - reviewerInfo = setupReviewer() + reviewerInfo = setupReviewer(); }); From da90e3c718cfda966e9aab7b3a1814b09348e993 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 22 Sep 2025 01:23:44 +0100 Subject: [PATCH 038/108] iframe poc --- ts/routes/reviewer/reviewer.svelte | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte index aee74e922..0cd281607 100644 --- a/ts/routes/reviewer/reviewer.svelte +++ b/ts/routes/reviewer/reviewer.svelte @@ -1,16 +1,40 @@
- {@html $html} +
From f68f5284a564b7787262f66d6b9ec23878e83623 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 22 Sep 2025 02:26:03 +0100 Subject: [PATCH 039/108] Fix: showQuestion _showAnswerButton --- qt/aqt/reviewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index ccbb43773..65ebbbbde 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1270,7 +1270,7 @@ class SvelteReviewer(Reviewer): else: maxTime = 0 self._remaining() - self.bottom.web.eval("_showQuestion(%s,%d);" % ("", maxTime)) + self.bottom.web.eval("showQuestion(\"\",%d);" % (maxTime)) def _linkHandler(self, url: str) -> None: if url == "bottomReady": From ccdc391097c57f873bef80e618e8f175e4bb8132 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Sun, 28 Sep 2025 00:43:02 +0100 Subject: [PATCH 040/108] Remove html writable --- qt/aqt/reviewer.py | 2 +- ts/routes/reviewer/+page.svelte | 4 ++-- ts/routes/reviewer/reviewer.svelte | 22 ++----------------- ts/routes/reviewer/reviewer.ts | 29 ++++++++++++++++++++----- ts/routes/reviewer/reviewerOuter.svelte | 21 ------------------ 5 files changed, 29 insertions(+), 49 deletions(-) delete mode 100644 ts/routes/reviewer/reviewerOuter.svelte diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 65ebbbbde..dfe00eff8 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1270,7 +1270,7 @@ class SvelteReviewer(Reviewer): else: maxTime = 0 self._remaining() - self.bottom.web.eval("showQuestion(\"\",%d);" % (maxTime)) + self.bottom.web.eval('showQuestion("",%d);' % (maxTime)) def _linkHandler(self, url: str) -> None: if url == "bottomReady": diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte index 2baa1b1f6..d89a8dc6c 100644 --- a/ts/routes/reviewer/+page.svelte +++ b/ts/routes/reviewer/+page.svelte @@ -1,10 +1,10 @@
- +
diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/reviewer.svelte index 0cd281607..921fd2248 100644 --- a/ts/routes/reviewer/reviewer.svelte +++ b/ts/routes/reviewer/reviewer.svelte @@ -1,27 +1,9 @@
diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index c2f4ea781..34288b1e3 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,18 +1,37 @@ import { writable } from "svelte/store"; +import { isNightMode } from "../../html-filter/helpers"; import { preloadAnswerImages } from "../../reviewer/images"; -export function setupReviewer() { - const html = writable(""); +export function setupReviewer(iframe: HTMLIFrameElement) { const cardClass = writable(""); + function updateHtml(htmlString) { + if (iframe?.contentDocument) { + const nightMode = isNightMode(); + iframe.contentDocument.body.innerHTML = htmlString; + iframe.contentDocument.head.innerHTML = document.head.innerHTML; + iframe.contentDocument.body.className = nightMode + ? "nightMode card" + : "card"; + const root = iframe.contentDocument.querySelector("html")!; + root.className = nightMode + ? "night-mode" + : ""; + root.setAttribute("data-bs-theme", nightMode ? "dark" : "light"); + // @ts-ignore + iframe.contentDocument.pycmd = bridgeCommand; + } + } + function showQuestion(q, a, cc) { - html.set(q); + updateHtml(q); + // html.set(q); cardClass.set(cc); preloadAnswerImages(a); } - globalThis._showAnswer = html.set; + globalThis._showAnswer = updateHtml; globalThis._showQuestion = showQuestion; - return { html, cardClass }; + return { cardClass }; } diff --git a/ts/routes/reviewer/reviewerOuter.svelte b/ts/routes/reviewer/reviewerOuter.svelte deleted file mode 100644 index c261675f7..000000000 --- a/ts/routes/reviewer/reviewerOuter.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - -{#if reviewerInfo} - -{/if} From 5998aa913d1fa42cec7ea40f61bdc67f937b22d8 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Sun, 28 Sep 2025 00:44:37 +0100 Subject: [PATCH 041/108] reviewer.svelte -> Reviewer.svelte --- ts/routes/reviewer/+page.svelte | 2 +- ts/routes/reviewer/{reviewer.svelte => Reviewer.svelte} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename ts/routes/reviewer/{reviewer.svelte => Reviewer.svelte} (100%) diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte index d89a8dc6c..4849370c7 100644 --- a/ts/routes/reviewer/+page.svelte +++ b/ts/routes/reviewer/+page.svelte @@ -1,6 +1,6 @@
diff --git a/ts/routes/reviewer/reviewer.svelte b/ts/routes/reviewer/Reviewer.svelte similarity index 100% rename from ts/routes/reviewer/reviewer.svelte rename to ts/routes/reviewer/Reviewer.svelte From 654701da4d860301923f788ad5c525e783aac080 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Sun, 28 Sep 2025 01:47:30 +0100 Subject: [PATCH 042/108] reviewer-inner poc --- qt/aqt/mediasrv.py | 1 + ts/routes/reviewer-inner/+page.svelte | 30 +++++++++++++++++ ts/routes/reviewer/Reviewer.svelte | 7 +++- .../reviewer-bottom/reviewer-bottom.ts | 5 --- ts/routes/reviewer/reviewer.ts | 33 ++++++++++--------- 5 files changed, 54 insertions(+), 22 deletions(-) create mode 100644 ts/routes/reviewer-inner/+page.svelte diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 83f45c933..eda971121 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -335,6 +335,7 @@ def is_sveltekit_page(path: str) -> bool: "import-page", "image-occlusion", "reviewer", + "reviewer-inner", ] diff --git a/ts/routes/reviewer-inner/+page.svelte b/ts/routes/reviewer-inner/+page.svelte new file mode 100644 index 000000000..060424096 --- /dev/null +++ b/ts/routes/reviewer-inner/+page.svelte @@ -0,0 +1,30 @@ + diff --git a/ts/routes/reviewer/Reviewer.svelte b/ts/routes/reviewer/Reviewer.svelte index 921fd2248..4383a2227 100644 --- a/ts/routes/reviewer/Reviewer.svelte +++ b/ts/routes/reviewer/Reviewer.svelte @@ -7,7 +7,12 @@
- +
\ No newline at end of file diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte index 9dd6160b7..f6360bdbe 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte @@ -28,28 +28,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
- - - {$remaining[0]} - + - - {$remaining[1]} - + - - {$remaining[2]} - - -
- {#if $answerButtons.length} - {#each $answerButtons as answerButton} - - {/each} - {:else} - - {/if} -
+ {#if $answerButtons.length} + {#each $answerButtons as answerButton} + + {/each} + {:else} + + + {$remaining[0]} + + + + {$remaining[1]} + + + + {$remaining[2]} + + + + {/if}
{/if}
-
+
-
- {#if $answerButtons.length} - {#each $answerButtons as answerButton} - - {/each} - {:else} - - - {$remaining[0]} - + - - {$remaining[1]} - + - - {$remaining[2]} - - - - {/if} -
-
+ {#if answerShown} + {#each $answerButtons as answerButton} + + {/each} + {:else} + + + {$remaining[0]} + + + + {$remaining[1]} + + + + {$remaining[2]} + + + + {/if} + +
diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte index a2224d2a0..8f978a5cd 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte @@ -8,17 +8,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import AnswerButton from "./AnswerButton.svelte"; import { bridgeCommand } from "@tslib/bridgecommand"; import * as tr from "@generated/ftl"; - import RemainingNumber from "./RemainingNumber.svelte"; import type { ReviewerState } from "../reviewer"; - import { writable } from "svelte/store"; + import Remaining from "./Remaining.svelte"; export let state: ReviewerState; const answerButtons = state.answerButtons; const answerShown = state.answerShown; - // Placeholders - const remaining = writable([0, 0, 0]); - const remainingIndex = writable(0); $: button_count = $answerShown ? $answerButtons.length : 1; @@ -39,17 +35,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {/each} {:else} - - - {$remaining[0]} - + - - {$remaining[1]} - + - - {$remaining[2]} - - + @@ -78,10 +64,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html grid-auto-flow: column; } - .remaining-count { - text-align: center; - } - .more, .edit { width: 100%; diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index f71f30347..8bee7a23a 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,20 +1,17 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import { - CardAnswer, - type NextCardDataResponse_AnswerButton, - type NextCardDataResponse_NextCardData, -} from "@generated/anki/scheduler_pb"; +import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; import { nextCardData } from "@generated/backend"; -import { writable } from "svelte/store"; +import { derived, writable } from "svelte/store"; export class ReviewerState { answerHtml = ""; - cardData: NextCardDataResponse_NextCardData | undefined = undefined; + _cardData: NextCardDataResponse_NextCardData | undefined = undefined; beginAnsweringMs = Date.now(); readonly cardClass = writable(""); - readonly answerButtons = writable([]); readonly answerShown = writable(false); + readonly cardData = writable(undefined); + readonly answerButtons = derived(this.cardData, ($cardData) => $cardData?.answerButtons ?? []); iframe: HTMLIFrameElement | undefined = undefined; @@ -36,22 +33,25 @@ export class ReviewerState { const resp = await nextCardData({ answer: answer || undefined, }); + // TODO: "Congratulation screen" logic - this.cardData = resp.nextCard; - this.answerButtons.set(this.cardData?.answerButtons ?? []); - const question = resp.nextCard?.front || ""; + this._cardData = resp.nextCard; + this.cardData.set(this._cardData); this.answerShown.set(false); + + const question = resp.nextCard?.front || ""; this.updateHtml(question); + this.beginAnsweringMs = Date.now(); } get currentCard() { - return this.cardData?.queue?.cards[0]; + return this._cardData?.queue?.cards[0]; } public showAnswer() { this.answerShown.set(true); - this.updateHtml(this.cardData?.back || ""); + this.updateHtml(this._cardData?.back || ""); } public easeButtonPressed(rating: number) { From 9550b127d9e290ba191d4c1f8430d574f4a898c1 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 7 Oct 2025 01:10:25 +0100 Subject: [PATCH 067/108] Basic fix for shortcuts --- qt/aqt/reviewer.py | 3 +++ ts/routes/reviewer/Reviewer.svelte | 1 + ts/routes/reviewer/reviewer.ts | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index a7366a258..8a82a9af6 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1298,6 +1298,9 @@ class SvelteReviewer(Reviewer): self.bottom.web = self.web self.mw.bottomWeb.hide() + def _shortcutKeys(self) -> Sequence[tuple[str, Callable] | tuple[Qt.Key, Callable]]: + return [] + # if the last element is a comment, then the RUN_STATE_MUTATION code # breaks due to the comment wrongly commenting out python code. diff --git a/ts/routes/reviewer/Reviewer.svelte b/ts/routes/reviewer/Reviewer.svelte index 4f7bc0536..f136a761f 100644 --- a/ts/routes/reviewer/Reviewer.svelte +++ b/ts/routes/reviewer/Reviewer.svelte @@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: if (iframe) { state.registerIFrame(iframe); + state.registerShortcuts(); } diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 8bee7a23a..45cdab145 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -25,6 +25,35 @@ export class ReviewerState { iframe.addEventListener("load", this.onReady.bind(this)); } + onKeyDown(e: KeyboardEvent) { + switch (e.key) { + case "1": { + this.easeButtonPressed(0); + break; + } + case "2": { + this.easeButtonPressed(1); + break; + } + case "3": { + this.easeButtonPressed(2); + break; + } + case "4": { + this.easeButtonPressed(3); + break; + } + case " ": { + this.showAnswer(); + break; + } + } + } + + public registerShortcuts() { + document.addEventListener("keydown", this.onKeyDown.bind(this)); + } + updateHtml(htmlString: string) { this.iframe?.contentWindow?.postMessage({ type: "html", value: htmlString }, "*"); } From b80dc25999a2b44bef170572a0183386d898920b Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 7 Oct 2025 01:22:31 +0100 Subject: [PATCH 068/108] Added: shown check for ease function --- ts/routes/reviewer/reviewer.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 45cdab145..04de5a9af 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -2,7 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; import { nextCardData } from "@generated/backend"; -import { derived, writable } from "svelte/store"; +import { derived, get, writable } from "svelte/store"; export class ReviewerState { answerHtml = ""; @@ -44,7 +44,11 @@ export class ReviewerState { break; } case " ": { - this.showAnswer(); + if (!get(this.answerShown)) { + this.showAnswer(); + } else { + this.easeButtonPressed(2); + } break; } } @@ -84,6 +88,10 @@ export class ReviewerState { } public easeButtonPressed(rating: number) { + if (!get(this.answerShown)) { + return; + } + const states = this.currentCard!.states!; const newState = [ From a48dcb0bb5444fe55531cdf2c9a57a7140ed3dde Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 7 Oct 2025 13:11:12 +0100 Subject: [PATCH 069/108] encode_iri_paths --- rslib/src/scheduler/service/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index 160ff8ba4..1bcc35b68 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -34,6 +34,7 @@ use crate::scheduler::states::CardState; use crate::scheduler::states::SchedulingStates; use crate::search::SortMode; use crate::stats::studied_today; +use crate::text::encode_iri_paths; impl crate::services::SchedulerService for Collection { /// This behaves like _updateCutoff() in older code - it also unburies at @@ -409,11 +410,17 @@ impl crate::services::SchedulerService for Collection { }) .collect(); + let prepare_card_text_for_display = |html: &str| { + let html = [style.clone(), html.to_string()].concat(); + let html = encode_iri_paths(&html).to_string(); + html + }; + Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), - front: [style.clone(), render.question().to_string()].concat(), - back: [style, render.answer().to_string()].concat(), + front: prepare_card_text_for_display(&render.question()), + back: prepare_card_text_for_display(&render.answer()), answer_buttons, }), From b77ee225e5e1ecfe96972aa343798c363b815bce Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 10 Oct 2025 20:33:22 +0100 Subject: [PATCH 070/108] Fix: Night mode --- ts/routes/reviewer-inner/index.ts | 23 ++++++++--------------- ts/routes/reviewer/+page.svelte | 6 +++++- ts/routes/reviewer/Reviewer.svelte | 4 ++-- ts/routes/reviewer/reviewer.ts | 20 +++++++++++++++++++- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 7bd319339..7480589de 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -2,6 +2,7 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import "../base.scss"; import "../../reviewer/reviewer.scss"; +import { enableNightMode } from "../reviewer/reviewer"; addEventListener("message", (e) => { switch (e.data.type) { @@ -9,21 +10,6 @@ addEventListener("message", (e) => { document.body.innerHTML = e.data.value; break; } - case "nightMode": { - // This method currently "Flashbangs" the user if they have nightmode on and is a placeholder - // I will probably use #night-mode in the url. - const root = document.querySelector("html")!; - const nightMode = e.data.value; - if (e.data.value) { - root.classList.add("night-mode"); - } else { - root.classList.remove("night-mode"); - } - document.body.className = nightMode ? "nightMode card" : "card"; - root.className = nightMode ? "night-mode" : ""; - root.setAttribute("data-bs-theme", nightMode ? "dark" : "light"); - break; - } default: { console.warn(`Unknown message type: ${e.data.type}`); break; @@ -39,3 +25,10 @@ function pycmd(cmd: string) { window.parent.postMessage({ type: "pycmd", value: cmd }, "*"); } globalThis.pycmd = pycmd; + +const params = new URLSearchParams(location.search); +const theme = params.get("nightMode"); +if (theme !== null) { + enableNightMode(); +} +document.documentElement.classList.add("card"); diff --git a/ts/routes/reviewer/+page.svelte b/ts/routes/reviewer/+page.svelte index 8e037789e..2e9053f07 100644 --- a/ts/routes/reviewer/+page.svelte +++ b/ts/routes/reviewer/+page.svelte @@ -3,11 +3,15 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
diff --git a/ts/routes/reviewer/Reviewer.svelte b/ts/routes/reviewer/Reviewer.svelte index f136a761f..6a71d5d4b 100644 --- a/ts/routes/reviewer/Reviewer.svelte +++ b/ts/routes/reviewer/Reviewer.svelte @@ -3,7 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> From 9085154e3d12976ea4678610e0d24116ec839ad0 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 22 Oct 2025 18:15:16 +0100 Subject: [PATCH 078/108] Added: Placeholder more menu Until I can access ContextMenu.svelte from the other pr. --- .../reviewer/reviewer-bottom/More.svelte | 59 +++++++++++++++++++ .../reviewer/reviewer-bottom/MoreItem.svelte | 14 +++++ .../reviewer-bottom/MoreSubmenu.svelte | 22 +++++++ .../reviewer-bottom/ReviewerBottom.svelte | 8 +-- 4 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 ts/routes/reviewer/reviewer-bottom/More.svelte create mode 100644 ts/routes/reviewer/reviewer-bottom/MoreItem.svelte create mode 100644 ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte diff --git a/ts/routes/reviewer/reviewer-bottom/More.svelte b/ts/routes/reviewer/reviewer-bottom/More.svelte new file mode 100644 index 000000000..a1a72611f --- /dev/null +++ b/ts/routes/reviewer/reviewer-bottom/More.svelte @@ -0,0 +1,59 @@ + + + + + + +
+ + { + showFlags = !showFlags; + }} + > + {tr.studyingFlagCard()} + +
+ {#each flags as flag} + {flag.colour} + {/each} +
+
+
+
+ + diff --git a/ts/routes/reviewer/reviewer-bottom/MoreItem.svelte b/ts/routes/reviewer/reviewer-bottom/MoreItem.svelte new file mode 100644 index 000000000..88a0f4b09 --- /dev/null +++ b/ts/routes/reviewer/reviewer-bottom/MoreItem.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte b/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte new file mode 100644 index 000000000..f017bfa15 --- /dev/null +++ b/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte @@ -0,0 +1,22 @@ + + + +
+ (showFloating = false)}> + + + + + + +
diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte index 8f978a5cd..6537df95f 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte @@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import * as tr from "@generated/ftl"; import type { ReviewerState } from "../reviewer"; import Remaining from "./Remaining.svelte"; + import More from "./More.svelte"; export let state: ReviewerState; @@ -42,12 +43,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {/if}
- +
From b2fe07462ac4d7713119eef6fbdaf34e5db91479 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 22 Oct 2025 18:36:42 +0100 Subject: [PATCH 079/108] Added: Context menu flag setting --- qt/aqt/mediasrv.py | 5 ++++- ts/routes/reviewer/reviewer-bottom/More.svelte | 18 +++++++++++++++--- .../reviewer-bottom/MoreSubmenu.svelte | 2 -- .../reviewer-bottom/ReviewerBottom.svelte | 2 +- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index d69e04610..9a57741de 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -745,6 +745,8 @@ exposed_backend_list = [ # DeckConfigService "get_ignored_before_count", "get_retention_workload", + # CardsService + "set_flag", ] @@ -812,8 +814,9 @@ def _check_dynamic_request_permissions(): "/_anki/setSchedulingStates", "/_anki/i18nResources", "/_anki/congratsInfo", - # TODO: Unsure about this + # TODO: Correctly set the auth token "/_anki/nextCardData", + "/_anki/setFlag", ): pass else: diff --git a/ts/routes/reviewer/reviewer-bottom/More.svelte b/ts/routes/reviewer/reviewer-bottom/More.svelte index a1a72611f..3ba88f0ad 100644 --- a/ts/routes/reviewer/reviewer-bottom/More.svelte +++ b/ts/routes/reviewer/reviewer-bottom/More.svelte @@ -7,11 +7,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import * as tr from "@generated/ftl"; import MoreSubmenu from "./MoreSubmenu.svelte"; import MoreItem from "./MoreItem.svelte"; + import { setFlag } from "@generated/backend"; + import type { ReviewerState } from "../reviewer"; let showFloating = false; let showFlags = false; + export let state: ReviewerState; - let flags = [ + const flags = [ { colour: tr.actionsFlagRed(), shortcut: "Ctrl+1" }, { colour: tr.actionsFlagOrange(), shortcut: "Ctrl+2" }, { colour: tr.actionsFlagGreen(), shortcut: "Ctrl+3" }, @@ -20,6 +23,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html { colour: tr.actionsFlagTurquoise(), shortcut: "Ctrl+6" }, { colour: tr.actionsFlagPurple(), shortcut: "Ctrl+7" }, ]; + + function changeFlag(index: number) { + setFlag({ cardIds: [state.currentCard!.card!.id], flag: index }); + } @@ -44,8 +51,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {tr.studyingFlagCard()}
- {#each flags as flag} - {flag.colour} + {#each flags as flag, i} + changeFlag(i + 1)} + > + {flag.colour} + {/each}
diff --git a/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte b/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte index f017bfa15..463a66b38 100644 --- a/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte +++ b/ts/routes/reviewer/reviewer-bottom/MoreSubmenu.svelte @@ -3,10 +3,8 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> diff --git a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte index 6537df95f..7941ec83b 100644 --- a/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte +++ b/ts/routes/reviewer/reviewer-bottom/ReviewerBottom.svelte @@ -43,7 +43,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {/if}
- +
From fc880259a81c536108b4fd4332eca04c86a54152 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 24 Oct 2025 09:58:43 +0100 Subject: [PATCH 080/108] Give main webview api access --- qt/aqt/mediasrv.py | 3 --- qt/aqt/webview.py | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 9a57741de..ca8bea681 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -814,9 +814,6 @@ def _check_dynamic_request_permissions(): "/_anki/setSchedulingStates", "/_anki/i18nResources", "/_anki/congratsInfo", - # TODO: Correctly set the auth token - "/_anki/nextCardData", - "/_anki/setFlag", ): pass else: diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 95d84c00e..fefbae17c 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -142,6 +142,7 @@ class AnkiWebPage(QWebEnginePage): AnkiWebViewKind.IMPORT_ANKI_PACKAGE, AnkiWebViewKind.IMPORT_CSV, AnkiWebViewKind.IMPORT_LOG, + AnkiWebViewKind.MAIN, ) global _profile_with_api_access, _profile_without_api_access From 3cf58419d7269c5bd43f0c240f8b21c5e8d0e4e3 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 24 Oct 2025 10:44:31 +0100 Subject: [PATCH 081/108] Fix: More button shrunk slightly --- ts/routes/reviewer/reviewer-bottom/More.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/routes/reviewer/reviewer-bottom/More.svelte b/ts/routes/reviewer/reviewer-bottom/More.svelte index 3ba88f0ad..d6783e2c6 100644 --- a/ts/routes/reviewer/reviewer-bottom/More.svelte +++ b/ts/routes/reviewer/reviewer-bottom/More.svelte @@ -68,4 +68,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html div :global(button) { width: fit-content; } + + button { + line-height: 18px; + } From cd5f25469ef9705e5fdf459d063be526258eae57 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 27 Oct 2025 11:21:03 +0000 Subject: [PATCH 082/108] MathJax Problems with font cors (I'm shocked that using the import with the .js worked without a hitch O_O) --- ts/reviewer/index.ts | 2 +- ts/routes/reviewer-inner/index.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ts/reviewer/index.ts b/ts/reviewer/index.ts index d0370cbc9..63771f0b0 100644 --- a/ts/reviewer/index.ts +++ b/ts/reviewer/index.ts @@ -104,7 +104,7 @@ async function setInnerHTML(element: Element, html: string): Promise { } } -const renderError = (type: string) => (error: unknown): string => { +export const renderError = (type: string) => (error: unknown): string => { const errorMessage = String(error).substring(0, 2000); let errorStack: string; if (error instanceof Error) { diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 5304cb344..091dcf696 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -2,15 +2,18 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import "../base.scss"; import "../../reviewer/reviewer.scss"; +import "mathjax/es5/tex-chtml-full.js"; +import { renderError } from "../../reviewer"; import { enableNightMode } from "../reviewer/reviewer"; import type { InnerReviewerRequest } from "./reviewerRequest"; +declare const MathJax: any; const urlParams = new URLSearchParams(location.search); const style = document.createElement("style"); document.head.appendChild(style); -addEventListener("message", (e: MessageEvent) => { +addEventListener("message", async (e: MessageEvent) => { switch (e.data.type) { case "html": { document.body.innerHTML = e.data.value; @@ -26,6 +29,17 @@ addEventListener("message", (e: MessageEvent) => { document.body.classList.add("nightMode"); } } + + // wait for mathjax to ready + await MathJax.startup.promise + .then(() => { + // clear MathJax buffers from previous typesets + MathJax.typesetClear(); + + return MathJax.typesetPromise([document.body]); + }) + .catch(renderError("MathJax")); + break; } default: { From 385d487220dc1bf8d53e423d52542bb59dce4bac Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 29 Oct 2025 14:16:15 +0000 Subject: [PATCH 083/108] Fix/Missing python import in write_header --- rslib/proto/python.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rslib/proto/python.rs b/rslib/proto/python.rs index a5adb4179..a2aa09707 100644 --- a/rslib/proto/python.rs +++ b/rslib/proto/python.rs @@ -249,6 +249,7 @@ import anki.stats_pb2 import anki.sync_pb2 import anki.tags_pb2 import anki.ankihub_pb2 +import anki.frontend_pb2 class RustBackendGenerated: def _run_command(self, service: int, method: int, input: Any) -> bytes: From 386133743e705c5f258b9b54b71c5b5861b48bd4 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 29 Oct 2025 15:29:38 +0000 Subject: [PATCH 084/108] Added: Playaudio endpoint --- proto/anki/frontend.proto | 8 ++++++++ qt/aqt/mediasrv.py | 11 ++++++++++- qt/aqt/sound.py | 12 ++++++++---- ts/routes/reviewer-inner/index.ts | 6 +++++- ts/routes/reviewer/reviewer.ts | 12 +++++++++++- 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index 1d733a369..1f6b23101 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -30,6 +30,8 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); + + rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); } service BackendFrontendService {} @@ -43,3 +45,9 @@ message SetSchedulingStatesRequest { string key = 1; scheduler.SchedulingStates states = 2; } + +message PlayAudioRequest { + bool answer_side = 1; + uint32 index = 2; + uint64 cid = 3; +} \ No newline at end of file diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index ca8bea681..c8e145f1d 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -28,9 +28,10 @@ import aqt import aqt.main import aqt.operations from anki import hooks -from anki.cards import Card +from anki.cards import Card, CardId from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs +from anki.frontend_pb2 import PlayAudioRequest from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest from anki.scheduler_pb2 import NextCardDataResponse from anki.template import ( @@ -45,6 +46,7 @@ from aqt.operations import on_op_finished from aqt.operations.deck import update_deck_configs as update_deck_configs_op from aqt.progress import ProgressUpdate from aqt.qt import * +from aqt.sound import play_clicked_audio_with_index from aqt.theme import ThemeManager from aqt.utils import aqt_data_path, show_warning, tr @@ -683,6 +685,12 @@ def next_card_data() -> bytes: return data.SerializeToString() +def play_audio(): + req = PlayAudioRequest.FromString(request.data) + card = aqt.mw.col.get_card(CardId(req.cid)) + play_clicked_audio_with_index(req.index, req.answer_side, card) + + post_handler_list = [ congrats_info, get_deck_configs_for_update, @@ -700,6 +708,7 @@ post_handler_list = [ deck_options_ready, save_custom_colours, next_card_data, + play_audio, ] diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index f54ebd3e8..c7896b34b 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -925,11 +925,15 @@ def play_clicked_audio(pycmd: str, card: Card) -> None: """eg. if pycmd is 'play:q:0', play the first audio on the question side.""" play, context, str_idx = pycmd.split(":") idx = int(str_idx) - if context == "q": - tags = card.question_av_tags() - else: + play_clicked_audio_with_index(idx, context == "q", card) + + +def play_clicked_audio_with_index(index: int, answer_side: bool, card: Card): + if answer_side: tags = card.answer_av_tags() - av_player.play_tags([tags[idx]]) + else: + tags = card.question_av_tags() + av_player.play_tags([tags[index]]) # Init defaults diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 091dcf696..b80ccabbc 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -54,6 +54,10 @@ base.href = "/"; document.head.appendChild(base); function pycmd(cmd: string) { - window.parent.postMessage({ type: "pycmd", value: cmd }, "*"); + const match = cmd.match(/play:(q|a):(\d+)/); + if (match) { + const [_, context, index] = match; + window.parent.postMessage({ type: "audio", answerSide: context == "a", index: parseInt(index) }, "*"); + } } globalThis.pycmd = pycmd; diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 6b466663b..a9e0c2b9d 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; -import { nextCardData } from "@generated/backend"; +import { nextCardData, playAudio } from "@generated/backend"; import { derived, get, writable } from "svelte/store"; import type { InnerReviewerRequest } from "../reviewer-inner/reviewerRequest"; @@ -37,6 +37,16 @@ export class ReviewerState { onReady() { this.showQuestion(null); + addEventListener("message", this.onMessage.bind(this)); + } + + onMessage(e: MessageEvent) { + switch (e.data.type) { + case "audio": { + playAudio({ answerSide: e.data.answerSide, index: e.data.index, cid: this.currentCard!.card!.id }); + break; + } + } } public registerIFrame(iframe: HTMLIFrameElement) { From 246e6f6df630102f630c4c8586d64a721930eb2e Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Wed, 29 Oct 2025 15:48:21 +0000 Subject: [PATCH 085/108] Added: "ReviewerRequest" type --- ts/routes/reviewer-inner/index.ts | 8 ++++++-- .../{reviewerRequest.ts => innerReviewerRequest.ts} | 0 ts/routes/reviewer/reviewer.ts | 5 +++-- ts/routes/reviewer/reviewerRequest.ts | 9 +++++++++ 4 files changed, 18 insertions(+), 4 deletions(-) rename ts/routes/reviewer-inner/{reviewerRequest.ts => innerReviewerRequest.ts} (100%) create mode 100644 ts/routes/reviewer/reviewerRequest.ts diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index b80ccabbc..dc32fac87 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -5,7 +5,8 @@ import "../../reviewer/reviewer.scss"; import "mathjax/es5/tex-chtml-full.js"; import { renderError } from "../../reviewer"; import { enableNightMode } from "../reviewer/reviewer"; -import type { InnerReviewerRequest } from "./reviewerRequest"; +import type { ReviewerRequest } from "../reviewer/reviewerRequest"; +import type { InnerReviewerRequest } from "./innerReviewerRequest"; declare const MathJax: any; const urlParams = new URLSearchParams(location.search); @@ -57,7 +58,10 @@ function pycmd(cmd: string) { const match = cmd.match(/play:(q|a):(\d+)/); if (match) { const [_, context, index] = match; - window.parent.postMessage({ type: "audio", answerSide: context == "a", index: parseInt(index) }, "*"); + window.parent.postMessage( + { type: "audio", answerSide: context == "a", index: parseInt(index) } satisfies ReviewerRequest, + "*", + ); } } globalThis.pycmd = pycmd; diff --git a/ts/routes/reviewer-inner/reviewerRequest.ts b/ts/routes/reviewer-inner/innerReviewerRequest.ts similarity index 100% rename from ts/routes/reviewer-inner/reviewerRequest.ts rename to ts/routes/reviewer-inner/innerReviewerRequest.ts diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index a9e0c2b9d..d22f9482d 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -3,7 +3,8 @@ import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; import { nextCardData, playAudio } from "@generated/backend"; import { derived, get, writable } from "svelte/store"; -import type { InnerReviewerRequest } from "../reviewer-inner/reviewerRequest"; +import type { InnerReviewerRequest } from "../reviewer-inner/innerReviewerRequest"; +import type { ReviewerRequest } from "./reviewerRequest"; export function isNightMode() { // https://stackoverflow.com/a/57795518 @@ -40,7 +41,7 @@ export class ReviewerState { addEventListener("message", this.onMessage.bind(this)); } - onMessage(e: MessageEvent) { + onMessage(e: MessageEvent) { switch (e.data.type) { case "audio": { playAudio({ answerSide: e.data.answerSide, index: e.data.index, cid: this.currentCard!.card!.id }); diff --git a/ts/routes/reviewer/reviewerRequest.ts b/ts/routes/reviewer/reviewerRequest.ts new file mode 100644 index 000000000..aa4d3c786 --- /dev/null +++ b/ts/routes/reviewer/reviewerRequest.ts @@ -0,0 +1,9 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +interface AudioMessage { + type: "audio"; + answerSide: boolean; + index: number; +} + +export type ReviewerRequest = AudioMessage; From 28d41c8a9cd344bfeb09e095f503a72aa8b931a3 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 30 Oct 2025 15:49:01 +0000 Subject: [PATCH 086/108] Revert "Fix/Missing python import in write_header" This reverts commit 385d487220dc1bf8d53e423d52542bb59dce4bac. --- rslib/proto/python.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rslib/proto/python.rs b/rslib/proto/python.rs index a2aa09707..a5adb4179 100644 --- a/rslib/proto/python.rs +++ b/rslib/proto/python.rs @@ -249,7 +249,6 @@ import anki.stats_pb2 import anki.sync_pb2 import anki.tags_pb2 import anki.ankihub_pb2 -import anki.frontend_pb2 class RustBackendGenerated: def _run_command(self, service: int, method: int, input: Any) -> bytes: From 7561bf8d6ab0d759cc0af175c70c3b94097f14dd Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 30 Oct 2025 17:56:02 +0000 Subject: [PATCH 087/108] Added: Placeholder audio autoplay --- proto/anki/frontend.proto | 4 +++- qt/aqt/mediasrv.py | 9 +++++++-- qt/aqt/sound.py | 11 +++-------- ts/routes/reviewer/reviewer.ts | 8 +++++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index 1f6b23101..03a9ffec6 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -31,6 +31,8 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); + // Plays an audio tag at an index in a specific card + // If the index is blank, plays all audio on that side rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); } @@ -48,6 +50,6 @@ message SetSchedulingStatesRequest { message PlayAudioRequest { bool answer_side = 1; - uint32 index = 2; + optional uint32 index = 2; uint64 cid = 3; } \ No newline at end of file diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index c8e145f1d..3657379d1 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -46,7 +46,7 @@ from aqt.operations import on_op_finished from aqt.operations.deck import update_deck_configs as update_deck_configs_op from aqt.progress import ProgressUpdate from aqt.qt import * -from aqt.sound import play_clicked_audio_with_index +from aqt.sound import play_tags from aqt.theme import ThemeManager from aqt.utils import aqt_data_path, show_warning, tr @@ -688,7 +688,12 @@ def next_card_data() -> bytes: def play_audio(): req = PlayAudioRequest.FromString(request.data) card = aqt.mw.col.get_card(CardId(req.cid)) - play_clicked_audio_with_index(req.index, req.answer_side, card) + # TODO: Pass tags with next_card_data rather than rendering the card here. + tags = card.answer_av_tags() if req.answer_side else card.question_av_tags() + if req.index is None: + play_tags(tags) + else: + play_tags([tags[req.index]]) post_handler_list = [ diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index c7896b34b..2a2169079 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -925,16 +925,11 @@ def play_clicked_audio(pycmd: str, card: Card) -> None: """eg. if pycmd is 'play:q:0', play the first audio on the question side.""" play, context, str_idx = pycmd.split(":") idx = int(str_idx) - play_clicked_audio_with_index(idx, context == "q", card) + tags = card.question_av_tags() if context == "q" else card.answer_av_tags() + play_tags([tags[idx]]) -def play_clicked_audio_with_index(index: int, answer_side: bool, card: Card): - if answer_side: - tags = card.answer_av_tags() - else: - tags = card.question_av_tags() - av_player.play_tags([tags[index]]) - +play_tags = av_player.play_tags # Init defaults ########################################################################## diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index d22f9482d..2c76ba9b9 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -41,10 +41,14 @@ export class ReviewerState { addEventListener("message", this.onMessage.bind(this)); } + playAudio(answerSide: boolean, index?: number) { + playAudio({ answerSide, index, cid: this.currentCard!.card!.id }); + } + onMessage(e: MessageEvent) { switch (e.data.type) { case "audio": { - playAudio({ answerSide: e.data.answerSide, index: e.data.index, cid: this.currentCard!.card!.id }); + this.playAudio(e.data.answerSide, e.data.index); break; } } @@ -108,6 +112,7 @@ export class ReviewerState { const question = resp.nextCard?.front || ""; this.updateHtml(question, resp?.nextCard?.css, resp?.nextCard?.bodyClass); + this.playAudio(false) this.beginAnsweringMs = Date.now(); } @@ -118,6 +123,7 @@ export class ReviewerState { public showAnswer() { this.answerShown.set(true); + this.playAudio(true) this.updateHtml(this._cardData?.back || ""); } From fc8a2f97397ad9b9e0181b3a83e6aee454c51195 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 30 Oct 2025 18:00:09 +0000 Subject: [PATCH 088/108] Fix: Hide iframe until loaded to prevent flash --- ts/routes/reviewer/Reviewer.svelte | 1 + ts/routes/reviewer/reviewer.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/ts/routes/reviewer/Reviewer.svelte b/ts/routes/reviewer/Reviewer.svelte index 6a71d5d4b..7c958ecea 100644 --- a/ts/routes/reviewer/Reviewer.svelte +++ b/ts/routes/reviewer/Reviewer.svelte @@ -32,5 +32,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html iframe { width: 100%; height: 100%; + visibility: hidden; } diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 2c76ba9b9..e69a786a1 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -37,6 +37,7 @@ export class ReviewerState { iframe: HTMLIFrameElement | undefined = undefined; onReady() { + this.iframe!.style.visibility = "visible"; this.showQuestion(null); addEventListener("message", this.onMessage.bind(this)); } From 112f951a13356002c316dbb8d6613bf99b96db9a Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Thu, 30 Oct 2025 18:11:41 +0000 Subject: [PATCH 089/108] Fix: use HasField --- qt/aqt/mediasrv.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 3657379d1..2ed59ffd5 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -690,10 +690,10 @@ def play_audio(): card = aqt.mw.col.get_card(CardId(req.cid)) # TODO: Pass tags with next_card_data rather than rendering the card here. tags = card.answer_av_tags() if req.answer_side else card.question_av_tags() - if req.index is None: - play_tags(tags) - else: + if req.HasField("index"): play_tags([tags[req.index]]) + else: + play_tags(tags) post_handler_list = [ From 8da0491ae5accb5bd7e1fbbf5aa9fc5495d106ce Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 08:28:39 +0000 Subject: [PATCH 090/108] Refactor PlayAudio --- proto/anki/frontend.proto | 8 +++----- proto/anki/scheduler.proto | 7 +++++-- qt/aqt/mediasrv.py | 22 ++++++++++++---------- rslib/src/scheduler/service/mod.rs | 4 +++- ts/routes/reviewer/reviewer.ts | 11 ++++------- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index 03a9ffec6..bd4c3d34b 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -10,6 +10,7 @@ package anki.frontend; import "anki/scheduler.proto"; import "anki/generic.proto"; import "anki/search.proto"; +import "anki/card_rendering.proto"; service FrontendService { // Returns values from the reviewer @@ -31,8 +32,7 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); - // Plays an audio tag at an index in a specific card - // If the index is blank, plays all audio on that side + // Plays the listed audio tags rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); } @@ -49,7 +49,5 @@ message SetSchedulingStatesRequest { } message PlayAudioRequest { - bool answer_side = 1; - optional uint32 index = 2; - uint64 cid = 3; + repeated card_rendering.AVTag tags = 1; } \ No newline at end of file diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index e9ee3b89f..14f5b184d 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -306,13 +306,16 @@ message NextCardDataResponse { string css = 5; string body_class = 6; + repeated card_rendering.AVTag question_av_tags = 7; + repeated card_rendering.AVTag answer_av_tags = 8; + // TODO: We can probably make this a little faster by using oneof and // preventing the partial_front and back being sent to svelte where it isn't // used. Alternatively we can use a completely different message for both // Rust -> Python and the Python -> Svelte though this would be more // complicated to implement. - repeated card_rendering.RenderedTemplateNode partial_front = 7; - repeated card_rendering.RenderedTemplateNode partial_back = 8; + repeated card_rendering.RenderedTemplateNode partial_front = 9; + repeated card_rendering.RenderedTemplateNode partial_back = 10; } optional NextCardData next_card = 1; diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 2ed59ffd5..f3f5d716f 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -28,7 +28,7 @@ import aqt import aqt.main import aqt.operations from anki import hooks -from anki.cards import Card, CardId +from anki.cards import Card from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs from anki.frontend_pb2 import PlayAudioRequest @@ -38,6 +38,7 @@ from anki.template import ( PartiallyRenderedCard, TemplateRenderContext, apply_custom_filters, + av_tags_to_native, ) from anki.utils import dev_mode from aqt.changenotetype import ChangeNotetypeDialog @@ -670,8 +671,15 @@ def next_card_data() -> bytes: qside, ) - qside = ctx.col()._backend.extract_av_tags(text=qside, question_side=True).text - aside = ctx.col()._backend.extract_av_tags(text=aside, question_side=False).text + q_avtags = ctx.col()._backend.extract_av_tags(text=qside, question_side=True) + a_avtags = ctx.col()._backend.extract_av_tags(text=aside, question_side=False) + + # Assumes the av tags are empty in the original response + data.next_card.question_av_tags.extend(q_avtags.av_tags) + data.next_card.answer_av_tags.extend(a_avtags.av_tags) + + qside = q_avtags.text + aside = a_avtags.text qside = aqt.mw.prepare_card_text_for_display(qside) aside = aqt.mw.prepare_card_text_for_display(aside) @@ -687,13 +695,7 @@ def next_card_data() -> bytes: def play_audio(): req = PlayAudioRequest.FromString(request.data) - card = aqt.mw.col.get_card(CardId(req.cid)) - # TODO: Pass tags with next_card_data rather than rendering the card here. - tags = card.answer_av_tags() if req.answer_side else card.question_av_tags() - if req.HasField("index"): - play_tags([tags[req.index]]) - else: - play_tags(tags) + play_tags(av_tags_to_native(req.tags)) post_handler_list = [ diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index d9cd5a009..a49f31d4f 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -413,11 +413,13 @@ impl crate::services::SchedulerService for Collection { next_card: Some(NextCardData { queue: Some(queue.into()), - css: render.css, + css: render.css.clone(), // Filled by python front: "".to_string(), back: "".to_string(), body_class: "".to_string(), + question_av_tags: vec![], + answer_av_tags: vec![], partial_front: rendered_nodes_to_proto(render.qnodes), partial_back: rendered_nodes_to_proto(render.anodes), diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index e69a786a1..0e1c8ec51 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -42,14 +42,11 @@ export class ReviewerState { addEventListener("message", this.onMessage.bind(this)); } - playAudio(answerSide: boolean, index?: number) { - playAudio({ answerSide, index, cid: this.currentCard!.card!.id }); - } - onMessage(e: MessageEvent) { switch (e.data.type) { case "audio": { - this.playAudio(e.data.answerSide, e.data.index); + const tags = get(this.answerShown) ? this._cardData!.answerAvTags : this._cardData!.questionAvTags; + playAudio({ tags: [tags[e.data.index]] }); break; } } @@ -113,7 +110,7 @@ export class ReviewerState { const question = resp.nextCard?.front || ""; this.updateHtml(question, resp?.nextCard?.css, resp?.nextCard?.bodyClass); - this.playAudio(false) + playAudio({ tags: this._cardData!.questionAvTags }); this.beginAnsweringMs = Date.now(); } @@ -124,7 +121,7 @@ export class ReviewerState { public showAnswer() { this.answerShown.set(true); - this.playAudio(true) + playAudio({ tags: this._cardData!.answerAvTags }); this.updateHtml(this._cardData?.back || ""); } From d47cb1bd55bbbae8608daf38bce7b2f4f733c6a4 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 08:36:20 +0000 Subject: [PATCH 091/108] PlayAudio -> PlayAVTags --- proto/anki/frontend.proto | 6 +++--- qt/aqt/mediasrv.py | 8 ++++---- ts/routes/reviewer/reviewer.ts | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/proto/anki/frontend.proto b/proto/anki/frontend.proto index bd4c3d34b..358edfda6 100644 --- a/proto/anki/frontend.proto +++ b/proto/anki/frontend.proto @@ -32,8 +32,8 @@ service FrontendService { // Save colour picker's custom colour palette rpc SaveCustomColours(generic.Empty) returns (generic.Empty); - // Plays the listed audio tags - rpc PlayAudio(PlayAudioRequest) returns (generic.Empty); + // Plays the listed AV tags + rpc PlayAVTags(PlayAVTagsRequest) returns (generic.Empty); } service BackendFrontendService {} @@ -48,6 +48,6 @@ message SetSchedulingStatesRequest { scheduler.SchedulingStates states = 2; } -message PlayAudioRequest { +message PlayAVTagsRequest { repeated card_rendering.AVTag tags = 1; } \ No newline at end of file diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index f3f5d716f..898a05b25 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -31,7 +31,7 @@ from anki import hooks from anki.cards import Card from anki.collection import OpChanges, OpChangesOnly, Progress, SearchNode from anki.decks import UpdateDeckConfigs -from anki.frontend_pb2 import PlayAudioRequest +from anki.frontend_pb2 import PlayAVTagsRequest from anki.scheduler.v3 import SchedulingStatesWithContext, SetSchedulingStatesRequest from anki.scheduler_pb2 import NextCardDataResponse from anki.template import ( @@ -693,8 +693,8 @@ def next_card_data() -> bytes: return data.SerializeToString() -def play_audio(): - req = PlayAudioRequest.FromString(request.data) +def play_avtags(): + req = PlayAVTagsRequest.FromString(request.data) play_tags(av_tags_to_native(req.tags)) @@ -715,7 +715,7 @@ post_handler_list = [ deck_options_ready, save_custom_colours, next_card_data, - play_audio, + play_avtags, ] diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 0e1c8ec51..cee3b2038 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; -import { nextCardData, playAudio } from "@generated/backend"; +import { nextCardData, playAvtags } from "@generated/backend"; import { derived, get, writable } from "svelte/store"; import type { InnerReviewerRequest } from "../reviewer-inner/innerReviewerRequest"; import type { ReviewerRequest } from "./reviewerRequest"; @@ -46,7 +46,7 @@ export class ReviewerState { switch (e.data.type) { case "audio": { const tags = get(this.answerShown) ? this._cardData!.answerAvTags : this._cardData!.questionAvTags; - playAudio({ tags: [tags[e.data.index]] }); + playAvtags({ tags: [tags[e.data.index]] }); break; } } @@ -110,7 +110,7 @@ export class ReviewerState { const question = resp.nextCard?.front || ""; this.updateHtml(question, resp?.nextCard?.css, resp?.nextCard?.bodyClass); - playAudio({ tags: this._cardData!.questionAvTags }); + playAvtags({ tags: this._cardData!.questionAvTags }); this.beginAnsweringMs = Date.now(); } @@ -121,7 +121,7 @@ export class ReviewerState { public showAnswer() { this.answerShown.set(true); - playAudio({ tags: this._cardData!.answerAvTags }); + playAvtags({ tags: this._cardData!.answerAvTags }); this.updateHtml(this._cardData?.back || ""); } From cf9c265570a4ac8345e3617a63e4219512b90467 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 09:01:29 +0000 Subject: [PATCH 092/108] Fix: Respect autoplay setting --- proto/anki/scheduler.proto | 9 +++++---- rslib/src/scheduler/service/mod.rs | 13 ++++++++----- ts/routes/reviewer/reviewer.ts | 8 ++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 14f5b184d..3588c4117 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -305,17 +305,18 @@ message NextCardDataResponse { string back = 4; string css = 5; string body_class = 6; + bool autoplay = 7; - repeated card_rendering.AVTag question_av_tags = 7; - repeated card_rendering.AVTag answer_av_tags = 8; + repeated card_rendering.AVTag question_av_tags = 8; + repeated card_rendering.AVTag answer_av_tags = 9; // TODO: We can probably make this a little faster by using oneof and // preventing the partial_front and back being sent to svelte where it isn't // used. Alternatively we can use a completely different message for both // Rust -> Python and the Python -> Svelte though this would be more // complicated to implement. - repeated card_rendering.RenderedTemplateNode partial_front = 9; - repeated card_rendering.RenderedTemplateNode partial_back = 10; + repeated card_rendering.RenderedTemplateNode partial_front = 10; + repeated card_rendering.RenderedTemplateNode partial_back = 11; } optional NextCardData next_card = 1; diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index a49f31d4f..addbc4c87 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -409,22 +409,25 @@ impl crate::services::SchedulerService for Collection { }) .collect(); + let config = self.deck_config_for_card(&next_card.card)?; + Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), css: render.css.clone(), + partial_front: rendered_nodes_to_proto(render.qnodes), + partial_back: rendered_nodes_to_proto(render.anodes), + + answer_buttons, + autoplay: !config.inner.disable_autoplay, + // Filled by python front: "".to_string(), back: "".to_string(), body_class: "".to_string(), question_av_tags: vec![], answer_av_tags: vec![], - - partial_front: rendered_nodes_to_proto(render.qnodes), - partial_back: rendered_nodes_to_proto(render.anodes), - - answer_buttons, }), }) } else { diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index cee3b2038..cac8429fd 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -110,7 +110,9 @@ export class ReviewerState { const question = resp.nextCard?.front || ""; this.updateHtml(question, resp?.nextCard?.css, resp?.nextCard?.bodyClass); - playAvtags({ tags: this._cardData!.questionAvTags }); + if (this._cardData?.autoplay) { + playAvtags({ tags: this._cardData!.questionAvTags }); + } this.beginAnsweringMs = Date.now(); } @@ -121,7 +123,9 @@ export class ReviewerState { public showAnswer() { this.answerShown.set(true); - playAvtags({ tags: this._cardData!.answerAvTags }); + if (this._cardData?.autoplay) { + playAvtags({ tags: this._cardData!.answerAvTags }); + } this.updateHtml(this._cardData?.back || ""); } From bbf575e491d3e620b0b1a85304f858269bdbd0fe Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 12:06:58 +0000 Subject: [PATCH 093/108] Added: TypeAnswer replacement --- proto/anki/scheduler.proto | 1 + rslib/src/scheduler/service/mod.rs | 39 ++++++++++++++++++++++++++- ts/routes/reviewer/reviewer.ts | 6 ++++- ts/routes/reviewer/reviewerRequest.ts | 7 ++++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 3588c4117..2cf0108f8 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -306,6 +306,7 @@ message NextCardDataResponse { string css = 5; string body_class = 6; bool autoplay = 7; + optional string typed_answer = 12; repeated card_rendering.AVTag question_av_tags = 8; repeated card_rendering.AVTag answer_av_tags = 9; diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index addbc4c87..5ba32acb9 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -4,6 +4,8 @@ mod answering; mod states; +use std::sync::LazyLock; + use anki_proto::cards; use anki_proto::generic; use anki_proto::scheduler; @@ -25,6 +27,7 @@ use fsrs::ComputeParametersInput; use fsrs::FSRSItem; use fsrs::FSRSReview; use fsrs::FSRS; +use regex::Regex; use crate::backend::Backend; use crate::card_rendering::service::rendered_nodes_to_proto; @@ -34,7 +37,9 @@ use crate::scheduler::new::NewCardDueOrder; use crate::scheduler::states::CardState; use crate::scheduler::states::SchedulingStates; use crate::search::SortMode; +use crate::services::NotesService; use crate::stats::studied_today; +use crate::template::RenderedNode; impl crate::services::SchedulerService for Collection { /// This behaves like _updateCutoff() in older code - it also unburies at @@ -411,16 +416,48 @@ impl crate::services::SchedulerService for Collection { let config = self.deck_config_for_card(&next_card.card)?; + // Typed answer replacements + static ANSWER_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"\[\[type:(.+?:)?(.+?)\]\]").unwrap()); + + const ANSWER_HTML: &str = "
+ +
"; + + let mut q_nodes = render.qnodes; + let typed_answer_parent_node = q_nodes.iter_mut().find_map(|node| { + if let RenderedNode::Text { text } = node { + let mut out = None; + *text = ANSWER_REGEX + .replace(text, |cap: ®ex::Captures<'_>| { + out = Some(cap[2].to_string()); + ANSWER_HTML + }) + .to_string(); + out + } else { + None + } + }); + + let typed_answer = typed_answer_parent_node.map(|field| { + let note = self.get_note(next_card.card.note_id.into()).unwrap(); + let notetype = self.get_notetype(note.notetype_id.into()).unwrap().unwrap(); + note.fields[notetype.get_field_ord(&field).unwrap()].clone() + }); + Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), css: render.css.clone(), - partial_front: rendered_nodes_to_proto(render.qnodes), + partial_front: rendered_nodes_to_proto(q_nodes), partial_back: rendered_nodes_to_proto(render.anodes), answer_buttons, autoplay: !config.inner.disable_autoplay, + typed_answer, // Filled by python front: "".to_string(), diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index cac8429fd..40149c457 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -27,6 +27,7 @@ export function updateNightMode() { export class ReviewerState { answerHtml = ""; + currentTypedAnswer = ""; _cardData: NextCardDataResponse_NextCardData | undefined = undefined; beginAnsweringMs = Date.now(); readonly cardClass = writable(""); @@ -42,13 +43,16 @@ export class ReviewerState { addEventListener("message", this.onMessage.bind(this)); } - onMessage(e: MessageEvent) { + async onMessage(e: MessageEvent) { switch (e.data.type) { case "audio": { const tags = get(this.answerShown) ? this._cardData!.answerAvTags : this._cardData!.questionAvTags; playAvtags({ tags: [tags[e.data.index]] }); break; } + case "typed": { + this.currentTypedAnswer = e.data.value; + } } } diff --git a/ts/routes/reviewer/reviewerRequest.ts b/ts/routes/reviewer/reviewerRequest.ts index aa4d3c786..6cefa1919 100644 --- a/ts/routes/reviewer/reviewerRequest.ts +++ b/ts/routes/reviewer/reviewerRequest.ts @@ -6,4 +6,9 @@ interface AudioMessage { index: number; } -export type ReviewerRequest = AudioMessage; +interface CompareTypedAnswerMessage { + type: "typed"; + value: string; +} + +export type ReviewerRequest = AudioMessage | CompareTypedAnswerMessage; From b66a10ea26451eef3d561090e06c0d130fbfcc75 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 12:38:11 +0000 Subject: [PATCH 094/108] Added: ShowQusestion TypedAnswer replacement --- qt/aqt/mediasrv.py | 1 + ts/routes/reviewer-inner/index.ts | 11 +++++++++++ ts/routes/reviewer/reviewer.ts | 23 ++++++++++++++++++++--- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 898a05b25..aab4e58c0 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -763,6 +763,7 @@ exposed_backend_list = [ "get_retention_workload", # CardsService "set_flag", + "compare_answer", ] diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index dc32fac87..ba70ffde3 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -65,3 +65,14 @@ function pycmd(cmd: string) { } } globalThis.pycmd = pycmd; + +function _typeAnsPress() { + const elem = document.getElementById("typeans")! as HTMLInputElement; + let key = (window.event as KeyboardEvent).key; + key = key.length == 1 ? key : ""; + window.parent.postMessage( + { type: "typed", value: elem.value + key } satisfies ReviewerRequest, + "*", + ); +} +globalThis._typeAnsPress = _typeAnsPress; diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 40149c457..1fa8e526a 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -1,7 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { CardAnswer, type NextCardDataResponse_NextCardData } from "@generated/anki/scheduler_pb"; -import { nextCardData, playAvtags } from "@generated/backend"; +import { compareAnswer, nextCardData, playAvtags } from "@generated/backend"; import { derived, get, writable } from "svelte/store"; import type { InnerReviewerRequest } from "../reviewer-inner/innerReviewerRequest"; import type { ReviewerRequest } from "./reviewerRequest"; @@ -25,6 +25,8 @@ export function updateNightMode() { } } +const typedAnswerRegex = /\[\[type:(.+?:)?(.+?)\]\]/m; + export class ReviewerState { answerHtml = ""; currentTypedAnswer = ""; @@ -125,12 +127,27 @@ export class ReviewerState { return this._cardData?.queue?.cards[0]; } - public showAnswer() { + async showTypedAnswer(html: string) { + if (!this._cardData?.typedAnswer) { + return html; + } + const compareAnswerResp = await compareAnswer({ + expected: this._cardData?.typedAnswer, + provided: this.currentTypedAnswer, + combining: false, + }); + const display = compareAnswerResp.val; + + console.log({ typedAnswerRegex, html, display }); + return html.replace(typedAnswerRegex, display); + } + + public async showAnswer() { this.answerShown.set(true); if (this._cardData?.autoplay) { playAvtags({ tags: this._cardData!.answerAvTags }); } - this.updateHtml(this._cardData?.back || ""); + this.updateHtml(await this.showTypedAnswer(this._cardData?.back || "")); } public easeButtonPressed(rating: number) { From 23babca417ef9ad85f074db1d6c42621acca2639 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 12:41:12 +0000 Subject: [PATCH 095/108] postParentMessage function --- ts/routes/reviewer-inner/index.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index ba70ffde3..bcf537b4b 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -54,14 +54,22 @@ const base = document.createElement("base"); base.href = "/"; document.head.appendChild(base); +function postParentMessage(message: ReviewerRequest) { + window.parent.postMessage( + message, + "*", + ); +} + function pycmd(cmd: string) { const match = cmd.match(/play:(q|a):(\d+)/); if (match) { const [_, context, index] = match; - window.parent.postMessage( - { type: "audio", answerSide: context == "a", index: parseInt(index) } satisfies ReviewerRequest, - "*", - ); + postParentMessage({ + type: "audio", + answerSide: context === "a", + index: parseInt(index), + }); } } globalThis.pycmd = pycmd; @@ -70,9 +78,8 @@ function _typeAnsPress() { const elem = document.getElementById("typeans")! as HTMLInputElement; let key = (window.event as KeyboardEvent).key; key = key.length == 1 ? key : ""; - window.parent.postMessage( - { type: "typed", value: elem.value + key } satisfies ReviewerRequest, - "*", + postParentMessage( + { type: "typed", value: elem.value + key }, ); } globalThis._typeAnsPress = _typeAnsPress; From 4144fefcd78b605fa71c432ada69e29da9011da2 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 12:42:24 +0000 Subject: [PATCH 096/108] CompareTypedAnswerMessage -> UpdateTypedAnswerMessage --- ts/routes/reviewer/reviewerRequest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/routes/reviewer/reviewerRequest.ts b/ts/routes/reviewer/reviewerRequest.ts index 6cefa1919..8ed9590cf 100644 --- a/ts/routes/reviewer/reviewerRequest.ts +++ b/ts/routes/reviewer/reviewerRequest.ts @@ -6,9 +6,9 @@ interface AudioMessage { index: number; } -interface CompareTypedAnswerMessage { +interface UpdateTypedAnswerMessage { type: "typed"; value: string; } -export type ReviewerRequest = AudioMessage | CompareTypedAnswerMessage; +export type ReviewerRequest = AudioMessage | UpdateTypedAnswerMessage; From dc016c509a519a9d8851a9b51f0bd2ae37e6b035 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 13:01:29 +0000 Subject: [PATCH 097/108] Fix: Handle Inner iframe keypresses --- ts/routes/reviewer-inner/index.ts | 20 +++++++++++++------- ts/routes/reviewer/reviewer.ts | 13 +++++++++++-- ts/routes/reviewer/reviewerRequest.ts | 7 ++++++- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index bcf537b4b..9b581b198 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -8,6 +8,13 @@ import { enableNightMode } from "../reviewer/reviewer"; import type { ReviewerRequest } from "../reviewer/reviewerRequest"; import type { InnerReviewerRequest } from "./innerReviewerRequest"; +function postParentMessage(message: ReviewerRequest) { + window.parent.postMessage( + message, + "*", + ); +} + declare const MathJax: any; const urlParams = new URLSearchParams(location.search); @@ -50,17 +57,16 @@ addEventListener("message", async (e: MessageEvent) => { } }); +addEventListener("keydown", (e) => { + if (e.key.length == 1 && "1234 ".includes(e.key) && !document.activeElement?.matches("#typeans")) { + postParentMessage({ type: "keyPress", key: e.key }); + } +}); + const base = document.createElement("base"); base.href = "/"; document.head.appendChild(base); -function postParentMessage(message: ReviewerRequest) { - window.parent.postMessage( - message, - "*", - ); -} - function pycmd(cmd: string) { const match = cmd.match(/play:(q|a):(\d+)/); if (match) { diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index 1fa8e526a..cd0514ec3 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -54,6 +54,11 @@ export class ReviewerState { } case "typed": { this.currentTypedAnswer = e.data.value; + break; + } + case "keyPress": { + this.handleKeyPress(e.data.key); + break; } } } @@ -63,8 +68,8 @@ export class ReviewerState { iframe.addEventListener("load", this.onReady.bind(this)); } - onKeyDown(e: KeyboardEvent) { - switch (e.key) { + handleKeyPress(key: string) { + switch (key) { case "1": { this.easeButtonPressed(0); break; @@ -92,6 +97,10 @@ export class ReviewerState { } } + onKeyDown(e: KeyboardEvent) { + this.handleKeyPress(e.key); + } + public registerShortcuts() { document.addEventListener("keydown", this.onKeyDown.bind(this)); } diff --git a/ts/routes/reviewer/reviewerRequest.ts b/ts/routes/reviewer/reviewerRequest.ts index 8ed9590cf..c57ea2c50 100644 --- a/ts/routes/reviewer/reviewerRequest.ts +++ b/ts/routes/reviewer/reviewerRequest.ts @@ -11,4 +11,9 @@ interface UpdateTypedAnswerMessage { value: string; } -export type ReviewerRequest = AudioMessage | UpdateTypedAnswerMessage; +interface KeyPressMessage { + type: "keyPress"; + key: string; +} + +export type ReviewerRequest = AudioMessage | UpdateTypedAnswerMessage | KeyPressMessage; From e16a49d376ad297b9f93a1d4d5d3710378b769d0 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 17:16:31 +0000 Subject: [PATCH 098/108] Added: New reviewer config bool --- ftl/core/preferences.ftl | 1 + proto/anki/config.proto | 2 ++ qt/aqt/forms/preferences.ui | 13 +++++++++++++ qt/aqt/main.py | 13 ++++++++----- qt/aqt/preferences.py | 3 +++ rslib/src/backend/config.rs | 1 + rslib/src/config/bool.rs | 1 + rslib/src/preferences.rs | 2 ++ 8 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index 23b72f267..a702cca4a 100644 --- a/ftl/core/preferences.ftl +++ b/ftl/core/preferences.ftl @@ -14,6 +14,7 @@ preferences-on-next-sync-force-changes-in = On next sync, force changes in one d preferences-paste-clipboard-images-as-png = Paste clipboard images as PNG preferences-paste-without-shift-key-strips-formatting = Paste without shift key strips formatting preferences-generate-latex-images-automatically = Generate LaTeX images (security risk) +preferences-use-new-reviewer = Use new reviewer preferences-latex-generation-disabled = LaTeX image generation is disabled in the preferences. preferences-periodically-sync-media = Periodically sync media preferences-please-restart-anki-to-complete-language = Please restart Anki to complete language change. diff --git a/proto/anki/config.proto b/proto/anki/config.proto index ea115f0fc..f7a04e38d 100644 --- a/proto/anki/config.proto +++ b/proto/anki/config.proto @@ -57,6 +57,7 @@ message ConfigKey { LOAD_BALANCER_ENABLED = 26; FSRS_SHORT_TERM_WITH_STEPS_ENABLED = 27; FSRS_LEGACY_EVALUATE = 28; + NEW_REVIEWER = 29; } enum String { SET_DUE_BROWSER = 0; @@ -120,6 +121,7 @@ message Preferences { uint32 time_limit_secs = 5; bool load_balancer_enabled = 6; bool fsrs_short_term_with_steps_enabled = 7; + bool new_reviewer = 8; } message Editing { bool adding_defaults_to_current_deck = 1; diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index 0035e1f42..2a59b5f9d 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -451,6 +451,19 @@ + + + + + 0 + 0 + + + + preferences_use_new_reviewer + + + diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 3affb3a2e..98d4409a3 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -255,13 +255,11 @@ class AnkiQt(QMainWindow): # screens self.setupDeckBrowser() self.setupOverview() - self.setupReviewer() + # self.setupReviewer() def finish_ui_setup(self) -> None: "Actions that are deferred until after add-on loading." self.toolbar.draw() - # add-ons are only available here after setupAddons - gui_hooks.reviewer_did_init(self.reviewer) def setupProfileAfterWebviewsLoaded(self) -> None: for w in (self.web, self.bottomWeb): @@ -679,6 +677,8 @@ class AnkiQt(QMainWindow): # dump error to stderr so it gets picked up by errors.py traceback.print_exc() + self.setupReviewer(self.backend.get_config_bool(Config.Bool.NEW_REVIEWER)) + return True def _loadCollection(self) -> None: @@ -1074,10 +1074,13 @@ title="{}" {}>{}""".format( self.overview = Overview(self) - def setupReviewer(self) -> None: + def setupReviewer(self, new: bool) -> None: from aqt.reviewer import Reviewer, SvelteReviewer - self.reviewer = SvelteReviewer(self) if True else Reviewer(self) + self.reviewer = SvelteReviewer(self) if new else Reviewer(self) + + # add-ons are only available here after setupAddons + gui_hooks.reviewer_did_init(self.reviewer) # Syncing ########################################################################## diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 939dd8c2c..8895becb1 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -138,6 +138,7 @@ class Preferences(QDialog): form.showProgress.setChecked(reviewing.show_remaining_due_counts) form.showPlayButtons.setChecked(not reviewing.hide_audio_play_buttons) form.interrupt_audio.setChecked(reviewing.interrupt_audio_when_answering) + form.new_reviewer.setChecked(reviewing.new_reviewer) editing = self.prefs.editing form.useCurrent.setCurrentIndex( @@ -173,6 +174,8 @@ class Preferences(QDialog): reviewing.time_limit_secs = form.timeLimit.value() * 60 reviewing.hide_audio_play_buttons = not self.form.showPlayButtons.isChecked() reviewing.interrupt_audio_when_answering = self.form.interrupt_audio.isChecked() + reviewing.new_reviewer = form.new_reviewer.isChecked() + aqt.mw.setupReviewer(reviewing.new_reviewer) editing = self.prefs.editing editing.adding_defaults_to_current_deck = not form.useCurrent.currentIndex() diff --git a/rslib/src/backend/config.rs b/rslib/src/backend/config.rs index b6e81ce2a..9bc1140a2 100644 --- a/rslib/src/backend/config.rs +++ b/rslib/src/backend/config.rs @@ -40,6 +40,7 @@ impl From for BoolKey { BoolKeyProto::LoadBalancerEnabled => BoolKey::LoadBalancerEnabled, BoolKeyProto::FsrsShortTermWithStepsEnabled => BoolKey::FsrsShortTermWithStepsEnabled, BoolKeyProto::FsrsLegacyEvaluate => BoolKey::FsrsLegacyEvaluate, + BoolKeyProto::NewReviewer => BoolKey::NewReviewer, } } } diff --git a/rslib/src/config/bool.rs b/rslib/src/config/bool.rs index c76787cb0..a5ba0e70f 100644 --- a/rslib/src/config/bool.rs +++ b/rslib/src/config/bool.rs @@ -44,6 +44,7 @@ pub enum BoolKey { FsrsLegacyEvaluate, LoadBalancerEnabled, FsrsShortTermWithStepsEnabled, + NewReviewer, #[strum(to_string = "normalize_note_text")] NormalizeNoteText, #[strum(to_string = "dayLearnFirst")] diff --git a/rslib/src/preferences.rs b/rslib/src/preferences.rs index 96be8e461..fa760baef 100644 --- a/rslib/src/preferences.rs +++ b/rslib/src/preferences.rs @@ -101,6 +101,7 @@ impl Collection { load_balancer_enabled: self.get_config_bool(BoolKey::LoadBalancerEnabled), fsrs_short_term_with_steps_enabled: self .get_config_bool(BoolKey::FsrsShortTermWithStepsEnabled), + new_reviewer: self.get_config_bool(BoolKey::NewReviewer), }) } @@ -125,6 +126,7 @@ impl Collection { BoolKey::FsrsShortTermWithStepsEnabled, s.fsrs_short_term_with_steps_enabled, )?; + self.set_config_bool_inner(BoolKey::NewReviewer, settings.new_reviewer)?; Ok(()) } From dae91bd1d84dc60a726d9ce5b522277aa4f6ebfa Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 17:26:07 +0000 Subject: [PATCH 099/108] keyPress -> keypress --- ts/routes/reviewer-inner/index.ts | 2 +- ts/routes/reviewer/reviewer.ts | 2 +- ts/routes/reviewer/reviewerRequest.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 9b581b198..bf2e85d27 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -59,7 +59,7 @@ addEventListener("message", async (e: MessageEvent) => { addEventListener("keydown", (e) => { if (e.key.length == 1 && "1234 ".includes(e.key) && !document.activeElement?.matches("#typeans")) { - postParentMessage({ type: "keyPress", key: e.key }); + postParentMessage({ type: "keypress", key: e.key }); } }); diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index cd0514ec3..ba138c08f 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -56,7 +56,7 @@ export class ReviewerState { this.currentTypedAnswer = e.data.value; break; } - case "keyPress": { + case "keypress": { this.handleKeyPress(e.data.key); break; } diff --git a/ts/routes/reviewer/reviewerRequest.ts b/ts/routes/reviewer/reviewerRequest.ts index c57ea2c50..bcd0f7fde 100644 --- a/ts/routes/reviewer/reviewerRequest.ts +++ b/ts/routes/reviewer/reviewerRequest.ts @@ -12,7 +12,7 @@ interface UpdateTypedAnswerMessage { } interface KeyPressMessage { - type: "keyPress"; + type: "keypress"; key: string; } From 4e8bf1381320bb5012648eadff3a3b333b1ebe59 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Fri, 31 Oct 2025 17:32:50 +0000 Subject: [PATCH 100/108] Fix: Check for focus with inner iframe keypress --- ts/routes/reviewer-inner/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index bf2e85d27..348e28068 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -58,7 +58,12 @@ addEventListener("message", async (e: MessageEvent) => { }); addEventListener("keydown", (e) => { - if (e.key.length == 1 && "1234 ".includes(e.key) && !document.activeElement?.matches("#typeans")) { + if (e.key === "Enter" && document.activeElement?.matches("#typeans")) { + postParentMessage({ type: "keypress", key: " " }); + } else if ( + e.key.length == 1 && "1234 ".includes(e.key) + && !document.activeElement?.matches("input[type=text], input[type=number], textarea") + ) { postParentMessage({ type: "keypress", key: e.key }); } }); From 7cb7d208c6a554bf537dd94d13fe698b5ed0dbd7 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 3 Nov 2025 23:07:03 +0000 Subject: [PATCH 101/108] Added: nc for typed answers --- proto/anki/scheduler.proto | 1 + rslib/src/scheduler/service/mod.rs | 12 +++++++++--- ts/routes/reviewer/reviewer.ts | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/proto/anki/scheduler.proto b/proto/anki/scheduler.proto index 2cf0108f8..dd8aaadb3 100644 --- a/proto/anki/scheduler.proto +++ b/proto/anki/scheduler.proto @@ -307,6 +307,7 @@ message NextCardDataResponse { string body_class = 6; bool autoplay = 7; optional string typed_answer = 12; + optional string typed_answer_args = 13; repeated card_rendering.AVTag question_av_tags = 8; repeated card_rendering.AVTag answer_av_tags = 9; diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index 5ba32acb9..a686a76e3 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -431,7 +431,10 @@ impl crate::services::SchedulerService for Collection { let mut out = None; *text = ANSWER_REGEX .replace(text, |cap: ®ex::Captures<'_>| { - out = Some(cap[2].to_string()); + out = Some(( + cap.get(1).map(|g| g.as_str().to_string()), + cap[2].to_string(), + )); ANSWER_HTML }) .to_string(); @@ -441,12 +444,14 @@ impl crate::services::SchedulerService for Collection { } }); - let typed_answer = typed_answer_parent_node.map(|field| { + let typed_answer = typed_answer_parent_node.as_ref().map(|field| { let note = self.get_note(next_card.card.note_id.into()).unwrap(); let notetype = self.get_notetype(note.notetype_id.into()).unwrap().unwrap(); - note.fields[notetype.get_field_ord(&field).unwrap()].clone() + note.fields[notetype.get_field_ord(&field.1).unwrap()].clone() }); + dbg!(&typed_answer_parent_node); + Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), @@ -458,6 +463,7 @@ impl crate::services::SchedulerService for Collection { answer_buttons, autoplay: !config.inner.disable_autoplay, typed_answer, + typed_answer_args: typed_answer_parent_node.and_then(|v| v.0), // Filled by python front: "".to_string(), diff --git a/ts/routes/reviewer/reviewer.ts b/ts/routes/reviewer/reviewer.ts index ba138c08f..7aa411d31 100644 --- a/ts/routes/reviewer/reviewer.ts +++ b/ts/routes/reviewer/reviewer.ts @@ -137,13 +137,13 @@ export class ReviewerState { } async showTypedAnswer(html: string) { - if (!this._cardData?.typedAnswer) { + if (!this._cardData?.typedAnswer || !this._cardData.typedAnswerArgs) { return html; } const compareAnswerResp = await compareAnswer({ expected: this._cardData?.typedAnswer, provided: this.currentTypedAnswer, - combining: false, + combining: !this._cardData.typedAnswerArgs.includes("nc"), }); const display = compareAnswerResp.val; From b80468554a3cab2e1668d439d739117631ca6bc5 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Mon, 3 Nov 2025 23:11:48 +0000 Subject: [PATCH 102/108] remove dbg --- rslib/src/scheduler/service/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rslib/src/scheduler/service/mod.rs b/rslib/src/scheduler/service/mod.rs index a686a76e3..d535d8ae9 100644 --- a/rslib/src/scheduler/service/mod.rs +++ b/rslib/src/scheduler/service/mod.rs @@ -450,8 +450,6 @@ impl crate::services::SchedulerService for Collection { note.fields[notetype.get_field_ord(&field.1).unwrap()].clone() }); - dbg!(&typed_answer_parent_node); - Ok(NextCardDataResponse { next_card: Some(NextCardData { queue: Some(queue.into()), From 42325fd548bb1334d12273add6bfbb41bee69d1b Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 4 Nov 2025 18:37:09 +0000 Subject: [PATCH 103/108] Fix: Bottom bar remains for windows --- qt/aqt/reviewer.py | 9 ++++----- qt/aqt/toolbar.py | 6 +++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 63bb2ba9f..e816348d7 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1275,7 +1275,7 @@ class SvelteReviewer(Reviewer): return "" idx, counts = self._v3.counts() - self.bottom.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") + self.web.eval(f"_updateRemaining({json.dumps(counts)},{idx})") return "" def _showAnswerButton(self) -> None: @@ -1284,7 +1284,7 @@ class SvelteReviewer(Reviewer): else: maxTime = 0 self._remaining() - self.bottom.web.eval('showQuestion("",%d);' % (maxTime)) + self.web.eval('showQuestion("",%d);' % (maxTime)) def _buttonTime(self, i: int, v3_labels: Sequence[str]) -> str: return v3_labels[i - 1] if self.mw.col.conf["estTimes"] else "" @@ -1298,14 +1298,13 @@ class SvelteReviewer(Reviewer): def _initWeb(self) -> None: self._reps = 0 + # hide the bottom bar + self.bottom.web.setHtml("") # main window self.web.load_sveltekit_page("reviewer") # block default drag & drop behavior while allowing drop events to be received by JS handlers self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") - # ensure bottom web functions trigger - self.bottom.web = self.web - self.mw.bottomWeb.hide() def _shortcutKeys(self) -> Sequence[tuple[str, Callable] | tuple[Qt.Key, Callable]]: return [] diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index be547b5ba..7c617483f 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -211,7 +211,11 @@ class BottomWebView(ToolbarWebView): def animate_height(self, height: int) -> None: self.web_height = height - if self.mw.pm.reduce_motion() or height == self.height(): + if ( + self.mw.pm.reduce_motion() + or self.mw.col.conf.get("newReviewer") + or height == self.height() + ): self.setFixedHeight(height) else: # Collapse/Expand animation From a4a1783bc234bdf66ec166edab20b0c70e3b4db4 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 4 Nov 2025 21:13:29 +0000 Subject: [PATCH 104/108] Fix: Use small arrow instead of placeholder I tested this on my english windows 10 and seemed to cause no problems. --- ts/routes/reviewer/reviewer-bottom/More.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/routes/reviewer/reviewer-bottom/More.svelte b/ts/routes/reviewer/reviewer-bottom/More.svelte index d6783e2c6..d2f924ddd 100644 --- a/ts/routes/reviewer/reviewer-bottom/More.svelte +++ b/ts/routes/reviewer/reviewer-bottom/More.svelte @@ -37,7 +37,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html }} title={tr.actionsShortcutKey({ val: "M" })} > - {tr.studyingMore()}↧ + {tr.studyingMore()}{"▾"}
From 65f38448573f6efe1bcb061eb4f3c2dc76afdbdb Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 4 Nov 2025 21:11:36 +0000 Subject: [PATCH 105/108] Fix: Enter key hotkey --- ts/routes/reviewer-inner/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/routes/reviewer-inner/index.ts b/ts/routes/reviewer-inner/index.ts index 348e28068..a4c1df522 100644 --- a/ts/routes/reviewer-inner/index.ts +++ b/ts/routes/reviewer-inner/index.ts @@ -58,7 +58,7 @@ addEventListener("message", async (e: MessageEvent) => { }); addEventListener("keydown", (e) => { - if (e.key === "Enter" && document.activeElement?.matches("#typeans")) { + if (e.key === "Enter") { postParentMessage({ type: "keypress", key: " " }); } else if ( e.key.length == 1 && "1234 ".includes(e.key) From 71bdb006e4cdd117a80019df4e8bb8ffb85f8923 Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 4 Nov 2025 21:20:32 +0000 Subject: [PATCH 106/108] disable pycmd --- qt/aqt/reviewer.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index e816348d7..f8cb9a754 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -1290,11 +1290,7 @@ class SvelteReviewer(Reviewer): return v3_labels[i - 1] if self.mw.col.conf["estTimes"] else "" def _linkHandler(self, url: str) -> None: - if url == "bottomReady": - self._showQuestion() - self._remaining() - return - super()._linkHandler(url) + pass def _initWeb(self) -> None: self._reps = 0 From 1b3c918a9739b6135457f5693550e2464150492c Mon Sep 17 00:00:00 2001 From: Luc Mcgrady Date: Tue, 4 Nov 2025 21:28:28 +0000 Subject: [PATCH 107/108] Fix: add display count placeholder --- .../reviewer/reviewer-bottom/Remaining.svelte | 20 ++++++++++--------- .../reviewer-bottom/RemainingNumber.svelte | 7 +++++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/ts/routes/reviewer/reviewer-bottom/Remaining.svelte b/ts/routes/reviewer/reviewer-bottom/Remaining.svelte index 6e7ae9075..1e1854983 100644 --- a/ts/routes/reviewer/reviewer-bottom/Remaining.svelte +++ b/ts/routes/reviewer/reviewer-bottom/Remaining.svelte @@ -14,21 +14,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - - {queue?.newCount} - + + + {"+"} - {queue?.learningCount} - + + count={queue?.learningCount} + > + {"+"} - {queue?.reviewCount} - + count={queue?.reviewCount} + >