From 9c733848b87ea4e9a5cc9eaac98cb407cebb05b2 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 31 Mar 2024 15:51:03 +0700 Subject: [PATCH] Revert "Revert "Preserve HTML formatting inside clozes (#3038)"" This reverts commit e911b4b69ae433818510da098d0ac2661e88664f. Trying again now that 24.04 is out. --- qt/aqt/editor.py | 2 +- ts/editor/ClozeButtons.svelte | 10 ++- ts/editor/NoteEditor.svelte | 13 +++- .../RichTextClozeButtons.svelte | 11 ++- .../mathjax-overlay/MathjaxButtons.svelte | 2 +- .../mathjax-overlay/MathjaxOverlay.svelte | 7 +- ts/lib/tslib/wrap.ts | 69 +++++++++++++++++++ 7 files changed, 95 insertions(+), 19 deletions(-) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index b6c28c308..e17cf8823 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -1246,7 +1246,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too highest += 1 # must start at 1 highest = max(1, highest) - self.web.eval("wrap('{{c%d::', '}}');" % highest) + self.web.eval("wrapCloze(%d);" % highest) def setupForegroundButton(self) -> None: self.fcolour = self.mw.pm.profile.get("lastColour", "#00f") diff --git a/ts/editor/ClozeButtons.svelte b/ts/editor/ClozeButtons.svelte index 547760565..fdb29c320 100644 --- a/ts/editor/ClozeButtons.svelte +++ b/ts/editor/ClozeButtons.svelte @@ -55,18 +55,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html async function onIncrementCloze(): Promise { const highestCloze = getCurrentHighestCloze(true); - dispatch("surround", { - prefix: `{{c${highestCloze}::`, - suffix: "}}", + dispatch("cloze", { + n: highestCloze, }); } async function onSameCloze(): Promise { const highestCloze = getCurrentHighestCloze(false); - dispatch("surround", { - prefix: `{{c${highestCloze}::`, - suffix: "}}", + dispatch("cloze", { + n: highestCloze, }); } diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index 730af3155..2875fc499 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -392,7 +392,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { ImageOcclusionFieldIndexes } from "@generated/anki/image_occlusion_pb"; import { getImageOcclusionFields } from "@generated/backend"; - import { wrapInternal } from "@tslib/wrap"; + import { wrapClozeInternal, wrapInternal } from "@tslib/wrap"; import Shortcut from "$lib/components/Shortcut.svelte"; @@ -547,6 +547,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html }); } + function wrapCloze(n: number): void { + if (!$focusedInput || !editingInputIsRichText($focusedInput)) { + return; + } + + $focusedInput.element.then((element) => { + wrapClozeInternal(element, n); + }); + } + Object.assign(globalThis, { saveSession, setFields, @@ -565,6 +575,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html setNoteId, setNotetypeMeta, wrap, + wrapCloze, setMathjaxEnabled, setShrinkImages, setCloseHTMLTags, diff --git a/ts/editor/editor-toolbar/RichTextClozeButtons.svelte b/ts/editor/editor-toolbar/RichTextClozeButtons.svelte index 04aed2581..b41b37e85 100644 --- a/ts/editor/editor-toolbar/RichTextClozeButtons.svelte +++ b/ts/editor/editor-toolbar/RichTextClozeButtons.svelte @@ -3,7 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> - + diff --git a/ts/editor/mathjax-overlay/MathjaxButtons.svelte b/ts/editor/mathjax-overlay/MathjaxButtons.svelte index d83941d5a..75c187809 100644 --- a/ts/editor/mathjax-overlay/MathjaxButtons.svelte +++ b/ts/editor/mathjax-overlay/MathjaxButtons.svelte @@ -39,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - + { + on:cloze={async ({ detail }) => { const editor = await mathjaxEditor.editor; - const { prefix, suffix } = detail; - + const { n } = detail; editor.replaceSelection( - prefix + editor.getSelection() + suffix, + `{{c${n}::` + editor.getSelection() + "}}", ); }} /> diff --git a/ts/lib/tslib/wrap.ts b/ts/lib/tslib/wrap.ts index 39b10e9d1..042b2ae99 100644 --- a/ts/lib/tslib/wrap.ts +++ b/ts/lib/tslib/wrap.ts @@ -1,7 +1,9 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import { placeCaretAfter } from "../domlib/place-caret"; import { getRange, getSelection } from "./cross-browser"; +import { nodeIsElement } from "./dom"; function wrappedExceptForWhitespace(text: string, front: string, back: string): string { const match = text.match(/^(\s*)([^]*?)(\s*)$/)!; @@ -53,3 +55,70 @@ export function wrapInternal( moveCursorInside(selection, back); } } + +export function wrapClozeInternal(base: Element, n: number): void { + const selection = getSelection(base)!; + const range = getRange(selection); + if (!range) { + return; + } + + // Expand the range to include parent nodes whose children are already included. + // This is to work around .extractContents() adding redundant empty elements + let startParent: Node | null = range.startContainer.parentNode; + if ( + startParent !== base + && startParent?.firstChild === range.startContainer && range.startOffset === 0 + ) { + range.setStartBefore(startParent); + } + let endParent: Node | null = range.endContainer.parentNode; + if ( + endParent !== base + && endParent?.lastChild === range.endContainer && ( + (!nodeIsElement(range.endContainer) + && range.endOffset === range.endContainer.textContent?.length) + || (nodeIsElement(range.endContainer) + && range.endOffset === range.endContainer.childNodes.length) + ) + ) { + range.setEndAfter(endParent); + } + let expand: boolean; + do { + expand = false; + if ( + startParent + && startParent.parentNode !== base && startParent.parentNode?.firstChild === startParent + && range.isPointInRange(startParent.parentNode, startParent.parentNode?.childNodes.length) + ) { + startParent = startParent.parentNode; + range.setStartBefore(startParent); + expand = true; + } + if ( + endParent && endParent.parentNode !== base && endParent.parentNode?.lastChild === endParent + && range.isPointInRange(endParent.parentNode, 0) + ) { + endParent = endParent.parentNode; + range.setEndAfter(endParent); + expand = true; + } + if (range.endOffset === 0) { + range.setEndBefore(range.endContainer); + expand = true; + } + } while (expand); + + const fragment = range.extractContents(); + if (fragment.childNodes.length === 0) { + document.execCommand("inserthtml", false, `{{c${n}::}}`); + } else { + const startNode = document.createTextNode(`{{c${n}::`); + const endNode = document.createTextNode("}}"); + range.insertNode(endNode); + range.insertNode(fragment); + range.insertNode(startNode); + placeCaretAfter(endNode); + } +}