Instead of transforming to {\lt}, transform to < (#1818)

* Instead of transforming to {\lt}, transform to <

- In Mathjax editor
- This way you can also use Mathjax convenience shortcuts like <=> in
  chemistry mode: \ce{<=>}

* Remove unused translateEntitiesToMathjax() (dae)

https://github.com/ankitects/anki/pull/1818#discussion_r857238310
This commit is contained in:
Henrik Giesel 2022-04-25 05:42:54 +02:00 committed by GitHub
parent ee70006ec4
commit 2be1f4c56d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 37 additions and 30 deletions

View File

@ -23,13 +23,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { randomUUID } from "../lib/uuid"; import { randomUUID } from "../lib/uuid";
import { pageTheme } from "../sveltelib/theme"; import { pageTheme } from "../sveltelib/theme";
import { convertMathjax } from "./mathjax"; import { convertMathjax, unescapeSomeEntities } from "./mathjax";
export let mathjax: string; export let mathjax: string;
export let block: boolean; export let block: boolean;
export let fontSize: number; export let fontSize: number;
$: [converted, title] = convertMathjax(mathjax, $pageTheme.isDark, fontSize); $: [converted, title] = convertMathjax(
unescapeSomeEntities(mathjax),
$pageTheme.isDark,
fontSize,
);
$: empty = title === "MathJax"; $: empty = title === "MathJax";
$: encoded = encodeURIComponent(converted); $: encoded = encodeURIComponent(converted);

View File

@ -15,14 +15,6 @@ const mathjaxTagPattern =
const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu; const mathjaxBlockDelimiterPattern = /\\\[(.*?)\\\]/gsu;
const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu; const mathjaxInlineDelimiterPattern = /\\\((.*?)\\\)/gsu;
/**
* If the user enters the Mathjax with delimiters, "<" and ">" will
* be first translated to entities.
*/
function translateEntitiesToMathjax(value: string) {
return value.replace(/&lt;/g, "{\\lt}").replace(/&gt;/g, "{\\gt}");
}
export const Mathjax: DecoratedElementConstructor = class Mathjax export const Mathjax: DecoratedElementConstructor = class Mathjax
extends HTMLElement extends HTMLElement
implements DecoratedElement implements DecoratedElement
@ -45,12 +37,10 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
static toUndecorated(stored: string): string { static toUndecorated(stored: string): string {
return stored return stored
.replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => { .replace(mathjaxBlockDelimiterPattern, (_match: string, text: string) => {
const escaped = translateEntitiesToMathjax(text); return `<${Mathjax.tagName} block="true">${text}</${Mathjax.tagName}>`;
return `<${Mathjax.tagName} block="true">${escaped}</${Mathjax.tagName}>`;
}) })
.replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => { .replace(mathjaxInlineDelimiterPattern, (_match: string, text: string) => {
const escaped = translateEntitiesToMathjax(text); return `<${Mathjax.tagName}>${text}</${Mathjax.tagName}>`;
return `<${Mathjax.tagName}>${escaped}</${Mathjax.tagName}>`;
}); });
} }
@ -107,7 +97,7 @@ export const Mathjax: DecoratedElementConstructor = class Mathjax
return; return;
} }
this.dataset.mathjax = this.innerText; this.dataset.mathjax = this.innerHTML;
this.innerHTML = ""; this.innerHTML = "";
this.style.whiteSpace = "normal"; this.style.whiteSpace = "normal";

View File

@ -65,3 +65,14 @@ export function convertMathjax(
return [svg.outerHTML, title]; return [svg.outerHTML, title];
} }
/**
* Escape characters which are technically legal in Mathjax, but confuse HTML.
*/
export function escapeSomeEntities(value: string): string {
return value.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
export function unescapeSomeEntities(value: string): string {
return value.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
}

View File

@ -87,13 +87,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}, },
); );
}); });
/**
* Escape characters which are technically legal in Mathjax, but confuse HTML.
*/
export function escapeSomeEntities(value: string): string {
return value.replace(/</g, "{\\lt}").replace(/>/g, "{\\gt}");
}
</script> </script>
<div class="mathjax-editor"> <div class="mathjax-editor">
@ -101,7 +94,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{code} {code}
{configuration} {configuration}
bind:api={codeMirror} bind:api={codeMirror}
on:change={({ detail }) => code.set(escapeSomeEntities(detail))} on:change={({ detail: mathjaxText }) => code.set(mathjaxText)}
on:blur on:blur
/> />
</div> </div>

View File

@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import WithDropdown from "../../components/WithDropdown.svelte"; import WithDropdown from "../../components/WithDropdown.svelte";
import { escapeSomeEntities, unescapeSomeEntities } from "../../editable/mathjax";
import { Mathjax } from "../../editable/mathjax-element"; import { Mathjax } from "../../editable/mathjax-element";
import { on } from "../../lib/events"; import { on } from "../../lib/events";
import { noop } from "../../lib/functional"; import { noop } from "../../lib/functional";
@ -20,8 +21,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const { container, api } = context.get(); const { container, api } = context.get();
const { editable, preventResubscription } = api; const { editable, preventResubscription } = api;
const code = writable("");
let activeImage: HTMLImageElement | null = null; let activeImage: HTMLImageElement | null = null;
let mathjaxElement: HTMLElement | null = null; let mathjaxElement: HTMLElement | null = null;
let allow = noop; let allow = noop;
@ -30,6 +29,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let selectAll = false; let selectAll = false;
let position: CodeMirrorLib.Position | undefined = undefined; let position: CodeMirrorLib.Position | undefined = undefined;
/**
* Will contain the Mathjax text with unescaped entities.
* This is the text displayed in the actual editor window.
*/
const code = writable("");
function showHandle(image: HTMLImageElement, pos?: CodeMirrorLib.Position): void { function showHandle(image: HTMLImageElement, pos?: CodeMirrorLib.Position): void {
allow = preventResubscription(); allow = preventResubscription();
position = pos; position = pos;
@ -39,9 +44,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
activeImage = image; activeImage = image;
mathjaxElement = activeImage.closest(Mathjax.tagName)!; mathjaxElement = activeImage.closest(Mathjax.tagName)!;
code.set(mathjaxElement.dataset.mathjax ?? ""); code.set(unescapeSomeEntities(mathjaxElement.dataset.mathjax ?? ""));
unsubscribe = code.subscribe((value: string) => { unsubscribe = code.subscribe((value: string) => {
mathjaxElement!.dataset.mathjax = value; mathjaxElement!.dataset.mathjax = escapeSomeEntities(value);
}); });
} }

View File

@ -23,11 +23,11 @@ function adjustInputFragment(fragment: DocumentFragment): void {
} }
} }
export function storedToFragment(html: string): DocumentFragment { export function storedToFragment(storedHTML: string): DocumentFragment {
/* We need .createContextualFragment so that customElements are initialized */ /* We need .createContextualFragment so that customElements are initialized */
const fragment = document const fragment = document
.createRange() .createRange()
.createContextualFragment(createDummyDoc(adjustInputHTML(html))); .createContextualFragment(createDummyDoc(adjustInputHTML(storedHTML)));
adjustInputFragment(fragment); adjustInputFragment(fragment);
return fragment; return fragment;
@ -56,5 +56,6 @@ export function fragmentToStored(fragment: DocumentFragment): string {
const clone = document.importNode(fragment, true); const clone = document.importNode(fragment, true);
adjustOutputFragment(clone); adjustOutputFragment(clone);
return adjustOutputHTML(fragmentToString(clone)); const storedHTML = adjustOutputHTML(fragmentToString(clone));
return storedHTML;
} }

View File

@ -110,6 +110,9 @@ export function nodeContainsInlineContent(node: Node): boolean {
return true; return true;
} }
/**
* Consumes the input fragment.
*/
export function fragmentToString(fragment: DocumentFragment): string { export function fragmentToString(fragment: DocumentFragment): string {
const fragmentDiv = document.createElement("div"); const fragmentDiv = document.createElement("div");
fragmentDiv.appendChild(fragment); fragmentDiv.appendChild(fragment);