mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
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:
parent
b23a6af63e
commit
efaaae8ce4
11 changed files with 60 additions and 3 deletions
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
##################################################
|
||||
|
||||
|
|
|
@ -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())});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
<ClozeButtons on:surround alwaysEnabled={true} />
|
||||
{#if isClozeField}
|
||||
<ClozeButtons on:surround alwaysEnabled={true} />
|
||||
{/if}
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue