diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 86e8a25b1..d3b8f343b 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -172,63 +172,13 @@ class AddCards(QMainWindow): if deck_id := self.col.default_deck_for_notetype(notetype_id): self.deck_chooser.selected_deck_id = deck_id - # only used for detecting changed sticky fields on close - self._last_added_note = None - - # copy fields into new note with the new notetype - old_note = self.editor.note - new_note = self._new_note() - if old_note: - old_field_names = list(old_note.keys()) - new_field_names = list(new_note.keys()) - copied_field_names = set() - for f in new_note.note_type()["flds"]: - field_name = f["name"] - # copy identical non-empty fields - if field_name in old_field_names and old_note[field_name]: - new_note[field_name] = old_note[field_name] - copied_field_names.add(field_name) - new_idx = 0 - for old_idx, old_field_value in enumerate(old_field_names): - # skip previously copied identical fields in new note - while ( - new_idx < len(new_field_names) - and new_field_names[new_idx] in copied_field_names - ): - new_idx += 1 - if new_idx >= len(new_field_names): - break - # copy non-empty old fields - if ( - old_field_value not in copied_field_names - and old_note.fields[old_idx] - ): - new_note.fields[new_idx] = old_note.fields[old_idx] - new_idx += 1 - - new_note.tags = old_note.tags - - # and update editor state - self.editor.note = new_note - self.editor.loadNote( - focusTo=min(self.editor.last_field_index or 0, len(new_note.fields) - 1) - ) - gui_hooks.addcards_did_change_note_type( - self, old_note.note_type(), new_note.note_type() - ) + if notetype_id: + self.editor.set_nid(None, mid=notetype_id, focus_to=0) def _load_new_note(self, sticky_fields_from: Note | None = None) -> None: - note = self._new_note() - if old_note := sticky_fields_from: - flds = note.note_type()["flds"] - # copy fields from old note - if old_note: - for n in range(min(len(note.fields), len(old_note.fields))): - if flds[n]["sticky"]: - note.fields[n] = old_note.fields[n] - # and tags - note.tags = old_note.tags - self.setAndFocusNote(note) + self.editor.set_nid( + None, mid=self.notetype_chooser.selected_notetype_id, focus_to=0 + ) def on_operation_did_execute( self, changes: OpChanges, handler: object | None @@ -279,12 +229,7 @@ class AddCards(QMainWindow): aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),)) def add_current_note(self) -> None: - if self.editor.current_notetype_is_image_occlusion(): - self.editor.update_occlusions_field() - self.editor.call_after_note_saved(self._add_current_note) - self.editor.reset_image_occlusion() - else: - self.editor.call_after_note_saved(self._add_current_note) + self.editor.web.eval(f"addCurrentNote({self.deck_chooser.selected_deck_id})") def _add_current_note(self) -> None: note = self.editor.note diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 76e959a04..582271b62 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -28,6 +28,7 @@ import aqt import aqt.forms import aqt.operations import aqt.sound +from anki._legacy import deprecated from anki.cards import Card from anki.collection import Config from anki.consts import MODEL_CLOZE @@ -37,7 +38,6 @@ from anki.models import NotetypeDict, NotetypeId, StockNotetype from anki.notes import Note, NoteId from anki.utils import checksum, is_mac, is_win, namedtmp from aqt import AnkiQt, gui_hooks -from aqt.operations.note import update_note from aqt.operations.notetype import update_notetype_legacy from aqt.qt import * from aqt.sound import av_player @@ -129,7 +129,7 @@ class Editor: self.mw = mw self.widget = widget self.parentWindow = parentWindow - self.note: Note | None = None + self.nid: NoteId | None = None # legacy argument provided? if addMode is not None: editor_mode = EditorMode.ADD_CARDS if addMode else EditorMode.EDIT_CURRENT @@ -380,10 +380,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too ###################################################################### def onBridgeCmd(self, cmd: str) -> Any: - if not self.note: - # shutdown - return - # focus lost or key/button pressed? if cmd.startswith("blur") or cmd.startswith("key"): (type, ord_str) = cmd.split(":", 1) @@ -475,6 +471,18 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too # Setting/unsetting the current note ###################################################################### + def set_nid( + self, + nid: NoteId | None, + mid: int, + focus_to: int | None = None, + ) -> None: + "Make note with ID `nid` the current note." + self.nid = nid + self.currentField = None + self.load_note(mid, focus_to=focus_to) + + @deprecated(replaced_by=set_nid) def set_note( self, note: Note | None, @@ -482,10 +490,10 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too focusTo: int | None = None, ) -> None: "Make NOTE the current note." - self.note = note self.currentField = None - if self.note: - self.loadNote(focusTo=focusTo) + if note: + self.nid = note.id + self.load_note(mid=note.mid, focus_to=focusTo) elif hide: self.widget.hide() @@ -493,43 +501,35 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too self.loadNote(self.currentField) @on_editor_ready - def loadNote(self, focusTo: int | None = None) -> None: - if not self.note: - return + def load_note(self, mid: int, focus_to: int | None = None) -> None: self.widget.show() - # note_fields_status = self.note.fields_check() def oncallback(arg: Any) -> None: - if not self.note: + if not self.nid: return # we currently do this synchronously to ensure we load before the # sidebar on browser startup - if focusTo is not None: + if focus_to is not None: self.web.setFocus() gui_hooks.editor_did_load_note(self) assert self.mw.pm.profile is not None - js = f"loadNote({self.note.id}, {self.note.mid}, {json.dumps(focusTo)}, {json.dumps(self.orig_note_id)});" + js = f"loadNote({json.dumps(self.nid)}, {mid}, {json.dumps(focus_to)}, {json.dumps(self.orig_note_id)});" js = gui_hooks.editor_will_load_note(js, self.note, self) self.web.evalWithCallback( f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback ) - def _save_current_note(self) -> None: - "Call after note is updated with data from webview." - if not self.note: - return - - update_note(parent=self.widget, note=self.note).run_in_background( - initiator=self - ) + @deprecated(replaced_by=load_note) + def loadNote(self, focusTo: int | None = None) -> None: + self.load_note(self.note.mid, focus_to=focusTo) def call_after_note_saved( self, callback: Callable, keepFocus: bool = False ) -> None: "Save unsaved edits then call callback()." - if not self.note: + if not self.nid: # calling code may not expect the callback to fire immediately self.mw.progress.single_shot(10, callback) return @@ -1007,6 +1007,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude, ) + @property + def note(self) -> Note | None: + if self.nid is None: + return None + return self.mw.col.get_note(self.nid) + def note_type(self) -> NotetypeDict: assert self.note is not None note_type = self.note.note_type() diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py index 45eac407c..b263cd1b3 100644 --- a/qt/aqt/mediasrv.py +++ b/qt/aqt/mediasrv.py @@ -704,6 +704,7 @@ exposed_backend_list = [ "get_note", "new_note", "note_fields_check", + "add_note", # NotetypesService "get_notetype", "get_notetype_names", diff --git a/ts/routes/editor/NoteEditor.svelte b/ts/routes/editor/NoteEditor.svelte index 08fa4c2d5..d7b2e214f 100644 --- a/ts/routes/editor/NoteEditor.svelte +++ b/ts/routes/editor/NoteEditor.svelte @@ -365,6 +365,57 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html setClozeHint(hint); } + async function loadNewNote() { + await loadNote(0n, notetypeMeta.id, 0, null); + } + + async function noteCanBeAdded(): Promise { + let problem: string | null = null; + const result = await noteFieldsCheck(note!); + if(result.state === NoteFieldsCheckResponse_State.EMPTY) { + if(isImageOcclusion) { + problem = tr.notetypesNoOcclusionCreated2(); + } else { + problem = tr.addingTheFirstFieldIsEmpty(); + } + } + if(result.state === NoteFieldsCheckResponse_State.MISSING_CLOZE) { + // TODO: askUser(tr.addingYouHaveAClozeDeletionNote()) + return false; + } + if(result.state === NoteFieldsCheckResponse_State.NOTETYPE_NOT_CLOZE) { + problem = tr.addingClozeOutsideClozeNotetype(); + } + if(result.state === NoteFieldsCheckResponse_State.FIELD_NOT_CLOZE) { + problem = tr.addingClozeOutsideClozeField(); + } + return problem ? false : true; + } + + async function addCurrentNoteInner(deckId: bigint) { + if(!await noteCanBeAdded()) { + return; + } + await addNote({ + note: note!, + deckId, + }); + await loadNewNote(); + } + + export async function addCurrentNote(deckId: bigint) { + if(mode !== "add") { + return; + } + if(isImageOcclusion) { + saveOcclusions(); + await addCurrentNoteInner(deckId); + resetIOImageLoaded(); + } else { + await addCurrentNoteInner(deckId); + } + } + export function focusIfField(x: number, y: number): boolean { const elements = document.elementsFromPoint(x, y); const first = elements[0].closest(".field-container"); @@ -465,6 +516,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html editorUpdateNote, decodeIriPaths, noteFieldsCheck, + addNote, } from "@generated/backend"; import { wrapInternal } from "@tslib/wrap"; import { getProfileConfig, getMeta, setMeta, getColConfig } from "@tslib/profile"; @@ -605,11 +657,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } async function loadNote( - nid: bigint, + nid: bigint | null, notetypeId: bigint, focusTo: number, originalNoteId: bigint | null, - ) { + ): Promise { const notetype = await getNotetype({ ntid: notetypeId, }); @@ -627,7 +679,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } else { setNote( await getNote({ - nid, + nid!, }), ); } @@ -679,7 +731,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html html: imageField, mode: { kind: "edit", - noteId: nid, + noteId: nid!, }, }); } else if (originalNoteId) { @@ -694,6 +746,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } await updateDuplicateDisplay(); triggerChanges(); + + return note!.id; } $: signalEditorState(editorState); @@ -749,6 +803,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html resetIOImageLoaded, saveOcclusions, setSticky, + addCurrentNote, ...oldEditorAdapter, });