From 9b2378c3d28ba57f2a71f7336b4cf943dbc011d7 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 00:32:30 +0200 Subject: [PATCH 01/72] Introduce editable module --- qt/aqt/data/web/css/BUILD.bazel | 10 +- ts/editable/BUILD.bazel | 102 ++++++++++++++++++ .../editable-base.scss} | 0 ts/{editor => editable}/editable-container.ts | 2 + ts/{editor => editable}/editable.ts | 25 +++-- ts/editable/index.ts | 5 + ts/editor/BUILD.bazel | 14 +-- ts/editor/FormatBlockButtons.svelte | 7 +- ts/editor/WithImageConstrained.svelte | 4 +- ts/editor/codable.ts | 2 + ts/editor/editing-area.ts | 6 +- ts/editor/editor-field.ts | 2 + ts/editor/helpers.ts | 91 ---------------- ts/editor/index.ts | 24 ++--- ts/editor/input-handlers.ts | 2 +- ts/editor/label-container.ts | 2 + ts/lib/dom.ts | 89 +++++++++++++++ ts/tsconfig.json | 1 + 18 files changed, 255 insertions(+), 133 deletions(-) create mode 100644 ts/editable/BUILD.bazel rename ts/{editor/editable.scss => editable/editable-base.scss} (100%) rename ts/{editor => editable}/editable-container.ts (95%) rename ts/{editor => editable}/editable.ts (70%) create mode 100644 ts/editable/index.ts create mode 100644 ts/lib/dom.ts diff --git a/qt/aqt/data/web/css/BUILD.bazel b/qt/aqt/data/web/css/BUILD.bazel index 6ac2deb4c..30f5a4788 100644 --- a/qt/aqt/data/web/css/BUILD.bazel +++ b/qt/aqt/data/web/css/BUILD.bazel @@ -21,11 +21,19 @@ copy_files_into_group( name = "editor", srcs = [ "editor.css", - "editable.css", ], package = "//ts/editor", ) + +copy_files_into_group( + name = "editable", + srcs = [ + "editable-build.css", + ], + package = "//ts/editable", +) + copy_files_into_group( name = "reviewer", srcs = [ diff --git a/ts/editable/BUILD.bazel b/ts/editable/BUILD.bazel new file mode 100644 index 000000000..fa22483a0 --- /dev/null +++ b/ts/editable/BUILD.bazel @@ -0,0 +1,102 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_library") +load("//ts/svelte:svelte.bzl", "compile_svelte", "svelte_check") +load("//ts:prettier.bzl", "prettier_test") +load("//ts:eslint.bzl", "eslint_test") +load("//ts:esbuild.bzl", "esbuild") +load("//ts:vendor.bzl", "copy_bootstrap_icons", "copy_mdi_icons") +load("//ts:compile_sass.bzl", "compile_sass") + +svelte_files = glob(["*.svelte"]) + +svelte_names = [f.replace(".svelte", "") for f in svelte_files] + +filegroup( + name = "svelte_components", + srcs = svelte_names, + visibility = ["//visibility:public"], +) + +compile_svelte( + name = "svelte", + srcs = svelte_files, + visibility = ["//visibility:public"], + deps = [ + "//ts/components", + ], +) + +compile_sass( + srcs = [ + "editable-base.scss", + ], + group = "editable_scss", + visibility = ["//visibility:public"], + deps = [ + "//ts/sass:scrollbar_lib", + "//ts/sass/codemirror", + ], +) + +ts_library( + name = "editable", + srcs = glob(["*.ts"]), + module_name = "editable", + tsconfig = "//ts:tsconfig.json", + visibility = ["//visibility:public"], + deps = [ + "//ts/lib", + "//ts/sveltelib", + "//ts/components", + "//ts:image_module_support", + "@npm//svelte", + ] + svelte_names, +) + +esbuild( + name = "editable-build", + args = [ + "--loader:.svg=text", + "--resolve-extensions=.mjs,.js", + "--log-level=warning", + ], + entry_point = "index.ts", + output_css = "editable-build.css", + visibility = ["//visibility:public"], + deps = [ + "editable_ts", + "svelte_components", + "//ts/components", + "//ts/components:svelte_components", + "@npm//protobufjs", + ], +) + +# Tests +################ + +prettier_test( + name = "format_check", + srcs = glob([ + "*.ts", + "*.svelte", + ]), +) + +eslint_test( + name = "eslint", + srcs = glob( + [ + "*.ts", + ], + ), +) + +svelte_check( + name = "svelte_check", + srcs = glob([ + "*.ts", + "*.svelte", + ]) + [ + "//ts/components", + ], +) diff --git a/ts/editor/editable.scss b/ts/editable/editable-base.scss similarity index 100% rename from ts/editor/editable.scss rename to ts/editable/editable-base.scss diff --git a/ts/editor/editable-container.ts b/ts/editable/editable-container.ts similarity index 95% rename from ts/editor/editable-container.ts rename to ts/editable/editable-container.ts index 3448e662e..ed9d2c7e4 100644 --- a/ts/editor/editable-container.ts +++ b/ts/editable/editable-container.ts @@ -57,3 +57,5 @@ export class EditableContainer extends HTMLDivElement { return this.baseRule!.style.direction === "rtl"; } } + +customElements.define("anki-editable-container", EditableContainer, { extends: "div" }); diff --git a/ts/editor/editable.ts b/ts/editable/editable.ts similarity index 70% rename from ts/editor/editable.ts rename to ts/editable/editable.ts index 38ee75646..57ce5c7ce 100644 --- a/ts/editor/editable.ts +++ b/ts/editable/editable.ts @@ -1,10 +1,19 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import { bridgeCommand } from "./lib"; -import { elementIsBlock, caretToEnd, getBlockElement } from "./helpers"; -import { inCodable } from "./toolbar"; -import { wrap } from "./wrap"; +import { bridgeCommand } from "lib/bridgecommand"; +import { elementIsBlock, getBlockElement } from "lib/dom"; +// import { inCodable } from "./toolbar"; +// import { wrap } from "./wrap"; + +export function caretToEnd(node: Node): void { + const range = document.createRange(); + range.selectNodeContents(node); + range.collapse(false); + const selection = (node.getRootNode() as Document | ShadowRoot).getSelection()!; + selection.removeAllRanges(); + selection.addRange(range); +} function containsInlineContent(element: Element): boolean { for (const child of element.children) { @@ -37,7 +46,8 @@ export class Editable extends HTMLElement { focus(): void { super.focus(); - inCodable.set(false); + // TODO + // inCodable.set(false); } caretToEnd(): void { @@ -45,7 +55,8 @@ export class Editable extends HTMLElement { } surroundSelection(before: string, after: string): void { - wrap(before, after); + // TODO + // wrap(before, after); } onEnter(event: KeyboardEvent): void { @@ -63,3 +74,5 @@ export class Editable extends HTMLElement { event.preventDefault(); } } + +customElements.define("anki-editable", Editable); diff --git a/ts/editable/index.ts b/ts/editable/index.ts new file mode 100644 index 000000000..9dd688339 --- /dev/null +++ b/ts/editable/index.ts @@ -0,0 +1,5 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import "./editable-container"; +import "./editable"; diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 2d25e998c..e2ff25dae 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -25,18 +25,6 @@ compile_svelte( ], ) -compile_sass( - srcs = [ - "editable.scss", - ], - group = "editable_scss", - visibility = ["//visibility:public"], - deps = [ - "//ts/sass:scrollbar_lib", - "//ts/sass/codemirror", - ], -) - compile_sass( srcs = [ "fields.scss", @@ -71,6 +59,7 @@ ts_library( "//ts/lib", "//ts/sveltelib", "//ts/components", + "//ts/editable", "//ts/html-filter", "//ts:image_module_support", "@npm//svelte", @@ -156,6 +145,7 @@ esbuild( "bootstrap-icons", "mdi-icons", "svelte_components", + "//ts/editable", "//ts/components", "//ts/components:svelte_components", "@npm//protobufjs", diff --git a/ts/editor/FormatBlockButtons.svelte b/ts/editor/FormatBlockButtons.svelte index d2fc2a140..b7cf587c8 100644 --- a/ts/editor/FormatBlockButtons.svelte +++ b/ts/editor/FormatBlockButtons.svelte @@ -14,7 +14,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import OnlyEditable from "./OnlyEditable.svelte"; import CommandIconButton from "./CommandIconButton.svelte"; - import { getCurrentField, getListItem } from "./helpers"; + import { getListItem } from "lib/dom"; + import { getCurrentField } from "./helpers"; import { ulIcon, olIcon, @@ -31,7 +32,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html function outdentListItem() { const currentField = getCurrentField(); - if (getListItem(currentField.editableContainer.shadowRoot!)) { + if (getListItem(currentField!.editableContainer.shadowRoot!)) { document.execCommand("outdent"); } else { alert("Indent/unindent currently only works with lists."); @@ -40,7 +41,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html function indentListItem() { const currentField = getCurrentField(); - if (getListItem(currentField.editableContainer.shadowRoot!)) { + if (getListItem(currentField!.editableContainer.shadowRoot!)) { document.execCommand("indent"); } else { alert("Indent/unindent currently only works with lists."); diff --git a/ts/editor/WithImageConstrained.svelte b/ts/editor/WithImageConstrained.svelte index a9e303b2d..ab48e6fe5 100644 --- a/ts/editor/WithImageConstrained.svelte +++ b/ts/editor/WithImageConstrained.svelte @@ -4,12 +4,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html --> + +{@html converted} diff --git a/ts/editable/mathjax-components.ts b/ts/editable/mathjax-components.ts new file mode 100644 index 000000000..9b905b376 --- /dev/null +++ b/ts/editable/mathjax-components.ts @@ -0,0 +1,50 @@ +import MathjaxBlock_svelte from "./MathjaxBlock.svelte"; + +class MathjaxBlock extends HTMLElement { + connectedCallback() { + this.contentEditable = "false"; + + const mathjax = (this.dataset.mathjax = this.innerText); + this.innerHTML = ""; + + new MathjaxBlock_svelte({ + target: this, + props: { mathjax }, + }); + } +} + +// customElements.define("anki-mathjax-inline", MathjaxInline); +customElements.define("anki-mathjax-block", MathjaxBlock); + +const mathjaxInlineTagPattern = + /(.*?)<\/anki-mathjax-inline>/gsu; +const mathjaxBlockTagPattern = /(.*?)<\/anki-mathjax-block>/gsu; + +export function toMathjaxDelimiters(html: string): string { + return html + .replace( + mathjaxInlineTagPattern, + (_match: string, text: string) => `\(${text}\)` + ) + .replace( + mathjaxBlockTagPattern, + (_match: string, text: string) => `\[${text}\]` + ); +} + +const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; +const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; + +export function toMathjaxTags(html: string): string { + return html + .replace( + mathjaxInlineDelimiterPattern, + (_match: string, text: string) => + `${text}` + ) + .replace( + mathjaxBlockDelimiterPattern, + (_match: string, text: string) => `${text}` + ); +} diff --git a/ts/editable/mathjax.ts b/ts/editable/mathjax.ts new file mode 100644 index 000000000..556e64dcf --- /dev/null +++ b/ts/editable/mathjax.ts @@ -0,0 +1,38 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import { mathjax } from "mathjax-full/js/mathjax"; +import { TeX } from "mathjax-full/js/input/tex"; +import { CHTML } from "mathjax-full/js/output/chtml"; +import { HTMLAdaptor } from "mathjax-full/js/adaptors/HTMLAdaptor"; +import { RegisterHTMLHandler } from "mathjax-full/js/handlers/html"; + +import { AllPackages } from "mathjax-full/js/input/tex/AllPackages.js"; +import "mathjax-full/js/util/entities/all"; + +// @ts-expect-error Minor interface mismatch: document.documentElement.nodeValue might be null +const adaptor = new HTMLAdaptor(window); +RegisterHTMLHandler(adaptor); + +const texOptions = { + displayMath: [["\\[", "\\]"]], + processRefs: false, + processEnvironments: false, + processEscapes: false, + packages: AllPackages, +}; + +export function convertMathjax(input: string): string { + const tex = new TeX(texOptions); + const chtml = new CHTML({ + fontURL: "/_anki/js/vendor/mathjax/output/chtml/fonts/woff-v2", + }); + + const html = mathjax.document(input, { InputJax: tex, OutputJax: chtml }); + html.render(); + + return ( + adaptor.innerHTML(adaptor.head(html.document)) + + adaptor.innerHTML(adaptor.body(html.document)) + ); +} diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index e2ff25dae..a358c6358 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -148,6 +148,7 @@ esbuild( "//ts/editable", "//ts/components", "//ts/components:svelte_components", + "//ts/editable:svelte_components", "@npm//protobufjs", ], ) diff --git a/ts/editor/index.ts b/ts/editor/index.ts index 0d55e318b..cc4b32317 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -22,13 +22,14 @@ import { saveField } from "./change-timer"; import "./fields.css"; -import "editable/editable"; -import "editable/editable-container"; import "./label-container"; import "./codable"; import "./editor-field"; import type { EditorField } from "./editor-field"; import { EditingArea } from "./editing-area"; +import "editable/editable-container"; +import "editable/editable"; +import "editable/mathjax-components"; import { initToolbar, fieldFocused } from "./toolbar"; import { initTagEditor } from "./tag-editor"; diff --git a/ts/licenses.json b/ts/licenses.json index 2f177c860..af0913912 100644 --- a/ts/licenses.json +++ b/ts/licenses.json @@ -164,6 +164,14 @@ "path": "node_modules/commander", "licenseFile": "node_modules/commander/LICENSE" }, + "commander@8.1.0": { + "licenses": "MIT", + "repository": "https://github.com/tj/commander.js", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "path": "node_modules/speech-rule-engine/node_modules/commander", + "licenseFile": "node_modules/speech-rule-engine/node_modules/commander/LICENSE" + }, "css-browser-selector@0.6.5": { "licenses": "CC-BY-SA-2.5", "repository": "https://github.com/verbatim/css_browser_selector", @@ -426,6 +434,14 @@ "path": "node_modules/delaunator", "licenseFile": "node_modules/delaunator/LICENSE" }, + "esm@3.2.25": { + "licenses": "MIT", + "repository": "https://github.com/standard-things/esm", + "publisher": "John-David Dalton", + "email": "john.david.dalton@gmail.com", + "path": "node_modules/esm", + "licenseFile": "node_modules/esm/LICENSE" + }, "iconv-lite@0.6.3": { "licenses": "MIT", "repository": "https://github.com/ashtuchkin/iconv-lite", @@ -489,12 +505,31 @@ "path": "node_modules/marked", "licenseFile": "node_modules/marked/LICENSE.md" }, + "mathjax-full@3.2.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mathjax/Mathjax-src", + "path": "node_modules/mathjax-full", + "licenseFile": "node_modules/mathjax-full/LICENSE" + }, "mathjax@3.1.4": { "licenses": "Apache-2.0", "repository": "https://github.com/mathjax/MathJax", "path": "node_modules/mathjax", "licenseFile": "node_modules/mathjax/LICENSE" }, + "mhchemparser@4.1.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mhchem/mhchemParser", + "publisher": "Martin Hensel", + "path": "node_modules/mhchemparser", + "licenseFile": "node_modules/mhchemparser/LICENSE.txt" + }, + "mj-context-menu@0.6.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/zorkow/context-menu", + "path": "node_modules/mj-context-menu", + "licenseFile": "node_modules/mj-context-menu/README.md" + }, "protobufjs@6.11.2": { "licenses": "BSD-3-Clause", "repository": "https://github.com/protobufjs/protobuf.js", @@ -526,6 +561,28 @@ "url": "https://github.com/ChALkeR", "path": "node_modules/safer-buffer", "licenseFile": "node_modules/safer-buffer/LICENSE" + }, + "speech-rule-engine@3.3.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/zorkow/speech-rule-engine", + "path": "node_modules/speech-rule-engine", + "licenseFile": "node_modules/speech-rule-engine/LICENSE" + }, + "wicked-good-xpath@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/google/wicked-good-xpath", + "publisher": "Google Inc.", + "path": "node_modules/wicked-good-xpath", + "licenseFile": "node_modules/wicked-good-xpath/LICENSE" + }, + "xmldom-sre@0.1.31": { + "licenses": "MIT*", + "repository": "https://github.com/zorkow/xmldom", + "publisher": "jindw", + "email": "jindw@xidea.org", + "url": "http://www.xidea.org", + "path": "node_modules/xmldom-sre", + "licenseFile": "node_modules/xmldom-sre/LICENSE" } } diff --git a/ts/package.json b/ts/package.json index 1b68e2934..24722dcfc 100644 --- a/ts/package.json +++ b/ts/package.json @@ -72,6 +72,7 @@ "lodash-es": "^4.17.21", "marked": "=2.0.5", "mathjax": "^3.1.2", + "mathjax-full": "^3.2.0", "protobufjs": "^6.10.2" }, "resolutions": { diff --git a/ts/yarn.lock b/ts/yarn.lock index d2dd309de..a403e4680 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -1596,6 +1596,11 @@ commander@7: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== +commander@>=7.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.1.0.tgz#db36e3e66edf24ff591d639862c6ab2c52664362" + integrity sha512-mf45ldcuHSYShkplHHGKWb4TrmwQadxOn7v4WuhDJy0ZVoY5JFajaRDKD0PNe5qXzBX0rhovjTnP6Kz9LETcuA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2154,6 +2159,11 @@ eslint@^7.24.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -3356,6 +3366,16 @@ marked@=2.0.5, marked@^2.0.3: resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.5.tgz#2d15c759b9497b0e7b5b57f4c2edabe1002ef9e7" integrity sha512-yfCEUXmKhBPLOzEC7c+tc4XZdIeTdGoRCZakFMkCxodr7wDXqoapIME4wjcpBPJLNyUnKJ3e8rb8wlAgnLnaDw== +mathjax-full@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.2.0.tgz#e53269842a943d4df10502937518991268996c5c" + integrity sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA== + dependencies: + esm "^3.2.25" + mhchemparser "^4.1.0" + mj-context-menu "^0.6.1" + speech-rule-engine "^3.3.3" + mathjax@^3.1.2: version "3.1.4" resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-3.1.4.tgz#4e8932d12845c0abae8b7f1976ea98cb505e8420" @@ -3376,6 +3396,11 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +mhchemparser@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/mhchemparser/-/mhchemparser-4.1.1.tgz#a2142fdab37a02ec8d1b48a445059287790becd5" + integrity sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA== + micromatch@^4.0.2, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" @@ -3418,6 +3443,11 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mj-context-menu@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.6.1.tgz#a043c5282bf7e1cf3821de07b13525ca6f85aa69" + integrity sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA== + mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" @@ -4094,6 +4124,15 @@ spdx-satisfies@^5.0.0: spdx-expression-parse "^3.0.0" spdx-ranges "^2.0.0" +speech-rule-engine@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz#781ed03cbcf3279f94d1d80241025ea954c6d571" + integrity sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ== + dependencies: + commander ">=7.0.0" + wicked-good-xpath "^1.3.0" + xmldom-sre "^0.1.31" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -4505,6 +4544,11 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +wicked-good-xpath@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c" + integrity sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w= + word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" @@ -4554,6 +4598,11 @@ xmlcreate@^2.0.3: resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.3.tgz#df9ecd518fd3890ab3548e1b811d040614993497" integrity sha512-HgS+X6zAztGa9zIK3Y3LXuJes33Lz9x+YyTxgrkIdabu2vqcGOWwdfCpf1hWLRrd553wd4QCDf6BBO6FfdsRiQ== +xmldom-sre@^0.1.31: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4" + integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" From b0b2ae3ece863874acb445de89135d9879fddb05 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 03:40:56 +0200 Subject: [PATCH 03/72] Transform to mathjax components for fieldHTML --- ts/editable/MathjaxBlock.svelte | 1 - ts/editable/mathjax-components.ts | 5 +++-- ts/editor/editing-area.ts | 7 +++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ts/editable/MathjaxBlock.svelte b/ts/editable/MathjaxBlock.svelte index ba78b659a..0ff5f6a5f 100644 --- a/ts/editable/MathjaxBlock.svelte +++ b/ts/editable/MathjaxBlock.svelte @@ -6,7 +6,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { convertMathjax } from "./mathjax"; export let mathjax: string; - $: console.log(mathjax, convertMathjax(`\\(${mathjax}\\)`)); $: converted = convertMathjax(`\\(${mathjax}\\)`); diff --git a/ts/editable/mathjax-components.ts b/ts/editable/mathjax-components.ts index 9b905b376..7ed48f1bf 100644 --- a/ts/editable/mathjax-components.ts +++ b/ts/editable/mathjax-components.ts @@ -41,10 +41,11 @@ export function toMathjaxTags(html: string): string { .replace( mathjaxInlineDelimiterPattern, (_match: string, text: string) => - `${text}` + `${text}` ) .replace( mathjaxBlockDelimiterPattern, - (_match: string, text: string) => `${text}` + (_match: string, text: string) => + `${text}` ); } diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts index c7bdc8468..ee226995d 100644 --- a/ts/editor/editing-area.ts +++ b/ts/editor/editing-area.ts @@ -17,6 +17,7 @@ import { bridgeCommand } from "./lib"; import { onInput, onKey, onKeyUp } from "./input-handlers"; import { onFocus, onBlur } from "./focus-handlers"; import { nightModeKey } from "components/context-keys"; +import { toMathjaxTags, toMathjaxDelimiters } from "editable/mathjax-components"; function onCutOrCopy(): void { bridgeCommand("cutOrCopy"); @@ -92,11 +93,13 @@ export class EditingArea extends HTMLDivElement { } set fieldHTML(content: string) { - this.imageHandle.then(() => (this.activeInput.fieldHTML = content)); + this.imageHandle.then( + () => (this.activeInput.fieldHTML = toMathjaxTags(content)) + ); } get fieldHTML(): string { - return this.activeInput.fieldHTML; + return toMathjaxDelimiters(this.activeInput.fieldHTML); } connectedCallback(): void { From 1465d3a8481498fc7a00b9bca3c8027b97a3020d Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 04:21:35 +0200 Subject: [PATCH 04/72] Survive to Codable and back --- .../{MathjaxBlock.svelte => Mathjax.svelte} | 5 +- ts/editable/editable.ts | 16 +++++- ts/editable/mathjax-components.ts | 57 ++++++++++++++++--- 3 files changed, 65 insertions(+), 13 deletions(-) rename ts/editable/{MathjaxBlock.svelte => Mathjax.svelte} (55%) diff --git a/ts/editable/MathjaxBlock.svelte b/ts/editable/Mathjax.svelte similarity index 55% rename from ts/editable/MathjaxBlock.svelte rename to ts/editable/Mathjax.svelte index 0ff5f6a5f..e2304db39 100644 --- a/ts/editable/MathjaxBlock.svelte +++ b/ts/editable/Mathjax.svelte @@ -6,7 +6,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { convertMathjax } from "./mathjax"; export let mathjax: string; - $: converted = convertMathjax(`\\(${mathjax}\\)`); + export let type: "inline" | "block" | "chemistry"; + + $: delimiters = type === "inline" ? ["\\[", "\\]"] : ["\\(", "\\)"]; + $: converted = convertMathjax(`${delimiters[0]}${mathjax}${delimiters[1]}`); {@html converted} diff --git a/ts/editable/editable.ts b/ts/editable/editable.ts index 57ce5c7ce..e817cb47d 100644 --- a/ts/editable/editable.ts +++ b/ts/editable/editable.ts @@ -25,6 +25,8 @@ function containsInlineContent(element: Element): boolean { return true; } +const components = ["anki-mathjax-block", "anki-mathjax-inline"]; + export class Editable extends HTMLElement { set fieldHTML(content: string) { this.innerHTML = content; @@ -35,9 +37,17 @@ export class Editable extends HTMLElement { } get fieldHTML(): string { - return containsInlineContent(this) && this.innerHTML.endsWith("
") - ? this.innerHTML.slice(0, -4) // trim trailing
- : this.innerHTML; + const clone = this.cloneNode(true) as Element; + + for (const component of components) { + for (const element of clone.getElementsByTagName(component)) { + (element as any).undecorate(); + } + } + + return containsInlineContent(clone) && this.innerHTML.endsWith("
") + ? clone.innerHTML.slice(0, -4) // trim trailing
+ : clone.innerHTML; } connectedCallback(): void { diff --git a/ts/editable/mathjax-components.ts b/ts/editable/mathjax-components.ts index 7ed48f1bf..dc5b3a64f 100644 --- a/ts/editable/mathjax-components.ts +++ b/ts/editable/mathjax-components.ts @@ -1,35 +1,74 @@ -import MathjaxBlock_svelte from "./MathjaxBlock.svelte"; +import Mathjax_svelte from "./Mathjax.svelte"; -class MathjaxBlock extends HTMLElement { +class MathjaxInline extends HTMLElement { connectedCallback() { + this.decorate(); + } + + decorate(): void { this.contentEditable = "false"; const mathjax = (this.dataset.mathjax = this.innerText); + const type = "inline"; this.innerHTML = ""; - new MathjaxBlock_svelte({ + new Mathjax_svelte({ target: this, - props: { mathjax }, + props: { mathjax, type }, }); } + + undecorate(): void { + this.removeAttribute("contentEditable"); + this.innerHTML = this.dataset.mathjax ?? ""; + delete this.dataset.mathjax; + } +} + +customElements.define("anki-mathjax-inline", MathjaxInline); + +class MathjaxBlock extends HTMLElement { + connectedCallback() { + this.decorate(); + } + + decorate(): void { + this.contentEditable = "false"; + + const mathjax = (this.dataset.mathjax = this.innerText); + const type = "block"; + this.innerHTML = ""; + + new Mathjax_svelte({ + target: this, + props: { mathjax, type }, + }); + } + + undecorate(): void { + this.removeAttribute("contentEditable"); + this.innerHTML = this.dataset.mathjax ?? ""; + delete this.dataset.mathjax; + } } -// customElements.define("anki-mathjax-inline", MathjaxInline); customElements.define("anki-mathjax-block", MathjaxBlock); +// TODO mathjax regex will prob. fail at double quotes const mathjaxInlineTagPattern = - /(.*?)<\/anki-mathjax-inline>/gsu; -const mathjaxBlockTagPattern = /(.*?)<\/anki-mathjax-block>/gsu; + /]*?data-mathjax="(.*?)"[^>]*?>.*?<\/anki-mathjax-inline>/gsu; +const mathjaxBlockTagPattern = + /]*?data-mathjax="(.*?)"[^>]*?>.*?<\/anki-mathjax-block>/gsu; export function toMathjaxDelimiters(html: string): string { return html .replace( mathjaxInlineTagPattern, - (_match: string, text: string) => `\(${text}\)` + (_match: string, text: string) => `\\(${text}\\)` ) .replace( mathjaxBlockTagPattern, - (_match: string, text: string) => `\[${text}\]` + (_match: string, text: string) => `\\[${text}\\]` ); } From cd36fe2518670a2a9c1b01af987058b4f45eda4c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 05:13:42 +0200 Subject: [PATCH 05/72] Correctly include editable css --- qt/aqt/data/web/css/BUILD.bazel | 1 + ts/editable/BUILD.bazel | 1 + ts/editable/Mathjax.svelte | 10 +++++++++- ts/editable/editable-container.ts | 2 +- ts/editable/index.ts | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/qt/aqt/data/web/css/BUILD.bazel b/qt/aqt/data/web/css/BUILD.bazel index 30f5a4788..724099ac2 100644 --- a/qt/aqt/data/web/css/BUILD.bazel +++ b/qt/aqt/data/web/css/BUILD.bazel @@ -47,6 +47,7 @@ filegroup( srcs = [ "css_local", "editor", + "editable", "reviewer", ], visibility = ["//qt:__subpackages__"], diff --git a/ts/editable/BUILD.bazel b/ts/editable/BUILD.bazel index e2dc0b775..3c5447b76 100644 --- a/ts/editable/BUILD.bazel +++ b/ts/editable/BUILD.bazel @@ -65,6 +65,7 @@ esbuild( visibility = ["//visibility:public"], deps = [ "editable", + "editable_scss", "svelte_components", "//ts/components", "//ts/components:svelte_components", diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index e2304db39..532fb5f85 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -12,4 +12,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: converted = convertMathjax(`${delimiters[0]}${mathjax}${delimiters[1]}`); -{@html converted} +
+ {@html converted} +
+ + diff --git a/ts/editable/editable-container.ts b/ts/editable/editable-container.ts index ed9d2c7e4..68f06a14a 100644 --- a/ts/editable/editable-container.ts +++ b/ts/editable/editable-container.ts @@ -20,7 +20,7 @@ export class EditableContainer extends HTMLDivElement { const rootStyle = document.createElement("link"); rootStyle.setAttribute("rel", "stylesheet"); - rootStyle.setAttribute("href", "./_anki/css/editable.css"); + rootStyle.setAttribute("href", "./_anki/css/editable-build.css"); shadow.appendChild(rootStyle); this.baseStyle = document.createElement("style"); diff --git a/ts/editable/index.ts b/ts/editable/index.ts index 9dd688339..157ced9e6 100644 --- a/ts/editable/index.ts +++ b/ts/editable/index.ts @@ -1,5 +1,7 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import "./editable-base.css"; import "./editable-container"; import "./editable"; +import "./mathjax-components"; From b0378690c0f4290259deddab0b5e6160e12d4e9c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 05:38:45 +0200 Subject: [PATCH 06/72] Enable edit mode for mathjax blocks --- ts/editable/Mathjax.svelte | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index 532fb5f85..4ae075a3b 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -8,16 +8,39 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let mathjax: string; export let type: "inline" | "block" | "chemistry"; - $: delimiters = type === "inline" ? ["\\[", "\\]"] : ["\\(", "\\)"]; + let edit = false; + $: delimiters = type === "inline" ? ["\\(", "\\)"] : ["\\[", "\\]"]; $: converted = convertMathjax(`${delimiters[0]}${mathjax}${delimiters[1]}`); + + function autofocus(element: HTMLElement): void { + element.focus(); + } -
- {@html converted} -
+{#if edit} + {#if type === "block"} +
(edit = false)} + use:autofocus + /> + {:else} + (edit = false)} + use:autofocus + /> + {/if} +{:else} +
(edit = true)}> + {@html converted} +
+{/if} From 7950078e2b065fe451782d0e0b36c960a9e31a84 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 18:57:34 +0200 Subject: [PATCH 07/72] Nicely portray mathjax with right color + alignment in Editor --- ts/editable/BUILD.bazel | 1 + ts/editable/Mathjax.svelte | 46 +++++++++++-------------------- ts/editable/mathjax-components.ts | 6 ---- ts/editable/mathjax.ts | 38 ++++++------------------- 4 files changed, 25 insertions(+), 66 deletions(-) diff --git a/ts/editable/BUILD.bazel b/ts/editable/BUILD.bazel index 3c5447b76..0b8a417f4 100644 --- a/ts/editable/BUILD.bazel +++ b/ts/editable/BUILD.bazel @@ -50,6 +50,7 @@ ts_library( "//ts:image_module_support", "@npm//svelte", "@npm//mathjax-full", + "@npm//mathjax", ] + svelte_names, ) diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index 4ae075a3b..6188898e8 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -8,39 +8,25 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let mathjax: string; export let type: "inline" | "block" | "chemistry"; - let edit = false; - $: delimiters = type === "inline" ? ["\\(", "\\)"] : ["\\[", "\\]"]; - $: converted = convertMathjax(`${delimiters[0]}${mathjax}${delimiters[1]}`); - - function autofocus(element: HTMLElement): void { - element.focus(); - } + $: converted = convertMathjax(mathjax); + $: encoded = encodeURIComponent(converted); -{#if edit} - {#if type === "block"} -
(edit = false)} - use:autofocus - /> - {:else} - (edit = false)} - use:autofocus - /> - {/if} -{:else} -
(edit = true)}> - {@html converted} -
-{/if} +Mathjax diff --git a/ts/editable/mathjax-components.ts b/ts/editable/mathjax-components.ts index dc5b3a64f..7da335ce9 100644 --- a/ts/editable/mathjax-components.ts +++ b/ts/editable/mathjax-components.ts @@ -6,8 +6,6 @@ class MathjaxInline extends HTMLElement { } decorate(): void { - this.contentEditable = "false"; - const mathjax = (this.dataset.mathjax = this.innerText); const type = "inline"; this.innerHTML = ""; @@ -19,7 +17,6 @@ class MathjaxInline extends HTMLElement { } undecorate(): void { - this.removeAttribute("contentEditable"); this.innerHTML = this.dataset.mathjax ?? ""; delete this.dataset.mathjax; } @@ -33,8 +30,6 @@ class MathjaxBlock extends HTMLElement { } decorate(): void { - this.contentEditable = "false"; - const mathjax = (this.dataset.mathjax = this.innerText); const type = "block"; this.innerHTML = ""; @@ -46,7 +41,6 @@ class MathjaxBlock extends HTMLElement { } undecorate(): void { - this.removeAttribute("contentEditable"); this.innerHTML = this.dataset.mathjax ?? ""; delete this.dataset.mathjax; } diff --git a/ts/editable/mathjax.ts b/ts/editable/mathjax.ts index 556e64dcf..ba360bc66 100644 --- a/ts/editable/mathjax.ts +++ b/ts/editable/mathjax.ts @@ -1,38 +1,16 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import { mathjax } from "mathjax-full/js/mathjax"; -import { TeX } from "mathjax-full/js/input/tex"; -import { CHTML } from "mathjax-full/js/output/chtml"; -import { HTMLAdaptor } from "mathjax-full/js/adaptors/HTMLAdaptor"; -import { RegisterHTMLHandler } from "mathjax-full/js/handlers/html"; - -import { AllPackages } from "mathjax-full/js/input/tex/AllPackages.js"; -import "mathjax-full/js/util/entities/all"; - -// @ts-expect-error Minor interface mismatch: document.documentElement.nodeValue might be null -const adaptor = new HTMLAdaptor(window); -RegisterHTMLHandler(adaptor); - -const texOptions = { - displayMath: [["\\[", "\\]"]], - processRefs: false, - processEnvironments: false, - processEscapes: false, - packages: AllPackages, -}; +import "mathjax/es5/tex-svg-full"; export function convertMathjax(input: string): string { - const tex = new TeX(texOptions); - const chtml = new CHTML({ - fontURL: "/_anki/js/vendor/mathjax/output/chtml/fonts/woff-v2", - }); + const svg = globalThis.MathJax.tex2svg(input).children[0]; - const html = mathjax.document(input, { InputJax: tex, OutputJax: chtml }); - html.render(); + const style = document.createElement("style") as HTMLStyleElement; - return ( - adaptor.innerHTML(adaptor.head(html.document)) + - adaptor.innerHTML(adaptor.body(html.document)) - ); + const styles = `svg { color: white; font-size: 24px; }`; + style.appendChild(document.createTextNode(styles)); + + svg.insertBefore(style, svg.children[0]); + return svg.outerHTML; } From 167370fcb79ca2735f6027c44d7e3b003585e43e Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 20:52:55 +0200 Subject: [PATCH 08/72] Do not start ImageHandle for images with [data-anki] --- ts/editor/WithImageConstrained.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/editor/WithImageConstrained.svelte b/ts/editor/WithImageConstrained.svelte index ab48e6fe5..038c4712a 100644 --- a/ts/editor/WithImageConstrained.svelte +++ b/ts/editor/WithImageConstrained.svelte @@ -70,7 +70,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html continue; } - if (node.tagName === "IMG") { + if (node.tagName === "IMG" && !(node as HTMLElement).dataset.anki) { result.push(node as HTMLImageElement); } else { result.push(...filterImages(node.children)); From 6953ea97032f22e21a5cc7791a85b2afc4d3c01c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 21:01:35 +0200 Subject: [PATCH 09/72] Use margin:auto instead of text-align:center for mathjax-block --- ts/editable/Mathjax.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index 6188898e8..accac535a 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -26,7 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html &.block { display: block; - text-align: center; + margin: auto; } } From 14aaa10bf35929504afce06067d1cb89be2f865b Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 21:41:34 +0200 Subject: [PATCH 10/72] Implement latex highlighting for Latex in codable --- ts/editor/codable.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ts/editor/codable.ts b/ts/editor/codable.ts index 5e4f778f2..2774a4faf 100644 --- a/ts/editor/codable.ts +++ b/ts/editor/codable.ts @@ -2,7 +2,9 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import * as CodeMirror from "codemirror/lib/codemirror"; +import "codemirror/mode/python/python"; import "codemirror/mode/htmlmixed/htmlmixed"; +import "codemirror/mode/stex/stex"; import "codemirror/addon/fold/foldcode"; import "codemirror/addon/fold/foldgutter"; import "codemirror/addon/fold/xml-fold"; @@ -11,8 +13,22 @@ import "codemirror/addon/edit/closetag.js"; import { inCodable } from "./toolbar"; +const latex = { + name: "stex", + inMathMode: true, +}; + +const htmlanki = { + name: "htmlmixed", + tags: { + "anki-mathjax-inline": [[null, null, latex]], + "anki-mathjax-block": [[null, null, latex]], + "anki-mathjax-chemistry": [[null, null, latex]], + }, +}; + const codeMirrorOptions = { - mode: "htmlmixed", + mode: htmlanki, theme: "monokai", lineNumbers: true, lineWrapping: true, From 6b14afda273a153e3d923fbad9fe192b4f8656ee Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Wed, 4 Aug 2021 22:01:03 +0200 Subject: [PATCH 11/72] Add MathjaxHandle --- ts/editor/MathjaxHandle.svelte | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 ts/editor/MathjaxHandle.svelte diff --git a/ts/editor/MathjaxHandle.svelte b/ts/editor/MathjaxHandle.svelte new file mode 100644 index 000000000..ed5c35df0 --- /dev/null +++ b/ts/editor/MathjaxHandle.svelte @@ -0,0 +1,7 @@ + + From d7e0f77439a3492e432436c0601e6a1f277e79a4 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 06:01:43 +0200 Subject: [PATCH 12/72] Unify anki-mathjax-* to just anki-mathjax --- ts/editable/Mathjax.svelte | 4 +- ts/editable/editable.ts | 11 ++-- ts/editable/index.ts | 2 +- ts/editable/mathjax-component.ts | 72 ++++++++++++++++++++++++++ ts/editable/mathjax-components.ts | 84 ------------------------------- ts/editable/mathjax.ts | 2 - ts/editor/codable.ts | 4 +- ts/editor/editing-area.ts | 2 +- ts/editor/index.ts | 2 +- 9 files changed, 86 insertions(+), 97 deletions(-) create mode 100644 ts/editable/mathjax-component.ts delete mode 100644 ts/editable/mathjax-components.ts diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index accac535a..79eb49367 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { convertMathjax } from "./mathjax"; export let mathjax: string; - export let type: "inline" | "block" | "chemistry"; + export let block: boolean; $: converted = convertMathjax(mathjax); $: encoded = encodeURIComponent(converted); @@ -14,7 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html Mathjax]*?block="(.*?)")?[^>]*?>(.*?)<\/anki-mathjax>/gsu; + +export function toMathjaxDelimiters(html: string): string { + return html.replace( + mathjaxTagPattern, + (_match: string, block: string | undefined, text: string) => + typeof block === "string" && block !== "false" + ? `\\[${text}\\]` + : `\\(${text}\\)` + ); +} + +const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; +const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; + +export function toMathjaxTags(html: string): string { + return html + .replace( + mathjaxBlockDelimiterPattern, + (_match: string, text: string) => + `${text}` + ) + .replace( + mathjaxInlineDelimiterPattern, + (_match: string, text: string) => `${text}` + ); +} diff --git a/ts/editable/mathjax-components.ts b/ts/editable/mathjax-components.ts deleted file mode 100644 index 7da335ce9..000000000 --- a/ts/editable/mathjax-components.ts +++ /dev/null @@ -1,84 +0,0 @@ -import Mathjax_svelte from "./Mathjax.svelte"; - -class MathjaxInline extends HTMLElement { - connectedCallback() { - this.decorate(); - } - - decorate(): void { - const mathjax = (this.dataset.mathjax = this.innerText); - const type = "inline"; - this.innerHTML = ""; - - new Mathjax_svelte({ - target: this, - props: { mathjax, type }, - }); - } - - undecorate(): void { - this.innerHTML = this.dataset.mathjax ?? ""; - delete this.dataset.mathjax; - } -} - -customElements.define("anki-mathjax-inline", MathjaxInline); - -class MathjaxBlock extends HTMLElement { - connectedCallback() { - this.decorate(); - } - - decorate(): void { - const mathjax = (this.dataset.mathjax = this.innerText); - const type = "block"; - this.innerHTML = ""; - - new Mathjax_svelte({ - target: this, - props: { mathjax, type }, - }); - } - - undecorate(): void { - this.innerHTML = this.dataset.mathjax ?? ""; - delete this.dataset.mathjax; - } -} - -customElements.define("anki-mathjax-block", MathjaxBlock); - -// TODO mathjax regex will prob. fail at double quotes -const mathjaxInlineTagPattern = - /]*?data-mathjax="(.*?)"[^>]*?>.*?<\/anki-mathjax-inline>/gsu; -const mathjaxBlockTagPattern = - /]*?data-mathjax="(.*?)"[^>]*?>.*?<\/anki-mathjax-block>/gsu; - -export function toMathjaxDelimiters(html: string): string { - return html - .replace( - mathjaxInlineTagPattern, - (_match: string, text: string) => `\\(${text}\\)` - ) - .replace( - mathjaxBlockTagPattern, - (_match: string, text: string) => `\\[${text}\\]` - ); -} - -const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; -const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; - -export function toMathjaxTags(html: string): string { - return html - .replace( - mathjaxInlineDelimiterPattern, - (_match: string, text: string) => - `${text}` - ) - .replace( - mathjaxBlockDelimiterPattern, - (_match: string, text: string) => - `${text}` - ); -} diff --git a/ts/editable/mathjax.ts b/ts/editable/mathjax.ts index ba360bc66..2f6e8b5db 100644 --- a/ts/editable/mathjax.ts +++ b/ts/editable/mathjax.ts @@ -1,8 +1,6 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import "mathjax/es5/tex-svg-full"; - export function convertMathjax(input: string): string { const svg = globalThis.MathJax.tex2svg(input).children[0]; diff --git a/ts/editor/codable.ts b/ts/editor/codable.ts index 2774a4faf..3839c9551 100644 --- a/ts/editor/codable.ts +++ b/ts/editor/codable.ts @@ -21,9 +21,7 @@ const latex = { const htmlanki = { name: "htmlmixed", tags: { - "anki-mathjax-inline": [[null, null, latex]], - "anki-mathjax-block": [[null, null, latex]], - "anki-mathjax-chemistry": [[null, null, latex]], + "anki-mathjax": [[null, null, latex]], }, }; diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts index ee226995d..7da073e11 100644 --- a/ts/editor/editing-area.ts +++ b/ts/editor/editing-area.ts @@ -17,7 +17,7 @@ import { bridgeCommand } from "./lib"; import { onInput, onKey, onKeyUp } from "./input-handlers"; import { onFocus, onBlur } from "./focus-handlers"; import { nightModeKey } from "components/context-keys"; -import { toMathjaxTags, toMathjaxDelimiters } from "editable/mathjax-components"; +import { toMathjaxTags, toMathjaxDelimiters } from "editable/mathjax-component"; function onCutOrCopy(): void { bridgeCommand("cutOrCopy"); diff --git a/ts/editor/index.ts b/ts/editor/index.ts index cc4b32317..c183cfe48 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -29,7 +29,7 @@ import type { EditorField } from "./editor-field"; import { EditingArea } from "./editing-area"; import "editable/editable-container"; import "editable/editable"; -import "editable/mathjax-components"; +import "editable/mathjax-component"; import { initToolbar, fieldFocused } from "./toolbar"; import { initTagEditor } from "./tag-editor"; From 9fb0ce973b98d7348a1f39f33b6f3f6155e21e06 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 19:25:35 +0200 Subject: [PATCH 13/72] Implement moveNodesInsertedBeforeEndToAfterEnd This will prevent the user typing into the decorated elements by accident because they place cursor behind it --- ts/editable/editable.ts | 9 +++-- ts/editable/mathjax-component.ts | 63 ++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/ts/editable/editable.ts b/ts/editable/editable.ts index 187bb3ca1..f05f7b922 100644 --- a/ts/editable/editable.ts +++ b/ts/editable/editable.ts @@ -50,9 +50,12 @@ export class Editable extends HTMLElement { } } - return containsInlineContent(clone) && this.innerHTML.endsWith("
") - ? clone.innerHTML.slice(0, -4) // trim trailing
- : clone.innerHTML; + const result = + containsInlineContent(clone) && clone.innerHTML.endsWith("
") + ? clone.innerHTML.slice(0, -4) // trim trailing
+ : clone.innerHTML; + + return result; } connectedCallback(): void { diff --git a/ts/editable/mathjax-component.ts b/ts/editable/mathjax-component.ts index f72a93278..150743c4d 100644 --- a/ts/editable/mathjax-component.ts +++ b/ts/editable/mathjax-component.ts @@ -1,9 +1,49 @@ import "mathjax/es5/tex-svg-full"; +import { nodeIsElement } from "lib/dom"; import Mathjax_svelte from "./Mathjax.svelte"; +function moveNodesInsertedBeforeEndToAfterEnd(element: Element): () => void { + const allowedChildNodes = element.childNodes.length; + + const observer = new MutationObserver(() => { + for (const node of [...element.childNodes].slice(allowedChildNodes)) { + element.removeChild(node); + + let referenceNode: Node; + + if (nodeIsElement(node)) { + referenceNode = element.insertAdjacentElement("afterend", node)!; + } else { + element.insertAdjacentText("afterend", (node as Text).wholeText); + referenceNode = element.nextSibling!; + } + + if (!referenceNode) { + continue; + } + + const range = document.createRange(); + range.setStartAfter(referenceNode); + range.collapse(false); + + const selection = document.getSelection()!; + selection.removeAllRanges(); + selection.addRange(range); + } + }); + + observer.observe(element, { characterData: true, subtree: true }); + return () => observer.disconnect(); +} + class Mathjax extends HTMLElement { block: boolean = false; + disconnect: () => void = () => {}; + + constructor() { + super(); + } static get observedAttributes(): string[] { return ["block"]; @@ -11,6 +51,11 @@ class Mathjax extends HTMLElement { connectedCallback(): void { this.decorate(); + this.disconnect = moveNodesInsertedBeforeEndToAfterEnd(this); + } + + disconnectedCallback(): void { + this.disconnect(); } attributeChangedCallback(_name: string, _old: string, newValue: string): void { @@ -41,17 +86,18 @@ class Mathjax extends HTMLElement { customElements.define("anki-mathjax", Mathjax); -// TODO mathjax regex will prob. fail at double quotes const mathjaxTagPattern = /]*?block="(.*?)")?[^>]*?>(.*?)<\/anki-mathjax>/gsu; export function toMathjaxDelimiters(html: string): string { return html.replace( mathjaxTagPattern, - (_match: string, block: string | undefined, text: string) => - typeof block === "string" && block !== "false" + (_match: string, block: string | undefined, text: string) => { + console.log("delim", _match, block, "text", text); + return typeof block === "string" && block !== "false" ? `\\[${text}\\]` - : `\\(${text}\\)` + : `\\(${text}\\)`; + } ); } @@ -62,11 +108,14 @@ export function toMathjaxTags(html: string): string { return html .replace( mathjaxBlockDelimiterPattern, - (_match: string, text: string) => - `${text}` + (_match: string, text: string) => ( + console.log(text), `${text}` + ) ) .replace( mathjaxInlineDelimiterPattern, - (_match: string, text: string) => `${text}` + (_match: string, text: string) => ( + console.log(text), `${text}` + ) ); } From 6a1fae53df075f7c2b72fedeae33ad6d9a589cbf Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 20:54:25 +0200 Subject: [PATCH 14/72] Formalize "Decorated components API" --- ts/editable/decorated.ts | 45 ++++++++++++++++++ ts/editable/editable.ts | 13 ++--- ts/editable/mathjax-component.ts | 82 +++++++++++++++++--------------- ts/editor/editing-area.ts | 20 ++++++-- 4 files changed, 107 insertions(+), 53 deletions(-) create mode 100644 ts/editable/decorated.ts diff --git a/ts/editable/decorated.ts b/ts/editable/decorated.ts new file mode 100644 index 000000000..c86601255 --- /dev/null +++ b/ts/editable/decorated.ts @@ -0,0 +1,45 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +/** + * decorated elements know three states: + * - stored, which is stored in the DB, e.g. `\(\alpha + \beta\)` + * - undecorated, which is displayed to the user in Codable, e.g. `\alpha + \beta` + * - decorated, which is displayed to the user in Editable, e.g. `` + */ + +export interface DecoratedElement extends HTMLElement { + /** + * Transforms itself from undecorated to decorated state. + * Should be called in connectedCallback. + */ + decorate(): void; + /** + * Transforms itself from decorated to undecorated state. + */ + undecorate(): void; +} + +export interface DecoratedElementConstructor extends CustomElementConstructor { + prototype: DecoratedElement; + tagName: string; + /** + * Transforms elements in input HTML from undecorated to stored state. + */ + toStored(undecorated: string): string; + /** + * Transforms elements in input HTML from stored to undecorated state. + */ + toUndecorated(stored: string): string; +} + +class DefineArray extends Array { + push(...elements: DecoratedElementConstructor[]) { + for (const element of elements) { + customElements.define(element.tagName, element); + } + return super.push(...elements); + } +} + +export const decoratedComponents: DecoratedElementConstructor[] = new DefineArray(); diff --git a/ts/editable/editable.ts b/ts/editable/editable.ts index f05f7b922..23d8d4361 100644 --- a/ts/editable/editable.ts +++ b/ts/editable/editable.ts @@ -1,6 +1,8 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import type { DecoratedElement } from "./decorated"; +import { decoratedComponents } from "./decorated"; import { bridgeCommand } from "lib/bridgecommand"; import { elementIsBlock, getBlockElement } from "lib/dom"; // import { inCodable } from "./toolbar"; @@ -25,13 +27,6 @@ function containsInlineContent(element: Element): boolean { return true; } -interface DecoratedElement extends HTMLElement { - decorate(): void; - undecorate(): void; -} - -const decoratedTags = ["anki-mathjax"]; - export class Editable extends HTMLElement { set fieldHTML(content: string) { this.innerHTML = content; @@ -44,8 +39,8 @@ export class Editable extends HTMLElement { get fieldHTML(): string { const clone = this.cloneNode(true) as Element; - for (const component of decoratedTags) { - for (const element of clone.getElementsByTagName(component)) { + for (const component of decoratedComponents) { + for (const element of clone.getElementsByTagName(component.tagName)) { (element as DecoratedElement).undecorate(); } } diff --git a/ts/editable/mathjax-component.ts b/ts/editable/mathjax-component.ts index 150743c4d..5c8fa4a55 100644 --- a/ts/editable/mathjax-component.ts +++ b/ts/editable/mathjax-component.ts @@ -1,4 +1,7 @@ import "mathjax/es5/tex-svg-full"; + +import type { DecoratedElement, DecoratedElementConstructor } from "./decorated"; +import { decoratedComponents } from "./decorated"; import { nodeIsElement } from "lib/dom"; import Mathjax_svelte from "./Mathjax.svelte"; @@ -37,9 +40,44 @@ function moveNodesInsertedBeforeEndToAfterEnd(element: Element): () => void { return () => observer.disconnect(); } -class Mathjax extends HTMLElement { - block: boolean = false; - disconnect: () => void = () => {}; +const mathjaxTagPattern = + /]*?block="(.*?)")?[^>]*?>(.*?)<\/anki-mathjax>/gsu; + +const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; +const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; + +export const Mathjax: DecoratedElementConstructor = class Mathjax + extends HTMLElement + implements DecoratedElement +{ + static tagName = "anki-mathjax"; + + static toStored(undecorated: string): string { + return undecorated.replace( + mathjaxTagPattern, + (_match: string, block: string | undefined, text: string) => { + return typeof block === "string" && block !== "false" + ? `\\[${text}\\]` + : `\\(${text}\\)`; + } + ); + } + + static toUndecorated(stored: string): string { + return stored + .replace( + mathjaxBlockDelimiterPattern, + (_match: string, text: string) => + `${text}` + ) + .replace( + mathjaxInlineDelimiterPattern, + (_match: string, text: string) => `${text}` + ); + } + + block = false; + disconnect: () => void = () => {/* noop */}; constructor() { super(); @@ -82,40 +120,6 @@ class Mathjax extends HTMLElement { this.removeAttribute("block"); } } -} +}; -customElements.define("anki-mathjax", Mathjax); - -const mathjaxTagPattern = - /]*?block="(.*?)")?[^>]*?>(.*?)<\/anki-mathjax>/gsu; - -export function toMathjaxDelimiters(html: string): string { - return html.replace( - mathjaxTagPattern, - (_match: string, block: string | undefined, text: string) => { - console.log("delim", _match, block, "text", text); - return typeof block === "string" && block !== "false" - ? `\\[${text}\\]` - : `\\(${text}\\)`; - } - ); -} - -const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; -const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; - -export function toMathjaxTags(html: string): string { - return html - .replace( - mathjaxBlockDelimiterPattern, - (_match: string, text: string) => ( - console.log(text), `${text}` - ) - ) - .replace( - mathjaxInlineDelimiterPattern, - (_match: string, text: string) => ( - console.log(text), `${text}` - ) - ); -} +decoratedComponents.push(Mathjax); diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts index 7da073e11..0b448a581 100644 --- a/ts/editor/editing-area.ts +++ b/ts/editor/editing-area.ts @@ -17,7 +17,7 @@ import { bridgeCommand } from "./lib"; import { onInput, onKey, onKeyUp } from "./input-handlers"; import { onFocus, onBlur } from "./focus-handlers"; import { nightModeKey } from "components/context-keys"; -import { toMathjaxTags, toMathjaxDelimiters } from "editable/mathjax-component"; +import { decoratedComponents } from "editable/decorated"; function onCutOrCopy(): void { bridgeCommand("cutOrCopy"); @@ -93,13 +93,23 @@ export class EditingArea extends HTMLDivElement { } set fieldHTML(content: string) { - this.imageHandle.then( - () => (this.activeInput.fieldHTML = toMathjaxTags(content)) - ); + this.imageHandle.then(() => { + let result = content; + for (const component of decoratedComponents) { + result = component.toUndecorated(result); + } + + this.activeInput.fieldHTML = result; + }); } get fieldHTML(): string { - return toMathjaxDelimiters(this.activeInput.fieldHTML); + let result = this.activeInput.fieldHTML; + for (const component of decoratedComponents) { + result = component.toStored(result); + } + + return result; } connectedCallback(): void { From 922461ea47f8ca9f9e322fdf9c06722417caf929 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 5 Aug 2021 22:18:40 +0200 Subject: [PATCH 15/72] Rename showImageHandle to showHandles --- ts/editable/Mathjax.svelte | 8 +------- ts/editable/mathjax-component.ts | 4 +++- ts/editor/editing-area.ts | 10 +++++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/ts/editable/Mathjax.svelte b/ts/editable/Mathjax.svelte index 79eb49367..b34aaa52d 100644 --- a/ts/editable/Mathjax.svelte +++ b/ts/editable/Mathjax.svelte @@ -12,13 +12,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html $: encoded = encodeURIComponent(converted); -Mathjax +Mathjax diff --git a/ts/editor/MathjaxHandleInlineBlock.svelte b/ts/editor/MathjaxHandleInlineBlock.svelte new file mode 100644 index 000000000..e0a3c176b --- /dev/null +++ b/ts/editor/MathjaxHandleInlineBlock.svelte @@ -0,0 +1,38 @@ + + + + + + mathjaxElement.setAttribute("block", "false")} + >{@html inlineIcon} + + + + mathjaxElement.setAttribute("block", "true")} + >{@html blockIcon} + + diff --git a/ts/editor/icons.ts b/ts/editor/icons.ts index 7386dcb2c..a29daecd6 100644 --- a/ts/editor/icons.ts +++ b/ts/editor/icons.ts @@ -46,3 +46,6 @@ export { default as floatRightIcon } from "./format-float-right.svg"; export { default as sizeActual } from "./image-size-select-actual.svg"; export { default as sizeMinimized } from "./image-size-select-large.svg"; + +export { default as inlineIcon } from "./format-wrap-square.svg"; +export { default as blockIcon } from "./format-wrap-top-bottom.svg"; From 241d5cdd1314da0831f56251b589460755e49139 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 6 Aug 2021 04:25:19 +0200 Subject: [PATCH 19/72] Fix MathJax overlay after rebase --- ts/editor/MathjaxHandle.svelte | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ts/editor/MathjaxHandle.svelte b/ts/editor/MathjaxHandle.svelte index 1e75cf1cc..e4e41393e 100644 --- a/ts/editor/MathjaxHandle.svelte +++ b/ts/editor/MathjaxHandle.svelte @@ -15,8 +15,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html let updateSelection: () => void; - - {#if activeImage} +{#if activeImage} +
- {/if} - + +{/if} + +
{ + updateSelection(); + dropdownObject.update(); + }} + > + + + +
+
+ {/if} + From 0f92664d4a5481541f9881dd841cfb7e01bd5665 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 6 Aug 2021 16:52:34 +0200 Subject: [PATCH 21/72] First implementation of MathjaxHandleEditor + use manual focus highlighting on editing-area --- ts/editable/editable-base.scss | 4 ++ ts/editable/mathjax-component.ts | 13 +++++-- ts/editor/BUILD.bazel | 3 +- ts/editor/MathjaxHandle.svelte | 40 ++++++++++++------- ts/editor/MathjaxHandleEditor.svelte | 57 ++++++++++++++++++++++++++++ ts/editor/codable.ts | 1 - ts/editor/editor-field.ts | 2 +- ts/editor/fields.scss | 14 +++++++ ts/sass/button-mixins.scss | 2 +- 9 files changed, 115 insertions(+), 21 deletions(-) create mode 100644 ts/editor/MathjaxHandleEditor.svelte diff --git a/ts/editable/editable-base.scss b/ts/editable/editable-base.scss index 360c9d83c..2c1323d6b 100644 --- a/ts/editable/editable-base.scss +++ b/ts/editable/editable-base.scss @@ -10,6 +10,10 @@ anki-editable { outline: none; } + &:focus { + outline: none; + } + * { max-width: 100%; } diff --git a/ts/editable/mathjax-component.ts b/ts/editable/mathjax-component.ts index cb4af2b74..f3d137ceb 100644 --- a/ts/editable/mathjax-component.ts +++ b/ts/editable/mathjax-component.ts @@ -87,7 +87,7 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax } static get observedAttributes(): string[] { - return ["block"]; + return ["block", "data-mathjax"]; } connectedCallback(): void { @@ -99,9 +99,14 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax this.disconnect(); } - attributeChangedCallback(_name: string, _old: string, newValue: string): void { - this.block = newValue !== "false"; - this.component?.$set({ block: this.block }); + attributeChangedCallback(name: string, _old: string, newValue: string): void { + switch (name) { + case "block": + this.block = newValue !== "false"; + this.component?.$set({ block: this.block }); + case "data-mathjax": + this.component?.$set({ mathjax: newValue }); + } } decorate(): void { diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 58091e799..aa5d1d298 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -33,8 +33,9 @@ compile_sass( visibility = ["//visibility:public"], deps = [ "//ts/sass:base_lib", - "//ts/sass:buttons_lib", "//ts/sass:scrollbar_lib", + "//ts/sass:buttons_lib", + "//ts/sass:button_mixins_lib", ], ) diff --git a/ts/editor/MathjaxHandle.svelte b/ts/editor/MathjaxHandle.svelte index bdbcda1b4..e6dd3d626 100644 --- a/ts/editor/MathjaxHandle.svelte +++ b/ts/editor/MathjaxHandle.svelte @@ -5,18 +5,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html createDropdown(event.detail.selection)} > - event.stopPropagation()} /> + event.stopPropagation()} + on:dblclick={() => (edit = true)} + /> - -
{ - updateSelection(); - dropdownObject.update(); - }} - > - - - -
-
+ {#if !edit} + + + + {:else} + +
{ + updateSelection(); + dropdownObject.update(); + }} + > + + + +
+
+ {/if} {/if}
diff --git a/ts/editor/MathjaxHandleEditor.svelte b/ts/editor/MathjaxHandleEditor.svelte new file mode 100644 index 000000000..fc996d89e --- /dev/null +++ b/ts/editor/MathjaxHandleEditor.svelte @@ -0,0 +1,57 @@ + + + + +
+