Cloze button get disabled outside of cloze field (#3879)

* NF: replace `disabled` by `enabled`

This allows to remove the negations and, in my opinion, make the code
easier to understand and edit.

* Cloze button get disabled outside of cloze field

More specifically, if the user focus in a field that is not a cloze
field, the button are still there but appear as disabled. The shortcut
instead of adding the cloze context shows an alert explaining why this
can't be done.

While this message is already displayed when the user tries to add a
note with cloze in non-cloze field, I suspect it will save time to
stop the user as soon as possible from making mistake. This should
make very clear what is authorized and what is not.

It'll also be a reminder of whether the current field is a cloze or
not.

In order to do this, I added a back-end method (that I expect we may
reuse in ankidroid) to get the index of the fields used in cloze. This
set is sent to the note editor, which propagates it where needed.

In mathjax, the cloze symbol is removed when the selected field is not
a cloze field.
This commit is contained in:
Arthur Milchior 2025-04-24 10:37:41 +02:00 committed by GitHub
parent b23a6af63e
commit efaaae8ce4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 60 additions and 3 deletions

View file

@ -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;
}

View file

@ -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
##################################################

View file

@ -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())});

View file

@ -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<anki_proto::notetypes::GetClozeFieldOrdsResponse> {
Ok(anki_proto::notetypes::GetClozeFieldOrdsResponse {
ords: self
.get_notetype(input.into())?
.unwrap()
.cloze_fields()
.iter()
.map(|ord| (*ord) as u32)
.collect(),
})
}
}
impl From<anki_proto::notetypes::Notetype> for Notetype {

View file

@ -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";

View file

@ -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);

View file

@ -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}
/>
</Collapsible>
</svelte:fragment>

View file

@ -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<void> {
if (!richTextAPI.isClozeField) {
return;
}
const richText = await richTextAPI.element;
const { prefix, suffix } = detail;

View file

@ -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();
</script>
@ -40,7 +41,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</IconButton>
</ButtonGroup>
{#if isClozeField}
<ClozeButtons on:surround alwaysEnabled={true} />
{/if}
<ButtonGroup>
<IconButton

View file

@ -34,6 +34,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let cleanup: Callback;
let richTextInput: RichTextInputAPI | null = null;
let allowPromise = Promise.resolve();
// Whether the last focused input field corresponds to a cloze field.
let isClozeField: boolean = true;
async function initialize(input: EditingInputAPI | null): Promise<void> {
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
<MathjaxButtons
{isBlock}
{isClozeField}
on:setinline={async () => {
isBlock = false;
await updateBlockAttribute();

View file

@ -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<Record<string, any>>;
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;