From 2778b9220c095f81333ba0444cd89c72987ebba8 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 23 Nov 2021 01:27:32 +0100 Subject: [PATCH] Mathjax editor improvements (#1502) * Remove unnecessary stopPropagation of mathjax-overlay events * Use CodeMirror component for MathjaxHandle * Refactor ResizeObserver code in MathjaxHandle * Wrap setRange in CodeMirror in try/catch * Add Mathjax Editor bottom margin * Add custom Enter and Shift+Enter shortcuts for the MathjaxHandle * Format * Move placeCaretAfter to domlib * Move focus back to field after editing Mathjax * Put Cursor after Mathjax after accepting * Add delete button for Mathjax * Change border color of mathjax menu * Refactor into MathjaxMenu * Put caretKeyword in variable * Use one ResizeObserver for all Mathjax images * Add minmimum width for Mathjax editor * is still smaller than minimal window width * Add bazel directories to .prettierignore and format from root * exclude ftl/usage (dae) the json files that live there are output from our tooling, and formatting them means an extra step each time we want to update them also exclude .mypy_cache, which is output by scripts/mypy* * minor ftl tweak: newline -> new line (dae) --- .prettierignore | 4 + .prettierrc | 2 +- ftl/core/editing.ftl | 1 + rslib/src/notetype/styling.css | 10 +- sass/breakpoints.scss | 21 +-- ts/domlib/place-caret.ts | 14 ++ ts/editable/ContentEditable.svelte | 58 +++--- ts/editable/Mathjax.svelte | 114 +++++++----- ts/editable/mathjax-element.ts | 16 +- ts/editor/CodeMirror.svelte | 19 +- ts/editor/PlainTextInput.svelte | 7 +- ts/editor/RichTextInput.svelte | 13 +- ts/editor/code-mirror.ts | 5 +- ts/editor/mathjax-overlay/Editor.svelte | 81 --------- ts/editor/mathjax-overlay/InlineBlock.svelte | 36 ---- .../mathjax-overlay/MathjaxButtons.svelte | 54 ++++++ .../mathjax-overlay/MathjaxEditor.svelte | 59 ++++++ .../mathjax-overlay/MathjaxHandle.svelte | 168 ++++++++---------- ts/editor/mathjax-overlay/MathjaxMenu.svelte | 69 +++++++ ts/editor/mathjax-overlay/icons.ts | 1 + ts/lib/events.ts | 4 + ts/lib/uuid.ts | 17 ++ 22 files changed, 447 insertions(+), 326 deletions(-) create mode 100644 ts/domlib/place-caret.ts delete mode 100644 ts/editor/mathjax-overlay/Editor.svelte delete mode 100644 ts/editor/mathjax-overlay/InlineBlock.svelte create mode 100644 ts/editor/mathjax-overlay/MathjaxButtons.svelte create mode 100644 ts/editor/mathjax-overlay/MathjaxEditor.svelte create mode 100644 ts/editor/mathjax-overlay/MathjaxMenu.svelte create mode 100644 ts/lib/uuid.ts diff --git a/.prettierignore b/.prettierignore index 3fd69af96..efcaaca13 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,6 @@ licenses.json vendor +node_modules +bazel-* +ftl/usage +.mypy_cache \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index b6bc56abe..4f93aaee5 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,5 @@ "trailingComma": "all", "printWidth": 88, "tabWidth": 4, - "semi": true, + "semi": true } diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl index e6fcc47cc..746a210b8 100644 --- a/ftl/core/editing.ftl +++ b/ftl/core/editing.ftl @@ -30,6 +30,7 @@ editing-latex-math-env = LaTeX math env. editing-mathjax-block = MathJax block editing-mathjax-chemistry = MathJax chemistry editing-mathjax-inline = MathJax inline +editing-mathjax-placeholder = Press { $accept } to accept, { $newline } for new line. editing-media = Media editing-ordered-list = Ordered list editing-outdent = Decrease indent diff --git a/rslib/src/notetype/styling.css b/rslib/src/notetype/styling.css index c5a11b359..3872575fb 100644 --- a/rslib/src/notetype/styling.css +++ b/rslib/src/notetype/styling.css @@ -1,7 +1,7 @@ .card { - font-family: arial; - font-size: 20px; - text-align: center; - color: black; - background-color: white; + font-family: arial; + font-size: 20px; + text-align: center; + color: black; + background-color: white; } diff --git a/sass/breakpoints.scss b/sass/breakpoints.scss index 3993ba264..3eba9a0af 100644 --- a/sass/breakpoints.scss +++ b/sass/breakpoints.scss @@ -3,14 +3,7 @@ @use "sass:list"; @use "sass:map"; -$bps: ( - "xs", - "sm", - "md", - "lg", - "xl", - "xxl", -); +$bps: ("xs", "sm", "md", "lg", "xl", "xxl"); $breakpoints: ( list.nth($bps, 2): 576px, @@ -28,10 +21,10 @@ $breakpoints: ( } @else { @content; } -}; +} @mixin with-breakpoints($prefix, $dict) { - @each $property, $values in $dict { + @each $property, $values in $dict { @each $bp, $value in $values { @if map.get($breakpoints, $bp) { @media (min-width: map.get($breakpoints, $bp)) { @@ -46,7 +39,7 @@ $breakpoints: ( } } } -}; +} @function breakpoints-upto($upto) { $result: (); @@ -66,14 +59,14 @@ $breakpoints: ( $result: (); @each $bp in breakpoints-upto($upto) { - $result: list.append($result, ".#{$prefix}-#{$bp}", $separator: comma) + $result: list.append($result, ".#{$prefix}-#{$bp}", $separator: comma); } @return $result; } @mixin with-breakpoints-upto($prefix, $dict) { - @each $property, $values in $dict { + @each $property, $values in $dict { @each $bp, $value in $values { $selector: breakpoint-selector-upto($prefix, $bp); @@ -90,4 +83,4 @@ $breakpoints: ( } } } -}; +} ; diff --git a/ts/domlib/place-caret.ts b/ts/domlib/place-caret.ts new file mode 100644 index 000000000..a5698c4b8 --- /dev/null +++ b/ts/domlib/place-caret.ts @@ -0,0 +1,14 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import { getSelection } from "../lib/cross-browser"; + +export function placeCaretAfter(node: Node): void { + const range = new Range(); + range.setStartAfter(node); + range.collapse(false); + + const selection = getSelection(node)!; + selection.removeAllRanges(); + selection.addRange(range); +} diff --git a/ts/editable/ContentEditable.svelte b/ts/editable/ContentEditable.svelte index 625f3f1c2..f37831bb2 100644 --- a/ts/editable/ContentEditable.svelte +++ b/ts/editable/ContentEditable.svelte @@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import type { Writable } from "svelte/store"; import { updateAllState } from "../components/WithState.svelte"; import { saveSelection, restoreSelection } from "../domlib/location"; - import { on } from "../lib/events"; + import { on, preventDefault } from "../lib/events"; import { registerShortcut } from "../lib/shortcuts"; export let nodes: Writable; @@ -18,29 +18,31 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let inputManager: (editable: HTMLElement) => void; + let removeOnFocus: () => void; + let removeOnPointerdown: () => void; + + function onBlur(): void { + const location = saveSelection(editable); + + removeOnFocus = on( + editable, + "focus", + () => { + if (location) { + restoreSelection(editable, location); + } + }, + { once: true }, + ); + + removeOnPointerdown = on(editable, "pointerdown", () => removeOnFocus?.(), { + once: true, + }); + } + /* must execute before DOMMirror */ function saveLocation(editable: HTMLElement) { - let removeOnFocus: () => void; - let removeOnPointerdown: () => void; - - const removeOnBlur = on(editable, "blur", () => { - const location = saveSelection(editable); - - removeOnFocus = on( - editable, - "focus", - () => { - if (location) { - restoreSelection(editable, location); - } - }, - { once: true }, - ); - - removeOnPointerdown = on(editable, "pointerdown", () => removeOnFocus?.(), { - once: true, - }); - }); + const removeOnBlur = on(editable, "blur", onBlur); return { destroy() { @@ -54,10 +56,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let editable: HTMLElement; $: if (editable) { - registerShortcut((event) => event.preventDefault(), "Control+B", editable); - registerShortcut((event) => event.preventDefault(), "Control+U", editable); - registerShortcut((event) => event.preventDefault(), "Control+I", editable); - registerShortcut((event) => event.preventDefault(), "Control+R", editable); + for (const keyCombination of [ + "Control+B", + "Control+U", + "Control+I", + "Control+R", + ]) { + registerShortcut(preventDefault, keyCombination, editable); + } } diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index f66171bef..16a72f90f 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -2,67 +2,85 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> - + + Mathjax diff --git a/ts/editor/PlainTextInput.svelte b/ts/editor/PlainTextInput.svelte index 58ace8a64..6615305bf 100644 --- a/ts/editor/PlainTextInput.svelte +++ b/ts/editor/PlainTextInput.svelte @@ -16,7 +16,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -
+
diff --git a/ts/editor/mathjax-overlay/InlineBlock.svelte b/ts/editor/mathjax-overlay/InlineBlock.svelte deleted file mode 100644 index 8fe7565b4..000000000 --- a/ts/editor/mathjax-overlay/InlineBlock.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - mathjaxElement.setAttribute("block", "false")} - on:click>{@html inlineIcon} - - - - mathjaxElement.setAttribute("block", "true")} - on:click>{@html blockIcon} - - diff --git a/ts/editor/mathjax-overlay/MathjaxButtons.svelte b/ts/editor/mathjax-overlay/MathjaxButtons.svelte new file mode 100644 index 000000000..d6309692f --- /dev/null +++ b/ts/editor/mathjax-overlay/MathjaxButtons.svelte @@ -0,0 +1,54 @@ + + + + + + + + mathjaxElement.setAttribute("block", "false")} + on:click>{@html inlineIcon} + + + + mathjaxElement.setAttribute("block", "true")} + on:click>{@html blockIcon} + + + + + + + + dispatch("delete")}>{@html deleteIcon} + + + + diff --git a/ts/editor/mathjax-overlay/MathjaxEditor.svelte b/ts/editor/mathjax-overlay/MathjaxEditor.svelte new file mode 100644 index 000000000..d95b52030 --- /dev/null +++ b/ts/editor/mathjax-overlay/MathjaxEditor.svelte @@ -0,0 +1,59 @@ + + + +
+ code.set(detail)} + on:blur + autofocus + /> +
+ + diff --git a/ts/editor/mathjax-overlay/MathjaxHandle.svelte b/ts/editor/mathjax-overlay/MathjaxHandle.svelte index 4570b2006..6c812cf41 100644 --- a/ts/editor/mathjax-overlay/MathjaxHandle.svelte +++ b/ts/editor/mathjax-overlay/MathjaxHandle.svelte @@ -4,35 +4,71 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> - {#if activeImage} - (dropdownApi = createDropdown(event.detail.selection))} - > - - - - - - - -
- - - - - -
-
+ on:reset={() => resetHandle(false)} + on:delete={() => resetHandle(true)} + /> {/if}
- - diff --git a/ts/editor/mathjax-overlay/MathjaxMenu.svelte b/ts/editor/mathjax-overlay/MathjaxMenu.svelte new file mode 100644 index 000000000..a1606b9bf --- /dev/null +++ b/ts/editor/mathjax-overlay/MathjaxMenu.svelte @@ -0,0 +1,69 @@ + + + +
+ + + + + + + dispatch("reset")} + /> + + dispatch("reset")} /> + + + dispatch("delete")} + /> + +
+ + diff --git a/ts/editor/mathjax-overlay/icons.ts b/ts/editor/mathjax-overlay/icons.ts index 99534e872..176911b69 100644 --- a/ts/editor/mathjax-overlay/icons.ts +++ b/ts/editor/mathjax-overlay/icons.ts @@ -3,3 +3,4 @@ export { default as inlineIcon } from "@mdi/svg/svg/format-wrap-square.svg"; export { default as blockIcon } from "@mdi/svg/svg/format-wrap-top-bottom.svg"; +export { default as deleteIcon } from "@mdi/svg/svg/delete.svg"; diff --git a/ts/lib/events.ts b/ts/lib/events.ts index 37cbbd751..0215a4ab5 100644 --- a/ts/lib/events.ts +++ b/ts/lib/events.ts @@ -31,3 +31,7 @@ export function on>( return () => target.removeEventListener(eventType, handler as EventListener, options); } + +export function preventDefault(event: Event): void { + event.preventDefault(); +} diff --git a/ts/lib/uuid.ts b/ts/lib/uuid.ts new file mode 100644 index 000000000..fc4052758 --- /dev/null +++ b/ts/lib/uuid.ts @@ -0,0 +1,17 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +/** + * TODO replace with crypto.randomUUID + */ +export function randomUUID(): string { + const value = `${1e7}-${1e3}-${4e3}-${8e3}-${1e11}`; + + return value.replace(/[018]/g, (character: string): string => + ( + Number(character) ^ + (crypto.getRandomValues(new Uint8Array(1))[0] & + (15 >> (Number(character) / 4))) + ).toString(16), + ); +}