/* 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 `
  • ${token.text}
  • \n`; } // Regular list item return `
  • ${token.text}
  • \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 `
    ${highlighted}
    \n`; } return `
    ${code}
    \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 = '

    Start writing to see preview...

    '; 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 = `

    Preview error: ${err.message}

    `; } }, 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;