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, title: str,
content: str, content: str,
theme_css: str, theme_css: str,
is_dark: bool = False is_dark: bool = False,
show_print_button: bool = False
) -> str: ) -> str:
""" """
Generate a standalone HTML document for a note. 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) content: Raw markdown content (images should already be base64 embedded)
theme_css: CSS content for theming theme_css: CSS content for theming
is_dark: Whether using a dark theme (for Mermaid/Highlight.js) is_dark: Whether using a dark theme (for Mermaid/Highlight.js)
show_print_button: Whether to show a print button (for preview mode)
Returns: Returns:
Complete HTML document as string Complete HTML document as string
@ -327,6 +329,24 @@ def generate_export_html(
highlight_theme = 'github-dark' if is_dark else 'github' highlight_theme = 'github-dark' if is_dark else 'github'
mermaid_theme = 'dark' if is_dark else 'default' 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 = f'''<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -667,10 +687,54 @@ def generate_export_html(
padding: 0.5in; padding: 0.5in;
max-width: none; 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> </style>
</head> </head>
<body> <body>
{print_toolbar_html}
<div class="markdown-preview" id="content"></div> <div class="markdown-preview" id="content"></div>
<script> <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"]) @api_router.get("/export/{note_path:path}", tags=["Export"])
@limiter.limit("30/minute") @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. 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: Query Parameters:
theme: Optional theme name (defaults to current theme or 'light') 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: Returns:
HTML file download HTML file (download or inline based on download parameter)
""" """
try: try:
notes_dir = Path(config['storage']['notes_dir']) notes_dir = Path(config['storage']['notes_dir'])
@ -1288,15 +1289,17 @@ async def export_note_to_html(request: Request, note_path: str, theme: Optional[
# Get note title # Get note title
title = Path(note_path).stem title = Path(note_path).stem
# Generate HTML # Generate HTML (show print button only when not downloading)
html_content = generate_export_html( html_content = generate_export_html(
title=title, title=title,
content=content_with_links, content=content_with_links,
theme_css=theme_css, theme_css=theme_css,
is_dark=is_dark is_dark=is_dark,
show_print_button=not download
) )
# Return as downloadable file # Return as downloadable file or inline (for print preview)
if download:
filename = f"{title}.html" filename = f"{title}.html"
return Response( return Response(
content=html_content, content=html_content,
@ -1305,6 +1308,9 @@ async def export_note_to_html(request: Request, note_path: str, theme: Optional[
"Content-Disposition": f'attachment; filename="{filename}"' "Content-Disposition": f'attachment; filename="{filename}"'
} }
) )
else:
# Return inline for browser display (print preview)
return HTMLResponse(content=html_content)
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:

View File

@ -450,7 +450,7 @@ Returns the relationship graph between notes with link detection.
### Export Note as HTML ### Export Note as HTML
```http ```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. 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`) | | `note_path` | path | Path to the note (e.g., `folder/note.md`) |
| `theme` | query (optional) | Theme name for styling (defaults to `light`) | | `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:** **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:** **Features:**
- Fully self-contained HTML with embedded CSS - 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 - Wikilinks converted to decorative spans
- YAML frontmatter stripped - YAML frontmatter stripped
- Responsive design with print support - Responsive design with print support
- Print toolbar with Print/Close buttons (preview mode only)
**Rate Limit:** 30 requests/minute **Rate Limit:** 30 requests/minute
**Example:** **Example:**
```bash ```bash
# Export with default theme # Export with default theme (downloads file)
curl -O http://localhost:8000/api/export/notes/Welcome.md 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" 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 // Copy current note link to clipboard
async copyNoteLink() { async copyNoteLink() {
if (!this.currentNote) return; 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> <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> </svg>
</button> </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> <div style="width: 1px; height: 20px; background-color: var(--border-primary);"></div>
<button <button
@click="copyNoteLink()" @click="copyNoteLink()"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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