@@ -249,45 +250,18 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
return;
}
- await libraryLoader.requireLibrary(libraryLoader.PRINT_THIS);
+ window.print();
+ }
- let $promotedAttributes = $("");
-
- if (this.note.getPromotedDefinitionAttributes().length > 0) {
- $promotedAttributes = (await attributeRenderer.renderNormalAttributes(this.note)).$renderedAttributes;
+ async exportAsPdfEvent() {
+ if (!this.noteContext.isActive()) {
+ return;
}
- const { assetPath } = window.glob;
- const cssToLoad = [
- `${assetPath}/node_modules/codemirror/lib/codemirror.css`,
- `${assetPath}/libraries/ckeditor/ckeditor-content.css`,
- `${assetPath}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
- `${assetPath}/node_modules/katex/dist/katex.min.css`,
- `${assetPath}/stylesheets/print.css`,
- `${assetPath}/stylesheets/relation_map.css`,
- `${assetPath}/stylesheets/ckeditor-theme.css`
- ];
-
- if (isSyntaxHighlightEnabled()) {
- cssToLoad.push(getStylesheetUrl("default:vs"));
- }
-
- this.$widget.find(".note-detail-printable:visible").printThis({
- header: $("
").append($("
").text(this.note.title)).append($promotedAttributes).prop("outerHTML"),
-
- footer: `
-
-
-
-
-`,
- importCSS: false,
- loadCSS: cssToLoad,
- debug: true
+ const { ipcRenderer } = utils.dynamicRequire("electron");
+ ipcRenderer.send("export-as-pdf", {
+ title: this.note.title,
+ landscape: this.note.hasAttribute("label", "printLandscape")
});
}
diff --git a/src/public/stylesheets/print.css b/src/public/stylesheets/print.css
index d40396259..34521d733 100644
--- a/src/public/stylesheets/print.css
+++ b/src/public/stylesheets/print.css
@@ -1,40 +1,162 @@
-@media print {
- html body {
- /* https://github.com/zadam/trilium/issues/3202 */
- color: black;
- }
-
- .no-print,
- .no-print * {
- display: none !important;
- }
-
- .relation-map-wrapper {
- height: 100vh !important;
- }
-
- .table thead th,
- .table td,
- .table th {
- /* Fix center vertical alignment of table cells */
- vertical-align: middle;
- }
-
- pre {
- box-shadow: unset !important;
- border: 0.75pt solid gray !important;
- border-radius: 2pt !important;
- }
-
- span[style] {
- print-color-adjust: exact;
- -webkit-print-color-adjust: exact;
- }
-
- /* Fix visibility of checkbox checkmarks
- see https://github.com/TriliumNext/Notes/issues/901 */
- .ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable="false"] > input[checked]::after {
- /* fallback to default ck-editor green */
- border-color: hsl(126, 64%, 41%);
- }
+:root {
+ --main-background-color: white;
+ --root-background: var(--main-background-color);
+ --launcher-pane-background-color: var(--main-background-color);
+ --main-text-color: black;
+ --input-text-color: var(--main-text-color);
}
+
+.no-print,
+.no-print *,
+.tab-row-container,
+.tab-row-widget,
+#launcher-pane,
+#left-pane,
+#right-pane,
+.title-row .note-icon-widget,
+.title-row .button-widget,
+.ribbon-container,
+.promoted-attributes-widget,
+.scroll-padding-widget,
+.note-list-widget,
+.spacer {
+ display: none !important;
+}
+
+body.mobile #mobile-sidebar-wrapper,
+body.mobile .classic-toolbar-widget,
+body.mobile .action-button {
+ display: none !important;
+}
+
+body.mobile #detail-container {
+ max-height: unset;
+}
+
+body.mobile .note-title-widget {
+ padding: 0 !important;
+}
+
+body,
+#root-widget,
+#rest-pane > div.component:first-child,
+.note-detail-printable,
+.note-detail-editable-text-editor {
+ height: unset !important;
+ overflow: auto;
+}
+
+.note-title-widget input,
+.note-detail-editable-text,
+.note-detail-editable-text-editor {
+ padding: 0 !important;
+}
+
+html,
+body {
+ width: unset !important;
+ height: unset !important;
+ overflow: visible;
+ position: unset;
+ /* https://github.com/zadam/trilium/issues/3202 */
+ color: black;
+}
+
+#root-widget,
+#horizontal-main-container,
+#rest-pane,
+#vertical-main-container,
+#center-pane,
+.split-note-container-widget,
+.note-split:not(.hidden-ext),
+body.mobile #mobile-rest-container {
+ display: block !important;
+ overflow: auto;
+ border-radius: 0 !important;
+}
+
+#center-pane,
+#rest-pane,
+.note-split,
+body.mobile #detail-container {
+ width: unset !important;
+ max-width: unset !important;
+}
+
+.component {
+ contain: none !important;
+}
+
+/* Respect page breaks */
+.page-break {
+ page-break-after: always;
+ break-after: always;
+}
+
+.page-break > * {
+ display: none !important;
+}
+
+.relation-map-wrapper {
+ height: 100vh !important;
+}
+
+.table thead th,
+.table td,
+.table th {
+ /* Fix center vertical alignment of table cells */
+ vertical-align: middle;
+}
+
+pre {
+ box-shadow: unset !important;
+ border: 0.75pt solid gray !important;
+ border-radius: 2pt !important;
+}
+
+th,
+span[style] {
+ print-color-adjust: exact;
+ -webkit-print-color-adjust: exact;
+}
+
+/*
+ * Text note specific fixes
+ */
+.ck-widget {
+ outline: none !important;
+}
+
+.ck-placeholder,
+.ck-widget__type-around,
+.ck-widget__selection-handle {
+ display: none !important;
+}
+
+.ck-widget.table td.ck-editor__nested-editable.ck-editor__nested-editable_focused,
+.ck-widget.table td.ck-editor__nested-editable:focus,
+.ck-widget.table th.ck-editor__nested-editable.ck-editor__nested-editable_focused,
+.ck-widget.table th.ck-editor__nested-editable:focus {
+ background: unset !important;
+ outline: unset !important;
+}
+
+/* Fix visibility of checkbox checkmarks
+ see https://github.com/TriliumNext/Notes/issues/901 */
+.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable="false"] > input[checked]::after {
+ /* fallback to default ck-editor green */
+ border-color: hsl(126, 64%, 41%);
+}
+
+.include-note .include-note-content {
+ max-height: unset !important;
+ overflow: unset !important;
+}
+
+/*
+ * Code note specific fixes.
+ */
+.note-detail-code pre {
+ border: unset !important;
+ border-radius: unset !important;
+}
\ No newline at end of file
diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css
index 24cfee62f..8f883d8ff 100644
--- a/src/public/stylesheets/style.css
+++ b/src/public/stylesheets/style.css
@@ -1604,4 +1604,4 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
border-color: var(--hover-item-border-color);
background: var(--hover-item-background-color);
color: var(--hover-item-text-color);
-}
+}
\ No newline at end of file
diff --git a/src/public/stylesheets/theme-next/notes/text.css b/src/public/stylesheets/theme-next/notes/text.css
index 4100564d1..56a12e1c4 100644
--- a/src/public/stylesheets/theme-next/notes/text.css
+++ b/src/public/stylesheets/theme-next/notes/text.css
@@ -104,11 +104,13 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
opacity: 1;
}
-.ck-content p code {
- border: 1px solid var(--card-border-color);
- box-shadow: var(--card-box-shadow);
- border-radius: 6px;
- background-color: var(--card-background-color);
+@media (screen) {
+ .ck-content p code {
+ border: 1px solid var(--card-border-color);
+ box-shadow: var(--card-box-shadow);
+ border-radius: 6px;
+ background-color: var(--card-background-color);
+ }
}
.note-detail-printable:not(.word-wrap) pre code {
diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json
index d58f0fd56..c0d7d0eb1 100644
--- a/src/public/translations/en/translation.json
+++ b/src/public/translations/en/translation.json
@@ -672,7 +672,8 @@
"save_revision": "Save revision",
"convert_into_attachment_failed": "Converting note '{{title}}' failed.",
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
- "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?"
+ "convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?",
+ "print_pdf": "Export as PDF..."
},
"onclick_button": {
"no_click_handler": "Button widget '{{componentId}}' has no defined click handler"
diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json
index 87be0aa79..280a00302 100644
--- a/src/public/translations/ro/translation.json
+++ b/src/public/translations/ro/translation.json
@@ -824,7 +824,8 @@
"search_in_note": "Caută în notiță",
"convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.",
"convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.",
- "convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?"
+ "convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
+ "print_pdf": "Exportare ca PDF..."
},
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
diff --git a/src/routes/assets.ts b/src/routes/assets.ts
index c20c468d0..959de788b 100644
--- a/src/routes/assets.ts
+++ b/src/routes/assets.ts
@@ -66,8 +66,6 @@ async function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/jquery-hotkeys/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/jquery-hotkeys/")));
- app.use(`/${assetPath}/node_modules/print-this/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/print-this/")));
-
app.use(`/${assetPath}/node_modules/split.js/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/split.js/dist/")));
app.use(`/${assetPath}/node_modules/panzoom/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/panzoom/dist/")));
diff --git a/src/services/keyboard_actions.ts b/src/services/keyboard_actions.ts
index 95ee3e7b8..8d96a915f 100644
--- a/src/services/keyboard_actions.ts
+++ b/src/services/keyboard_actions.ts
@@ -503,6 +503,12 @@ function getDefaultKeyboardActions() {
description: t("keyboard_actions.print-active-note"),
scope: "window"
},
+ {
+ actionName: "exportAsPdf",
+ defaultShortcuts: [],
+ description: t("keyboard_actions.export-as-pdf"),
+ scope: "window"
+ },
{
actionName: "openNoteExternally",
defaultShortcuts: [],
diff --git a/src/services/keyboard_actions_interface.ts b/src/services/keyboard_actions_interface.ts
index f0f032e6a..68d708309 100644
--- a/src/services/keyboard_actions_interface.ts
+++ b/src/services/keyboard_actions_interface.ts
@@ -75,6 +75,7 @@ const enum KeyboardActionNamesEnum {
toggleRibbonTabSimilarNotes,
toggleRightPane,
printActiveNote,
+ exportAsPdf,
openNoteExternally,
renderActiveNote,
runActiveNote,
diff --git a/src/services/window.ts b/src/services/window.ts
index 84c8f5a46..939ed0e90 100644
--- a/src/services/window.ts
+++ b/src/services/window.ts
@@ -1,3 +1,4 @@
+import fs from "fs/promises";
import path from "path";
import url from "url";
import port from "./port.js";
@@ -7,12 +8,13 @@ import sqlInit from "./sql_init.js";
import cls from "./cls.js";
import keyboardActionsService from "./keyboard_actions.js";
import remoteMain from "@electron/remote/main/index.js";
-import type { App, BrowserWindow, BrowserWindowConstructorOptions, WebContents } from "electron";
-import { ipcMain } from "electron";
-import { isDev, isMac, isWindows } from "./utils.js";
+import { BrowserWindow, shell, type App, type BrowserWindowConstructorOptions, type WebContents } from "electron";
+import { dialog, ipcMain } from "electron";
+import { formatDownloadTitle, isDev, isMac, isWindows } from "./utils.js";
import { fileURLToPath } from "url";
import { dirname } from "path";
+import { t } from "i18next";
// Prevent the window being garbage collected
let mainWindow: BrowserWindow | null;
@@ -46,6 +48,50 @@ ipcMain.on("create-extra-window", (event, arg) => {
createExtraWindow(arg.extraWindowHash);
});
+interface ExportAsPdfOpts {
+ title: string;
+ landscape: boolean;
+}
+
+ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => {
+ const browserWindow = BrowserWindow.fromWebContents(e.sender);
+ if (!browserWindow) {
+ return;
+ }
+
+ const filePath = dialog.showSaveDialogSync(browserWindow, {
+ defaultPath: formatDownloadTitle(opts.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: opts.landscape
+ });
+ } catch (e) {
+ dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-export-message"));
+ return;
+ }
+
+ try {
+ await fs.writeFile(filePath, buffer);
+ } catch (e) {
+ dialog.showErrorBox(t("pdf.unable-to-export-title"), t("pdf.unable-to-save-message"));
+ return;
+ }
+
+ shell.openPath(filePath);
+});
+
async function createMainWindow(app: App) {
if ("setUserTasks" in app) {
app.setUserTasks([
diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs
index aa325834d..da74e65c8 100644
--- a/src/views/desktop.ejs
+++ b/src/views/desktop.ejs
@@ -62,6 +62,7 @@
<% } %>
+