diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index ae9249a07..d1ad135e1 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -71,7 +71,12 @@ class AddCards(QMainWindow): self.setAndFocusNote(new_note) def setupEditor(self) -> None: - self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self, True) + self.editor = aqt.editor.Editor( + self.mw, + self.form.fieldsArea, + self, + editorMode=aqt.editor.EditorMode.ADD_CARDS, + ) self.editor.web.eval("noteEditorPromise.then(() => activateStickyShortcuts());") def setup_choosers(self) -> None: diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 6eca263b3..6926e74b3 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -418,7 +418,12 @@ class Browser(QMainWindow): ) gui_hooks.editor_did_init.append(add_preview_button) - self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self) + self.editor = aqt.editor.Editor( + self.mw, + self.form.fieldsArea, + self, + editorMode=aqt.editor.EditorMode.BROWSER, + ) gui_hooks.editor_did_init.remove(add_preview_button) @ensure_editor_saved diff --git a/qt/aqt/data/web/css/BUILD.bazel b/qt/aqt/data/web/css/BUILD.bazel index 2bdcca54c..8d677a20e 100644 --- a/qt/aqt/data/web/css/BUILD.bazel +++ b/qt/aqt/data/web/css/BUILD.bazel @@ -15,14 +15,6 @@ compile_sass( ], ) -copy_files_into_group( - name = "editor", - srcs = [ - "editor.css", - ], - package = "//ts/editor", -) - copy_files_into_group( name = "editable", srcs = [ @@ -31,6 +23,16 @@ copy_files_into_group( package = "//ts/editable", ) +copy_files_into_group( + name = "editor", + srcs = [ + "browser_editor.css", + "reviewer_editor.css", + "note_creator.css", + ], + package = "//ts/editor", +) + copy_files_into_group( name = "reviewer", srcs = [ diff --git a/qt/aqt/data/web/js/BUILD.bazel b/qt/aqt/data/web/js/BUILD.bazel index b8fac74a4..a28fb5562 100644 --- a/qt/aqt/data/web/js/BUILD.bazel +++ b/qt/aqt/data/web/js/BUILD.bazel @@ -26,7 +26,9 @@ typescript( copy_files_into_group( name = "editor", srcs = [ - "editor.js", + "browser_editor.js", + "reviewer_editor.js", + "note_creator.js", ], package = "//ts/editor", ) @@ -43,9 +45,9 @@ filegroup( name = "js", srcs = [ "aqt", - "editor", "mathjax.js", "reviewer", + "editor", "//qt/aqt/data/web/js/vendor", ], visibility = ["//qt:__subpackages__"], diff --git a/qt/aqt/editcurrent.py b/qt/aqt/editcurrent.py index 4d841b5bc..cdee9388b 100644 --- a/qt/aqt/editcurrent.py +++ b/qt/aqt/editcurrent.py @@ -24,7 +24,12 @@ class EditCurrent(QDialog): self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close).setShortcut( QKeySequence("Ctrl+Return") ) - self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self) + self.editor = aqt.editor.Editor( + self.mw, + self.form.fieldsArea, + self, + editorMode=aqt.editor.EditorMode.EDIT_CURRENT, + ) self.editor.card = self.mw.reviewer.card self.editor.set_note(self.mw.reviewer.card.note(), focusTo=0) restoreGeom(self, "editcurrent") diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 65c36134a..7a312dd2a 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -13,6 +13,7 @@ import urllib.error import urllib.parse import urllib.request import warnings +from enum import Enum from random import randrange from typing import Any, Callable, Match, cast @@ -80,6 +81,12 @@ audio = ( ) +class EditorMode(Enum): + ADD_CARDS = 0 + EDIT_CURRENT = 1 + BROWSER = 2 + + class Editor: """The screen that embeds an editing widget should listen for changes via the `operation_did_execute` hook, and call set_note() when the editor needs @@ -91,13 +98,23 @@ class Editor: """ def __init__( - self, mw: AnkiQt, widget: QWidget, parentWindow: QWidget, addMode: bool = False + self, + mw: AnkiQt, + widget: QWidget, + parentWindow: QWidget, + addMode: bool | None = None, + *, + editorMode: EditorMode = EditorMode.EDIT_CURRENT, ) -> None: self.mw = mw self.widget = widget self.parentWindow = parentWindow self.note: Note | None = None - self.addMode = addMode + # legacy argument provided? + if addMode is not None: + editorMode = EditorMode.ADD_CARDS if addMode else EditorMode.EDIT_CURRENT + self.addMode = editorMode is EditorMode.ADD_CARDS + self.editorMode = editorMode self.currentField: int | None = None # Similar to currentField, but not set to None on a blur. May be # outside the bounds of the current notetype. @@ -124,11 +141,18 @@ class Editor: self.web.set_bridge_command(self.onBridgeCmd, self) self.outerLayout.addWidget(self.web, 1) + if self.editorMode == EditorMode.ADD_CARDS: + file = "note_creator" + elif self.editorMode == EditorMode.BROWSER: + file = "browser_editor" + else: + file = "reviewer_editor" + # then load page self.web.stdHtml( "", - css=["css/editor.css"], - js=["js/editor.js"], + css=[f"css/{file}.css"], + js=[f"js/{file}.js"], context=self, default_css=False, ) @@ -137,7 +161,7 @@ class Editor: gui_hooks.editor_did_init_left_buttons(lefttopbtns, self) lefttopbtns_defs = [ - f"noteEditorPromise.then((noteEditor) => noteEditor.toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1));" + f"uiPromise.then((noteEditor) => noteEditor.toolbar.notetypeButtons.appendButton({{ component: editorToolbar.Raw, props: {{ html: {json.dumps(button)} }} }}, -1));" for button in lefttopbtns ] lefttopbtns_js = "\n".join(lefttopbtns_defs) @@ -150,7 +174,7 @@ class Editor: righttopbtns_defs = ", ".join([json.dumps(button) for button in righttopbtns]) righttopbtns_js = ( f""" -noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{ +uiPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{ component: editorToolbar.AddonButtons, id: "addons", props: {{ buttons: [ {righttopbtns_defs} ] }}, @@ -501,9 +525,7 @@ noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{ js += " setSticky(%s);" % json.dumps(sticky) js = gui_hooks.editor_will_load_note(js, self.note, self) - self.web.evalWithCallback( - f"noteEditorPromise.then(() => {{ {js} }})", oncallback - ) + self.web.evalWithCallback(f"uiPromise.then(() => {{ {js} }})", oncallback) def _save_current_note(self) -> None: "Call after note is updated with data from webview." @@ -557,8 +579,8 @@ noteEditorPromise.then(noteEditor => noteEditor.toolbar.toolbar.appendGroup({{ elif result == NoteFieldsCheckResult.FIELD_NOT_CLOZE: cloze_hint = tr.adding_cloze_outside_cloze_field() - self.web.eval(f"setBackgrounds({json.dumps(cols)});") - self.web.eval(f"setClozeHint({json.dumps(cloze_hint)});") + self.web.eval(f"uiPromise.then(() => setBackgrounds({json.dumps(cols)}));") + self.web.eval(f"uiPromise.then(() => setClozeHint({json.dumps(cloze_hint)}));") def showDupes(self) -> None: aqt.dialogs.open( @@ -1333,11 +1355,11 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting) def set_cloze_button(editor: Editor) -> None: if editor.note.note_type()["type"] == MODEL_CLOZE: editor.web.eval( - 'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.showButton("cloze")); ' + 'uiPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.showButton("cloze")); ' ) else: editor.web.eval( - 'noteEditorPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.hideButton("cloze")); ' + 'uiPromise.then((noteEditor) => noteEditor.toolbar.templateButtons.hideButton("cloze")); ' ) diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index e668721fa..389b7b61b 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -35,28 +35,52 @@ _ts_deps = [ compile_svelte(deps = _ts_deps) typescript( - name = "editor_ts", + name = "editor", deps = _ts_deps + [ ":svelte", ], ) +_esbuild_deps = [ + ":editor", + ":editor_css", + "//sass:button_mixins_lib", + "@npm//@mdi", + "@npm//bootstrap-icons", + "@npm//protobufjs", +] + esbuild( - name = "editor", + name = "browser_editor", args = { "loader": {".svg": "text"}, }, - entry_point = "index_wrapper.ts", - output_css = "editor.css", + entry_point = "index_browser.ts", + output_css = "browser_editor.css", visibility = ["//visibility:public"], - deps = [ - ":editor_css", - ":editor_ts", - "//sass:button_mixins_lib", - "@npm//@mdi", - "@npm//bootstrap-icons", - "@npm//protobufjs", - ], + deps = _esbuild_deps, +) + +esbuild( + name = "reviewer_editor", + args = { + "loader": {".svg": "text"}, + }, + entry_point = "index_reviewer.ts", + output_css = "reviewer_editor.css", + visibility = ["//visibility:public"], + deps = _esbuild_deps, +) + +esbuild( + name = "note_creator", + args = { + "loader": {".svg": "text"}, + }, + entry_point = "index_creator.ts", + output_css = "note_creator.css", + visibility = ["//visibility:public"], + deps = _esbuild_deps, ) # Tests diff --git a/ts/editor/BrowserEditor.svelte b/ts/editor/BrowserEditor.svelte new file mode 100644 index 000000000..fad9e991e --- /dev/null +++ b/ts/editor/BrowserEditor.svelte @@ -0,0 +1,19 @@ + + + + diff --git a/ts/editor/NoteCreator.svelte b/ts/editor/NoteCreator.svelte new file mode 100644 index 000000000..fad9e991e --- /dev/null +++ b/ts/editor/NoteCreator.svelte @@ -0,0 +1,19 @@ + + + + diff --git a/ts/editor/OldEditorAdapter.svelte b/ts/editor/OldEditorAdapter.svelte index dae07472e..b18e6d8f1 100644 --- a/ts/editor/OldEditorAdapter.svelte +++ b/ts/editor/OldEditorAdapter.svelte @@ -248,7 +248,38 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html }), ); + import { wrapInternal } from "../lib/wrap"; + onMount(() => { + function wrap(before: string, after: string): void { + if (!get(focusInRichText)) { + return; + } + + const input = get(activeInput!) as RichTextInputAPI; + + input.element.then((element) => { + wrapInternal(element, before, after, false); + }); + } + + Object.assign(globalThis, { + setFields, + setDescriptions, + setFonts, + focusField, + setColorButtons, + setTags, + setSticky, + setBackgrounds, + setClozeHint, + saveNow: saveFieldNow, + activateStickyShortcuts, + focusIfField, + setNoteId, + wrap, + }); + document.addEventListener("visibilitychange", saveOnPageHide); return () => document.removeEventListener("visibilitychange", saveOnPageHide); }); diff --git a/ts/editor/PlainTextInput.svelte b/ts/editor/PlainTextInput.svelte index 852bb6ada..bddacdf02 100644 --- a/ts/editor/PlainTextInput.svelte +++ b/ts/editor/PlainTextInput.svelte @@ -4,13 +4,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> + + diff --git a/ts/editor/index.ts b/ts/editor/index.ts index 647320cb4..6577f4c56 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -60,53 +60,4 @@ export const i18n = setupI18n({ ], }); -import OldEditorAdapter from "./OldEditorAdapter.svelte"; -import type { NoteEditorAPI } from "./OldEditorAdapter.svelte"; - -async function setupNoteEditor(): Promise { - await i18n; - - const api: Partial = {}; - - const noteEditor = new OldEditorAdapter({ - target: document.body, - props: { api: api as NoteEditorAPI }, - }); - - Object.assign(globalThis, { - setFields: noteEditor.setFields, - setDescriptions: noteEditor.setDescriptions, - setFonts: noteEditor.setFonts, - focusField: noteEditor.focusField, - setColorButtons: noteEditor.setColorButtons, - setTags: noteEditor.setTags, - setSticky: noteEditor.setSticky, - setBackgrounds: noteEditor.setBackgrounds, - setClozeHint: noteEditor.setClozeHint, - saveNow: noteEditor.saveFieldNow, - activateStickyShortcuts: noteEditor.activateStickyShortcuts, - focusIfField: noteEditor.focusIfField, - setNoteId: noteEditor.setNoteId, - }); - - return api as NoteEditorAPI; -} - -import { get } from "svelte/store"; -import { wrapInternal } from "../lib/wrap"; -import type { RichTextInputAPI } from "./RichTextInput.svelte"; - -export async function wrap(before: string, after: string): Promise { - const noteEditor = await noteEditorPromise; - - if (!get(noteEditor.focusInRichText)) { - return; - } - - const activeInput = get(noteEditor.activeInput) as RichTextInputAPI; - const element = await activeInput.element; - wrapInternal(element, before, after, false); -} - -export const noteEditorPromise = setupNoteEditor(); export { editorToolbar } from "./EditorToolbar.svelte"; diff --git a/ts/editor/index_browser.ts b/ts/editor/index_browser.ts new file mode 100644 index 000000000..25ebaa18d --- /dev/null +++ b/ts/editor/index_browser.ts @@ -0,0 +1,27 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { i18n } from "."; +import BrowserEditor from "./BrowserEditor.svelte"; +import { promiseWithResolver } from "../lib/promise"; +import { globalExport } from "../lib/globals"; + +const [uiPromise, uiResolve] = promiseWithResolver(); + +async function setupBrowserEditor(): Promise { + await i18n; + + new BrowserEditor({ + target: document.body, + props: { uiResolve }, + }); +} + +setupBrowserEditor(); + +import * as editor from "."; + +globalExport({ + ...editor, + uiPromise, + noteEditorPromise: uiPromise, +}); diff --git a/ts/editor/index_creator.ts b/ts/editor/index_creator.ts new file mode 100644 index 000000000..c28be2256 --- /dev/null +++ b/ts/editor/index_creator.ts @@ -0,0 +1,27 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { i18n } from "."; +import NoteCreator from "./NoteCreator.svelte"; +import { promiseWithResolver } from "../lib/promise"; +import { globalExport } from "../lib/globals"; + +const [uiPromise, uiResolve] = promiseWithResolver(); + +async function setupNoteCreator(): Promise { + await i18n; + + new NoteCreator({ + target: document.body, + props: { uiResolve }, + }); +} + +setupNoteCreator(); + +import * as editor from "."; + +globalExport({ + ...editor, + uiPromise, + noteEditorPromise: uiPromise, +}); diff --git a/ts/editor/index_reviewer.ts b/ts/editor/index_reviewer.ts new file mode 100644 index 000000000..eab11b5ae --- /dev/null +++ b/ts/editor/index_reviewer.ts @@ -0,0 +1,27 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { i18n } from "../editor"; +import ReviewerEditor from "./ReviewerEditor.svelte"; +import { promiseWithResolver } from "../lib/promise"; +import { globalExport } from "../lib/globals"; + +const [uiPromise, uiResolve] = promiseWithResolver(); + +async function setupReviewerEditor(): Promise { + await i18n; + + new ReviewerEditor({ + target: document.body, + props: { uiResolve }, + }); +} + +setupReviewerEditor(); + +import * as editor from "../editor"; + +globalExport({ + ...editor, + uiPromise, + noteEditorPromise: uiPromise, +}); diff --git a/ts/editor/index_wrapper.ts b/ts/editor/index_wrapper.ts deleted file mode 100644 index 6c0ec80af..000000000 --- a/ts/editor/index_wrapper.ts +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright: Ankitects Pty Ltd and contributors -// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -// extend the global namespace with our exports - not sure if there's a better way with esbuild -import * as globals from "./index"; -for (const key in globals) { - window[key] = globals[key]; -} - -// but also export as window.anki -window["anki"] = globals; diff --git a/ts/lib/globals.ts b/ts/lib/globals.ts new file mode 100644 index 000000000..3107a4fa4 --- /dev/null +++ b/ts/lib/globals.ts @@ -0,0 +1,11 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +export function globalExport(globals: Record): void { + for (const key in globals) { + window[key] = globals[key]; + } + + // but also export as window.anki + window["anki"] = globals; +}