diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index dbfe78cd0..2be0d986b 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -83,7 +83,7 @@ export type CommandMappings = { }; showExportDialog: CommandData & { notePath: string; - defaultType: "single"; + defaultType: "single" | "subtree"; }; showDeleteNotesDialog: CommandData & { branchIdsToDelete: string[]; diff --git a/src/public/app/widgets/dialogs/export.js b/src/public/app/widgets/dialogs/export.ts similarity index 81% rename from src/public/app/widgets/dialogs/export.js rename to src/public/app/widgets/dialogs/export.ts index 5bc9b3707..8f1fe5306 100644 --- a/src/public/app/widgets/dialogs/export.js +++ b/src/public/app/widgets/dialogs/export.ts @@ -1,11 +1,12 @@ import treeService from "../../services/tree.js"; import utils from "../../services/utils.js"; import ws from "../../services/ws.js"; -import toastService from "../../services/toast.js"; +import toastService, { type ToastOptions } from "../../services/toast.js"; import froca from "../../services/froca.js"; import openService from "../../services/open.js"; import BasicWidget from "../basic_widget.js"; import { t } from "../../services/i18n.js"; +import type { EventData } from "../../components/app_context.js"; const TPL = ` + +
+ +
`; export default class ExportDialog extends BasicWidget { + + private taskId: string; + private branchId: string | null; + private modal?: bootstrap.Modal; + private $form!: JQuery; + private $noteTitle!: JQuery; + private $subtreeFormats!: JQuery; + private $singleFormats!: JQuery; + private $subtreeType!: JQuery; + private $singleType!: JQuery; + private $exportButton!: JQuery; + private $opmlVersions!: JQuery; + constructor() { super(); @@ -125,6 +146,8 @@ export default class ExportDialog extends BasicWidget { doRender() { this.$widget = $(TPL); + // Remove once bootstrap is fixed. + // @ts-ignore this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget); this.$form = this.$widget.find(".export-form"); this.$noteTitle = this.$widget.find(".export-note-title"); @@ -136,7 +159,7 @@ export default class ExportDialog extends BasicWidget { this.$opmlVersions = this.$widget.find(".opml-versions"); this.$form.on("submit", () => { - this.modal.hide(); + this.modal?.hide(); const exportType = this.$widget.find("input[name='export-type']:checked").val(); @@ -149,13 +172,15 @@ export default class ExportDialog extends BasicWidget { const exportVersion = exportFormat === "opml" ? this.$widget.find("input[name='opml-version']:checked").val() : "1.0"; - this.exportBranch(this.branchId, exportType, exportFormat, exportVersion); + if (this.branchId) { + this.exportBranch(this.branchId, String(exportType), String(exportFormat), String(exportVersion)); + } return false; }); this.$widget.find("input[name=export-type]").on("change", (e) => { - if (e.currentTarget.value === "subtree") { + if ((e.currentTarget as HTMLInputElement).value === "subtree") { if (this.$widget.find("input[name=export-subtree-format]:checked").length === 0) { this.$widget.find("input[name=export-subtree-format]:first").prop("checked", true); } @@ -173,7 +198,7 @@ export default class ExportDialog extends BasicWidget { }); this.$widget.find("input[name=export-subtree-format]").on("change", (e) => { - if (e.currentTarget.value === "opml") { + if ((e.currentTarget as HTMLInputElement).value === "opml") { this.$opmlVersions.slideDown(); } else { this.$opmlVersions.slideUp(); @@ -181,7 +206,7 @@ export default class ExportDialog extends BasicWidget { }); } - async showExportDialogEvent({ notePath, defaultType }) { + async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) { this.taskId = ""; this.$exportButton.removeAttr("disabled"); @@ -201,11 +226,15 @@ export default class ExportDialog extends BasicWidget { const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath); - this.branchId = await froca.getBranchId(parentNoteId, noteId); - this.$noteTitle.text(await treeService.getNoteTitle(noteId)); + if (parentNoteId) { + this.branchId = await froca.getBranchId(parentNoteId, noteId); + } + if (noteId) { + this.$noteTitle.text(await treeService.getNoteTitle(noteId)); + } } - exportBranch(branchId, type, format, version) { + exportBranch(branchId: string, type: string, format: string, version: string) { this.taskId = utils.randomString(10); const url = openService.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${this.taskId}`); @@ -215,12 +244,14 @@ export default class ExportDialog extends BasicWidget { } ws.subscribeToMessages(async (message) => { - const makeToast = (id, message) => ({ - id: id, - title: t("export.export_status"), - message: message, - icon: "arrow-square-up-right" - }); + function makeToast(id: string, message: string): ToastOptions { + return { + id: id, + title: t("export.export_status"), + message: message, + icon: "arrow-square-up-right" + }; + }; if (message.taskType !== "export") { return; diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 21f84dbc1..11cc6f71d 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -261,6 +261,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { const { ipcRenderer } = utils.dynamicRequire("electron"); ipcRenderer.send("export-as-pdf", { title: this.note.title, + pageSize: this.note.getAttributeValue("label", "pageSize") ?? "Letter", landscape: this.note.hasAttribute("label", "printLandscape") }); } diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index c0d7d0eb1..e29c258f0 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -109,7 +109,8 @@ "choose_export_type": "Choose export type first please", "export_status": "Export status", "export_in_progress": "Export in progress: {{progressCount}}", - "export_finished_successfully": "Export finished successfully." + "export_finished_successfully": "Export finished successfully.", + "format_pdf": "PDF - for printing or sharing purposes." }, "help": { "fullDocumentation": "Help (full documentation is available online)", diff --git a/src/services/export/pdf.ts b/src/services/export/pdf.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/window.ts b/src/services/window.ts index ff391875b..8c3fd9118 100644 --- a/src/services/window.ts +++ b/src/services/window.ts @@ -51,6 +51,7 @@ ipcMain.on("create-extra-window", (event, arg) => { interface ExportAsPdfOpts { title: string; landscape: boolean; + pageSize: "A0" | "A1" | "A2" | "A3" | "A4" | "A5" | "A6" | "Legal" | "Letter" | "Tabloid" | "Ledger"; } ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { @@ -76,6 +77,7 @@ ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => { try { buffer = await browserWindow.webContents.printToPDF({ landscape: opts.landscape, + pageSize: opts.pageSize, generateDocumentOutline: true, generateTaggedPDF: true, displayHeaderFooter: true,