diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index d3b8f343b..d4d94f0d4 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -110,9 +110,6 @@ class AddCards(QMainWindow): ) def reopen(self, mw: AnkiQt) -> None: - if not self.editor.fieldsAreBlank(): - return - defaults = self.col.defaults_for_adding( current_review_card=self.mw.reviewer.card ) @@ -315,9 +312,6 @@ class AddCards(QMainWindow): onOk() def afterSave() -> None: - if self.editor.fieldsAreBlank(self._last_added_note): - return onOk() - ask_user_dialog( tr.adding_discard_current_input(), callback=callback, diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index deedd2970..496d893a7 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -9,6 +9,7 @@ import json import mimetypes import os from collections.abc import Callable +from dataclasses import dataclass from enum import Enum from random import randrange from typing import Any @@ -16,7 +17,7 @@ from typing import Any from anki._legacy import deprecated from anki.cards import Card from anki.hooks import runFilter -from anki.models import NotetypeDict, StockNotetype +from anki.models import NotetypeDict, NotetypeId, StockNotetype from anki.notes import Note, NoteId from anki.utils import is_win from aqt import AnkiQt, gui_hooks @@ -81,6 +82,21 @@ def on_editor_ready(func: Callable) -> Callable: return decorated +@dataclass +class NoteInfo: + "Used to hold partial note info fetched from the webview" + + id: NoteId | None + mid: NotetypeId + fields: list[str] + + def __post_init__(self) -> None: + if self.id is not None: + self.id = NoteId(int(self.id)) + if self.mid is not None: + self.mid = NotetypeId(int(self.mid)) + + 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 @@ -103,7 +119,7 @@ class Editor: self.mw = mw self.widget = widget self.parentWindow = parentWindow - self.nid: NoteId | None = None + self.mid: NotetypeId | None = None # legacy argument provided? if addMode is not None: editor_mode = EditorMode.ADD_CARDS if addMode else EditorMode.EDIT_CURRENT @@ -118,8 +134,6 @@ class Editor: # current card, for card layout self.card: Card | None = None self.state: EditorState = EditorState.INITIAL - # used for the io mask editor's context menu - self.last_io_image_path: str | None = None self._ready = False self._ready_callbacks: list[Callable[[], None]] = [] self._init_links() @@ -127,7 +141,6 @@ class Editor: self.add_webview() self.setupWeb() self.setupShortcuts() - # gui_hooks.editor_did_init(self) # Initial setup ############################################################ @@ -327,7 +340,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too def _onFields(self) -> None: from aqt.fields import FieldDialog - FieldDialog(self.mw, self.note_type(), parent=self.parentWindow) + def on_note_info(note_info: NoteInfo) -> None: + FieldDialog( + self.mw, self.mw.col.models.get(note_info.mid), parent=self.parentWindow + ) + + self.get_note_info(on_note_info) def onCardLayout(self) -> None: self.call_after_note_saved(self._onCardLayout) @@ -340,16 +358,23 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too else: ord = 0 - assert self.note is not None - CardLayout( - self.mw, - self.note, - ord=ord, - parent=self.parentWindow, - fill_empty=False, - ) - if is_win: - self.parentWindow.activateWindow() + def on_note_info(note_info: NoteInfo) -> None: + if note_info.id: + note = self.mw.col.get_note(note_info.id) + else: + note = Note(self.mw.col, note_info.mid) + note.fields = note_info.fields + CardLayout( + self.mw, + note, + ord=ord, + parent=self.parentWindow, + fill_empty=False, + ) + if is_win: + self.parentWindow.activateWindow() + + self.get_note_info(on_note_info) # JS->Python bridge ###################################################################### @@ -361,62 +386,16 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too ord = int(ord_str) if type == "blur": self.currentField = None - # run any filters - if self.note and gui_hooks.editor_did_unfocus_field( - False, self.note, ord - ): - # something updated the note; update it after a subsequent focus - # event has had time to fire - self.mw.progress.timer( - 100, self.loadNoteKeepingFocus, False, parent=self.widget - ) else: - if self.note: - gui_hooks.editor_did_fire_typing_timer(self.note) + pass # focused into field? elif cmd.startswith("focus"): (type, num) = cmd.split(":", 1) self.last_field_index = self.currentField = int(num) - if self.note: - gui_hooks.editor_did_focus_field(self.note, self.currentField) - - elif cmd.startswith("toggleStickyAll"): - model = self.note_type() - flds = model["flds"] - - any_sticky = any([fld["sticky"] for fld in flds]) - result = [] - for fld in flds: - if not any_sticky or fld["sticky"]: - fld["sticky"] = not fld["sticky"] - - result.append(fld["sticky"]) - - update_notetype_legacy(parent=self.mw, notetype=model).run_in_background( - initiator=self - ) - - return result - - elif cmd.startswith("toggleSticky"): - (type, num) = cmd.split(":", 1) - ord = int(num) - - model = self.note_type() - fld = model["flds"][ord] - new_state = not fld["sticky"] - fld["sticky"] = new_state - - update_notetype_legacy(parent=self.mw, notetype=model).run_in_background( - initiator=self - ) - - return new_state elif cmd.startswith("saveTags"): - if self.note: - gui_hooks.editor_did_update_tags(self.note) + pass elif cmd.startswith("editorState"): (_, new_state_id, old_state_id) = cmd.split(":", 2) @@ -496,17 +475,10 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too assert self.mw.pm.profile is not None js = f"loadNote({json.dumps(self.nid)}, {mid}, {json.dumps(focus_to)}, {json.dumps(self.orig_note_id)});" - if self.note: - js = gui_hooks.editor_will_load_note(js, self.note, self) self.web.evalWithCallback( f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback ) - @deprecated(replaced_by=load_note) - def loadNote(self, focusTo: int | None = None) -> None: - assert self.note is not None - self.load_note(self.note.mid, focus_to=focusTo) - def call_after_note_saved( self, callback: Callable, keepFocus: bool = False ) -> None: @@ -519,19 +491,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too saveNow = call_after_note_saved - def fieldsAreBlank(self, previousNote: Note | None = None) -> bool: - if not self.note: - return True - m = self.note_type() - for c, f in enumerate(self.note.fields): - f = f.replace("
", "").strip() - notChangedvalues = {"", "
"} - if previousNote and m["flds"][c]["sticky"]: - notChangedvalues.add(previousNote.fields[c].replace("
", "").strip()) - if f not in notChangedvalues: - return False - return True - def cleanup(self) -> None: av_player.stop_and_clear_queue_if_caller(self.editorMode) self.set_note(None) @@ -557,21 +516,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too # Image occlusion ###################################################################### - def current_notetype_is_image_occlusion(self) -> bool: - if not self.note: - return False - - return ( - self.note_type().get("originalStockKind", None) - == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION - ) - def setup_mask_editor(self, image_path: str) -> None: try: if self.editorMode == EditorMode.ADD_CARDS: self.setup_mask_editor_for_new_note(image_path=image_path) else: - assert self.note is not None self.setup_mask_editor_for_existing_note(image_path=image_path) except Exception as e: showWarning(str(e)) @@ -613,17 +562,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too copy=Editor.onCopy, ) - @property - def note(self) -> Note | None: - if self.nid is None: - return None - return self.mw.col.get_note(self.nid) + def get_note_info(self, on_done: Callable[[NoteInfo], None]) -> None: + def wrapped_on_done(note_info: dict[str, Any]) -> None: + on_done(NoteInfo(**note_info)) - def note_type(self) -> NotetypeDict: - assert self.note is not None - note_type = self.note.note_type() - assert note_type is not None - return note_type + self.web.evalWithCallback("getNoteInfo()", wrapped_on_done) # Pasting, drag & drop, and keyboard layouts @@ -651,3 +594,4 @@ class EditorWebView(AnkiWebView): def onPaste(self) -> None: self.triggerPageAction(QWebEnginePage.WebAction.Paste) + self.triggerPageAction(QWebEnginePage.WebAction.Paste) diff --git a/ts/routes/editor/NoteEditor.svelte b/ts/routes/editor/NoteEditor.svelte index d707d9706..af256825c 100644 --- a/ts/routes/editor/NoteEditor.svelte +++ b/ts/routes/editor/NoteEditor.svelte @@ -449,6 +449,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html return true; } + export function getNoteInfo() { + return { + id: note?.id.toString() ?? null, + mid: notetypeMeta.id.toString(), + fields: note?.fields ?? [], + }; + } + + let richTextInputs: RichTextInput[] = []; $: richTextInputs = richTextInputs.filter(Boolean); @@ -870,6 +879,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html setPlainTexts, setDescriptions, setFonts, + getNoteInfo, focusField, setTags, setTagsCollapsed,