Add a temporary workaround for addcards

This commit is contained in:
Abdo 2025-06-02 06:04:17 +03:00
parent b3aa8e93f3
commit 05360e2d19
4 changed files with 97 additions and 90 deletions

View file

@ -172,63 +172,13 @@ class AddCards(QMainWindow):
if deck_id := self.col.default_deck_for_notetype(notetype_id): if deck_id := self.col.default_deck_for_notetype(notetype_id):
self.deck_chooser.selected_deck_id = deck_id self.deck_chooser.selected_deck_id = deck_id
# only used for detecting changed sticky fields on close if notetype_id:
self._last_added_note = None self.editor.set_nid(None, mid=notetype_id, focus_to=0)
# 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()
)
def _load_new_note(self, sticky_fields_from: Note | None = None) -> None: def _load_new_note(self, sticky_fields_from: Note | None = None) -> None:
note = self._new_note() self.editor.set_nid(
if old_note := sticky_fields_from: None, mid=self.notetype_chooser.selected_notetype_id, focus_to=0
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)
def on_operation_did_execute( def on_operation_did_execute(
self, changes: OpChanges, handler: object | None self, changes: OpChanges, handler: object | None
@ -279,12 +229,7 @@ class AddCards(QMainWindow):
aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),)) aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),))
def add_current_note(self) -> None: def add_current_note(self) -> None:
if self.editor.current_notetype_is_image_occlusion(): self.editor.web.eval(f"addCurrentNote({self.deck_chooser.selected_deck_id})")
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)
def _add_current_note(self) -> None: def _add_current_note(self) -> None:
note = self.editor.note note = self.editor.note

View file

@ -28,6 +28,7 @@ import aqt
import aqt.forms import aqt.forms
import aqt.operations import aqt.operations
import aqt.sound import aqt.sound
from anki._legacy import deprecated
from anki.cards import Card from anki.cards import Card
from anki.collection import Config from anki.collection import Config
from anki.consts import MODEL_CLOZE 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.notes import Note, NoteId
from anki.utils import checksum, is_mac, is_win, namedtmp from anki.utils import checksum, is_mac, is_win, namedtmp
from aqt import AnkiQt, gui_hooks from aqt import AnkiQt, gui_hooks
from aqt.operations.note import update_note
from aqt.operations.notetype import update_notetype_legacy from aqt.operations.notetype import update_notetype_legacy
from aqt.qt import * from aqt.qt import *
from aqt.sound import av_player from aqt.sound import av_player
@ -129,7 +129,7 @@ class Editor:
self.mw = mw self.mw = mw
self.widget = widget self.widget = widget
self.parentWindow = parentWindow self.parentWindow = parentWindow
self.note: Note | None = None self.nid: NoteId | None = None
# legacy argument provided? # legacy argument provided?
if addMode is not None: if addMode is not None:
editor_mode = EditorMode.ADD_CARDS if addMode else EditorMode.EDIT_CURRENT 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: def onBridgeCmd(self, cmd: str) -> Any:
if not self.note:
# shutdown
return
# focus lost or key/button pressed? # focus lost or key/button pressed?
if cmd.startswith("blur") or cmd.startswith("key"): if cmd.startswith("blur") or cmd.startswith("key"):
(type, ord_str) = cmd.split(":", 1) (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 # 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( def set_note(
self, self,
note: Note | None, note: Note | None,
@ -482,10 +490,10 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
focusTo: int | None = None, focusTo: int | None = None,
) -> None: ) -> None:
"Make NOTE the current note." "Make NOTE the current note."
self.note = note
self.currentField = None self.currentField = None
if self.note: if note:
self.loadNote(focusTo=focusTo) self.nid = note.id
self.load_note(mid=note.mid, focus_to=focusTo)
elif hide: elif hide:
self.widget.hide() self.widget.hide()
@ -493,43 +501,35 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
self.loadNote(self.currentField) self.loadNote(self.currentField)
@on_editor_ready @on_editor_ready
def loadNote(self, focusTo: int | None = None) -> None: def load_note(self, mid: int, focus_to: int | None = None) -> None:
if not self.note:
return
self.widget.show() self.widget.show()
# note_fields_status = self.note.fields_check()
def oncallback(arg: Any) -> None: def oncallback(arg: Any) -> None:
if not self.note: if not self.nid:
return return
# we currently do this synchronously to ensure we load before the # we currently do this synchronously to ensure we load before the
# sidebar on browser startup # sidebar on browser startup
if focusTo is not None: if focus_to is not None:
self.web.setFocus() self.web.setFocus()
gui_hooks.editor_did_load_note(self) gui_hooks.editor_did_load_note(self)
assert self.mw.pm.profile is not None 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) js = gui_hooks.editor_will_load_note(js, self.note, self)
self.web.evalWithCallback( self.web.evalWithCallback(
f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback
) )
def _save_current_note(self) -> None: @deprecated(replaced_by=load_note)
"Call after note is updated with data from webview." def loadNote(self, focusTo: int | None = None) -> None:
if not self.note: self.load_note(self.note.mid, focus_to=focusTo)
return
update_note(parent=self.widget, note=self.note).run_in_background(
initiator=self
)
def call_after_note_saved( def call_after_note_saved(
self, callback: Callable, keepFocus: bool = False self, callback: Callable, keepFocus: bool = False
) -> None: ) -> None:
"Save unsaved edits then call callback()." "Save unsaved edits then call callback()."
if not self.note: if not self.nid:
# calling code may not expect the callback to fire immediately # calling code may not expect the callback to fire immediately
self.mw.progress.single_shot(10, callback) self.mw.progress.single_shot(10, callback)
return return
@ -1007,6 +1007,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude, 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: def note_type(self) -> NotetypeDict:
assert self.note is not None assert self.note is not None
note_type = self.note.note_type() note_type = self.note.note_type()

View file

@ -704,6 +704,7 @@ exposed_backend_list = [
"get_note", "get_note",
"new_note", "new_note",
"note_fields_check", "note_fields_check",
"add_note",
# NotetypesService # NotetypesService
"get_notetype", "get_notetype",
"get_notetype_names", "get_notetype_names",

View file

@ -365,6 +365,57 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setClozeHint(hint); setClozeHint(hint);
} }
async function loadNewNote() {
await loadNote(0n, notetypeMeta.id, 0, null);
}
async function noteCanBeAdded(): Promise<boolean> {
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 { export function focusIfField(x: number, y: number): boolean {
const elements = document.elementsFromPoint(x, y); const elements = document.elementsFromPoint(x, y);
const first = elements[0].closest(".field-container"); 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, editorUpdateNote,
decodeIriPaths, decodeIriPaths,
noteFieldsCheck, noteFieldsCheck,
addNote,
} from "@generated/backend"; } from "@generated/backend";
import { wrapInternal } from "@tslib/wrap"; import { wrapInternal } from "@tslib/wrap";
import { getProfileConfig, getMeta, setMeta, getColConfig } from "@tslib/profile"; 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( async function loadNote(
nid: bigint, nid: bigint | null,
notetypeId: bigint, notetypeId: bigint,
focusTo: number, focusTo: number,
originalNoteId: bigint | null, originalNoteId: bigint | null,
) { ): Promise<bigint> {
const notetype = await getNotetype({ const notetype = await getNotetype({
ntid: notetypeId, ntid: notetypeId,
}); });
@ -627,7 +679,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} else { } else {
setNote( setNote(
await getNote({ await getNote({
nid, nid!,
}), }),
); );
} }
@ -679,7 +731,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
html: imageField, html: imageField,
mode: { mode: {
kind: "edit", kind: "edit",
noteId: nid, noteId: nid!,
}, },
}); });
} else if (originalNoteId) { } else if (originalNoteId) {
@ -694,6 +746,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
await updateDuplicateDisplay(); await updateDuplicateDisplay();
triggerChanges(); triggerChanges();
return note!.id;
} }
$: signalEditorState(editorState); $: signalEditorState(editorState);
@ -749,6 +803,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
resetIOImageLoaded, resetIOImageLoaded,
saveOcclusions, saveOcclusions,
setSticky, setSticky,
addCurrentNote,
...oldEditorAdapter, ...oldEditorAdapter,
}); });