anki/ts/editable/mathjax-element.ts
Henrik Giesel 30bbbaf00b
Use eslint for sorting our imports (#1637)
* Make eslint sort our imports

* fix missing deps in eslint rule (dae)

Caught on Linux due to the stricter sandboxing

* Remove exports-last eslint rule (for now?)

* Adjust browserslist settings

- We use ResizeObserver which is not supported in browsers like KaiOS,
  Baidu or Android UC

* Raise minimum iOS version 13.4

- It's the first version that supports ResizeObserver

* Apply new eslint rules to sort imports
2022-02-04 18:36:34 +10:00

175 lines
5.1 KiB
TypeScript

// 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";
import { placeCaretAfter, placeCaretBefore } from "../domlib/place-caret";
import { on } from "../lib/events";
import type { DecoratedElement, DecoratedElementConstructor } from "./decorated";
import { FrameElement, frameElement } from "./frame-element";
import Mathjax_svelte from "./Mathjax.svelte";
const mathjaxTagPattern =
/<anki-mathjax(?:[^>]*?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) =>
`<${Mathjax.tagName} block="true">${text}</${Mathjax.tagName}>`,
)
.replace(
mathjaxInlineDelimiterPattern,
(_match: string, text: string) =>
`<${Mathjax.tagName}>${text}</${Mathjax.tagName}>`,
);
}
block = false;
frame?: FrameElement;
component?: Mathjax_svelte;
static get observedAttributes(): string[] {
return ["block", "data-mathjax"];
}
connectedCallback(): void {
this.decorate();
this.addEventListeners();
}
disconnectedCallback(): void {
this.removeEventListeners();
}
attributeChangedCallback(name: string, old: string, newValue: string): void {
if (newValue === old) {
return;
}
switch (name) {
case "block":
this.block = newValue !== "false";
this.component?.$set({ block: this.block });
this.frame?.setAttribute("block", String(this.block));
break;
case "data-mathjax":
this.component?.$set({ mathjax: newValue });
break;
}
}
decorate(): void {
if (this.hasAttribute("decorated")) {
this.undecorate();
}
if (this.parentElement?.tagName === FrameElement.tagName.toUpperCase()) {
this.frame = this.parentElement as FrameElement;
} else {
frameElement(this, this.block);
/* Framing will place this element inside of an anki-frame element,
* causing the connectedCallback to be called again.
* If we'd continue decorating at this point, we'd loose all the information */
return;
}
this.dataset.mathjax = this.innerText;
this.innerHTML = "";
this.style.whiteSpace = "normal";
this.component = new Mathjax_svelte({
target: this,
props: {
mathjax: this.dataset.mathjax,
block: this.block,
fontSize: 20,
},
});
if (this.hasAttribute("focusonmount")) {
this.component.moveCaretAfter();
}
this.setAttribute("contentEditable", "false");
this.setAttribute("decorated", "true");
}
undecorate(): void {
if (this.parentElement?.tagName === FrameElement.tagName.toUpperCase()) {
this.parentElement.replaceWith(this);
}
this.innerHTML = this.dataset.mathjax ?? "";
delete this.dataset.mathjax;
this.removeAttribute("style");
this.removeAttribute("focusonmount");
if (this.block) {
this.setAttribute("block", "true");
} else {
this.removeAttribute("block");
}
this.removeAttribute("contentEditable");
this.removeAttribute("decorated");
}
removeMoveInStart?: () => void;
removeMoveInEnd?: () => void;
addEventListeners(): void {
this.removeMoveInStart = on(
this,
"moveinstart" as keyof HTMLElementEventMap,
() => this.component!.selectAll(),
);
this.removeMoveInEnd = on(this, "moveinend" as keyof HTMLElementEventMap, () =>
this.component!.selectAll(),
);
}
removeEventListeners(): void {
this.removeMoveInStart?.();
this.removeMoveInStart = undefined;
this.removeMoveInEnd?.();
this.removeMoveInEnd = undefined;
}
placeCaretBefore(): void {
if (this.frame) {
placeCaretBefore(this.frame);
}
}
placeCaretAfter(): void {
if (this.frame) {
placeCaretAfter(this.frame);
}
}
};