diff --git a/proto/anki/notetypes.proto b/proto/anki/notetypes.proto index d6dadc03f..06bf463bf 100644 --- a/proto/anki/notetypes.proto +++ b/proto/anki/notetypes.proto @@ -33,6 +33,7 @@ service NotetypesService { rpc GetFieldNames(NotetypeId) returns (generic.StringList); rpc RestoreNotetypeToStock(RestoreNotetypeToStockRequest) returns (collection.OpChanges); + rpc GetClozeFieldOrds(NotetypeId) returns (GetClozeFieldOrdsResponse); } // Implicitly includes any of the above methods that are not listed in the @@ -242,3 +243,7 @@ enum ClozeField { CLOZE_FIELD_TEXT = 0; CLOZE_FIELD_BACK_EXTRA = 1; } + +message GetClozeFieldOrdsResponse { + repeated uint32 ords = 1; +} \ No newline at end of file diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 4157bef16..230084359 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -281,6 +281,10 @@ class ModelManager(DeprecatedNamesMixin): def sort_idx(self, notetype: NotetypeDict) -> int: return notetype["sortf"] + def cloze_fields(self, mid: NotetypeId) -> Sequence[int]: + """The list of index of fields that are used by cloze deletion in the note type with id `mid`.""" + return self.col._backend.get_cloze_field_ords(mid) + # Adding & changing fields ################################################## diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index b3530a19e..c1eb14b18 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -555,6 +555,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too note_type = self.note_type() flds = note_type["flds"] collapsed = [fld["collapsed"] for fld in flds] + cloze_fields_ords = self.mw.col.models.cloze_fields(self.note.mid) + cloze_fields = [ord in cloze_fields_ords for ord in range(len(flds))] plain_texts = [fld.get("plainText", False) for fld in flds] descriptions = [fld.get("description", "") for fld in flds] notetype_meta = {"id": self.note.mid, "modTime": note_type["mod"]} @@ -584,6 +586,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())}); setNotetypeMeta({json.dumps(notetype_meta)}); setCollapsed({json.dumps(collapsed)}); + setClozeFields({json.dumps(cloze_fields)}); setPlainTexts({json.dumps(plain_texts)}); setDescriptions({json.dumps(descriptions)}); setFonts({json.dumps(self.fonts())}); diff --git a/rslib/src/notetype/service.rs b/rslib/src/notetype/service.rs index fe899b035..3e0add71e 100644 --- a/rslib/src/notetype/service.rs +++ b/rslib/src/notetype/service.rs @@ -212,6 +212,21 @@ impl crate::services::NotetypesService for Collection { ) .map(Into::into) } + + fn get_cloze_field_ords( + &mut self, + input: anki_proto::notetypes::NotetypeId, + ) -> error::Result { + Ok(anki_proto::notetypes::GetClozeFieldOrdsResponse { + ords: self + .get_notetype(input.into())? + .unwrap() + .cloze_fields() + .iter() + .map(|ord| (*ord) as u32) + .collect(), + }) + } } impl From for Notetype { diff --git a/ts/editor/ClozeButtons.svelte b/ts/editor/ClozeButtons.svelte index 03d346012..e9faae540 100644 --- a/ts/editor/ClozeButtons.svelte +++ b/ts/editor/ClozeButtons.svelte @@ -71,8 +71,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html }); } - $: disabled = - !alwaysEnabled && (!$focusedInput || !editingInputIsRichText($focusedInput)); + $: enabled = + alwaysEnabled || + ($focusedInput && + editingInputIsRichText($focusedInput) && + $focusedInput.isClozeField); + $: disabled = !enabled; const incrementKeyCombination = "Control+Shift+C"; const sameKeyCombination = "Control+Alt+Shift+C"; diff --git a/ts/editor/EditorField.svelte b/ts/editor/EditorField.svelte index 89e797a29..4fc80e0c1 100644 --- a/ts/editor/EditorField.svelte +++ b/ts/editor/EditorField.svelte @@ -16,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html description: string; collapsed: boolean; hidden: boolean; + isClozeField: boolean; } export interface EditorFieldAPI { @@ -82,6 +83,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html element, direction: directionStore, editingArea: editingArea as EditingAreaAPI, + isClozeField: field.isClozeField, }); setContextProperty(api); diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index 9bf66d858..17ced575b 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -144,6 +144,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html fieldsCollapsed = sessionOptions[notetypeMeta?.id]?.fieldsCollapsed ?? defaultCollapsed; } + let clozeFields: boolean[] = []; + export function setClozeFields(defaultClozeFields: boolean[]): void { + clozeFields = defaultClozeFields; + } let richTextsHidden: boolean[] = []; let plainTextsHidden: boolean[] = []; @@ -276,6 +280,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html direction: fonts[index][2] ? "rtl" : "ltr", collapsed: fieldsCollapsed[index], hidden: hideFieldInOcclusionType(index, ioFields), + isClozeField: clozeFields[index], })) as FieldData[]; let lastSavedTags: string[] | null = null; @@ -573,6 +578,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html saveSession, setFields, setCollapsed, + setClozeFields, setPlainTexts, setDescriptions, setFonts, @@ -762,6 +768,7 @@ the AddCards dialog) should be implemented in the user of this component. $focusedInput = null; }} bind:this={richTextInputs[index]} + isClozeField={field.isClozeField} /> diff --git a/ts/editor/editor-toolbar/RichTextClozeButtons.svelte b/ts/editor/editor-toolbar/RichTextClozeButtons.svelte index 04aed2581..74d7b8db8 100644 --- a/ts/editor/editor-toolbar/RichTextClozeButtons.svelte +++ b/ts/editor/editor-toolbar/RichTextClozeButtons.svelte @@ -14,6 +14,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: richTextAPI = $focusedInput as RichTextInputAPI; async function onSurround({ detail }): Promise { + if (!richTextAPI.isClozeField) { + return; + } const richText = await richTextAPI.element; const { prefix, suffix } = detail; diff --git a/ts/editor/mathjax-overlay/MathjaxButtons.svelte b/ts/editor/mathjax-overlay/MathjaxButtons.svelte index 03098c96a..06f620114 100644 --- a/ts/editor/mathjax-overlay/MathjaxButtons.svelte +++ b/ts/editor/mathjax-overlay/MathjaxButtons.svelte @@ -15,6 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import ClozeButtons from "../ClozeButtons.svelte"; export let isBlock: boolean; + export let isClozeField: boolean; const dispatch = createEventDispatcher(); @@ -40,7 +41,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - + {#if isClozeField} + + {/if} { cleanup?.(); @@ -50,6 +52,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html on(container, "movecaretafter" as any, showOnAutofocus), on(container, "selectall" as any, showSelectAll), ); + isClozeField = input.isClozeField; } // Wait if the mathjax overlay is still active @@ -242,6 +245,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html { isBlock = false; await updateBlockAttribute(); diff --git a/ts/editor/rich-text-input/RichTextInput.svelte b/ts/editor/rich-text-input/RichTextInput.svelte index 74abed4e9..88549aefc 100644 --- a/ts/editor/rich-text-input/RichTextInput.svelte +++ b/ts/editor/rich-text-input/RichTextInput.svelte @@ -22,6 +22,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html /** The API exposed by the editable component */ editable: ContentEditableAPI; customStyles: Promise>; + isClozeField: boolean; } function editingInputIsRichText( @@ -84,6 +85,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let hidden = false; export const focusFlag = new Flag(); + export let isClozeField: boolean; const { focusedInput } = noteEditorContext.get(); const { content, editingInputs } = editingAreaContext.get(); @@ -156,6 +158,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html inputHandler, editable: {} as ContentEditableAPI, customStyles, + isClozeField, }; const allContexts = getAllContexts(); @@ -204,6 +207,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } + $: { + api.isClozeField = isClozeField; + } + onMount(() => { $editingInputs.push(api); $editingInputs = $editingInputs;