Skip to content

Conversation

@djedi
Copy link
Owner

@djedi djedi commented Dec 12, 2025

Summary

  • Add support for rendering Mermaid diagrams in the markdown preview
  • Lazy load mermaid.js (~2MB) only when mermaid code blocks are detected to minimize bundle impact
  • Theme-aware rendering that syncs with app's light/dark theme
  • Display helpful error messages for invalid diagram syntax
  • Fix preview to display full-width in replace mode (preview-only)

Usage

Use standard mermaid code blocks in your notes:

```mermaid
graph TD
    A[Start] --> B{Is it working?}
    B -->|Yes| C[Great!]
    B -->|No| D[Debug]
    D --> B
```

Supports all mermaid diagram types: flowcharts, sequence diagrams, class diagrams, state diagrams, ERDs, Gantt charts, pie charts, and more.

Test plan

  • Create a note with a mermaid code block
  • Open preview (Cmd+K V for side-by-side or Shift+Cmd+V for full preview)
  • Verify diagram renders correctly
  • Toggle between light/dark themes and verify diagram updates
  • Test invalid mermaid syntax shows error message
  • Verify preview displays full-width in replace mode

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.
Copilot AI review requested due to automatic review settings December 12, 2025 16:41
@djedi djedi merged commit a6fa854 into master Dec 12, 2025
5 checks passed
@djedi djedi deleted the feature/mermaid-support branch December 12, 2025 16:41
Copy link
Contributor

Copilot AI left a 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.

Comment on lines +78 to +84
// Update mermaid theme based on current app theme
mermaid.initialize({
startOnLoad: false,
theme: themeService.isDark ? 'dark' : 'default',
securityLevel: 'strict',
fontFamily: 'Fira Code, monospace',
});
Copy link

Copilot AI Dec 12, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +651 to +661
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;
}
Copy link

Copilot AI Dec 12, 2025

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).

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +97
const id = `mermaid-diagram-${mermaidIdCounter++}`;
// Render the diagram
const { svg } = await mermaid.render(id, code);
Copy link

Copilot AI Dec 12, 2025

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.

Copilot uses AI. Check for mistakes.
try {
// Generate unique ID for this diagram
const id = `mermaid-diagram-${mermaidIdCounter++}`;
Copy link

Copilot AI Dec 12, 2025

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.

Suggested change
const id = `mermaid-diagram-${mermaidIdCounter++}`;
const id = `mermaid-diagram-${crypto.randomUUID()}`;

Copilot uses AI. Check for mistakes.
// 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'}`;
Copy link

Copilot AI Dec 12, 2025

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.

Suggested change
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')));

Copilot uses AI. Check for mistakes.
const renderMermaidDiagrams = async () => {
// Find all mermaid code blocks in the rendered content
const container = document.querySelector('.preview-content');
Copy link

Copilot AI Dec 12, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +114
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);
}
}
};
Copy link

Copilot AI Dec 12, 2025

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants