From 80797f9cfe966f64bc8de2292819f28148d0226b Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Mon, 6 Apr 2026 12:58:38 +0200 Subject: [PATCH] added print preview option --- backend/export.py | 72 +++++++++++++++++++++++++++++++++++++++++--- backend/main.py | 38 +++++++++++++---------- documentation/API.md | 14 ++++++--- frontend/app.js | 16 ++++++++++ frontend/index.html | 12 ++++++++ locales/de-DE.json | 1 + locales/en-GB.json | 1 + locales/en-US.json | 1 + locales/es-ES.json | 1 + locales/fr-FR.json | 1 + locales/hu-HU.json | 1 + locales/it-IT.json | 1 + locales/ja-JP.json | 1 + locales/ru-RU.json | 1 + locales/sl-SI.json | 1 + locales/zh-CN.json | 1 + 16 files changed, 139 insertions(+), 24 deletions(-) diff --git a/backend/export.py b/backend/export.py index 06bdb47..0a0bd8f 100644 --- a/backend/export.py +++ b/backend/export.py @@ -300,18 +300,20 @@ 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. Uses marked.js for client-side markdown rendering. - + Args: title: The note title (for and display) 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,12 +687,56 @@ 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> // Protect LaTeX delimiters \\(...\\) and \\[...\\] from marked.js escaping marked.use({{ diff --git a/backend/main.py b/backend/main.py index 511435c..d4cb453 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1230,18 +1230,19 @@ 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. - + The HTML includes all necessary CSS, MathJax, Mermaid, and syntax highlighting for offline viewing. Images are embedded as base64. - + 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: diff --git a/documentation/API.md b/documentation/API.md index 1560f5c..90fbef5 100644 --- a/documentation/API.md +++ b/documentation/API.md @@ -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 ``` --- diff --git a/frontend/app.js b/frontend/app.js index c1898a2..4acd1a0 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -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; diff --git a/frontend/index.html b/frontend/index.html index 09a9ab7..a9ebe8d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -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()" diff --git a/locales/de-DE.json b/locales/de-DE.json index 1ca4d15..103ef4c 100644 --- a/locales/de-DE.json +++ b/locales/de-DE.json @@ -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" diff --git a/locales/en-GB.json b/locales/en-GB.json index 05200d6..dbb98de 100644 --- a/locales/en-GB.json +++ b/locales/en-GB.json @@ -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" diff --git a/locales/en-US.json b/locales/en-US.json index 78f0d4f..329a237 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -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" diff --git a/locales/es-ES.json b/locales/es-ES.json index c56bab0..873c5c0 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -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" diff --git a/locales/fr-FR.json b/locales/fr-FR.json index 6076eee..ebd5832 100644 --- a/locales/fr-FR.json +++ b/locales/fr-FR.json @@ -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" diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 1b211f7..45d2be4 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -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" diff --git a/locales/it-IT.json b/locales/it-IT.json index 04171fb..962c299 100644 --- a/locales/it-IT.json +++ b/locales/it-IT.json @@ -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" diff --git a/locales/ja-JP.json b/locales/ja-JP.json index ef2751c..4e320aa 100644 --- a/locales/ja-JP.json +++ b/locales/ja-JP.json @@ -104,6 +104,7 @@ "delete_note": "ノートを削除", "delete_image": "画像を削除", "export_html": "HTMLとしてエクスポート", + "print_preview": "印刷プレビュー", "copy_link": "リンクをクリップボードにコピー", "add_favorite": "お気に入りに追加", "remove_favorite": "お気に入りから削除" diff --git a/locales/ru-RU.json b/locales/ru-RU.json index b62fe4f..82dd0c4 100644 --- a/locales/ru-RU.json +++ b/locales/ru-RU.json @@ -104,6 +104,7 @@ "delete_note": "Удалить заметку", "delete_image": "Удалить изображение", "export_html": "Экспорт в HTML", + "print_preview": "Предварительный просмотр печати", "copy_link": "Копировать ссылку", "add_favorite": "Добавить в избранное", "remove_favorite": "Удалить из избранного" diff --git a/locales/sl-SI.json b/locales/sl-SI.json index 7d2efcd..88f7ff2 100644 --- a/locales/sl-SI.json +++ b/locales/sl-SI.json @@ -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" diff --git a/locales/zh-CN.json b/locales/zh-CN.json index d964072..1ec6238 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -104,6 +104,7 @@ "delete_note": "删除笔记", "delete_image": "删除图片", "export_html": "导出为 HTML", + "print_preview": "打印预览", "copy_link": "复制链接到剪贴板", "add_favorite": "添加到收藏夹", "remove_favorite": "从收藏夹移除"