diff --git a/ftl/core/adding.ftl b/ftl/core/adding.ftl index 03ea6b865..4a9756dc5 100644 --- a/ftl/core/adding.ftl +++ b/ftl/core/adding.ftl @@ -6,4 +6,6 @@ adding-history = History adding-note-deleted = (Note deleted) adding-shortcut = Shortcut: { $val } adding-the-first-field-is-empty = The first field is empty. -adding-you-have-a-cloze-deletion-note = You have a cloze deletion note type but have not made any cloze deletions. Proceed? +adding-you-have-a-cloze-deletion-note = You have a cloze notetype but have not made any cloze deletions. Proceed? +adding-cloze-outside-cloze-notetype = Cloze deletion can only be used on cloze notetypes. +adding-cloze-outside-cloze-field = Cloze deletion can only be used in fields which use the 'cloze:' filter. This is typically the first field. diff --git a/pylib/.pylintrc b/pylib/.pylintrc index c12747171..ba446e936 100644 --- a/pylib/.pylintrc +++ b/pylib/.pylintrc @@ -11,7 +11,7 @@ ignored-classes= QueuedCards, UnburyDeckIn, BuryOrSuspendCardsIn, - NoteIsDuplicateOrEmptyOut, + NoteFieldsCheckOut, BackendError, SetDeckCollapsedIn, diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index 2741bbc47..2f5e3f1f4 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -14,7 +14,8 @@ from anki.consts import MODEL_STD from anki.models import NotetypeDict, NotetypeId, TemplateDict from anki.utils import joinFields -DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State +DuplicateOrEmptyResult = _pb.NoteFieldsCheckOut.State +NoteFieldsCheckResult = _pb.NoteFieldsCheckOut.State # types NoteId = NewType("NoteId", int) @@ -190,12 +191,10 @@ class Note: addTag = add_tag delTag = remove_tag - # Unique/duplicate check + # Unique/duplicate/cloze check ################################################## - def duplicate_or_empty(self) -> DuplicateOrEmptyResult.V: - return self.col._backend.note_is_duplicate_or_empty( - self._to_backend_note() - ).state + def fields_check(self) -> NoteFieldsCheckResult.V: + return self.col._backend.note_fields_check(self._to_backend_note()).state - dupeOrEmpty = duplicate_or_empty + dupeOrEmpty = duplicate_or_empty = fields_check diff --git a/pylib/tests/test_collection.py b/pylib/tests/test_collection.py index 95b941708..f92daa365 100644 --- a/pylib/tests/test_collection.py +++ b/pylib/tests/test_collection.py @@ -74,15 +74,15 @@ def test_noteAddDelete(): c0 = note.cards()[0] assert "three" in c0.q() # it should not be a duplicate - assert not note.duplicate_or_empty() + assert not note.fields_check() # now let's make a duplicate note2 = col.newNote() note2["Front"] = "one" note2["Back"] = "" - assert note2.duplicate_or_empty() + assert note2.fields_check() # empty first field should not be permitted either note2["Front"] = " " - assert note2.duplicate_or_empty() + assert note2.fields_check() def test_fieldChecksum(): diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 9fe86773a..7a2f15154 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -6,10 +6,9 @@ from typing import Callable, List, Optional import aqt.editor import aqt.forms from anki.collection import OpChanges, SearchNode -from anki.consts import MODEL_CLOZE from anki.decks import DeckId from anki.models import NotetypeId -from anki.notes import DuplicateOrEmptyResult, Note, NoteId +from anki.notes import Note, NoteFieldsCheckResult, NoteId from anki.utils import htmlToTextLine, isMac from aqt import AnkiQt, gui_hooks from aqt.deckchooser import DeckChooser @@ -231,9 +230,16 @@ class AddCards(QDialog): ).run_in_background() def _note_can_be_added(self, note: Note) -> bool: - result = note.duplicate_or_empty() - if result == DuplicateOrEmptyResult.EMPTY: + result = note.fields_check() + if result == NoteFieldsCheckResult.EMPTY: problem = tr.adding_the_first_field_is_empty() + elif result == NoteFieldsCheckResult.MISSING_CLOZE: + if not askUser(tr.adding_you_have_a_cloze_deletion_note()): + return False + elif result == NoteFieldsCheckResult.NOTETYPE_NOT_CLOZE: + problem = tr.adding_cloze_outside_cloze_notetype() + elif result == NoteFieldsCheckResult.FIELD_NOT_CLOZE: + problem = tr.adding_cloze_outside_cloze_field() else: # duplicate entries are allowed these days problem = None @@ -244,12 +250,6 @@ class AddCards(QDialog): showWarning(problem, help=HelpPage.ADDING_CARD_AND_NOTE) return False - # missing cloze deletion? - if note.model()["type"] == MODEL_CLOZE: - if not note.cloze_numbers_in_fields(): - if not askUser(tr.adding_you_have_a_cloze_deletion_note()): - return False - return True def keyPressEvent(self, evt: QKeyEvent) -> None: diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 5caf8e546..b07137d69 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -27,7 +27,7 @@ from anki.collection import Config, SearchNode from anki.consts import MODEL_CLOZE from anki.hooks import runFilter from anki.httpclient import HttpClient -from anki.notes import DuplicateOrEmptyResult, Note +from anki.notes import Note, NoteFieldsCheckResult from anki.utils import checksum, isLin, isWin, namedtmp from aqt import AnkiQt, colors, gui_hooks from aqt.operations import QueryOp @@ -79,8 +79,9 @@ audio = ( _html = """
+ """ @@ -129,7 +130,7 @@ class Editor: # then load page self.web.stdHtml( - _html % tr.editing_show_duplicates(), + _html.format(tr.editing_show_duplicates()), css=[ "css/editor.css", ], @@ -437,7 +438,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ self.widget.show() self.updateTags() - dupe_status = self.note.duplicate_or_empty() + note_fields_status = self.note.fields_check() def oncallback(arg: Any) -> None: if not self.note: @@ -445,7 +446,7 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ self.setupForegroundButton() # we currently do this synchronously to ensure we load before the # sidebar on browser startup - self._update_duplicate_display(dupe_status) + self._update_duplicate_display(note_fields_status) if focusTo is not None: self.web.setFocus() gui_hooks.editor_did_load_note(self) @@ -494,25 +495,31 @@ $editorToolbar.then(({{ toolbar }}) => toolbar.appendGroup({{ if not note: return - def on_done(result: DuplicateOrEmptyResult.V) -> None: + def on_done(result: NoteFieldsCheckResult.V) -> None: if self.note != note: return self._update_duplicate_display(result) QueryOp( parent=self.parentWindow, - op=lambda _: note.duplicate_or_empty(), + op=lambda _: note.fields_check(), success=on_done, ).run_in_background() checkValid = _check_and_update_duplicate_display_async - def _update_duplicate_display(self, result: DuplicateOrEmptyResult.V) -> None: + def _update_duplicate_display(self, result: NoteFieldsCheckResult.V) -> None: cols = [""] * len(self.note.fields) - if result == DuplicateOrEmptyResult.DUPLICATE: + cloze_hint = "" + if result == NoteFieldsCheckResult.DUPLICATE: cols[0] = "dupe" + elif result == NoteFieldsCheckResult.NOTETYPE_NOT_CLOZE: + cloze_hint = tr.adding_cloze_outside_cloze_notetype() + 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)});") def showDupes(self) -> None: aqt.dialogs.open( diff --git a/rslib/backend.proto b/rslib/backend.proto index 0ebfb68f3..521432dfe 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -174,7 +174,7 @@ service NotesService { rpc ClozeNumbersInNote(Note) returns (ClozeNumbersInNoteOut); rpc AfterNoteUpdates(AfterNoteUpdatesIn) returns (OpChangesWithCount); rpc FieldNamesForNotes(FieldNamesForNotesIn) returns (FieldNamesForNotesOut); - rpc NoteIsDuplicateOrEmpty(Note) returns (NoteIsDuplicateOrEmptyOut); + rpc NoteFieldsCheck(Note) returns (NoteFieldsCheckOut); rpc CardsOfNote(NoteId) returns (CardIds); } @@ -1231,11 +1231,14 @@ message ReparentDecksIn { int64 new_parent = 2; } -message NoteIsDuplicateOrEmptyOut { +message NoteFieldsCheckOut { enum State { NORMAL = 0; EMPTY = 1; DUPLICATE = 2; + MISSING_CLOZE = 3; + NOTETYPE_NOT_CLOZE = 4; + FIELD_NOT_CLOZE = 5; } State state = 1; } diff --git a/rslib/src/backend/notes.rs b/rslib/src/backend/notes.rs index 673c32058..59025c1f8 100644 --- a/rslib/src/backend/notes.rs +++ b/rslib/src/backend/notes.rs @@ -120,11 +120,11 @@ impl NotesService for Backend { }) } - fn note_is_duplicate_or_empty(&self, input: pb::Note) -> Result