mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Formalize "Decorated components API"
This commit is contained in:
parent
9fb0ce973b
commit
6a1fae53df
4 changed files with 107 additions and 53 deletions
45
ts/editable/decorated.ts
Normal file
45
ts/editable/decorated.ts
Normal file
|
@ -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. `<anki-mathjax>\alpha + \beta</anki-mathjax>`
|
||||
* - decorated, which is displayed to the user in Editable, e.g. `<anki-mathjax data-mathjax="\alpha + \beta"><img src="data:..."></anki-mathjax>`
|
||||
*/
|
||||
|
||||
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();
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
/<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) =>
|
||||
`<anki-mathjax block="true">${text}</anki-mathjax>`
|
||||
)
|
||||
.replace(
|
||||
mathjaxInlineDelimiterPattern,
|
||||
(_match: string, text: string) => `<anki-mathjax>${text}</anki-mathjax>`
|
||||
);
|
||||
}
|
||||
|
||||
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 =
|
||||
/<anki-mathjax(?:[^>]*?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), `<anki-mathjax block="true">${text}</anki-mathjax>`
|
||||
)
|
||||
)
|
||||
.replace(
|
||||
mathjaxInlineDelimiterPattern,
|
||||
(_match: string, text: string) => (
|
||||
console.log(text), `<anki-mathjax>${text}</anki-mathjax>`
|
||||
)
|
||||
);
|
||||
}
|
||||
decoratedComponents.push(Mathjax);
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue