-
Notifications
You must be signed in to change notification settings - Fork 46
feat: add mermaid diagram support in markdown preview #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Add support for rendering mermaid diagrams in the markdown preview: - Lazy load mermaid.js only when mermaid code blocks are detected - Theme-aware rendering (dark/light themes sync with app) - Display helpful error messages for invalid diagram syntax - Style diagrams to match the existing preview aesthetics
When preview-only mode is active (editor hidden), the preview wasn't stretching to fill the available width. Add preview-full class that applies flex: 1 and width: 100% to ensure full-width display.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds Mermaid.js diagram rendering support to the markdown preview functionality. The implementation includes lazy loading of the ~2MB mermaid library, theme-aware rendering that syncs with the application's light/dark mode, error handling for invalid diagram syntax, and a CSS fix to display full-width previews in replace mode.
Key Changes:
- Lazy loads mermaid.js only when mermaid code blocks are detected in the markdown
- Implements theme-aware diagram rendering that watches for theme changes
- Adds error handling to display helpful messages when diagram syntax is invalid
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| client/src/components/MarkdownPreview.vue | Core implementation: adds mermaid lazy loading, diagram rendering logic, theme watching, error handling, and CSS styling for diagrams and error messages |
| client/src/views/Note.vue | Adds CSS class for full-width preview in replace mode |
| client/src/views/Day.vue | Adds CSS class for full-width preview in replace mode (same pattern as Note.vue) |
| client/package.json | Adds mermaid ^11.12.2 dependency |
| client/package-lock.json | Lock file updates for mermaid and its dependencies including d3, cytoscape, katex, and other transitive dependencies |
Files not reviewed (1)
- client/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Update mermaid theme based on current app theme | ||
| mermaid.initialize({ | ||
| startOnLoad: false, | ||
| theme: themeService.isDark ? 'dark' : 'default', | ||
| securityLevel: 'strict', | ||
| fontFamily: 'Fira Code, monospace', | ||
| }); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mermaid configuration is duplicated in both loadMermaid (lines 55-60) and renderMermaidDiagrams (lines 79-84). Consider extracting this configuration into a separate function or constant to avoid duplication and ensure consistency.
| background-color: rgba(255, 82, 82, 0.1); | ||
| border: 1px solid #ff5252; | ||
| border-radius: 4px; | ||
| color: #ff5252; | ||
| font-family: 'Fira Code', monospace; | ||
| font-size: 0.9em; | ||
| } | ||
| .preview-content :deep(.mermaid-error strong) { | ||
| color: #ff5252; | ||
| } |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message styling uses a hardcoded red color (#ff5252) which may not have sufficient color contrast in all themes or for users with color blindness. Consider using CSS custom properties that adapt to the theme, or ensure the color contrast ratio meets WCAG AA standards (at least 4.5:1 for normal text).
| const id = `mermaid-diagram-${mermaidIdCounter++}`; | ||
| // Render the diagram | ||
| const { svg } = await mermaid.render(id, code); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each time a mermaid diagram is rendered, a new unique ID is generated and used. Mermaid internally caches rendered SVG content by ID, which could lead to memory leaks over time as old diagram SVGs accumulate in memory but are never garbage collected. Consider calling mermaid's cleanup methods or reusing IDs in a way that allows old diagrams to be properly cleaned up.
| try { | ||
| // Generate unique ID for this diagram | ||
| const id = `mermaid-diagram-${mermaidIdCounter++}`; |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The mermaidIdCounter is never reset, which could lead to extremely large ID values over time in a long-running application. Consider resetting the counter or using a more appropriate unique identifier strategy such as generating UUIDs or using a combination of timestamp and sequence number.
| const id = `mermaid-diagram-${mermaidIdCounter++}`; | |
| const id = `mermaid-diagram-${crypto.randomUUID()}`; |
| // Show error message in place of the diagram | ||
| const errorDiv = document.createElement('div'); | ||
| errorDiv.className = 'mermaid-error'; | ||
| errorDiv.innerHTML = `<strong>Mermaid Error:</strong> ${error instanceof Error ? error.message : 'Failed to render diagram'}`; |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using innerHTML to display error messages could lead to XSS vulnerabilities if the error message contains user-controlled content or malicious script tags. Consider using textContent for the error message portion, or properly escape the error message before inserting it into the DOM.
| errorDiv.innerHTML = `<strong>Mermaid Error:</strong> ${error instanceof Error ? error.message : 'Failed to render diagram'}`; | |
| const strong = document.createElement('strong'); | |
| strong.textContent = 'Mermaid Error:'; | |
| errorDiv.appendChild(strong); | |
| errorDiv.appendChild(document.createTextNode(' ' + (error instanceof Error ? error.message : 'Failed to render diagram'))); |
| const renderMermaidDiagrams = async () => { | ||
| // Find all mermaid code blocks in the rendered content | ||
| const container = document.querySelector('.preview-content'); |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using document.querySelector to find the preview container is fragile and could select the wrong element if multiple MarkdownPreview components are mounted. Consider using a template ref to directly access the container element instead of querying the document.
| const renderMermaidDiagrams = async () => { | ||
| // Find all mermaid code blocks in the rendered content | ||
| const container = document.querySelector('.preview-content'); | ||
| if (!container) return; | ||
| const mermaidBlocks = container.querySelectorAll('pre > code.language-mermaid'); | ||
| if (mermaidBlocks.length === 0) return; | ||
| // Lazy load mermaid | ||
| const mermaid = await loadMermaid(); | ||
| // Update mermaid theme based on current app theme | ||
| mermaid.initialize({ | ||
| startOnLoad: false, | ||
| theme: themeService.isDark ? 'dark' : 'default', | ||
| securityLevel: 'strict', | ||
| fontFamily: 'Fira Code, monospace', | ||
| }); | ||
| for (const block of mermaidBlocks) { | ||
| const code = block.textContent || ''; | ||
| const preElement = block.parentElement; | ||
| if (!preElement) continue; | ||
| try { | ||
| // Generate unique ID for this diagram | ||
| const id = `mermaid-diagram-${mermaidIdCounter++}`; | ||
| // Render the diagram | ||
| const { svg } = await mermaid.render(id, code); | ||
| // Create a wrapper div with the rendered SVG | ||
| const wrapper = document.createElement('div'); | ||
| wrapper.className = 'mermaid-diagram'; | ||
| wrapper.innerHTML = svg; | ||
| // Replace the pre element with the rendered diagram | ||
| preElement.replaceWith(wrapper); | ||
| } catch (error) { | ||
| // Show error message in place of the diagram | ||
| const errorDiv = document.createElement('div'); | ||
| errorDiv.className = 'mermaid-error'; | ||
| errorDiv.innerHTML = `<strong>Mermaid Error:</strong> ${error instanceof Error ? error.message : 'Failed to render diagram'}`; | ||
| preElement.replaceWith(errorDiv); | ||
| } | ||
| } | ||
| }; |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The renderMermaidDiagrams function replaces DOM elements directly using replaceWith, but this mutates the rendered HTML content. When the preview is updated again (e.g., on theme change), the original code blocks are gone and replaced with SVG elements, so they cannot be re-rendered with the new theme. This will cause diagrams to not update when the theme changes. Consider either: 1) Preserving the original mermaid code in a data attribute before replacing the element, or 2) Re-rendering the entire markdown when the theme changes to restore the original code blocks.
Summary
Usage
Use standard mermaid code blocks in your notes:
Supports all mermaid diagram types: flowcharts, sequence diagrams, class diagrams, state diagrams, ERDs, Gantt charts, pie charts, and more.
Test plan