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 {