Improve Markdown; Add Mermaid/LaTeX

This commit is contained in:
Jordan Thompson
2025-12-30 18:14:29 -08:00
parent c63fbb8ed1
commit e8962e25fd
15 changed files with 2472 additions and 23 deletions
+8 -3
View File
@@ -20,10 +20,15 @@ A versatile ComfyUI custom node that renders HTML, SVG, Markdown, and code conte
## Features
### Content Rendering
- **Auto-detection** of content type: HTML, SVG, Markdown, Python, JavaScript, CSS, or plain text
- **Auto-detection** of content type: HTML, SVG, Markdown, JSON, YAML, CSV, Python, JavaScript, CSS, Terminal (ANSI), or plain text
- **SVG support** - Renders SVG graphics centered in the viewport
- **Markdown parsing** - Full support for headers, lists, links, images, code blocks, tables, and emphasis
- **Code formatting** - Code content displayed with monospace font in styled blocks
- **Markdown parsing** - Full support for headers, lists, links, images, code blocks, tables, emphasis, and Mermaid diagrams
- **JSON viewer** - Collapsible tree view with syntax highlighting
- **CSV/Tables** - Automatic table rendering with styled headers
- **YAML** - Syntax highlighted display
- **LaTeX/Math** - Renders mathematical formulas using KaTeX (`$$...$$` or `$...$`)
- **Terminal output** - Parses ANSI escape codes for colored terminal logs
- **Code formatting** - Code content displayed with monospace font and Prism.js syntax highlighting
- **ComfyUI theme integration** - Automatically matches ComfyUI's theme colors
### List Support
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

+432 -20
View File
@@ -14,6 +14,10 @@ const STATE = {
cleanupListenersAttached: false,
lastScale: 1,
prismScripts: null,
mermaidScript: null,
katexCore: null,
katexAutoRender: null,
katexCss: null,
};
const PRISM_FILES = [
@@ -26,6 +30,10 @@ const PRISM_FILES = [
"prism-bash.min.txt",
];
const MERMAID_FILE = "mermaid.min.txt";
const KATEX_FILES = ["katex.min.txt", "katex-auto-render.min.txt"];
const KATEX_CSS_FILE = "katex-with-fonts.min.css.txt";
async function loadPrismScripts() {
if (STATE.prismScripts) return STATE.prismScripts;
@@ -47,6 +55,61 @@ async function loadPrismScripts() {
loadPrismScripts();
async function loadMermaidScript() {
if (STATE.mermaidScript) return STATE.mermaidScript;
try {
const basePath = import.meta.url.substring(0, import.meta.url.lastIndexOf("/"));
const res = await fetch(`${basePath}/${MERMAID_FILE}`);
STATE.mermaidScript = res.ok ? await res.text() : "";
return STATE.mermaidScript;
} catch (e) {
console.error("[WAS Viewer] Failed to load Mermaid script:", e);
return "";
}
}
async function loadKatexScripts() {
if (STATE.katexCore && STATE.katexAutoRender && STATE.katexCss) return true;
try {
const basePath = import.meta.url.substring(0, import.meta.url.lastIndexOf("/"));
const [katexCore, autoRender, katexCss] = await Promise.all([
fetch(`${basePath}/${KATEX_FILES[0]}`).then(r => r.ok ? r.text() : ""),
fetch(`${basePath}/${KATEX_FILES[1]}`).then(r => r.ok ? r.text() : ""),
fetch(`${basePath}/${KATEX_CSS_FILE}`).then(r => r.ok ? r.text() : "")
]);
STATE.katexCore = katexCore;
STATE.katexAutoRender = autoRender;
STATE.katexCss = katexCss;
return true;
} catch (e) {
console.error("[WAS Viewer] Failed to load KaTeX scripts:", e);
return false;
}
}
function refreshAllViewers() {
for (const [nodeId, elements] of STATE.nodeIdToElements.entries()) {
if (elements && elements.lastContentHash) {
elements.lastContentHash = "";
const node = app?.graph?.getNodeById(parseInt(nodeId));
if (node) {
try {
updateIframeContent(node, elements);
} catch (e) {}
}
}
}
}
loadMermaidScript().then(() => {
if (STATE.mermaidScript) refreshAllViewers();
});
loadKatexScripts().then(() => {
if (STATE.katexCore && STATE.katexCss) refreshAllViewers();
});
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
@@ -106,7 +169,43 @@ function detectContentType(content) {
return "html";
}
const scores = { html: 0, markdown: 0, python: 0, javascript: 0, css: 0 };
const scores = { html: 0, markdown: 0, python: 0, javascript: 0, css: 0, json: 0, yaml: 0, csv: 0, ansi: 0 };
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
try {
JSON.parse(trimmed);
return "json";
} catch {}
}
if (/\x1b\[[\d;]*m/.test(trimmed) || /\033\[[\d;]*m/.test(trimmed)) {
return "ansi";
}
const lines = trimmed.split("\n");
const csvLines = lines.filter(l => l.includes(",") && !l.includes("{") && !l.includes("<"));
if (csvLines.length >= 2) {
const firstLineCommas = (lines[0].match(/,/g) || []).length;
if (firstLineCommas >= 1) {
const consistentCommas = csvLines.filter(l => (l.match(/,/g) || []).length === firstLineCommas).length;
if (consistentCommas >= csvLines.length * 0.8) {
scores.csv += 5;
}
}
}
const yamlPatterns = [
[/^[\w-]+:\s*.+$/m, 2],
[/^[\w-]+:\s*$/m, 2],
[/^\s+-\s+.+$/m, 1],
[/^---\s*$/m, 3],
];
for (const [pattern, weight] of yamlPatterns) {
if (pattern.test(trimmed)) scores.yaml += weight;
}
if (trimmed.includes(": ") && !trimmed.includes("{") && !trimmed.includes("<")) {
scores.yaml += 1;
}
const htmlTags = ["<div", "<span", "<p>", "<h1", "<h2", "<h3", "<table", "<ul", "<ol",
"<img", "<a ", "<br", "<hr", "<em>", "<strong>", "<b>", "<i>", "<code>", "<pre>"];
@@ -198,21 +297,75 @@ function escapeHtml(text) {
return div.innerHTML;
}
function parseNestedList(match, listTag, itemTag) {
const regex = new RegExp(`<${itemTag}[^>]*data-level="(\\d+)"[^>]*(?:class="([^"]*)")?[^>]*>(.*?)<\\/${itemTag}>`, 'g');
const items = [];
let m;
while ((m = regex.exec(match)) !== null) {
items.push({ level: parseInt(m[1]), className: m[2] || '', content: m[3] });
}
if (items.length === 0) return match;
let result = '';
let stack = [];
for (const item of items) {
while (stack.length > 0 && stack[stack.length - 1] > item.level) {
result += `</li></${listTag}>`;
stack.pop();
}
if (stack.length === 0 || stack[stack.length - 1] < item.level) {
result += `<${listTag}>`;
stack.push(item.level);
} else if (stack[stack.length - 1] === item.level && result.length > 0) {
result += '</li>';
}
const classAttr = item.className ? ` class="${item.className}"` : '';
result += `<li${classAttr}>${item.content}`;
}
while (stack.length > 0) {
result += `</li></${listTag}>`;
stack.pop();
}
return result;
}
function parseMarkdown(md) {
let html = md;
// Normalize mixed LaTeX delimiters: $$\(...\)$$ -> $$...$$ and $$\[...\]$$ -> $$...$$
html = html.replace(/\$\$\s*\\\(([^]*?)\\\)\s*\$\$/g, '$$$$$1$$$$');
html = html.replace(/\$\$\s*\\\[([^]*?)\\\]\s*\$\$/g, '$$$$$1$$$$');
// Convert inline $$...$$ to $...$ when surrounded by text on same line (not at line start/end)
html = html.replace(/([^\n$])\$\$([^$\n]+)\$\$([^\n$])/g, '$1$$$2$$$3');
html = html.replace(/^```(?:mermaid|flow|flex)(?:\[([^\]]*)\])?\n([\s\S]*?)```$/gm, (_, options, code) => {
const centered = options && options.toLowerCase().includes('center');
const style = centered ? 'text-align:center;' : 'text-align:left;';
return `<div class="mermaid" style="${style}">${code.trim()}</div>`;
});
html = html.replace(/^```(\w*)\n([\s\S]*?)```$/gm, (_, lang, code) => {
return `<pre><code class="language-${lang || "text"}">${escapeHtml(code.trim())}</code></pre>`;
});
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
html = html.replace(/^######\s+(.*)$/gm, "<h6>$1</h6>");
html = html.replace(/^#####\s+(.*)$/gm, "<h5>$1</h5>");
html = html.replace(/^####\s+(.*)$/gm, "<h4>$1</h4>");
html = html.replace(/^###\s+(.*)$/gm, "<h3>$1</h3>");
html = html.replace(/^##\s+(.*)$/gm, "<h2>$1</h2>");
html = html.replace(/^#\s+(.*)$/gm, "<h1>$1</h1>");
html = html.replace(/^(.+)\n=+\s*$/gm, "<h1>$1</h1>");
html = html.replace(/^(.+)\n-+\s*$/gm, "<h2>$1</h2>");
html = html.replace(/^######\s*(.+)$/gm, "<h6>$1</h6>");
html = html.replace(/^#####\s*(.+)$/gm, "<h5>$1</h5>");
html = html.replace(/^####\s*(.+)$/gm, "<h4>$1</h4>");
html = html.replace(/^###\s*(.+)$/gm, "<h3>$1</h3>");
html = html.replace(/^##\s*(.+)$/gm, "<h2>$1</h2>");
html = html.replace(/^#\s*(.+)$/gm, "<h1>$1</h1>");
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;">');
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
@@ -225,14 +378,31 @@ function parseMarkdown(md) {
html = html.replace(/^>\s+(.*)$/gm, "<blockquote>$1</blockquote>");
html = html.replace(/^\d+\.\s+(.*)$/gm, "<oli>$1</oli>");
html = html.replace(/(<oli>.*<\/oli>\n?)+/g, (match) => {
return "<ol>" + match.replace(/<\/?oli>/g, (tag) => tag.replace("oli", "li")) + "</ol>";
html = html.replace(/^(\s*)- \[x\]\s+(.*)$/gim, (_, indent, text) => {
const level = Math.floor(indent.length / 2);
return `<uli data-level="${level}" class="task-item checked"><input type="checkbox" checked disabled> ${text}</uli>`;
});
html = html.replace(/^(\s*)- \[ \]\s+(.*)$/gm, (_, indent, text) => {
const level = Math.floor(indent.length / 2);
return `<uli data-level="${level}" class="task-item"><input type="checkbox" disabled> ${text}</uli>`;
});
html = html.replace(/^[-*+]\s+(.*)$/gm, "<uli>$1</uli>");
html = html.replace(/(<uli>.*<\/uli>\n?)+/g, (match) => {
return "<ul>" + match.replace(/<\/?uli>/g, (tag) => tag.replace("uli", "li")) + "</ul>";
html = html.replace(/^(\s*)\d+\.\s+(.*)$/gm, (_, indent, text) => {
const level = Math.floor(indent.length / 2);
return `<oli data-level="${level}">${text}</oli>`;
});
html = html.replace(/^(\s*)[-*+]\s+(.*)$/gm, (_, indent, text) => {
const level = Math.floor(indent.length / 2);
return `<uli data-level="${level}">${text}</uli>`;
});
html = html.replace(/(<oli[^>]*>.*?<\/oli>\s*)+/g, (match) => {
return parseNestedList(match, 'ol', 'oli');
});
html = html.replace(/(<uli[^>]*>.*?<\/uli>\s*)+/g, (match) => {
return parseNestedList(match, 'ul', 'uli');
});
html = html.replace(/^---$/gm, "<hr>");
@@ -273,12 +443,108 @@ function renderCodeContent(content, language) {
python: "python",
javascript: "javascript",
css: "css",
yaml: "yaml",
text: "plaintext",
};
const lang = langMap[language] || "plaintext";
return `<pre><code class="language-${lang}">${escapeHtml(content)}</code></pre>`;
}
function renderJsonTree(data, depth = 0) {
const indent = " ".repeat(depth);
if (data === null) return `<span class="json-null">null</span>`;
if (typeof data === "boolean") return `<span class="json-boolean">${data}</span>`;
if (typeof data === "number") return `<span class="json-number">${data}</span>`;
if (typeof data === "string") return `<span class="json-string">"${escapeHtml(data)}"</span>`;
if (Array.isArray(data)) {
if (data.length === 0) return `<span class="json-bracket">[]</span>`;
const id = `json-${Math.random().toString(36).slice(2, 9)}`;
const items = data.map((item, i) => {
const comma = i < data.length - 1 ? "," : "";
return `${indent} ${renderJsonTree(item, depth + 1)}${comma}`;
}).join("\n");
return `<span class="json-toggle" onclick="toggleJson('${id}')">[<span class="json-count">${data.length} items</span>]</span><div id="${id}" class="json-collapsible">\n${items}\n${indent}<span class="json-bracket">]</span></div>`;
}
if (typeof data === "object") {
const keys = Object.keys(data);
if (keys.length === 0) return `<span class="json-bracket">{}</span>`;
const id = `json-${Math.random().toString(36).slice(2, 9)}`;
const items = keys.map((key, i) => {
const comma = i < keys.length - 1 ? "," : "";
return `${indent} <span class="json-key">"${escapeHtml(key)}"</span>: ${renderJsonTree(data[key], depth + 1)}${comma}`;
}).join("\n");
return `<span class="json-toggle" onclick="toggleJson('${id}')">{<span class="json-count">${keys.length} keys</span>}</span><div id="${id}" class="json-collapsible">\n${items}\n${indent}<span class="json-bracket">}</span></div>`;
}
return escapeHtml(String(data));
}
function renderCsvTable(content) {
const lines = content.trim().split("\n");
if (lines.length === 0) return `<pre>${escapeHtml(content)}</pre>`;
const parseRow = (line) => {
const result = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === "," && !inQuotes) {
result.push(current.trim());
current = "";
} else {
current += char;
}
}
result.push(current.trim());
return result;
};
const headers = parseRow(lines[0]);
const rows = lines.slice(1).map(parseRow);
const thead = `<thead><tr>${headers.map(h => `<th>${escapeHtml(h)}</th>`).join("")}</tr></thead>`;
const tbody = `<tbody>${rows.map(row =>
`<tr>${row.map(cell => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`
).join("")}</tbody>`;
return `<div class="csv-container"><table class="csv-table">${thead}${tbody}</table></div>`;
}
function renderAnsiContent(content) {
const ansiColors = {
"30": "#000", "31": "#e74c3c", "32": "#2ecc71", "33": "#f39c12",
"34": "#3498db", "35": "#9b59b6", "36": "#1abc9c", "37": "#ecf0f1",
"90": "#7f8c8d", "91": "#ff6b6b", "92": "#69db7c", "93": "#ffd43b",
"94": "#74c0fc", "95": "#da77f2", "96": "#63e6be", "97": "#fff",
};
const bgColors = {
"40": "#000", "41": "#e74c3c", "42": "#2ecc71", "43": "#f39c12",
"44": "#3498db", "45": "#9b59b6", "46": "#1abc9c", "47": "#ecf0f1",
};
let html = escapeHtml(content);
html = html.replace(/(?:\x1b|\033)\[([\d;]*)m/g, (match, codes) => {
if (!codes || codes === "0") return "</span>";
const parts = codes.split(";");
let style = "";
for (const code of parts) {
if (ansiColors[code]) style += `color:${ansiColors[code]};`;
if (bgColors[code]) style += `background:${bgColors[code]};`;
if (code === "1") style += "font-weight:bold;";
if (code === "3") style += "font-style:italic;";
if (code === "4") style += "text-decoration:underline;";
}
return style ? `<span style="${style}">` : "";
});
return `<pre class="ansi-output">${html}</pre>`;
}
async function createZipBlob(files) {
const crc32 = (data) => {
let crc = 0xFFFFFFFF;
@@ -427,12 +693,15 @@ function buildIframeContent(content, contentType, theme, excluded = []) {
margin: 16px 0;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 20px;
margin-bottom: 10px;
margin-top: 16px;
margin-bottom: 8px;
color: ${theme.fg};
}
ul, ol { padding-left: 24px; }
li { margin: 4px 0; }
h1:first-child, h2:first-child, h3:first-child, h4:first-child, h5:first-child, h6:first-child {
margin-top: 0;
}
ul, ol { padding-left: 24px; margin: 8px 0; }
li { margin: 2px 0; }
.list-item {
background: rgba(0,0,0,0.2);
border: 1px solid ${theme.border};
@@ -501,6 +770,82 @@ function buildIframeContent(content, contentType, theme, excluded = []) {
white-space: pre-wrap;
word-wrap: break-word;
}
.json-tree pre {
margin: 0;
white-space: pre-wrap;
font-size: 13px;
}
.json-key { color: #9cdcfe; }
.json-string { color: #ce9178; }
.json-number { color: #b5cea8; }
.json-boolean { color: #569cd6; }
.json-null { color: #569cd6; }
.json-bracket { color: ${theme.fg}; }
.json-toggle {
cursor: pointer;
user-select: none;
}
.json-toggle:hover { opacity: 0.8; }
.json-count {
color: #6a9955;
font-size: 11px;
margin-left: 4px;
}
.json-collapsible {
margin-left: 16px;
}
.json-collapsible.collapsed {
display: none;
}
.csv-container {
overflow-x: auto;
}
.csv-table {
border-collapse: collapse;
width: 100%;
font-size: 13px;
}
.csv-table th, .csv-table td {
border: 1px solid ${theme.border};
padding: 8px 12px;
text-align: left;
}
.csv-table th {
background: rgba(0,0,0,0.3);
font-weight: bold;
}
.csv-table tr:nth-child(even) {
background: rgba(0,0,0,0.1);
}
.ansi-output {
background: #1e1e1e;
padding: 12px;
border-radius: 6px;
font-family: "Fira Code", Consolas, Monaco, monospace;
font-size: 13px;
line-height: 1.4;
}
.mermaid {
background: transparent;
text-align: center;
padding: 16px;
}
.math-block {
text-align: center;
margin: 16px 0;
overflow-x: auto;
}
.task-item {
list-style: none;
margin-left: -20px;
}
.task-item input[type="checkbox"] {
margin-right: 8px;
accent-color: ${theme.accent};
}
.task-item.checked {
color: ${theme.fg};
}
`;
const LIST_SEPARATOR = "\n---LIST_SEPARATOR---\n";
@@ -578,6 +923,23 @@ function buildIframeContent(content, contentType, theme, excluded = []) {
case "svg":
bodyContent = `<div style="display:flex;justify-content:center;align-items:center;min-height:100%;padding:16px;">${content}</div>`;
break;
case "json":
try {
const parsed = JSON.parse(content);
bodyContent = `<div class="json-tree"><pre>${renderJsonTree(parsed)}</pre></div>`;
} catch {
bodyContent = renderCodeContent(content, "javascript");
}
break;
case "csv":
bodyContent = renderCsvTable(content);
break;
case "yaml":
bodyContent = renderCodeContent(content, "yaml");
break;
case "ansi":
bodyContent = renderAnsiContent(content);
break;
case "python":
case "javascript":
case "css":
@@ -628,7 +990,7 @@ function buildIframeContent(content, contentType, theme, excluded = []) {
.token.italic { font-style: italic; }
`;
const needsPrism = ["python", "javascript", "css"].includes(contentType) ||
const needsPrism = ["python", "javascript", "css", "yaml"].includes(contentType) ||
content.includes("```") || content.includes("<code");
const prismScripts = needsPrism && STATE.prismScripts ? `
@@ -636,13 +998,59 @@ function buildIframeContent(content, contentType, theme, excluded = []) {
<script>document.addEventListener('DOMContentLoaded', () => Prism.highlightAll());<\/script>
` : "";
const jsonScript = contentType === "json" ? `
<script>
function toggleJson(id) {
const el = document.getElementById(id);
if (el) el.classList.toggle('collapsed');
}
<\/script>
` : "";
const hasMermaid = /```(?:mermaid|flow|flex)/.test(content) || content.includes("<div class=\"mermaid\">");
const mermaidScript = hasMermaid && STATE.mermaidScript ? `
<script>${STATE.mermaidScript}<\/script>
<script>
document.addEventListener('DOMContentLoaded', () => {
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
});
<\/script>
` : "";
const hasLatex = content.includes("$$") || content.includes("\\(") || content.includes("\\[") || /\$[^$\n]+\$/.test(content);
const katexStyles = hasLatex && STATE.katexCss ? `<style>${STATE.katexCss}</style>` : "";
const katexScript = hasLatex && STATE.katexCore && STATE.katexAutoRender ? `
<script>${STATE.katexCore}<\/script>
<script>${STATE.katexAutoRender}<\/script>
<script>
document.addEventListener('DOMContentLoaded', () => {
if (typeof renderMathInElement === 'function') {
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\\\(', right: '\\\\)', display: false},
{left: '\\\\[', right: '\\\\]', display: true}
],
ignoredTags: ['script', 'noscript', 'style', 'textarea', 'option'],
throwOnError: false
});
}
});
<\/script>
` : "";
return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>${baseStyles}${needsPrism ? prismTheme : ""}</style>
${katexStyles}
${prismScripts}
${jsonScript}
${mermaidScript}
${katexScript}
</head>
<body>${bodyContent}</body>
</html>`;
@@ -1219,8 +1627,8 @@ function ensureElementsForNode(node) {
`;
const iframe = document.createElement("iframe");
iframe.setAttribute("sandbox", "allow-scripts allow-forms allow-popups allow-modals allow-pointer-lock allow-downloads");
iframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen");
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-popups allow-modals allow-pointer-lock allow-downloads");
iframe.setAttribute("allow", "fullscreen");
iframe.setAttribute("allowfullscreen", "true");
iframe.style.cssText = `
position: absolute;
@@ -1355,6 +1763,10 @@ function updateIframeContent(node, elements) {
python: "Python",
javascript: "JavaScript",
css: "CSS",
json: "JSON",
yaml: "YAML",
csv: "CSV",
ansi: "Terminal",
text: "Text",
};
elements.typeLabel.textContent = `Type: ${typeNames[contentType] || "Unknown"}`;
+1
View File
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var i=r[e];if(void 0!==i)return i.exports;var a=r[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var i={};return function(){n.d(i,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,i=0,a=e.length;n<t.length;){var o=t[n];if(i<=0&&t.slice(n,n+a)===e)return n;"\\"===o?n++:"{"===o?i++:"}"===o&&i--,n++}return-1},a=/^\\begin{/,o=function(e,t){for(var n,i=[],o=new RegExp("("+t.map((function(e){return e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")})).join("|")+")");-1!==(n=e.search(o));){n>0&&(i.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=a.test(d)?d:e.slice(t[l].left.length,n);i.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&i.push({type:"text",data:e}),i},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var i=document.createDocumentFragment(),a=0;a<n.length;a++)if("text"===n[a].type)i.appendChild(document.createTextNode(n[a].data));else{var l=document.createElement("span"),d=n[a].data;r.displayMode=n[a].display;try{r.preProcess&&(d=r.preProcess(d)),t().render(d,l,r)}catch(e){if(!(e instanceof t().ParseError))throw e;r.errorCallback("KaTeX auto-render: Failed to parse `"+n[a].data+"` with ",e),i.appendChild(document.createTextNode(n[a].rawData));continue}i.appendChild(l)}return i},d=function e(t,r){for(var n=0;n<t.childNodes.length;n++){var i=t.childNodes[n];if(3===i.nodeType){for(var a=i.textContent,o=i.nextSibling,d=0;o&&o.nodeType===Node.TEXT_NODE;)a+=o.textContent,o=o.nextSibling,d++;var s=l(a,r);if(s){for(var f=0;f<d;f++)i.nextSibling.remove();n+=s.childNodes.length-1,t.replaceChild(s,i)}else n+=d}else 1===i.nodeType&&function(){var t=" "+i.className+" ";-1===r.ignoredTags.indexOf(i.nodeName.toLowerCase())&&r.ignoredClasses.every((function(e){return-1===t.indexOf(" "+e+" ")}))&&e(i,r)}()}},s=function(e,t){if(!e)throw new Error("No element provided to render");var r={};for(var n in t)t.hasOwnProperty(n)&&(r[n]=t[n]);r.delimiters=r.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],r.ignoredTags=r.ignoredTags||["script","noscript","style","textarea","pre","code","option"],r.ignoredClasses=r.ignoredClasses||[],r.errorCallback=r.errorCallback||console.error,r.macros=r.macros||{},d(e,r)}}(),i=i.default}()}));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2029
View File
File diff suppressed because one or more lines are too long