added html export function

This commit is contained in:
Gamosoft 2025-11-12 11:16:12 +01:00
parent 693504e428
commit 3e50a2dba8
4 changed files with 177 additions and 0 deletions

View File

@ -39,6 +39,7 @@ NoteDiscovery is a **lightweight, self-hosted note-taking application** that put
- 📱 **Responsive** - Works on desktop, tablet, and mobile - 📱 **Responsive** - Works on desktop, tablet, and mobile
- 📂 **Simple Storage** - Plain markdown files in folders - 📂 **Simple Storage** - Plain markdown files in folders
- 🧮 **Math Support** - LaTeX/MathJax for beautiful equations - 🧮 **Math Support** - LaTeX/MathJax for beautiful equations
- 📄 **HTML Export** - Share notes as standalone HTML files
## 🚀 Quick Start ## 🚀 Quick Start

View File

@ -10,6 +10,7 @@
- **Syntax highlighting** for code blocks (50+ languages) - **Syntax highlighting** for code blocks (50+ languages)
- **Copy code blocks** - One-click copy button on hover - **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
- **HTML Export** - Export notes as standalone HTML files
### Organization ### Organization
- **Folder hierarchy** - Organize notes in nested folders - **Folder hierarchy** - Organize notes in nested folders

View File

@ -2125,6 +2125,170 @@ function noteApp() {
setTimeout(() => { setTimeout(() => {
this.isScrolling = false; this.isScrolling = false;
}, CONFIG.SCROLL_SYNC_DELAY); }, 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 = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${noteName}</title>
<!-- Highlight.js for code syntax highlighting -->
${highlightTheme ? `<link rel="stylesheet" href="${highlightTheme}">` : '<!-- No highlight.js theme detected -->'}
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<!-- MathJax for LaTeX math rendering (same config as preview) -->
<script>
MathJax = {
tex: {
inlineMath: [['$', '$']],
displayMath: [['$$', '$$']],
processEscapes: true,
processEnvironments: true
},
options: {
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
},
startup: {
pageReady: () => {
return MathJax.startup.defaultPageReady().then(() => {
// Highlight code blocks after MathJax is done
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
});
}
}
};
</script>
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<style>
/* Theme CSS */
${themeCss}
/* Base styles */
* {
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 2rem;
max-width: 900px;
margin-left: auto;
margin-right: auto;
background-color: var(--bg-primary);
color: var(--text-primary);
}
/* Markdown preview styles extracted from current page */
${markdownStyles}
@media (max-width: 768px) {
body {
padding: 1rem;
}
}
@media print {
body {
padding: 0.5in;
max-width: none;
}
}
</style>
</head>
<body>
<div class="markdown-preview">
${renderedHTML}
</div>
</body>
</html>`;
// Create blob and download
const blob = new Blob([htmlDocument], { type: 'text/html;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${noteName}.html`;
document.body.appendChild(a);
a.click();
// Cleanup
URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('HTML export failed:', error);
alert(`Failed to export HTML: ${error.message}`);
}
} }
} }
} }

View File

@ -920,6 +920,17 @@
Preview Preview
</button> </button>
</div> </div>
<!-- Export HTML Button -->
<button
x-show="currentNote"
@click="exportToHTML()"
class="px-3 py-1.5 text-sm rounded transition hover:opacity-80"
style="background-color: var(--accent-secondary); color: var(--text-primary);"
title="Export as HTML"
>
📄 Export HTML
</button>
</div> </div>
</div> </div>