added mermaid diagram support!

This commit is contained in:
Gamosoft 2025-11-17 17:29:00 +01:00
parent 114a87c12b
commit f5aaf69b4e
14 changed files with 631 additions and 8 deletions

View File

@ -7,6 +7,32 @@ from typing import List, Dict
import re
def parse_theme_metadata(theme_path: Path) -> Dict[str, str]:
"""Parse theme metadata from CSS file comments"""
metadata = {
"type": "dark" # Default to dark for backward compatibility
}
try:
with open(theme_path, 'r', encoding='utf-8') as f:
# Read first few lines to find metadata
for i, line in enumerate(f):
if i > 10: # Only check first 10 lines
break
# Look for @theme-type metadata
if '@theme-type:' in line:
# Extract the value (light or dark)
match = re.search(r'@theme-type:\s*(light|dark)', line)
if match:
metadata["type"] = match.group(1)
break
except Exception as e:
print(f"Error parsing theme metadata from {theme_path}: {e}")
return metadata
def get_available_themes(themes_dir: str) -> List[Dict[str, str]]:
"""Get all available themes from the themes directory"""
themes_path = Path(themes_dir)
@ -30,9 +56,13 @@ def get_available_themes(themes_dir: str) -> List[Dict[str, str]]:
theme_name = theme_file.stem.replace("-", " ").replace("_", " ").title()
icon = theme_icons.get(theme_file.stem, "🎨")
# Parse theme metadata
metadata = parse_theme_metadata(theme_file)
themes.append({
"id": theme_file.stem,
"name": f"{icon} {theme_name}",
"type": metadata["type"], # Add theme type (light/dark)
"builtin": False
})

View File

@ -9,7 +9,8 @@
- **Undo/Redo** - Ctrl+Z / Ctrl+Y support
- **Syntax highlighting** for code blocks (50+ languages)
- **Copy code blocks** - One-click copy button on hover
- **LaTeX/Math rendering** - Beautiful mathematical equations with MathJax
- **LaTeX/Math rendering** - Beautiful mathematical equations with MathJax (see [MATHJAX.md](MATHJAX.md))
- **Mermaid diagrams** - Create flowcharts, sequence diagrams, and more (see [MERMAID.md](MERMAID.md))
- **HTML Export** - Export notes as standalone HTML files
### Organization

376
documentation/MERMAID.md Normal file
View File

@ -0,0 +1,376 @@
# Mermaid Diagrams
NoteDiscovery supports **Mermaid** diagrams directly in your markdown notes! Mermaid lets you create diagrams and visualizations using text-based definitions, making it easy to version control and collaborate.
## How to Use
Simply create a code block with the language set to `mermaid`:
````markdown
```mermaid
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug]
D --> B
```
````
## Basic Examples
### Flowchart
````markdown
```mermaid
graph LR
A[Square Rect] --> B((Circle))
A --> C(Round Rect)
B --> D{Rhombus}
C --> D
```
````
**Preview:**
```mermaid
graph LR
A[Square Rect] --> B((Circle))
A --> C(Round Rect)
B --> D{Rhombus}
C --> D
```
---
### Sequence Diagram
````markdown
```mermaid
sequenceDiagram
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
Alice-)John: See you later!
```
````
**Preview:**
```mermaid
sequenceDiagram
Alice->>John: Hello John, how are you?
John-->>Alice: Great!
Alice-)John: See you later!
```
---
### Class Diagram
````markdown
```mermaid
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal : +int age
Animal : +String gender
Animal: +isMammal()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
```
````
**Preview:**
```mermaid
classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal : +int age
Animal : +String gender
Animal: +isMammal()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
```
---
### State Diagram
````markdown
```mermaid
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
````
**Preview:**
```mermaid
stateDiagram-v2
[*] --> Still
Still --> [*]
Still --> Moving
Moving --> Still
Moving --> Crash
Crash --> [*]
```
---
### Gantt Chart
````markdown
```mermaid
gantt
title Project Timeline
dateFormat YYYY-MM-DD
section Planning
Research :a1, 2024-01-01, 30d
Design :after a1, 20d
section Development
Backend :2024-02-01, 40d
Frontend :2024-02-15, 35d
section Testing
Integration Tests :2024-03-20, 15d
```
````
**Preview:**
```mermaid
gantt
title Project Timeline
dateFormat YYYY-MM-DD
section Planning
Research :a1, 2024-01-01, 30d
Design :after a1, 20d
section Development
Backend :2024-02-01, 40d
Frontend :2024-02-15, 35d
section Testing
Integration Tests :2024-03-20, 15d
```
---
### Entity Relationship Diagram
````markdown
```mermaid
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
CUSTOMER {
string name
string email
string phone
}
ORDER {
int orderNumber
date orderDate
string status
}
```
````
**Preview:**
```mermaid
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE-ITEM : contains
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
CUSTOMER {
string name
string email
string phone
}
ORDER {
int orderNumber
date orderDate
string status
}
```
---
### Pie Chart
````markdown
```mermaid
pie title Pet Preferences
"Dogs" : 45
"Cats" : 30
"Birds" : 15
"Fish" : 10
```
````
**Preview:**
```mermaid
pie title Pet Preferences
"Dogs" : 45
"Cats" : 30
"Birds" : 15
"Fish" : 10
```
---
### Git Graph
````markdown
```mermaid
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
```
````
**Preview:**
```mermaid
gitGraph
commit
commit
branch develop
checkout develop
commit
commit
checkout main
merge develop
commit
```
---
### User Journey
````markdown
```mermaid
journey
title My Working Day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
```
````
**Preview:**
```mermaid
journey
title My Working Day
section Go to work
Make tea: 5: Me
Go upstairs: 3: Me
Do work: 1: Me, Cat
section Go home
Go downstairs: 5: Me
Sit down: 5: Me
```
---
### Mindmap
````markdown
```mermaid
mindmap
root((NoteDiscovery))
Features
Markdown
Themes
Search
Folders
Integrations
MathJax
Mermaid
Syntax Highlighting
Benefits
Fast
Simple
Offline-first
```
````
**Preview:**
```mermaid
mindmap
root((NoteDiscovery))
Features
Markdown
Themes
Search
Folders
Integrations
MathJax
Mermaid
Syntax Highlighting
Benefits
Fast
Simple
Offline-first
```
---
## Theme Support
Mermaid diagrams automatically adapt to your current NoteDiscovery theme:
- **Light themes** use the default Mermaid color scheme
- **Dark themes** use dark-optimized colors with proper contrast
- Theme changes automatically re-render all diagrams
## Tips
1. **Keep it simple**: Start with basic diagrams and add complexity as needed
2. **Use comments**: Add `%%` for comments in your Mermaid code
3. **Test syntax**: If a diagram doesn't render, check the Mermaid [documentation](https://mermaid.js.org/)
4. **Export**: Diagrams are included when you export notes to HTML
## More Information
For the complete Mermaid syntax and more diagram types, visit the official documentation:
- [Mermaid Documentation](https://mermaid.js.org/)
- [Live Editor](https://mermaid.live/) - Test your diagrams online
---
**Pro Tip**: Combine Mermaid diagrams with LaTeX math expressions and code blocks for comprehensive technical documentation! 📊

View File

@ -72,7 +72,32 @@ If your file is named `my-awesome-theme.css`, use `data-theme="my-awesome-theme"
}
```
#### 3. (Optional) Add a custom emoji icon
#### 3. Add theme type metadata (Recommended)
Add a comment at the **top of your CSS file** to indicate if your theme is light or dark:
```css
/* @theme-type: light */
/* OR */
/* @theme-type: dark */
```
**Example:**
```css
/* @theme-type: dark */
/* My Awesome Theme - A beautiful custom theme */
:root[data-theme="my-awesome-theme"] {
/* ... your CSS variables ... */
}
```
**Why is this needed?**
Some features (like Mermaid diagrams, Chart.js) need to know if the background is light or dark to adjust their rendering colors accordingly. This metadata is automatically parsed by the application.
**Default behavior:** If you don't add this metadata, your theme will default to `dark` for backward compatibility.
#### 4. (Optional) Add a custom emoji icon
Edit `backend/themes.py` and add your theme to the `theme_icons` dictionary:
@ -85,7 +110,7 @@ theme_icons = {
If you skip this step, your theme will use 🎨 as the default icon.
#### 4. Restart the application
#### 5. Restart the application
```bash
# If using Docker:

View File

@ -91,6 +91,9 @@ function noteApp() {
// Dropdown state
showNewDropdown: false,
// Mermaid state cache
lastMermaidTheme: null,
// DOM element cache (to avoid repeated querySelector calls)
_domCache: {
editor: null,
@ -294,6 +297,32 @@ function noteApp() {
highlightTheme.href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css';
}
}
// Re-render Mermaid diagrams with new theme if there's a current note
if (this.currentNote) {
// Small delay to allow theme CSS to load
setTimeout(() => {
// Clear existing Mermaid renders
const previewContent = document.querySelector('.markdown-preview');
if (previewContent) {
const mermaidContainers = previewContent.querySelectorAll('.mermaid-rendered');
mermaidContainers.forEach(container => {
// Replace with the original code block for re-rendering
const parent = container.parentElement;
if (parent && container.dataset.originalCode) {
const pre = document.createElement('pre');
const code = document.createElement('code');
code.className = 'language-mermaid';
code.textContent = container.dataset.originalCode;
pre.appendChild(code);
parent.replaceChild(pre, container);
}
});
}
// Re-render with new theme
this.renderMermaid();
}, 100);
}
} catch (error) {
console.error('Failed to load theme:', error);
}
@ -1635,6 +1664,102 @@ function noteApp() {
}
},
// Render Mermaid diagrams
async renderMermaid() {
if (typeof window.mermaid === 'undefined') {
console.warn('Mermaid not loaded yet');
return;
}
// Use requestAnimationFrame for better performance than setTimeout
requestAnimationFrame(async () => {
const previewContent = document.querySelector('.markdown-preview');
if (!previewContent) return;
// Get the appropriate theme based on current app theme
const themeType = this.getThemeType();
const mermaidTheme = themeType === 'light' ? 'default' : 'dark';
// Only reinitialize if theme changed (performance optimization)
if (this.lastMermaidTheme !== mermaidTheme) {
window.mermaid.initialize({
startOnLoad: false,
theme: mermaidTheme,
securityLevel: 'strict', // Use strict for better security
fontFamily: 'inherit'
});
this.lastMermaidTheme = mermaidTheme;
}
// Find all code blocks with language 'mermaid'
const mermaidBlocks = previewContent.querySelectorAll('pre code.language-mermaid');
// Early return if no diagrams to render
if (mermaidBlocks.length === 0) return;
for (let i = 0; i < mermaidBlocks.length; i++) {
const block = mermaidBlocks[i];
const pre = block.parentElement;
// Skip if already rendered (performance optimization)
if (pre.querySelector('.mermaid-rendered')) continue;
try {
const code = block.textContent;
const id = `mermaid-diagram-${Date.now()}-${i}`;
// Render the diagram
const { svg } = await window.mermaid.render(id, code);
// Create a container for the rendered diagram
const container = document.createElement('div');
container.className = 'mermaid-rendered';
container.style.cssText = 'background-color: transparent; padding: 20px; text-align: center; overflow-x: auto;';
container.innerHTML = svg;
// Store original code for theme re-rendering
container.dataset.originalCode = code;
// Replace the code block with the rendered diagram
pre.parentElement.replaceChild(container, pre);
} catch (error) {
console.error('Mermaid rendering error:', error);
// Add error indicator to the code block
const errorMsg = document.createElement('div');
errorMsg.style.cssText = 'color: var(--error); padding: 10px; border-left: 3px solid var(--error); margin-top: 10px;';
errorMsg.textContent = `⚠️ Mermaid diagram error: ${error.message}`;
pre.parentElement.insertBefore(errorMsg, pre.nextSibling);
}
}
});
},
// Get current theme type (light or dark)
// Returns: 'light' or 'dark'
// Used by features that need to adapt to theme brightness (e.g., Mermaid diagrams, Chart.js)
getThemeType() {
// Handle system theme
if (this.currentTheme === 'system') {
const isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
return isDark ? 'dark' : 'light';
}
// Try to get theme type from loaded theme metadata
const currentThemeData = this.availableThemes.find(t => t.id === this.currentTheme);
if (currentThemeData && currentThemeData.type) {
// Use metadata from theme file (light or dark)
return currentThemeData.type; // Already 'light' or 'dark'
}
// Backward compatibility: fallback to hardcoded map if metadata not available
const fallbackMap = {
'light': 'light',
'vs-blue': 'light'
};
return fallbackMap[this.currentTheme] || 'dark';
},
// Computed property for rendered markdown
get renderedMarkdown() {
if (!this.noteContent) return '<p style="color: var(--text-tertiary);">Nothing to preview yet...</p>';
@ -1690,12 +1815,17 @@ function noteApp() {
// Trigger MathJax rendering after DOM updates
this.typesetMath();
// Render Mermaid diagrams
this.renderMermaid();
// Apply syntax highlighting and add copy buttons to code blocks
setTimeout(() => {
// Use cached reference if available, otherwise query
const previewEl = this._domCache.previewContent || document.querySelector('.markdown-preview');
if (previewEl) {
previewEl.querySelectorAll('pre code').forEach((block) => {
// Exclude code blocks that are rendered by other tools (e.g., Mermaid diagrams)
// Note: MathJax uses $$...$$ delimiters (not code blocks) so no exclusion needed
previewEl.querySelectorAll('pre code:not(.language-mermaid)').forEach((block) => {
// Apply syntax highlighting
if (!block.classList.contains('hljs')) {
hljs.highlightElement(block);
@ -2231,10 +2361,11 @@ function noteApp() {
const rules = Array.from(sheet.cssRules || []);
for (const rule of rules) {
const cssText = rule.cssText;
// Include rules that target markdown-preview or mjx-container
// Include rules that target markdown-preview, mjx-container, or mermaid-rendered
if (cssText.includes('.markdown-preview') ||
cssText.includes('mjx-container') ||
cssText.includes('.MathJax')) {
cssText.includes('.MathJax') ||
cssText.includes('.mermaid-rendered')) {
markdownStyles += cssText + '\n';
}
}
@ -2271,8 +2402,8 @@ function noteApp() {
startup: {
pageReady: () => {
return MathJax.startup.defaultPageReady().then(() => {
// Highlight code blocks after MathJax is done
document.querySelectorAll('pre code').forEach((block) => {
// Highlight code blocks after MathJax is done (exclude diagram renderers)
document.querySelectorAll('pre code:not(.language-mermaid)').forEach((block) => {
hljs.highlightElement(block);
});
});
@ -2282,6 +2413,39 @@ function noteApp() {
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<!-- Mermaid.js for diagrams (if used in note) -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
const isDark = ${this.getThemeType() === 'dark'};
mermaid.initialize({
startOnLoad: false,
theme: isDark ? 'dark' : 'default',
securityLevel: 'strict',
fontFamily: 'inherit'
});
// Render any Mermaid code blocks
document.addEventListener('DOMContentLoaded', async () => {
const mermaidBlocks = document.querySelectorAll('pre code.language-mermaid');
for (let i = 0; i < mermaidBlocks.length; i++) {
const block = mermaidBlocks[i];
const pre = block.parentElement;
try {
const code = block.textContent;
const id = 'mermaid-diagram-' + i;
const { svg } = await mermaid.render(id, code);
const container = document.createElement('div');
container.className = 'mermaid-rendered';
container.style.cssText = 'background-color: transparent; padding: 20px; text-align: center; overflow-x: auto;';
container.innerHTML = svg;
pre.parentElement.replaceChild(container, pre);
} catch (error) {
console.error('Mermaid rendering error:', error);
}
}
});
</script>
<style>
/* Theme CSS */
${themeCss}

View File

@ -57,6 +57,19 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/css.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/http.min.js"></script>
<!-- Mermaid.js for diagram rendering -->
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
// Initialize with default dark theme (will be updated based on app theme)
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
securityLevel: 'loose',
fontFamily: 'inherit'
});
window.mermaid = mermaid;
</script>
<!-- Theme styles will be loaded dynamically -->
<link rel="stylesheet" id="theme-stylesheet" href="">
@ -321,6 +334,12 @@
margin: 0 0.2rem;
}
/* Mermaid diagram styling - minimal, let Mermaid handle its own rendering */
.markdown-preview .mermaid-rendered svg {
max-width: 100%;
height: auto;
}
/* Editor styling */
.editor-textarea {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;

View File

@ -1,5 +1,6 @@
/* Cobalt2 Theme */
/* Inspired by Wes Bos's Cobalt2 theme for VS Code */
/* @theme-type: dark */
:root[data-theme="cobalt2"] {
/* Background colors */

View File

@ -1,4 +1,5 @@
/* Dark Theme */
/* @theme-type: dark */
:root[data-theme="dark"] {
/* Background colors */
--bg-primary: #1f2937;

View File

@ -1,5 +1,6 @@
/* Dracula Theme - Inspired by Dracula Color Scheme */
/* https://draculatheme.com/ */
/* @theme-type: dark */
:root[data-theme="dracula"] {
/* Background colors */

View File

@ -1,4 +1,5 @@
/* Light Theme - Default */
/* @theme-type: light */
:root[data-theme="light"] {
/* Background colors */
--bg-primary: #ffffff;

View File

@ -1,5 +1,6 @@
/* Monokai Theme - Classic code editor theme */
/* Inspired by Sublime Text's Monokai */
/* @theme-type: dark */
:root[data-theme="monokai"] {
/* Background colors */

View File

@ -1,5 +1,6 @@
/* Nord Theme - Arctic, north-bluish color palette */
/* https://www.nordtheme.com/ */
/* @theme-type: dark */
:root[data-theme="nord"] {
/* Background colors */

View File

@ -1,5 +1,6 @@
/* Visual Studio Blue Theme */
/* Inspired by classic Visual Studio 2012-2015 Light Blue theme */
/* @theme-type: light */
:root[data-theme="vs-blue"] {
/* Background colors */

View File

@ -1,5 +1,6 @@
/* Vue High Contrast Theme */
/* Inspired by Vue Theme High Contrast - Dark with greenish tint */
/* @theme-type: dark */
:root[data-theme="vue-high-contrast"] {
/* Background colors - dark with green tint */