From 94e70c0318e941dcb3d6607dbdf7e9cba3c593fd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 15 Apr 2026 17:37:27 +0300 Subject: [PATCH] feat(print): integrate with print preview and merge with export to PDF --- .../src/translations/en/translation.json | 5 +- apps/client/src/widgets/NoteDetail.tsx | 86 +++++++++---------- .../src/widgets/dialogs/print_preview.tsx | 34 +++++++- .../client/src/widgets/ribbon/NoteActions.tsx | 1 - apps/server/src/services/window.ts | 53 ++++++++++++ 5 files changed, 128 insertions(+), 51 deletions(-) diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index c99729cc96..f18749449f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1808,7 +1808,7 @@ "note_detail": { "could_not_find_typewidget": "Could not find typeWidget for type '{{type}}'", "printing": "Printing in progress...", - "printing_pdf": "Exporting to PDF in progress...", + "printing_pdf": "Preparing print preview...", "print_report_title": "Print report", "print_report_error_title": "Failed to print", "print_report_stack_trace": "Stack trace", @@ -2325,6 +2325,9 @@ "title": "Print preview", "close": "Close", "save": "Save as PDF", + "print": "Print", + "export_pdf": "Export as PDF", + "system_print": "Print using system dialog", "orientation": "Orientation", "portrait": "Portrait", "landscape": "Landscape", diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 39da7717b4..e734bbf9c7 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -182,57 +182,51 @@ export default function NoteDetail() { useTriliumEvent("printActiveNote", () => { if (!noteContext?.isActive() || !note) return; - showToast("printing"); - if (isElectron()) { + // On Electron, open the print preview dialog. Actual print/PDF actions + // are triggered from the dialog's footer buttons. + showToast("exporting_pdf"); const { ipcRenderer } = dynamicRequire("electron"); - ipcRenderer.send("print-note", { - notePath: noteContext.notePath + ipcRenderer.send("export-as-pdf-preview", { + title: note.title, + notePath: noteContext.notePath, + pageSize: note.getAttributeValue("label", "printPageSize") ?? "Letter", + landscape: note.hasAttribute("label", "printLandscape"), + scale: parseFloat(note.getAttributeValue("label", "printScale") ?? "1") || 1, + margins: note.getAttributeValue("label", "printMargins") ?? "default", + pageRanges: "" }); - } else { - const iframe = document.createElement('iframe'); - iframe.src = `?print#${noteContext.notePath}`; - iframe.className = "print-iframe"; - document.body.appendChild(iframe); - iframe.onload = () => { - if (!iframe.contentWindow) { - toast.closePersistent("printing"); - document.body.removeChild(iframe); - return; + return; + } + + // Browser fallback: render the print page in a hidden iframe and use window.print(). + showToast("printing"); + const iframe = document.createElement('iframe'); + iframe.src = `?print#${noteContext.notePath}`; + iframe.className = "print-iframe"; + document.body.appendChild(iframe); + iframe.onload = () => { + if (!iframe.contentWindow) { + toast.closePersistent("printing"); + document.body.removeChild(iframe); + return; + } + + iframe.contentWindow.addEventListener("note-load-progress", (e) => { + showToast("printing", e.detail.progress); + }); + + iframe.contentWindow.addEventListener("note-ready", (e) => { + toast.closePersistent("printing"); + + if ("detail" in e) { + handlePrintReport(e.detail as PrintReport); } - iframe.contentWindow.addEventListener("note-load-progress", (e) => { - showToast("printing", e.detail.progress); - }); - - iframe.contentWindow.addEventListener("note-ready", (e) => { - toast.closePersistent("printing"); - - if ("detail" in e) { - handlePrintReport(e.detail as PrintReport); - } - - iframe.contentWindow?.print(); - document.body.removeChild(iframe); - }); - }; - } - }); - - useTriliumEvent("exportAsPdf", () => { - if (!noteContext?.isActive() || !note) return; - showToast("exporting_pdf"); - - const { ipcRenderer } = dynamicRequire("electron"); - ipcRenderer.send("export-as-pdf-preview", { - title: note.title, - notePath: noteContext.notePath, - pageSize: note.getAttributeValue("label", "printPageSize") ?? "Letter", - landscape: note.hasAttribute("label", "printLandscape"), - scale: parseFloat(note.getAttributeValue("label", "printScale") ?? "1") || 1, - margins: note.getAttributeValue("label", "printMargins") ?? "default", - pageRanges: "" - }); + iframe.contentWindow?.print(); + document.body.removeChild(iframe); + }); + }; }); return ( diff --git a/apps/client/src/widgets/dialogs/print_preview.tsx b/apps/client/src/widgets/dialogs/print_preview.tsx index 5862802204..4512707eee 100644 --- a/apps/client/src/widgets/dialogs/print_preview.tsx +++ b/apps/client/src/widgets/dialogs/print_preview.tsx @@ -110,7 +110,7 @@ export default function PrintPreviewDialog() { setLoading(false); } - function handleSave() { + function handleExportPdf() { if (!bufferRef.current) return; const { ipcRenderer } = dynamicRequire("electron"); @@ -121,6 +121,21 @@ export default function PrintPreviewDialog() { handleClose(); } + function handlePrint(silent: boolean) { + if (!isElectron()) return; + const { ipcRenderer } = dynamicRequire("electron"); + ipcRenderer.send("print-from-preview", { + notePath: notePathRef.current, + pageSize, + landscape, + scale, + margins: marginsStr, + pageRanges, + silent + }); + handleClose(); + } + function handleOrientationChange(newLandscape: boolean) { if (newLandscape === landscape) return; setLandscape(newLandscape); @@ -215,10 +230,23 @@ export default function PrintPreviewDialog() { show={shown} onHidden={handleClose} bodyStyle={{ height: "78vh", padding: 0, display: "flex" }} + footerAlignment="between" footer={ <> -