// HyperMD, copyright (c) by laobubu // Distributed under an MIT license: http://laobubu.net/HyperMD/LICENSE // // DESCRIPTION: Fold and Render TeX formula expressions. Works with *fold* addon. // // Provides *DumbRenderer* as the Default MathRenderer. // You may use others like MathJax, KaTeX via PowerPacks // (function (mod){ //[HyperMD] UMD patched! /*commonjs*/ ("object"==typeof exports&&"undefined"!=typeof module) ? mod(null, exports, require("codemirror"), require("../core"), require("./fold")) : /*amd*/ ("function"==typeof define&&define.amd) ? define(["require","exports","codemirror","../core","./fold"], mod) : /*plain env*/ mod(null, (this.HyperMD.FoldMath = this.HyperMD.FoldMath || {}), CodeMirror, HyperMD, HyperMD.Fold); })(function (require, exports, CodeMirror, core_1, fold_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var DEBUG = false; /********************************************************************************** */ //#region Exports /** * Detect if a token is a beginning of Math, and fold it! * * @see FolderFunc in ./fold.ts */ exports.MathFolder = function (stream, token) { var mathBeginRE = /formatting-math-begin\b/; if (!mathBeginRE.test(token.type)) return null; var cm = stream.cm; var line = stream.lineNo; // PATCHED: refuse to fold when an unclosed ``` fence opens above this // math line. The markdown tokenizer doesn't propagate state.code = -1 // past one line for unclosed fences, so the math-begin token is still // emitted - we have to detect it ourselves by counting fence lines. var fenceCount = 0; for (var ln = line - 1; ln >= 0; ln--) { if (/^\s*```/.test(cm.getLine(ln))) fenceCount++; } if (fenceCount % 2 === 1) { return null; } var maySpanLines = /math-2\b/.test(token.type); // $$ or \[ may span lines! var tokenLength = maySpanLines ? 2 : 1; // "$$" or "$" // PATCHED: \(...\) is inline (math-1) but its delimiter is 2 chars, // not 1, so override here. \[ is already 2 via maySpanLines. if (token.string === '\\(') { tokenLength = 2; } // CodeMirror GFM mode split "$$" into two tokens, so do a extra check. if (tokenLength == 2 && token.string.length == 1) { if (DEBUG) console.log("[FoldMath] $$ is splitted into 2 tokens"); var nextToken = stream.lineTokens[stream.i_token + 1]; if (!nextToken || !mathBeginRE.test(nextToken.type)) return null; } // Find the position of the "$" tail and compose a range var end_info = stream.findNext(/formatting-math-end\b/, maySpanLines); var from = { line: line, ch: token.start }; var to; var noEndingToken = false; if (end_info) { to = { line: end_info.lineNo, ch: end_info.token.start + tokenLength }; } else if (maySpanLines) { // end not found, but this is a multi-line math block. // fold to the end of doc var lastLineNo = cm.lastLine(); to = { line: lastLineNo, ch: cm.getLine(lastLineNo).length }; noEndingToken = true; } else { // Hmm... corrupted math ? return null; } // Range is ready. request the range var expr_from = { line: from.line, ch: from.ch + tokenLength }; var expr_to = { line: to.line, ch: to.ch - (noEndingToken ? 0 : tokenLength) }; var expr = cm.getRange(expr_from, expr_to).trim(); var foldMathAddon = exports.getAddon(cm); var reqAns = stream.requestRange(from, to); if (reqAns !== fold_1.RequestRangeResult.OK) { if (reqAns === fold_1.RequestRangeResult.CURSOR_INSIDE) foldMathAddon.editingExpr = expr; // try to trig preview event return null; } // Now let's make a math widget! // PATCHED: gate display mode on maySpanLines (= $$ or \[) rather than // tokenLength > 1. Inline \(...\) has tokenLength 2 but is always inline. var isDisplayMode = maySpanLines && from.ch == 0 && (noEndingToken || to.ch >= cm.getLine(to.line).length); var marker = insertMathMark(cm, from, to, expr, tokenLength, isDisplayMode); foldMathAddon.editingExpr = null; // try to hide preview return marker; }; /** * Insert a TextMarker, and try to render it with configured MathRenderer. */ function insertMathMark(cm, p1, p2, expression, tokenLength, isDisplayMode) { var span = document.createElement("span"); span.setAttribute("class", "hmd-fold-math math-" + (isDisplayMode ? 2 : 1)); span.setAttribute("title", expression); if (DEBUG) { console.log("insert", p1, p2, expression); } // PATCHED: pre-render synchronously when the renderer is ready, so CodeMirror // measures the final math height on first paint. Without this, the span is // sized by the 1-line raw-text placeholder, then jumps to full math height // once KaTeX fills it - visible as flicker when leaving the block. var foldMathAddon = exports.getAddon(cm); var mathRenderer = foldMathAddon.createRenderer(span, isDisplayMode ? "display" : ""); var mathPlaceholder = null; var preRendered = false; if (mathRenderer.isReady()) { mathRenderer.startRender(expression); preRendered = true; } else { mathPlaceholder = document.createElement("span"); mathPlaceholder.setAttribute("class", "hmd-fold-math-placeholder"); mathPlaceholder.textContent = expression; span.appendChild(mathPlaceholder); } var marker = cm.markText(p1, p2, { replacedWith: span, }); span.addEventListener("click", function () { return fold_1.breakMark(cm, marker, tokenLength); }, false); mathRenderer.onChanged = function () { if (mathPlaceholder) { span.removeChild(mathPlaceholder); mathPlaceholder = null; } marker.changed(); }; marker.on("clear", function () { mathRenderer.clear(); }); marker["mathRenderer"] = mathRenderer; if (!preRendered) { core_1.tryToRun(function () { if (DEBUG) console.log("[MATH] Trying to render ", expression); if (!mathRenderer.isReady()) return false; mathRenderer.startRender(expression); return true; }, 5, function () { marker.clear(); if (DEBUG) { console.log("[MATH] engine always not ready. faild to render ", expression, p1); } }); } return marker; } exports.insertMathMark = insertMathMark; //#endregion fold_1.registerFolder("math", exports.MathFolder, true); /********************************************************************************** */ //#region Default Renderer var DumbRenderer = /** @class */ (function () { function DumbRenderer(container, mode) { var _this = this; this.container = container; var img = document.createElement("img"); img.setAttribute("class", "hmd-math-dumb"); img.addEventListener("load", function () { if (_this.onChanged) _this.onChanged(_this.last_expr); }, false); this.img = img; container.appendChild(img); } DumbRenderer.prototype.startRender = function (expr) { this.last_expr = expr; this.img.src = "https://latex.codecogs.com/gif.latex?" + encodeURIComponent(expr); }; DumbRenderer.prototype.clear = function () { this.container.removeChild(this.img); }; /** indicate that if the Renderer is ready to execute */ DumbRenderer.prototype.isReady = function () { return true; // I'm always ready! }; return DumbRenderer; }()); exports.DumbRenderer = DumbRenderer; exports.defaultOption = { renderer: DumbRenderer, onPreview: null, onPreviewEnd: null, }; exports.suggestedOption = {}; core_1.suggestedEditorConfig.hmdFoldMath = exports.suggestedOption; CodeMirror.defineOption("hmdFoldMath", exports.defaultOption, function (cm, newVal) { ///// convert newVal's type to `Partial`, if it is not. if (!newVal) { newVal = {}; } else if (typeof newVal === "function") { newVal = { renderer: newVal }; } ///// apply config and write new values into cm var inst = exports.getAddon(cm); for (var k in exports.defaultOption) { inst[k] = (k in newVal) ? newVal[k] : exports.defaultOption[k]; } }); //#endregion /********************************************************************************** */ //#region Addon Class var FoldMath = /** @class */ (function () { function FoldMath(cm) { var _this = this; this.cm = cm; new core_1.FlipFlop( /** CHANGED */ function (expr) { _this.onPreview && _this.onPreview(expr); }, /** HIDE */ function () { _this.onPreviewEnd && _this.onPreviewEnd(); }, null).bind(this, "editingExpr"); } FoldMath.prototype.createRenderer = function (container, mode) { var RendererClass = this.renderer || DumbRenderer; return new RendererClass(container, mode); }; return FoldMath; }()); exports.FoldMath = FoldMath; //#endregion /** ADDON GETTER (Singleton Pattern): a editor can have only one FoldMath instance */ exports.getAddon = core_1.Addon.Getter("FoldMath", FoldMath, exports.defaultOption /** if has options */); });