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 {