mume-with-litvis
Version:
Fork of mume with added http://litvis.org/
1,145 lines • 65.3 kB
JavaScript
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