feat(desktop): display print errors

This commit is contained in:
Elian Doran 2026-02-10 17:26:58 +02:00
parent 89d39f5f2b
commit 5eb32744c3
No known key found for this signature in database
4 changed files with 131 additions and 60 deletions

View File

@ -18,6 +18,10 @@ export type PrintReport = {
} | {
type: "collection";
ignoredNoteIds: string[];
} | {
type: "error";
message: string;
stack?: string;
};
async function main() {

View File

@ -1797,6 +1797,8 @@
"printing": "Printing in progress...",
"printing_pdf": "Exporting to PDF in progress...",
"print_report_title": "Print report",
"print_report_error_title": "Failed to print",
"print_report_stack_trace": "Stack trace",
"print_report_collection_content_one": "{{count}} note in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_content_other": "{{count}} notes in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_details_button": "See details",

View File

@ -370,7 +370,33 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
}
function handlePrintReport(printReport?: PrintReport) {
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
if (!printReport) return;
if (printReport.type === "error") {
toast.showPersistent({
id: "print-error",
icon: "bx bx-error-circle",
title: t("note_detail.print_report_error_title"),
message: printReport.message,
buttons: printReport.stack ? [
{
text: t("note_detail.print_report_collection_details_button"),
onClick(api) {
api.dismissToast();
dialog.info(<>
<p>{printReport.message}</p>
<details>
<summary>{t("note_detail.print_report_stack_trace")}</summary>
<pre style="font-size: 0.85em; overflow-x: auto;">{printReport.stack}</pre>
</details>
</>, {
title: t("note_detail.print_report_error_title")
});
}
}
] : undefined
});
} else if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
toast.showPersistent({
id: "print-report",
icon: "bx bx-collection",

View File

@ -81,66 +81,82 @@ interface ExportAsPdfOpts {
}
electron.ipcMain.on("print-note", async (e, { notePath }: PrintOpts) => {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "printing");
browserWindow.webContents.print({}, (success, failureReason) => {
if (!success && failureReason !== "Print job canceled") {
electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason);
}
e.sender.send("print-done", printReport);
browserWindow.destroy();
});
try {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "printing");
browserWindow.webContents.print({}, (success, failureReason) => {
if (!success && failureReason !== "Print job canceled") {
electron.dialog.showErrorBox(t("pdf.unable-to-print"), failureReason);
}
e.sender.send("print-done", printReport);
browserWindow.destroy();
});
} catch (err) {
e.sender.send("print-done", {
type: "error",
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined
});
}
});
electron.ipcMain.on("export-as-pdf", async (e, { title, notePath, landscape, pageSize }: ExportAsPdfOpts) => {
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "exporting_pdf");
async function print() {
const filePath = electron.dialog.showSaveDialogSync(browserWindow, {
defaultPath: formatDownloadTitle(title, "file", "application/pdf"),
filters: [
{
name: t("pdf.export_filter"),
extensions: ["pdf"]
}
]
});
if (!filePath) return;
let buffer: Buffer;
try {
buffer = await browserWindow.webContents.printToPDF({
landscape,
pageSize,
generateDocumentOutline: true,
generateTaggedPDF: true,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `<div></div>`,
footerTemplate: `
<div class="pageNumber" style="width: 100%; text-align: center; font-size: 10pt;">
</div>
`
});
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message"));
return;
}
try {
await fs.writeFile(filePath, buffer);
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message"));
return;
}
electron.shell.openPath(filePath);
}
try {
await print();
} finally {
e.sender.send("print-done", printReport);
browserWindow.destroy();
const { browserWindow, printReport } = await getBrowserWindowForPrinting(e, notePath, "exporting_pdf");
async function print() {
const filePath = electron.dialog.showSaveDialogSync(browserWindow, {
defaultPath: formatDownloadTitle(title, "file", "application/pdf"),
filters: [
{
name: t("pdf.export_filter"),
extensions: ["pdf"]
}
]
});
if (!filePath) return;
let buffer: Buffer;
try {
buffer = await browserWindow.webContents.printToPDF({
landscape,
pageSize,
generateDocumentOutline: true,
generateTaggedPDF: true,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `<div></div>`,
footerTemplate: `
<div class="pageNumber" style="width: 100%; text-align: center; font-size: 10pt;">
</div>
`
});
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message"));
return;
}
try {
await fs.writeFile(filePath, buffer);
} catch (_e) {
electron.dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message"));
return;
}
electron.shell.openPath(filePath);
}
try {
await print();
} finally {
e.sender.send("print-done", printReport);
browserWindow.destroy();
}
} catch (err) {
e.sender.send("print-done", {
type: "error",
message: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined
});
}
});
@ -176,16 +192,29 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
throw err;
}
// Set up error logging in the renderer process before content loads
// Set up error tracking and logging in the renderer process
await browserWindow.webContents.executeJavaScript(`
(function() {
window._printWindowErrors = [];
window.addEventListener("error", (e) => {
console.error("Uncaught error:", e.message, "at", e.filename + ":" + e.lineno + ":" + e.colno);
const errorMsg = "Uncaught error: " + e.message + " at " + e.filename + ":" + e.lineno + ":" + e.colno;
console.error(errorMsg);
if (e.error?.stack) console.error(e.error.stack);
window._printWindowErrors.push({
type: 'error',
message: errorMsg,
stack: e.error?.stack
});
});
window.addEventListener("unhandledrejection", (e) => {
console.error("Unhandled rejection:", String(e.reason));
const errorMsg = "Unhandled rejection: " + String(e.reason);
console.error(errorMsg);
if (e.reason?.stack) console.error(e.reason.stack);
window._printWindowErrors.push({
type: 'rejection',
message: errorMsg,
stack: e.reason?.stack
});
});
})();
`).catch(err => log.error(`Failed to set up error handlers in print window: ${err}`));
@ -196,7 +225,17 @@ async function getBrowserWindowForPrinting(e: IpcMainEvent, notePath: string, ac
new Promise((resolve, reject) => {
if (window._noteReady) return resolve(window._noteReady);
// Check for errors periodically
const errorChecker = setInterval(() => {
if (window._printWindowErrors && window._printWindowErrors.length > 0) {
clearInterval(errorChecker);
const errors = window._printWindowErrors.map(e => e.message).join('; ');
reject(new Error("Print window errors: " + errors));
}
}, 100);
window.addEventListener("note-ready", (data) => {
clearInterval(errorChecker);
resolve(data.detail);
});
});