Spaces:
Running
Running
| /* | |
| 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; | |