UNPKG

mume-with-litvis

Version:

Fork of mume with added http://litvis.org/

1,145 lines 65.3 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; // preview controller (() => { let $ = null; class PreviewController { /** * This controller should be initialized when the html dom is loaded. */ constructor() { /** * VSCode API object got from acquireVsCodeApi */ this.vscodeAPI = null; /** * Whether finished loading preview */ this.doneLoadingPreview = false; /** * Whether to enable sidebar toc */ this.enableSidebarTOC = false; /** * .sidebar-toc element */ this.sidebarTOC = null; /** * .sidebar-toc element innerHTML generated by markdown-engine.ts */ this.sidebarTOCHTML = ""; this.refreshingIconTimeout = null; /** * Scroll map that maps buffer line to scrollTops of html elements */ this.scrollMap = null; /** * TextEditor total buffer line count */ this.totalLineCount = 0; /** * TextEditor cursor current line position */ this.currentLine = -1; /** * TextEditor inital line position */ this.initialLine = 0; /** * Used to delay preview scroll */ this.previewScrollDelay = 0; /** * Track the slide line number, and (h, v) indices */ this.slidesData = []; /** * Current slide offset */ this.currentSlideOffset = -1; /** * SetTimeout value */ this.scrollTimeout = null; /** * Configs */ this.config = {}; /** * Markdown file URI */ this.sourceUri = null; /** * Caches */ this.zenumlCache = {}; this.wavedromCache = {}; this.flowchartCache = {}; this.sequenceDiagramCache = {}; $ = window["$"]; /** init preview elements */ const previewElement = document.getElementsByClassName("mume")[0]; const hiddenPreviewElement = document.createElement("div"); hiddenPreviewElement.classList.add("mume"); hiddenPreviewElement.classList.add("markdown-preview"); hiddenPreviewElement.classList.add("hidden-preview"); hiddenPreviewElement.setAttribute("for", "preview"); hiddenPreviewElement.style.zIndex = "0"; previewElement.insertAdjacentElement("beforebegin", hiddenPreviewElement); /** init `window` events */ this.initWindowEvents(); /** init contextmenu */ this.initContextMenu(); /** load config */ this.config = JSON.parse(document.getElementById("mume-data").getAttribute("data-config")); this.sourceUri = this.config["sourceUri"]; /* if (this.config.vscode) { // remove vscode default styles const defaultStyles = document.getElementById('_defaultStyles') if (defaultStyles) defaultStyles.remove() } */ // console.log("init webview: " + this.sourceUri); // console.log(document.getElementsByTagName('html')[0].innerHTML) // console.log(JSON.stringify(config)) /** init preview properties */ (this.previewElement = previewElement), (this.hiddenPreviewElement = hiddenPreviewElement), (this.currentLine = this.config["line"] || -1); this.initialLine = this.config["initialLine"] || 0; this.presentationMode = previewElement.hasAttribute("data-presentation-mode"); (this.toolbar = { toolbar: document.getElementById("md-toolbar"), backToTopBtn: document.getElementsByClassName("back-to-top-btn")[0], refreshBtn: document.getElementsByClassName("refresh-btn")[0], sidebarTOCBtn: document.getElementsByClassName("sidebar-toc-btn")[0], }), (this.refreshingIcon = document.getElementsByClassName("refreshing-icon")[0]), /** init toolbar event */ this.initToolbarEvent(); /** init image helper */ this.initImageHelper(); /** keyboard event */ this.initKeyboardEvent(); /** set zoom */ this.setZoomLevel(); /** * If it's not presentation mode, then we need to tell the parent window * that the preview is loaded, and the markdown needs to be updated so that * we can update properties like `sidebarTOCHTML`, etc... */ if (!this.presentationMode) { previewElement.onscroll = this.scrollEvent.bind(this); this.postMessage("webviewFinishLoading", [this.sourceUri]); } else { // TODO: presentation preview to source sync this.config.scrollSync = true; // <= force to enable scrollSync for presentation this.initPresentationEvent(); } // make it possible for interactive vega to load local data files const base = document.createElement("base"); base.href = this.sourceUri; document.head.appendChild(base); // console.log(document.getElementsByTagName('html')[0].outerHTML) } /** * Post message to parent window * @param command * @param args */ postMessage(command, args = []) { if (this.config.vscode) { if (!this.vscodeAPI) { // @ts-ignore this.vscodeAPI = acquireVsCodeApi(); } // post message to vscode this.vscodeAPI.postMessage({ command, args, }); } else { window.parent.postMessage({ command, args, }, "file://"); } } /** * init events for tool bar */ initToolbarEvent() { const toolbarElement = this.toolbar.toolbar; const showToolbar = () => (toolbarElement.style.opacity = "1"); this.previewElement.onmouseenter = showToolbar; this.toolbar.toolbar.onmouseenter = showToolbar; this.previewElement.onmouseleave = () => (toolbarElement.style.opacity = "0"); this.initSideBarTOCButton(); this.initBackToTopButton(); this.initRefreshButton(); return toolbar; } /** * init events for keyboard */ initKeyboardEvent() { window.removeEventListener("keydown", this.keydown); window.addEventListener("keydown", this.keydown); } keydown(event) { this.postMessage("keydown", [this.sourceUri, event]); } /** * init .sidebar-toc-btn */ initSideBarTOCButton() { this.toolbar.sidebarTOCBtn.onclick = () => { if (this.presentationMode) { return window["Reveal"].toggleOverview(); } this.enableSidebarTOC = !this.enableSidebarTOC; if (this.enableSidebarTOC) { this.sidebarTOC = document.createElement("div"); // create new sidebar toc this.sidebarTOC.classList.add("md-sidebar-toc"); document.body.appendChild(this.sidebarTOC); document.body.classList.add("show-sidebar-toc"); this.renderSidebarTOC(); this.bindTagAClickEvent(); this.setZoomLevel(); } else { if (this.sidebarTOC) { this.sidebarTOC.remove(); } this.sidebarTOC = null; document.body.classList.remove("show-sidebar-toc"); this.previewElement.style.width = "100%"; } this.scrollMap = null; }; } /** * init .back-to-top-btn */ initBackToTopButton() { this.toolbar.backToTopBtn.onclick = () => { if (this.presentationMode) { return window["Reveal"].slide(0); } this.previewElement.scrollTop = 0; }; } /** * init .refresh-btn */ initRefreshButton() { this.toolbar.refreshBtn.onclick = () => { this.postMessage("refreshPreview", [this.sourceUri]); }; } /** * init contextmenu * reference: http://jsfiddle.net/w33z4bo0/1/ */ initContextMenu() { $["contextMenu"]({ selector: ".preview-container", items: { open_in_browser: { name: "Open in Browser", callback: () => this.postMessage("openInBrowser", [this.sourceUri]), }, sep1: "---------", html_export: { name: "HTML", items: { html_offline: { name: "HTML (offline)", callback: () => this.postMessage("htmlExport", [this.sourceUri, true]), }, html_cdn: { name: "HTML (cdn hosted)", callback: () => this.postMessage("htmlExport", [this.sourceUri, false]), }, }, }, chrome_export: { name: "Chrome (Puppeteer)", items: { chrome_pdf: { name: "PDF", callback: () => this.postMessage("chromeExport", [this.sourceUri, "pdf"]), }, chrome_png: { name: "PNG", callback: () => this.postMessage("chromeExport", [this.sourceUri, "png"]), }, chrome_jpeg: { name: "JPEG", callback: () => this.postMessage("chromeExport", [this.sourceUri, "jpeg"]), }, }, }, prince_export: { name: "PDF (prince)", callback: () => this.postMessage("princeExport", [this.sourceUri]), }, ebook_export: { name: "eBook", items: { ebook_epub: { name: "ePub", callback: () => this.postMessage("eBookExport", [this.sourceUri, "epub"]), }, ebook_mobi: { name: "mobi", callback: () => this.postMessage("eBookExport", [this.sourceUri, "mobi"]), }, ebook_pdf: { name: "PDF", callback: () => this.postMessage("eBookExport", [this.sourceUri, "pdf"]), }, ebook_html: { name: "HTML", callback: () => this.postMessage("eBookExport", [this.sourceUri, "html"]), }, }, }, pandoc_export: { name: "Pandoc", callback: () => this.postMessage("pandocExport", [this.sourceUri]), }, save_as_markdown: { name: "Save as Markdown", callback: () => this.postMessage("markdownExport", [this.sourceUri]), }, sep2: "---------", image_helper: { name: "Image Helper", callback: () => window["$"]("#image-helper-view").modal(), }, sep3: "---------", sync_source: { name: "Sync Source", callback: () => this.previewSyncSource(), }, seq4: "---------", preview_theme: { name: "Preview Theme", items: { atom_dark_css: { name: "atom-dark.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "atom-dark.css", ]); }, }, atom_light_css: { name: "atom-light.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "atom-light.css", ]); }, }, atom_material_css: { name: "atom-material.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "atom-material.css", ]); }, }, github_dark_css: { name: "github-dark.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "github-dark.css", ]); }, }, github_light_css: { name: "github-light.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "github-light.css", ]); }, }, gothic_css: { name: "gothic.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "gothic.css", ]); }, }, medium_css: { name: "medium.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "medium.css", ]); }, }, monokai_css: { name: "monokai.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "monokai.css", ]); }, }, newsprint_css: { name: "newsprint.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "newsprint.css", ]); }, }, night_css: { name: "night.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "night.css", ]); }, }, none_css: { name: "none.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "none.css", ]); }, }, one_dark_css: { name: "one-dark.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "one-dark.css", ]); }, }, one_light_css: { name: "one-light.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "one-light.css", ]); }, }, solarized_dark_css: { name: "solarized-dark.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "solarized-dark.css", ]); }, }, solarized_light_css: { name: "solarized-light.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "solarized-light.css", ]); }, }, vue_css: { name: "vue.css", callback: () => { this.postMessage("setPreviewTheme", [ this.sourceUri, "vue.css", ]); }, }, }, }, }, }); } /** * init image helper */ initImageHelper() { const imageHelper = document.getElementById("image-helper-view"); // url editor // used to insert image url const urlEditor = imageHelper.getElementsByClassName("url-editor")[0]; urlEditor.addEventListener("keypress", (event) => { if (event.keyCode === 13) { // enter key pressed let url = urlEditor.value.trim(); if (url.indexOf(" ") >= 0) { url = `<${url}>`; } if (url.length) { $["modal"].close(); // close modal this.postMessage("insertImageUrl", [this.sourceUri, url]); } return false; } else { return true; } }); const copyLabel = imageHelper.getElementsByClassName("copy-label")[0]; copyLabel.innerText = `Copy image to ${this.config.imageFolderPath[0] === "/" ? "workspace" : "relative"} ${this.config.imageFolderPath} folder`; const imageUploaderSelect = imageHelper.getElementsByClassName("uploader-select")[0]; imageUploaderSelect.value = this.config.imageUploader; // drop area has 2 events: // 1. paste(copy) image to imageFolderPath // 2. upload image const dropArea = window["$"](".drop-area", imageHelper); const fileUploader = window["$"](".file-uploader", imageHelper); dropArea.on("drop dragend dragstart dragenter dragleave drag dragover", (e) => { e.preventDefault(); e.stopPropagation(); if (e.type === "drop") { if (e.target.className.indexOf("paster") >= 0) { // paste const files = e.originalEvent.dataTransfer.files; for (const file of files) { this.postMessage("pasteImageFile", [this.sourceUri, file.path]); } } else { // upload const files = e.originalEvent.dataTransfer.files; for (const file of files) { this.postMessage("uploadImageFile", [ this.sourceUri, file.path, imageUploaderSelect.value, ]); } } $["modal"].close(); // close modal } }); dropArea.on("click", function (e) { e.preventDefault(); e.stopPropagation(); window["$"](this) .find('input[type="file"]') .click(); $["modal"].close(); // close modal }); fileUploader.on("click", (e) => { e.stopPropagation(); }); fileUploader.on("change", (e) => { if (e.target.className.indexOf("paster") >= 0) { // paste const files = e.target.files; for (const file of files) { this.postMessage("pasteImageFile", [this.sourceUri, file.path]); } fileUploader.val(""); } else { // upload const files = e.target.files; for (const file of files) { this.postMessage("uploadImageFile", [ this.sourceUri, file.path, imageUploaderSelect.value, ]); } fileUploader.val(""); } }); // show image uploaded history const a = imageHelper.querySelector("#show-uploaded-image-history"); a.onclick = (event) => { event.preventDefault(); event.stopPropagation(); $["modal"].close(); this.postMessage("showUploadedImageHistory", [this.sourceUri]); }; } /** * Init several events for presentation mode */ initPresentationEvent() { let initialSlide = null; const readyEvent = () => { if (initialSlide) { initialSlide.style.visibility = "visible"; } // several events... this.setupCodeChunks(); this.bindTagAClickEvent(); this.bindTaskListEvent(); // scroll slides window["Reveal"].addEventListener("slidechanged", (event) => { if (Date.now() < this.previewScrollDelay) { return; } const { indexh, indexv } = event; for (const slideData of this.slidesData) { const { h, v, line } = slideData; if (h === indexh && v === indexv) { this.postMessage("revealLine", [this.sourceUri, line + 6]); } } }); }; // analyze slides this.initSlidesData(); // slide to initial position window["Reveal"].configure({ transition: "none" }); this.scrollToRevealSourceLine(this.initialLine); window["Reveal"].configure({ transition: "slide" }); initialSlide = window["Reveal"].getCurrentSlide(); if (initialSlide) { initialSlide.style.visibility = "hidden"; } if (window["Reveal"].isReady()) { readyEvent(); } else { window["Reveal"].addEventListener("ready", readyEvent); } } // zoom in preview zoomIn() { this.config.zoomLevel = (this.config.zoomLevel || 1) + 0.1; this.setZoomLevel(); } // zoom out preview zoomOut() { this.config.zoomLevel = (this.config.zoomLevel || 1) - 0.1; this.setZoomLevel(); } // reset preview zoom resetZoom() { this.config.zoomLevel = 1; this.setZoomLevel(); } setZoomLevel() { const zoomLevel = this.config.zoomLevel || 1; this.previewElement.style["zoom"] = zoomLevel.toString(); if (this.enableSidebarTOC) { this.previewElement.style.width = `calc(100% - ${268 / zoomLevel}px)`; } this.scrollMap = null; if (!this.config.vscode) { this.postMessage("setZoomLevel", [this.sourceUri, zoomLevel]); } } /** * render mermaid graphs */ renderMermaid() { return new Promise((resolve, reject) => { const mermaid = window["mermaid"]; // window.mermaid doesn't work, has to be written as window['mermaid'] const mermaidGraphs = this.previewElement.getElementsByClassName("mermaid"); const validMermaidGraphs = []; for (let i = 0; i < mermaidGraphs.length; i++) { const mermaidGraph = mermaidGraphs[i]; try { mermaid.parse(mermaidGraph.textContent.trim()); validMermaidGraphs.push(mermaidGraph); } catch (error) { mermaidGraph.innerHTML = `<pre class="language-text">${error.str.toString()}</pre>`; } } if (!validMermaidGraphs.length) { return resolve(); } else { validMermaidGraphs.forEach((mermaidGraph, offset) => { const svgId = "svg-mermaid-" + Date.now() + "-" + offset; const code = mermaidGraph.textContent.trim(); try { mermaid.render(svgId, code, (svgCode) => { mermaidGraph.innerHTML = svgCode; }); } catch (error) { const noiseElement = document.getElementById("d" + svgId); if (noiseElement) { noiseElement.style.display = "none"; } mermaidGraph.innerHTML = `<pre class="language-text">${error.toString()}</pre>`; } }); return resolve(); } }); } /** * render interactive vega and vega lite * This function is copied from render flowchart */ renderInteractiveVega() { return new Promise((resolve, reject) => { const vegaElements = this.previewElement.querySelectorAll(".vega, .vega-lite"); function reportVegaError(el, error) { el.innerHTML = '<pre class="language-text">' + error.toString() + "</pre>"; } for (let i = 0; i < vegaElements.length; i++) { const vegaElement = vegaElements[i]; try { const spec = JSON.parse(vegaElement.textContent.trim()); window["vegaEmbed"](vegaElement, spec, { actions: false }).catch((error) => { reportVegaError(vegaElement, error); }); } catch (error) { reportVegaError(vegaElement, error); } } resolve(); }); } /** * render flowchart * This function doesn't work with `hiddenPreviewElement` */ renderFlowchart() { return new Promise((resolve, reject) => { const flowcharts = this.previewElement.getElementsByClassName("flow"); const newFlowchartCache = {}; for (let i = 0; i < flowcharts.length; i++) { const flow = flowcharts[i]; const text = flow.textContent.trim(); if (text in this.flowchartCache) { flow.innerHTML = this.flowchartCache[text]; } else { try { const diagram = window["flowchart"].parse(text); flow.innerHTML = ""; diagram.drawSVG(flow); } catch (error) { flow.innerHTML = '<pre class="language-text">' + error.toString() + "</pre>"; } } newFlowchartCache[text] = flow.innerHTML; } this.flowchartCache = newFlowchartCache; resolve(); }); } /** * render sequence diagram */ renderSequenceDiagram() { return new Promise((resolve, reject) => { const sequenceDiagrams = this.previewElement.getElementsByClassName("sequence"); const newSequenceDiagramCache = {}; for (let i = 0; i < sequenceDiagrams.length; i++) { const sequence = sequenceDiagrams[i]; const text = sequence.textContent.trim(); const theme = sequence.getAttribute("theme") || "simple"; const cacheKey = text + "$" + theme; if (cacheKey in this.sequenceDiagramCache) { sequence.innerHTML = this.sequenceDiagramCache[cacheKey]; } else { try { const diagram = window["Diagram"].parse(text); sequence.innerHTML = ""; diagram.drawSVG(sequence, { theme }); } catch (error) { sequence.innerHTML = '<pre class="language-text">' + error.toString() + "</pre>"; } } newSequenceDiagramCache[cacheKey] = sequence.innerHTML; } this.sequenceDiagramCache = newSequenceDiagramCache; resolve(); }); } /** * render wavedrom */ renderWavedrom() { return __awaiter(this, void 0, void 0, function* () { const els = this.hiddenPreviewElement.getElementsByClassName("wavedrom"); if (els.length) { const wavedromCache = {}; for (let i = 0; i < els.length; i++) { const el = els[i]; el.id = "wavedrom" + i; const text = el.textContent.trim(); if (!text.length) { continue; } if (text in this.wavedromCache) { // load cache const svg = this.wavedromCache[text]; el.innerHTML = svg; wavedromCache[text] = svg; continue; } try { const content = eval(`(${text})`); window["WaveDrom"].RenderWaveForm(i, content, "wavedrom"); wavedromCache[text] = el.innerHTML; } catch (error) { el.innerText = "Failed to eval WaveDrom code. " + error; } } this.wavedromCache = wavedromCache; } }); } /**UML * render zenuml */ renderZenUML() { return __awaiter(this, void 0, void 0, function* () { const els = this.hiddenPreviewElement.getElementsByClassName("zenuml"); if (els.length) { const zenumlCache = {}; for (let i = 0; i < els.length; i++) { const el = els[i]; el.id = "zenuml" + i; const text = el.textContent.trim(); if (!text.length) { continue; } if (text in this.zenumlCache) { // load cache const svg = this.zenumlCache[text]; el.innerHTML = svg; zenumlCache[text] = svg; continue; } try { const content = `<sequence-diagram>${text}</sequence-diagram>`; // window["WaveDrom"].RenderWaveForm(i, content, "wavedrom"); el.innerHTML = content; zenumlCache[text] = el.innerHTML; } catch (error) { el.innerText = "Failed to eval ZenUML code. " + error; } } this.zenumlCache = zenumlCache; } }); } /** * render MathJax expressions */ renderMathJax() { return new Promise((resolve, reject) => { if (this.config.mathRenderingOption === "MathJax" || this.config.usePandocParser) { const MathJax = window["MathJax"]; // .mathjax-exps, .math.inline, .math.display const unprocessedElements = this.hiddenPreviewElement.querySelectorAll(".mathjax-exps, .math.inline, .math.display"); if (!unprocessedElements.length) { return resolve(); } window["MathJax"].Hub.Queue(["Typeset", MathJax.Hub, this.hiddenPreviewElement], [ () => { // sometimes the this callback will be called twice // and only the second time will the Math expressions be rendered. // therefore, I added the line below to check whether math is already rendered. if (!this.hiddenPreviewElement.getElementsByClassName("MathJax") .length) { return; } this.scrollMap = null; return resolve(); }, ]); } else { return resolve(); } }); } /** * Run code chunk with 'id' * @param id */ runCodeChunk(id) { if (!this.config.enableScriptExecution) { return; } const codeChunk = document.querySelector(`.code-chunk[data-id="${id}"]`); const running = codeChunk.classList.contains("running"); if (running) { return; } codeChunk.classList.add("running"); if (codeChunk.getAttribute("data-cmd") === "javascript") { // javascript code chunk const code = codeChunk.getAttribute("data-code"); try { eval(`((function(){${code}$})())`); codeChunk.classList.remove("running"); // done running javascript code const CryptoJS = window["CryptoJS"]; const result = CryptoJS.AES.encrypt(codeChunk.getElementsByClassName("output-div")[0].outerHTML, "mume").toString(); this.postMessage("cacheCodeChunkResult", [ this.sourceUri, id, result, ]); } catch (e) { const outputDiv = codeChunk.getElementsByClassName("output-div")[0]; outputDiv.innerHTML = `<pre>${e.toString()}</pre>`; } } else { this.postMessage("runCodeChunk", [this.sourceUri, id]); } } /** * Run all code chunks */ runAllCodeChunks() { if (!this.config.enableScriptExecution) { return; } const codeChunks = this.previewElement.getElementsByClassName("code-chunk"); for (let i = 0; i < codeChunks.length; i++) { codeChunks[i].classList.add("running"); } this.postMessage("runAllCodeChunks", [this.sourceUri]); } /** * Run the code chunk that is the nearest to this.currentLine */ runNearestCodeChunk() { const currentLine = this.currentLine; const elements = this.previewElement.children; for (let i = elements.length - 1; i >= 0; i--) { if (elements[i].classList.contains("sync-line") && elements[i + 1] && elements[i + 1].classList.contains("code-chunk")) { if (currentLine >= parseInt(elements[i].getAttribute("data-line"), 10)) { const codeChunkId = elements[i + 1].getAttribute("data-id"); return this.runCodeChunk(codeChunkId); } } } } /** * Setup code chunks */ setupCodeChunks() { const codeChunks = this.previewElement.getElementsByClassName("code-chunk"); if (!codeChunks.length) { return; } for (let i = 0; i < codeChunks.length; i++) { const codeChunk = codeChunks[i]; const id = codeChunk.getAttribute("data-id"); // bind click event const runBtn = codeChunk.getElementsByClassName("run-btn")[0]; const runAllBtn = codeChunk.getElementsByClassName("run-all-btn")[0]; if (runBtn) { runBtn.addEventListener("click", () => { this.runCodeChunk(id); }); } if (runAllBtn) { runAllBtn.addEventListener("click", () => { this.runAllCodeChunks(); }); } } } /** * render sidebar toc */ renderSidebarTOC() { if (!this.enableSidebarTOC) { return; } if (this.sidebarTOCHTML) { this.sidebarTOC.innerHTML = this.sidebarTOCHTML; } else { this.sidebarTOC.innerHTML = `<p style="text-align:center;font-style: italic;">Outline (empty)</p>`; } } /** * init several preview events */ initEvents() { return __awaiter(this, void 0, void 0, function* () { yield Promise.all([ this.renderMathJax(), this.renderZenUML(), this.renderWavedrom(), ]); this.previewElement.innerHTML = this.hiddenPreviewElement.innerHTML; this.hiddenPreviewElement.innerHTML = ""; yield Promise.all([ this.renderFlowchart(), this.renderInteractiveVega(), this.renderSequenceDiagram(), this.renderMermaid(), ]); this.setupCodeChunks(); if (this.refreshingIconTimeout) { clearTimeout(this.refreshingIconTimeout); this.refreshingIconTimeout = null; } this.refreshingIcon.style.display = "none"; }); } /** * Bind <a href="..."></a> click events. */ bindTagAClickEvent() { const helper = (as) => { for (let i = 0; i < as.length; i++) { const a = as[i]; const href = decodeURIComponent(a.getAttribute("href")); // decodeURI here for Chinese like unicode heading if (href && href[0] === "#") { const targetElement = this.previewElement.querySelector(`[id=\"${encodeURIComponent(href.slice(1))}\"]`); // fix number id bug if (targetElement) { a.onclick = (event) => { event.preventDefault(); event.stopPropagation(); // jump to tag position let offsetTop = 0; let el = targetElement; while (el && el !== this.previewElement) { offsetTop += el.offsetTop; el = el.offsetParent; } if (this.previewElement.scrollTop > offsetTop) { this.previewElement.scrollTop = offsetTop - 32 - targetElement.offsetHeight; } else { this.previewElement.scrollTop = offsetTop; } }; } else { // without the `else` here, mpe package on Atom will fail to render preview (issue #824 and #827). a.onclick = (event) => { event.preventDefault(); event.stopPropagation(); }; } } else { a.onclick = (event) => { event.preventDefault(); event.stopPropagation(); this.postMessage("clickTagA", [ this.sourceUri, encodeURIComponent(href.replace(/\\/g, "/")), ]); }; } } }; helper(this.previewElement.getElementsByTagName("a")); if (this.sidebarTOC) { helper(this.sidebarTOC.getElementsByTagName("a")); } } /** * Initialize Tast list items checkbox click event. */ bindTaskListEvent() { const taskListItemCheckboxes = this.previewElement.getElementsByClassName("task-list-item-checkbox"); for (let i = 0; i < taskListItemCheckboxes.length; i++) { const checkbox = taskListItemCheckboxes[i]; let li = checkbox.parentElement; if (li.tagName !== "LI") { li = li.parentElement; } if (li.tagName === "LI") { li.classList.add("task-list-item"); // bind checkbox click event checkbox.onclick = (event) => { event.preventDefault(); const checked = checkbox.checked; if (checked) { checkbox.setAttribute("checked", ""); } else { checkbox.removeAttribute("checked"); } const dataLine = parseInt(checkbox.getAttribute("data-line"), 10); if (!isNaN(dataLine)) { this.postMessage("clickTaskListCheckbox", [ this.sourceUri, dataLine, ]); } }; } } } /** * update previewElement innerHTML content * @param html */ updateHTML(html, id, classes) { // If it's now presentationMode, then this function shouldn't be called. // If this function is called, then it might be in the case that // 1. Using singlePreview // 2. Switch from a presentationMode file to not presentationMode file. if (this.presentationMode) { this.postMessage("refreshPreview", [this.sourceUri]); } // editorScrollDelay = Date.now() + 500 this.previewScrollDelay = Date.now() + 500; this.hiddenPreviewElement.innerHTML = html; const scrollTop = this.previewElement.scrollTop; // init several events this.initEvents().then(() => { this.scrollMap = null; this.bindTagAClickEvent(); this.bindTaskListEvent(); // set id and classes this.previewElement.id = id || ""; this.previewElement.setAttribute("class", `mume markdown-preview ${classes}`); // scroll to initial position if (!this.doneLoadingPreview) { this.doneLoadingPreview = true; this.scrollToRevealSourceLine(this.initialLine); // clear @scrollMap after 2 seconds because sometimes // loading images will change scrollHeight. setTim