|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import Utils from "./utils.js";
|
|
|
|
|
|
const Preview = {
|
|
|
container: null,
|
|
|
renderer: null,
|
|
|
|
|
|
init() {
|
|
|
this.container = document.getElementById("markdown-preview");
|
|
|
this.setupMarked();
|
|
|
this.setupMermaid();
|
|
|
},
|
|
|
|
|
|
setupMarked() {
|
|
|
|
|
|
marked.setOptions({
|
|
|
breaks: true,
|
|
|
gfm: true,
|
|
|
headerIds: true
|
|
|
});
|
|
|
|
|
|
|
|
|
const renderer = {
|
|
|
listitem(token) {
|
|
|
|
|
|
if (token.task) {
|
|
|
return `<li class="task-list-item">
|
|
|
<input type="checkbox" ${token.checked ? "checked" : ""} disabled> ${token.text}
|
|
|
</li>\n`;
|
|
|
}
|
|
|
|
|
|
|
|
|
return `<li>${token.text}</li>\n`;
|
|
|
},
|
|
|
|
|
|
code(token) {
|
|
|
const lang = token.lang || "";
|
|
|
const code = token.text;
|
|
|
|
|
|
|
|
|
if (lang && window.Prism && Prism.languages[lang]) {
|
|
|
const highlighted = Prism.highlight(code, Prism.languages[lang], lang);
|
|
|
return `<pre><code class="language-${lang}">${highlighted}</code></pre>\n`;
|
|
|
}
|
|
|
|
|
|
return `<pre><code>${code}</code></pre>\n`;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
marked.use({ renderer });
|
|
|
},
|
|
|
|
|
|
setupMermaid() {
|
|
|
if (window.mermaid) {
|
|
|
mermaid.initialize({
|
|
|
startOnLoad: false,
|
|
|
theme: "dark",
|
|
|
securityLevel: "loose"
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
|
|
|
update() {
|
|
|
const content = window.app?.editor.getContent() || "";
|
|
|
this.render(content);
|
|
|
},
|
|
|
|
|
|
async render(markdown) {
|
|
|
if (!markdown) {
|
|
|
this.container.innerHTML = '<p style="color: var(--text-tertiary); text-align: center; padding: 2rem;">Start writing to see preview...</p>';
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
let html = await marked.parse(markdown);
|
|
|
|
|
|
|
|
|
if (typeof html !== "string") {
|
|
|
console.error("Marked.parse returned non-string:", typeof html, html);
|
|
|
html = String(html);
|
|
|
}
|
|
|
|
|
|
|
|
|
html = this.processKaTeX(html);
|
|
|
|
|
|
|
|
|
this.container.innerHTML = html;
|
|
|
|
|
|
|
|
|
this.renderMermaid();
|
|
|
|
|
|
|
|
|
this.highlightCode();
|
|
|
} catch (err) {
|
|
|
console.error("Preview render failed:", err);
|
|
|
this.container.innerHTML = `<p style="color: var(--error);">Preview error: ${err.message}</p>`;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
processKaTeX(html) {
|
|
|
if (!window.katex) return html;
|
|
|
|
|
|
|
|
|
html = html.replace(/\$([^\$]+)\$/g, (match, math) => {
|
|
|
try {
|
|
|
return katex.renderToString(math, { throwOnError: false });
|
|
|
} catch {
|
|
|
return match;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
html = html.replace(/\$\$([^\$]+)\$\$/g, (match, math) => {
|
|
|
try {
|
|
|
return katex.renderToString(math, {
|
|
|
throwOnError: false,
|
|
|
displayMode: true
|
|
|
});
|
|
|
} catch {
|
|
|
return match;
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return html;
|
|
|
},
|
|
|
|
|
|
renderMermaid() {
|
|
|
if (!window.mermaid) return;
|
|
|
|
|
|
const mermaidBlocks = this.container.querySelectorAll("code.language-mermaid");
|
|
|
mermaidBlocks.forEach((block, index) => {
|
|
|
const code = block.textContent;
|
|
|
const id = `mermaid-${Date.now()}-${index}`;
|
|
|
|
|
|
const div = document.createElement("div");
|
|
|
div.id = id;
|
|
|
div.className = "mermaid";
|
|
|
div.textContent = code;
|
|
|
|
|
|
block.parentElement.replaceWith(div);
|
|
|
|
|
|
try {
|
|
|
mermaid.init(undefined, `#${id}`);
|
|
|
} catch (err) {
|
|
|
console.warn("Mermaid render failed:", err);
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
highlightCode() {
|
|
|
if (!window.Prism) return;
|
|
|
|
|
|
this.container.querySelectorAll("pre code").forEach(block => {
|
|
|
if (!block.classList.contains("language-mermaid")) {
|
|
|
Prism.highlightElement(block);
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
|
|
|
|
|
|
getHTML() {
|
|
|
if (!this.container) {
|
|
|
console.warn("Preview container not initialized");
|
|
|
return "";
|
|
|
}
|
|
|
return this.container.innerHTML;
|
|
|
},
|
|
|
|
|
|
|
|
|
setTheme(theme) {
|
|
|
if (!this.container) {
|
|
|
console.warn("Preview container not initialized yet");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (theme === "github") {
|
|
|
this.container.classList.add("theme-github");
|
|
|
this.container.classList.remove("theme-elysia");
|
|
|
} else {
|
|
|
this.container.classList.add("theme-elysia");
|
|
|
this.container.classList.remove("theme-github");
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
|
|
|
export default Preview;
|
|
|
|