Elysia-Suite's picture
Upload 25 files
0b194e5 verified
/*
ELYSIA MARKDOWN STUDIO v1.0 - Preview Module
Real-time Markdown preview with extensions
*/
import Utils from "./utils.js";
const Preview = {
container: null,
renderer: null,
init() {
this.container = document.getElementById("markdown-preview");
this.setupMarked();
this.setupMermaid();
},
setupMarked() {
// Configure marked.js v15+ (async API)
marked.setOptions({
breaks: true,
gfm: true, // GitHub Flavored Markdown
headerIds: true
});
// Custom renderer for task lists (Marked.js v15+ token-based API)
const renderer = {
listitem(token) {
// In v15+, token has 'task' and 'checked' properties
if (token.task) {
return `<li class="task-list-item">
<input type="checkbox" ${token.checked ? "checked" : ""} disabled> ${token.text}
</li>\n`;
}
// Regular list item
return `<li>${token.text}</li>\n`;
},
code(token) {
const lang = token.lang || "";
const code = token.text;
// Highlight with Prism if available
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 {
// Convert markdown to HTML (v15+ can be async)
let html = await marked.parse(markdown);
// Ensure html is a string, not an object
if (typeof html !== "string") {
console.error("Marked.parse returned non-string:", typeof html, html);
html = String(html);
}
// Process KaTeX math
html = this.processKaTeX(html);
// Update container
this.container.innerHTML = html;
// Render mermaid diagrams
this.renderMermaid();
// Highlight code blocks
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;
// Inline math: $...$
html = html.replace(/\$([^\$]+)\$/g, (match, math) => {
try {
return katex.renderToString(math, { throwOnError: false });
} catch {
return match;
}
});
// Block math: $$...$$
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);
}
});
},
// Get current HTML (for export)
getHTML() {
if (!this.container) {
console.warn("Preview container not initialized");
return "";
}
return this.container.innerHTML;
},
// Apply theme
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;