diff --git a/backend/themes.py b/backend/themes.py index cdcf150..982fcd0 100644 --- a/backend/themes.py +++ b/backend/themes.py @@ -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 }) diff --git a/documentation/FEATURES.md b/documentation/FEATURES.md index 392b25b..d0a6022 100644 --- a/documentation/FEATURES.md +++ b/documentation/FEATURES.md @@ -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 @@ -106,6 +107,31 @@ $$ 📄 **See the [MATHJAX](MATHJAX.md) note for more examples and syntax reference.** +## 📊 Mermaid Diagrams + +### Visual Diagrams +- **Flowcharts** - Process flows and decision trees +- **Sequence diagrams** - System interactions over time +- **Class diagrams** - UML class relationships +- **State diagrams** - State machines and transitions +- **Gantt charts** - Project timelines +- **Pie charts** - Data visualization +- **Git graphs** - Branch and commit history +- **Theme support** - Adapts to your theme + +### Example +````markdown +```mermaid +graph TD + A[Start] --> B{Is it working?} + B -->|Yes| C[Great!] + B -->|No| D[Debug] + D --> B +``` +```` + +📄 **See the [MERMAID](MERMAID.md) note for diagram examples and syntax reference.** + ## ⚡ Keyboard Shortcuts | Windows/Linux | Mac | Action | diff --git a/documentation/MERMAID.md b/documentation/MERMAID.md new file mode 100644 index 0000000..158d134 --- /dev/null +++ b/documentation/MERMAID.md @@ -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! 📊 + diff --git a/documentation/THEMES.md b/documentation/THEMES.md index e7a7c3c..9fc6dd8 100644 --- a/documentation/THEMES.md +++ b/documentation/THEMES.md @@ -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: diff --git a/frontend/app.js b/frontend/app.js index c767e6e..0e6bb7a 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -59,6 +59,7 @@ function noteApp() { expandedFolders: new Set(), draggedNote: null, draggedFolder: null, + dragOverFolder: null, // Track which folder is being hovered during drag // Scroll sync state isScrolling: false, @@ -91,6 +92,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 +298,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); } @@ -412,15 +442,25 @@ function noteApp() {
@@ -496,7 +536,7 @@ function noteApp() { @dragstart="onNoteDragStart('${note.path.replace(/'/g, "\\'")}', $event)" @dragend="onNoteDragEnd()" @click="loadNote('${note.path.replace(/'/g, "\\'")}')" - class="note-item px-3 py-2 mb-1 text-sm rounded relative" + class="note-item px-3 py-2 mb-1 text-sm rounded relative border-2 border-transparent" style="${isCurrentNote ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'} cursor: pointer;" @mouseover="if('${note.path}' !== currentNote) $el.style.backgroundColor='var(--bg-hover)'" @mouseout="if('${note.path}' !== currentNote) $el.style.backgroundColor='transparent'" @@ -636,12 +676,21 @@ function noteApp() { // Move mode: drag to move note this.draggedNote = notePath; event.dataTransfer.effectAllowed = 'move'; + // Make drag image semi-transparent + if (event.target) { + event.target.style.opacity = '0.5'; + } } }, onNoteDragEnd() { this.draggedNote = null; this.draggedNoteForLink = null; + this.dragOverFolder = null; + // Reset opacity of all note items + document.querySelectorAll('.note-item').forEach(el => { + el.style.opacity = '1'; + }); }, // Handle dragover on editor to show cursor position @@ -747,12 +796,21 @@ function noteApp() { }, // Folder drag handlers - onFolderDragStart(folderPath) { + onFolderDragStart(folderPath, event) { this.draggedFolder = folderPath; + // Make drag image semi-transparent + if (event && event.target) { + event.target.style.opacity = '0.5'; + } }, onFolderDragEnd() { this.draggedFolder = null; + this.dragOverFolder = null; + // Reset opacity of all folder items + document.querySelectorAll('.folder-item').forEach(el => { + el.style.opacity = '1'; + }); }, async onFolderDrop(targetFolderPath) { @@ -1635,6 +1693,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 '

Nothing to preview yet...

'; @@ -1690,12 +1844,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); @@ -2228,19 +2387,32 @@ function noteApp() { for (const sheet of styleSheets) { try { + // Skip external stylesheets (CDN resources) to avoid CORS errors + // We link them directly in the exported HTML anyway + if (sheet.href && (sheet.href.startsWith('http://') || sheet.href.startsWith('https://'))) { + const currentOrigin = window.location.origin; + const sheetURL = new URL(sheet.href); + if (sheetURL.origin !== currentOrigin) { + // Skip cross-origin stylesheets (they're linked directly in export) + continue; + } + } + 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'; } } } catch (e) { - // Skip stylesheets that can't be accessed (CORS) - console.warn('Could not access stylesheet:', sheet.href, e); + // Gracefully skip stylesheets that can't be accessed + // (This should rarely happen now that we skip external stylesheets) + console.debug('Skipping stylesheet:', sheet.href); } } @@ -2271,8 +2443,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 +2454,39 @@ function noteApp() { + + +