diff --git a/README.md b/README.md index 6938b7b..eba1048 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ NoteDiscovery is a **lightweight, self-hosted note-taking application** that put - 📱 **Responsive** - Works on desktop, tablet, and mobile - 📂 **Simple Storage** - Plain markdown files in folders - 🧮 **Math Support** - LaTeX/MathJax for beautiful equations +- 📄 **HTML Export** - Share notes as standalone HTML files ## 🚀 Quick Start diff --git a/data/notes/FEATURES.md b/data/notes/FEATURES.md index c1292e4..93993f7 100644 --- a/data/notes/FEATURES.md +++ b/data/notes/FEATURES.md @@ -10,6 +10,7 @@ - **Syntax highlighting** for code blocks (50+ languages) - **Copy code blocks** - One-click copy button on hover - **LaTeX/Math rendering** - Beautiful mathematical equations with MathJax +- **HTML Export** - Export notes as standalone HTML files ### Organization - **Folder hierarchy** - Organize notes in nested folders diff --git a/frontend/app.js b/frontend/app.js index dbc95d2..086282c 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2125,6 +2125,170 @@ function noteApp() { setTimeout(() => { this.isScrolling = false; }, CONFIG.SCROLL_SYNC_DELAY); + }, + + // Export current note as HTML + async exportToHTML() { + if (!this.currentNote || !this.noteContent) { + alert('No note content to export'); + return; + } + + try { + // Get the note name without extension + const noteName = this.currentNoteName || 'note'; + + // Get current rendered HTML (this already has markdown converted and will have LaTeX delimiters) + const renderedHTML = this.renderedMarkdown; + + // Get current theme CSS + const currentTheme = this.currentTheme || 'light'; + const themeResponse = await fetch(`/api/themes/${currentTheme}`); + const themeText = await themeResponse.text(); + + // Check if response is JSON or plain CSS + let themeCss; + try { + const themeJson = JSON.parse(themeText); + // If it's JSON, extract the css field + themeCss = themeJson.css || themeText; + } catch (e) { + // If it's not JSON, use it as-is + themeCss = themeText; + } + + // Theme CSS uses :root[data-theme="..."] selector, but we need plain :root for export + // Strip the data-theme attribute selector so variables apply globally + themeCss = themeCss.replace(/:root\[data-theme="[^"]+"\]/g, ':root'); + + // Get highlight.js theme URL from current page + const highlightLinkElement = document.getElementById('highlight-theme'); + if (!highlightLinkElement || !highlightLinkElement.href) { + console.warn('Could not detect highlight.js theme, export may not match preview exactly'); + } + const highlightTheme = highlightLinkElement ? highlightLinkElement.href : ''; + + // Extract all markdown preview styles from current page + let markdownStyles = ''; + const styleSheets = Array.from(document.styleSheets); + + for (const sheet of styleSheets) { + try { + const rules = Array.from(sheet.cssRules || []); + for (const rule of rules) { + const cssText = rule.cssText; + // Include rules that target markdown-preview or mjx-container + if (cssText.includes('.markdown-preview') || + cssText.includes('mjx-container') || + cssText.includes('.MathJax')) { + markdownStyles += cssText + '\n'; + } + } + } catch (e) { + // Skip stylesheets that can't be accessed (CORS) + console.warn('Could not access stylesheet:', sheet.href, e); + } + } + + // Create standalone HTML document with MathJax + const htmlDocument = ` + +
+ + +