diff --git a/src/editor/deserialize.ts b/src/editor/deserialize.ts index bc1dd74c7d..a0c4d74275 100644 --- a/src/editor/deserialize.ts +++ b/src/editor/deserialize.ts @@ -143,11 +143,11 @@ function parseElement(n: HTMLElement, partCreator: PartCreator, lastNode: HTMLEl // math nodes are translated back into delimited latex strings if (n.hasAttribute("data-mx-maths")) { const delimLeft = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_left'] || "$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_left'] || "$$"; + ((SdkConfig.get()['latex_maths_delims'] || {})['inline'] || {})['left'] || "\\(" : + ((SdkConfig.get()['latex_maths_delims'] || {})['display'] || {})['left'] || "\\["; const delimRight = (n.nodeName == "SPAN") ? - (SdkConfig.get()['latex_maths_delims'] || {})['inline_right'] || "$" : - (SdkConfig.get()['latex_maths_delims'] || {})['display_right'] || "$$"; + ((SdkConfig.get()['latex_maths_delims'] || {})['inline'] || {})['right'] || "\\)" : + ((SdkConfig.get()['latex_maths_delims'] || {})['display'] || {})['right'] || "\\]"; const tex = n.getAttribute("data-mx-maths"); return partCreator.plain(delimLeft + tex + delimRight); } else if (!checkDescendInto(n)) { diff --git a/src/editor/serialize.ts b/src/editor/serialize.ts index d43cb4f38d..5167e3d376 100644 --- a/src/editor/serialize.ts +++ b/src/editor/serialize.ts @@ -47,21 +47,65 @@ export function mdSerialize(model: EditorModel) { export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} = {}) { let md = mdSerialize(model); + // copy of raw input to remove unwanted math later + const orig = md; if (SettingsStore.getValue("feature_latex_maths")) { - const displayPattern = (SdkConfig.get()['latex_maths_delims'] || {})['display_pattern'] || - "\\$\\$(([^$]|\\\\\\$)*)\\$\\$"; - const inlinePattern = (SdkConfig.get()['latex_maths_delims'] || {})['inline_pattern'] || - "\\$(([^$]|\\\\\\$)*)\\$"; + const patternNames = ['tex', 'latex']; + const patternTypes = ['display', 'inline']; + const patternDefaults = { + "tex": { + // detect math with tex delimiters, inline: $...$, display $$...$$ + // preferably use negative lookbehinds, not supported in all major browsers: + // const displayPattern = "^(?\n\n\n\n`; - }); + // conditions for display math detection $$...$$: + // - pattern starts at beginning of line or is not prefixed with backslash or dollar + // - left delimiter ($$) is not escaped by backslash + "display": "(^|[^\\\\$])\\$\\$(([^$]|\\\\\\$)+?)\\$\\$", - md = md.replace(RegExp(inlinePattern, "gm"), function(m, p1) { - const p1e = AllHtmlEntities.encode(p1); - return ``; + // conditions for inline math detection $...$: + // - pattern starts at beginning of line, follows whitespace character or punctuation + // - pattern is on a single line + // - left and right delimiters ($) are not escaped by backslashes + // - left delimiter is not followed by whitespace character + // - right delimiter is not prefixed with whitespace character + "inline": + "(^|\\s|[.,!?:;])(?!\\\\)\\$(?!\\s)(([^$\\n]|\\\\\\$)*([^\\\\\\s\\$]|\\\\\\$)(?:\\\\\\$)?)\\$", + }, + "latex": { + // detect math with latex delimiters, inline: \(...\), display \[...\] + + // conditions for display math detection \[...\]: + // - pattern starts at beginning of line or is not prefixed with backslash + // - pattern is not empty + "display": "(^|[^\\\\])\\\\\\[(?!\\\\\\])(.*?)\\\\\\]", + + // conditions for inline math detection \(...\): + // - pattern starts at beginning of line or is not prefixed with backslash + // - pattern is not empty + "inline": "(^|[^\\\\])\\\\\\((?!\\\\\\))(.*?)\\\\\\)", + }, + }; + + patternNames.forEach(function(patternName) { + patternTypes.forEach(function(patternType) { + // get the regex replace pattern from config or use the default + const pattern = (((SdkConfig.get()["latex_maths_delims"] || + {})[patternType] || {})["pattern"] || {})[patternName] || + patternDefaults[patternName][patternType]; + + md = md.replace(RegExp(pattern, "gms"), function(m, p1, p2) { + const p2e = AllHtmlEntities.encode(p2); + switch (patternType) { + case "display": + return `${p1}
${tex}
`)
- }
- });
+ if (SettingsStore.getValue("feature_latex_maths")) {
+ // original Markdown without LaTeX replacements
+ const parserOrig = new Markdown(orig);
+ const phtmlOrig = cheerio.load(parserOrig.toHTML(),
+ { _useHtmlParser2: true, decodeEntities: false });
+
+ // since maths delimiters are handled before Markdown,
+ // code blocks could contain mangled content.
+ // replace code blocks with original content
+ phtmlOrig('code').each(function(i) {
+ phtml('code').eq(i).text(phtmlOrig('code').eq(i).text());
+ });
+
+ // add fallback output for latex math, which should not be interpreted as markdown
+ phtml('div, span').each(function(i, e) {
+ const tex = phtml(e).attr('data-mx-maths')
+ if (tex) {
+ phtml(e).html(`${tex}
`)
+ }
+ });
+ }
return phtml.html();
}
// ensure removal of escape backslashes in non-Markdown messages