feat(print): basic PDF preview

This commit is contained in:
Elian Doran
2026-04-14 22:30:20 +03:00
parent 22eb2697d5
commit 71b3ee143f
6 changed files with 145 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { InfoProps } from "../widgets/dialogs/info.jsx";
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type { PrintPreviewData } from "../widgets/dialogs/print_preview.jsx";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import Component from "./component.js";
@@ -330,6 +331,7 @@ export type CommandMappings = {
toggleRightPane: CommandData;
printActiveNote: CommandData;
exportAsPdf: CommandData;
showPrintPreview: PrintPreviewData;
openNoteExternally: CommandData;
openNoteCustom: CommandData;
openNoteOnServer: CommandData;

View File

@@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
import PrintPreviewDialog from "../widgets/dialogs/print_preview.jsx";
import ToastContainer from "../widgets/Toast.jsx";
export function applyModals(rootContainer: RootContainer) {
@@ -51,6 +52,7 @@ export function applyModals(rootContainer: RootContainer) {
.child(<PromptDialog />)
.child(<IncorrectCpuArchDialog />)
.child(<PopupEditorDialog />)
.child(<PrintPreviewDialog />)
.child(<CallToActionDialog />)
.child(<ToastContainer />);
}

View File

@@ -2305,6 +2305,11 @@
"toggle": "Toggle right panel",
"custom_widget_go_to_source": "Go to source code"
},
"print_preview": {
"title": "Print preview",
"close": "Close",
"save": "Save as PDF"
},
"pdf": {
"attachments_one": "{{count}} attachment",
"attachments_other": "{{count}} attachments",

View File

@@ -4,6 +4,7 @@ import clsx from "clsx";
import { isValidElement, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import appContext from "../components/app_context";
import NoteContext from "../components/note_context";
import FNote from "../entities/fnote";
import type { PrintReport } from "../print";
@@ -146,11 +147,17 @@ export default function NoteDetail() {
toast.closePersistent("printing");
handlePrintReport(printReport);
};
const onPreviewResult = (_e: any, { buffer, title }: { buffer: Uint8Array; title: string }) => {
toast.closePersistent("printing");
appContext.triggerCommand("showPrintPreview", { pdfBuffer: buffer, title });
};
ipcRenderer.on("print-progress", onPrintProgress);
ipcRenderer.on("print-done", onPrintDone);
ipcRenderer.on("export-as-pdf-preview-result", onPreviewResult);
return () => {
ipcRenderer.off("print-progress", onPrintProgress);
ipcRenderer.off("print-done", onPrintDone);
ipcRenderer.off("export-as-pdf-preview-result", onPreviewResult);
};
}, []);
@@ -215,7 +222,7 @@ export default function NoteDetail() {
showToast("exporting_pdf");
const { ipcRenderer } = dynamicRequire("electron");
ipcRenderer.send("export-as-pdf", {
ipcRenderer.send("export-as-pdf-preview", {
title: note.title,
notePath: noteContext.notePath,
pageSize: note.getAttributeValue("label", "printPageSize") ?? "Letter",

View File

@@ -0,0 +1,68 @@
import { useRef, useState } from "preact/hooks";
import Modal from "../react/Modal";
import PdfViewer from "../type_widgets/file/PdfViewer";
import Button from "../react/Button";
import { useTriliumEvent } from "../react/hooks";
import { t } from "../../services/i18n";
import { dynamicRequire } from "../../services/utils";
export interface PrintPreviewData {
pdfBuffer: Uint8Array;
title: string;
}
export default function PrintPreviewDialog() {
const [shown, setShown] = useState(false);
const [pdfUrl, setPdfUrl] = useState<string>();
const bufferRef = useRef<Uint8Array>();
const titleRef = useRef("");
useTriliumEvent("showPrintPreview", (data: PrintPreviewData) => {
bufferRef.current = data.pdfBuffer;
titleRef.current = data.title;
const blob = new Blob([data.pdfBuffer as BlobPart], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
setPdfUrl(url);
setShown(true);
});
function handleClose() {
setShown(false);
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
setPdfUrl(undefined);
}
bufferRef.current = undefined;
}
function handleSave() {
if (!bufferRef.current) return;
const { ipcRenderer } = dynamicRequire("electron");
ipcRenderer.send("save-pdf", {
title: titleRef.current,
buffer: bufferRef.current
});
handleClose();
}
return (
<Modal
className="print-preview-dialog"
title={t("print_preview.title")}
size="xl"
show={shown}
onHidden={handleClose}
bodyStyle={{ height: "80vh", padding: 0 }}
footer={
<>
<Button text={t("print_preview.close")} onClick={handleClose} />
<Button text={t("print_preview.save")} className="btn-primary" onClick={handleSave} />
</>
}
>
{pdfUrl && <PdfViewer pdfUrl={pdfUrl} />}
</Modal>
);
}