Skip to content

Commit c09ef76

Browse files
authored
Merge pull request #1091 from TriliumNext/feature/different_printing_mechanism
Export as PDF
2 parents b59f98d + bd363da commit c09ef76

20 files changed

+311
-130
lines changed

bin/copy-dist.ts

-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ const copy = async () => {
8181
"node_modules/mermaid/dist/",
8282
"node_modules/jquery/dist/",
8383
"node_modules/jquery-hotkeys/",
84-
"node_modules/print-this/",
8584
"node_modules/split.js/dist/",
8685
"node_modules/panzoom/dist/",
8786
"node_modules/i18next/",

package-lock.json

-21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@
123123
"normalize-strings": "1.1.1",
124124
"normalize.css": "8.0.1",
125125
"panzoom": "9.4.3",
126-
"print-this": "2.0.0",
127126
"rand-token": "1.0.1",
128127
"react": "18.3.1",
129128
"react-dom": "18.3.1",

src/public/app/components/app_context.ts

+4
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ export type CommandMappings = {
8181
showOptions: CommandData & {
8282
section: string;
8383
};
84+
showExportDialog: CommandData & {
85+
notePath: string;
86+
defaultType: "single";
87+
};
8488
showDeleteNotesDialog: CommandData & {
8589
branchIdsToDelete: string[];
8690
callback: (value: ResolveOptions) => void;

src/public/app/services/library_loader.ts

-5
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,6 @@ const RELATION_MAP: Library = {
5151
css: ["stylesheets/relation_map.css"]
5252
};
5353

54-
const PRINT_THIS: Library = {
55-
js: ["node_modules/print-this/printThis.js"]
56-
};
57-
5854
const CALENDAR_WIDGET: Library = {
5955
css: ["stylesheets/calendar.css"]
6056
};
@@ -193,7 +189,6 @@ export default {
193189
CODE_MIRROR,
194190
ESLINT,
195191
RELATION_MAP,
196-
PRINT_THIS,
197192
CALENDAR_WIDGET,
198193
KATEX,
199194
WHEEL_ZOOM,

src/public/app/widgets/buttons/note_actions.js src/public/app/widgets/buttons/note_actions.ts

+50-12
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@ import dialogService from "../../services/dialog.js";
55
import server from "../../services/server.js";
66
import toastService from "../../services/toast.js";
77
import ws from "../../services/ws.js";
8-
import appContext from "../../components/app_context.js";
8+
import appContext, { type EventData } from "../../components/app_context.js";
99
import { t } from "../../services/i18n.js";
10+
import type FNote from "../../entities/fnote.js";
11+
import type { FAttachmentRow } from "../../entities/fattachment.js";
12+
13+
// TODO: Deduplicate with server
14+
interface ConvertToAttachmentResponse {
15+
attachment: FAttachmentRow;
16+
}
1017

1118
const TPL = `
1219
<div class="dropdown note-actions">
@@ -52,8 +59,12 @@ const TPL = `
5259
</li>
5360
5461
<li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button">
55-
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd></li>
62+
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd>
63+
</li>
5664
65+
<li data-trigger-command="exportAsPdf" class="dropdown-item export-as-pdf-button">
66+
<span class="bx bxs-file-pdf"></span> ${t("note_actions.print_pdf")}<kbd data-command="exportAsPdf"></kbd>
67+
</li>
5768
5869
<div class="dropdown-divider"></div>
5970
@@ -100,25 +111,45 @@ const TPL = `
100111
</div>`;
101112

102113
export default class NoteActionsWidget extends NoteContextAwareWidget {
114+
115+
private $convertNoteIntoAttachmentButton!: JQuery<HTMLElement>;
116+
private $findInTextButton!: JQuery<HTMLElement>;
117+
private $printActiveNoteButton!: JQuery<HTMLElement>;
118+
private $exportAsPdfButton!: JQuery<HTMLElement>;
119+
private $showSourceButton!: JQuery<HTMLElement>;
120+
private $showAttachmentsButton!: JQuery<HTMLElement>;
121+
private $renderNoteButton!: JQuery<HTMLElement>;
122+
private $saveRevisionButton!: JQuery<HTMLElement>;
123+
private $exportNoteButton!: JQuery<HTMLElement>;
124+
private $importNoteButton!: JQuery<HTMLElement>;
125+
private $openNoteExternallyButton!: JQuery<HTMLElement>;
126+
private $openNoteCustomButton!: JQuery<HTMLElement>;
127+
private $deleteNoteButton!: JQuery<HTMLElement>;
128+
103129
isEnabled() {
104130
return this.note?.type !== "launcher";
105131
}
106132

107133
doRender() {
108134
this.$widget = $(TPL);
109-
this.$widget.on("show.bs.dropdown", () => this.refreshVisibility(this.note));
135+
this.$widget.on("show.bs.dropdown", () => {
136+
if (this.note) {
137+
this.refreshVisibility(this.note);
138+
}
139+
});
110140

111141
this.$convertNoteIntoAttachmentButton = this.$widget.find("[data-trigger-command='convertNoteIntoAttachment']");
112142
this.$findInTextButton = this.$widget.find(".find-in-text-button");
113143
this.$printActiveNoteButton = this.$widget.find(".print-active-note-button");
144+
this.$exportAsPdfButton = this.$widget.find(".export-as-pdf-button");
114145
this.$showSourceButton = this.$widget.find(".show-source-button");
115146
this.$showAttachmentsButton = this.$widget.find(".show-attachments-button");
116147
this.$renderNoteButton = this.$widget.find(".render-note-button");
117148
this.$saveRevisionButton = this.$widget.find(".save-revision-button");
118149

119150
this.$exportNoteButton = this.$widget.find(".export-note-button");
120151
this.$exportNoteButton.on("click", () => {
121-
if (this.$exportNoteButton.hasClass("disabled")) {
152+
if (this.$exportNoteButton.hasClass("disabled") || !this.noteContext?.notePath) {
122153
return;
123154
}
124155

@@ -129,7 +160,11 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
129160
});
130161

131162
this.$importNoteButton = this.$widget.find(".import-files-button");
132-
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", { noteId: this.noteId }));
163+
this.$importNoteButton.on("click", () => {
164+
if (this.noteId) {
165+
this.triggerCommand("showImportDialog", { noteId: this.noteId });
166+
}
167+
});
133168

134169
this.$widget.on("click", ".dropdown-item", () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown("toggle"));
135170

@@ -138,15 +173,15 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
138173

139174
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
140175
this.$deleteNoteButton.on("click", () => {
141-
if (this.note.noteId === "root") {
176+
if (!this.note || this.note.noteId === "root") {
142177
return;
143178
}
144179

145180
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
146181
});
147182
}
148183

149-
async refreshVisibility(note) {
184+
async refreshVisibility(note: FNote) {
150185
const isInOptions = note.noteId.startsWith("_options");
151186

152187
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
@@ -156,7 +191,10 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
156191
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
157192
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
158193

159-
this.toggleDisabled(this.$printActiveNoteButton, ["text", "code"].includes(note.type));
194+
const canPrint = ["text", "code"].includes(note.type);
195+
this.toggleDisabled(this.$printActiveNoteButton, canPrint);
196+
this.toggleDisabled(this.$exportAsPdfButton, canPrint);
197+
this.$exportAsPdfButton.toggleClass("hidden-ext", !utils.isElectron());
160198

161199
this.$renderNoteButton.toggle(note.type === "render");
162200

@@ -177,11 +215,11 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
177215
}
178216

179217
async convertNoteIntoAttachmentCommand() {
180-
if (!(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
218+
if (!this.note || !(await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title })))) {
181219
return;
182220
}
183221

184-
const { attachment: newAttachment } = await server.post(`notes/${this.noteId}/convert-to-attachment`);
222+
const { attachment: newAttachment } = await server.post<ConvertToAttachmentResponse>(`notes/${this.noteId}/convert-to-attachment`);
185223

186224
if (!newAttachment) {
187225
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
@@ -198,15 +236,15 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
198236
});
199237
}
200238

201-
toggleDisabled($el, enable) {
239+
toggleDisabled($el: JQuery<HTMLElement>, enable: boolean) {
202240
if (enable) {
203241
$el.removeAttr("disabled");
204242
} else {
205243
$el.attr("disabled", "disabled");
206244
}
207245
}
208246

209-
entitiesReloadedEvent({ loadResults }) {
247+
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
210248
if (loadResults.isNoteReloaded(this.noteId)) {
211249
this.refresh();
212250
}

src/public/app/widgets/note_detail.js

+10-36
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
3232
import MindMapWidget from "./type_widgets/mind_map.js";
3333
import { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
3434
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
35+
import utils from "../services/utils.js";
3536

3637
const TPL = `
3738
<div class="note-detail">
@@ -249,45 +250,18 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
249250
return;
250251
}
251252

252-
await libraryLoader.requireLibrary(libraryLoader.PRINT_THIS);
253-
254-
let $promotedAttributes = $("");
255-
256-
if (this.note.getPromotedDefinitionAttributes().length > 0) {
257-
$promotedAttributes = (await attributeRenderer.renderNormalAttributes(this.note)).$renderedAttributes;
258-
}
253+
window.print();
254+
}
259255

260-
const { assetPath } = window.glob;
261-
const cssToLoad = [
262-
`${assetPath}/node_modules/codemirror/lib/codemirror.css`,
263-
`${assetPath}/libraries/ckeditor/ckeditor-content.css`,
264-
`${assetPath}/node_modules/bootstrap/dist/css/bootstrap.min.css`,
265-
`${assetPath}/node_modules/katex/dist/katex.min.css`,
266-
`${assetPath}/stylesheets/print.css`,
267-
`${assetPath}/stylesheets/relation_map.css`,
268-
`${assetPath}/stylesheets/ckeditor-theme.css`
269-
];
270-
271-
if (isSyntaxHighlightEnabled()) {
272-
cssToLoad.push(getStylesheetUrl("default:vs"));
256+
async exportAsPdfEvent() {
257+
if (!this.noteContext.isActive()) {
258+
return;
273259
}
274260

275-
this.$widget.find(".note-detail-printable:visible").printThis({
276-
header: $("<div>").append($("<h2>").text(this.note.title)).append($promotedAttributes).prop("outerHTML"),
277-
278-
footer: `
279-
<script src="${assetPath}/node_modules/katex/dist/katex.min.js"></script>
280-
<script src="${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
281-
<script src="${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
282-
<script>
283-
document.body.className += ' ck-content printed-content';
284-
285-
renderMathInElement(document.body, {trust: true});
286-
</script>
287-
`,
288-
importCSS: false,
289-
loadCSS: cssToLoad,
290-
debug: true
261+
const { ipcRenderer } = utils.dynamicRequire("electron");
262+
ipcRenderer.send("export-as-pdf", {
263+
title: this.note.title,
264+
landscape: this.note.hasAttribute("label", "printLandscape")
291265
});
292266
}
293267

0 commit comments

Comments
 (0)