added print preview option

This commit is contained in:
Gamosoft 2026-04-06 12:58:38 +02:00
parent 96b5fd8cfc
commit 80797f9cfe
16 changed files with 139 additions and 24 deletions

View File

@ -300,7 +300,8 @@ def generate_export_html(
title: str,
content: str,
theme_css: str,
is_dark: bool = False
is_dark: bool = False,
show_print_button: bool = False
) -> str:
"""
Generate a standalone HTML document for a note.
@ -311,6 +312,7 @@ def generate_export_html(
content: Raw markdown content (images should already be base64 embedded)
theme_css: CSS content for theming
is_dark: Whether using a dark theme (for Mermaid/Highlight.js)
show_print_button: Whether to show a print button (for preview mode)
Returns:
Complete HTML document as string
@ -327,6 +329,24 @@ def generate_export_html(
highlight_theme = 'github-dark' if is_dark else 'github'
mermaid_theme = 'dark' if is_dark else 'default'
# Print toolbar HTML (only shown in preview mode)
print_toolbar_html = '''
<div class="print-toolbar">
<button onclick="window.print()" title="Print">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"></path>
</svg>
Print
</button>
<button onclick="window.close()" title="Close">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
Close
</button>
</div>
''' if show_print_button else ''
html = f'''<!DOCTYPE html>
<html lang="en">
<head>
@ -667,10 +687,54 @@ def generate_export_html(
padding: 0.5in;
max-width: none;
}}
.print-toolbar {{
display: none !important;
}}
}}
/* Print toolbar (only shown in preview mode) */
.print-toolbar {{
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
display: flex;
gap: 0.5rem;
background: var(--bg-secondary, #f8f9fa);
padding: 0.5rem;
border-radius: 0.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
border: 1px solid var(--border-primary, #dee2e6);
}}
.print-toolbar button {{
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-primary, #dee2e6);
border-radius: 0.375rem;
background: var(--bg-primary, #ffffff);
color: var(--text-primary, #333333);
cursor: pointer;
font-size: 0.875rem;
font-family: inherit;
transition: background-color 0.15s, border-color 0.15s;
}}
.print-toolbar button:hover {{
background: var(--bg-tertiary, #e9ecef);
border-color: var(--accent-primary, #0366d6);
}}
.print-toolbar button svg {{
width: 1rem;
height: 1rem;
}}
</style>
</head>
<body>
{print_toolbar_html}
<div class="markdown-preview" id="content"></div>
<script>

View File

@ -1230,7 +1230,7 @@ async def remove_note(request: Request, note_path: str):
@api_router.get("/export/{note_path:path}", tags=["Export"])
@limiter.limit("30/minute")
async def export_note_to_html(request: Request, note_path: str, theme: Optional[str] = None):
async def export_note_to_html(request: Request, note_path: str, theme: Optional[str] = None, download: bool = True):
"""
Export a note as a standalone HTML file.
@ -1239,9 +1239,10 @@ async def export_note_to_html(request: Request, note_path: str, theme: Optional[
Query Parameters:
theme: Optional theme name (defaults to current theme or 'light')
download: If true (default), returns as file download. If false, displays in browser with print button.
Returns:
HTML file download
HTML file (download or inline based on download parameter)
"""
try:
notes_dir = Path(config['storage']['notes_dir'])
@ -1288,23 +1289,28 @@ async def export_note_to_html(request: Request, note_path: str, theme: Optional[
# Get note title
title = Path(note_path).stem
# Generate HTML
# Generate HTML (show print button only when not downloading)
html_content = generate_export_html(
title=title,
content=content_with_links,
theme_css=theme_css,
is_dark=is_dark
is_dark=is_dark,
show_print_button=not download
)
# Return as downloadable file
filename = f"{title}.html"
return Response(
content=html_content,
media_type="text/html",
headers={
"Content-Disposition": f'attachment; filename="{filename}"'
}
)
# Return as downloadable file or inline (for print preview)
if download:
filename = f"{title}.html"
return Response(
content=html_content,
media_type="text/html",
headers={
"Content-Disposition": f'attachment; filename="{filename}"'
}
)
else:
# Return inline for browser display (print preview)
return HTMLResponse(content=html_content)
except HTTPException:
raise
except Exception as e:

View File

@ -450,7 +450,7 @@ Returns the relationship graph between notes with link detection.
### Export Note as HTML
```http
GET /api/export/{note_path}?theme={theme_name}
GET /api/export/{note_path}?theme={theme_name}&download={true|false}
```
Exports a note as a standalone HTML file with all dependencies embedded for offline viewing.
@ -460,9 +460,11 @@ Exports a note as a standalone HTML file with all dependencies embedded for offl
|-----------|------|-------------|
| `note_path` | path | Path to the note (e.g., `folder/note.md`) |
| `theme` | query (optional) | Theme name for styling (defaults to `light`) |
| `download` | query (optional) | If `true` (default), returns as file download. If `false`, displays in browser with Print/Close buttons for print preview |
**Response:**
Returns an HTML file download with `Content-Disposition: attachment` header.
- `download=true`: Returns an HTML file with `Content-Disposition: attachment` header
- `download=false`: Returns inline HTML for browser display (print preview mode)
**Features:**
- Fully self-contained HTML with embedded CSS
@ -473,16 +475,20 @@ Returns an HTML file download with `Content-Disposition: attachment` header.
- Wikilinks converted to decorative spans
- YAML frontmatter stripped
- Responsive design with print support
- Print toolbar with Print/Close buttons (preview mode only)
**Rate Limit:** 30 requests/minute
**Example:**
```bash
# Export with default theme
# Export with default theme (downloads file)
curl -O http://localhost:8000/api/export/notes/Welcome.md
# Export with dark theme
# Export with dark theme (downloads file)
curl -O "http://localhost:8000/api/export/docs/API.md?theme=dracula"
# Print preview (open in browser)
# http://localhost:8000/api/export/docs/API.md?theme=light&download=false
```
---

View File

@ -5114,6 +5114,22 @@ function noteApp() {
}
},
// Open print preview in new window
printPreview() {
if (!this.currentNote || !this.noteContent) {
alert(this.t('notes.no_content'));
return;
}
// Build API URL with current theme and download=false for inline display
const currentTheme = this.currentTheme || 'light';
const encodedPath = this.currentNote.split('/').map(s => encodeURIComponent(s)).join('/');
const url = `/api/export/${encodedPath}?theme=${encodeURIComponent(currentTheme)}&download=false`;
// Open in new window/tab
window.open(url, '_blank');
},
// Copy current note link to clipboard
async copyNoteLink() {
if (!this.currentNote) return;

View File

@ -2542,6 +2542,18 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
</button>
<button
@click="printPreview()"
class="p-2 transition"
style="color: var(--text-secondary);"
:title="t('toolbar.print_preview')"
@mouseenter="$el.style.backgroundColor = 'var(--bg-secondary)'"
@mouseleave="$el.style.backgroundColor = 'transparent'"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"></path>
</svg>
</button>
<div style="width: 1px; height: 20px; background-color: var(--border-primary);"></div>
<button
@click="copyNoteLink()"

View File

@ -105,6 +105,7 @@
"delete_note": "Notiz löschen",
"delete_image": "Bild löschen",
"export_html": "Als HTML exportieren",
"print_preview": "Druckvorschau",
"copy_link": "Link in Zwischenablage kopieren",
"add_favorite": "Zu Favoriten hinzufügen",
"remove_favorite": "Aus Favoriten entfernen"

View File

@ -104,6 +104,7 @@
"delete_note": "Delete note",
"delete_image": "Delete image",
"export_html": "Export as HTML",
"print_preview": "Print preview",
"copy_link": "Copy link to clipboard",
"add_favorite": "Add to favourites",
"remove_favorite": "Remove from favourites"

View File

@ -105,6 +105,7 @@
"delete_note": "Delete note",
"delete_image": "Delete image",
"export_html": "Export as HTML",
"print_preview": "Print preview",
"copy_link": "Copy link to clipboard",
"add_favorite": "Add to favorites",
"remove_favorite": "Remove from favorites"

View File

@ -105,6 +105,7 @@
"delete_note": "Eliminar nota",
"delete_image": "Eliminar imagen",
"export_html": "Exportar como HTML",
"print_preview": "Vista previa de impresión",
"copy_link": "Copiar enlace al portapapeles",
"add_favorite": "Añadir a favoritos",
"remove_favorite": "Quitar de favoritos"

View File

@ -105,6 +105,7 @@
"delete_note": "Supprimer la note",
"delete_image": "Supprimer l'image",
"export_html": "Exporter en HTML",
"print_preview": "Aperçu avant impression",
"copy_link": "Copier le lien dans le presse-papiers",
"add_favorite": "Ajouter aux favoris",
"remove_favorite": "Retirer des favoris"

View File

@ -105,6 +105,7 @@
"delete_note": "Jegyzet törlése",
"delete_image": "Kép törlése",
"export_html": "Exportálás HTML-ként",
"print_preview": "Nyomtatási előnézet",
"copy_link": "Link másolása",
"add_favorite": "Hozzáadás a kedvencekhez",
"remove_favorite": "Eltávolítás a kedvencek közül"

View File

@ -104,6 +104,7 @@
"delete_note": "Elimina nota",
"delete_image": "Elimina immagine",
"export_html": "Esporta come HTML",
"print_preview": "Anteprima di stampa",
"copy_link": "Copia link negli appunti",
"add_favorite": "Aggiungi ai preferiti",
"remove_favorite": "Rimuovi dai preferiti"

View File

@ -104,6 +104,7 @@
"delete_note": "ノートを削除",
"delete_image": "画像を削除",
"export_html": "HTMLとしてエクスポート",
"print_preview": "印刷プレビュー",
"copy_link": "リンクをクリップボードにコピー",
"add_favorite": "お気に入りに追加",
"remove_favorite": "お気に入りから削除"

View File

@ -104,6 +104,7 @@
"delete_note": "Удалить заметку",
"delete_image": "Удалить изображение",
"export_html": "Экспорт в HTML",
"print_preview": "Предварительный просмотр печати",
"copy_link": "Копировать ссылку",
"add_favorite": "Добавить в избранное",
"remove_favorite": "Удалить из избранного"

View File

@ -104,6 +104,7 @@
"delete_note": "Izbriši zapis",
"delete_image": "Izbriši sliko",
"export_html": "Izvozi kot HTML",
"print_preview": "Predogled tiskanja",
"copy_link": "Kopiraj povezavo v odložišče",
"add_favorite": "Dodaj med priljubljene",
"remove_favorite": "Odstrani iz priljubljenih"

View File

@ -104,6 +104,7 @@
"delete_note": "删除笔记",
"delete_image": "删除图片",
"export_html": "导出为 HTML",
"print_preview": "打印预览",
"copy_link": "复制链接到剪贴板",
"add_favorite": "添加到收藏夹",
"remove_favorite": "从收藏夹移除"