mirror of
https://github.com/zadam/trilium.git
synced 2025-12-16 04:14:26 +01:00
feat(export/share): render non-text note types
This commit is contained in:
parent
35622a2122
commit
b475037127
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import dateUtils from "../date_utils.js";
|
import dateUtils from "../date_utils.js";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import mimeTypes from "mime-types";
|
|
||||||
import packageInfo from "../../../package.json" with { type: "json" };
|
import packageInfo from "../../../package.json" with { type: "json" };
|
||||||
import { getContentDisposition } from "../utils.js";
|
import { getContentDisposition } from "../utils.js";
|
||||||
import protectedSessionService from "../protected_session.js";
|
import protectedSessionService from "../protected_session.js";
|
||||||
@ -33,16 +32,15 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
|
|||||||
const archive = archiver("zip", {
|
const archive = archiver("zip", {
|
||||||
zlib: { level: 9 } // Sets the compression level.
|
zlib: { level: 9 } // Sets the compression level.
|
||||||
});
|
});
|
||||||
|
const rewriteFn = (zipExportOptions?.customRewriteLinks ? zipExportOptions?.customRewriteLinks(rewriteLinks, getNoteTargetUrl) : rewriteLinks);
|
||||||
|
const provider= buildProvider();
|
||||||
|
|
||||||
const noteIdToMeta: Record<string, NoteMeta> = {};
|
const noteIdToMeta: Record<string, NoteMeta> = {};
|
||||||
const rewriteFn = (zipExportOptions?.customRewriteLinks ? zipExportOptions?.customRewriteLinks(rewriteLinks, getNoteTargetUrl) : rewriteLinks);
|
|
||||||
|
|
||||||
function buildProvider() {
|
function buildProvider() {
|
||||||
const providerData: ZipExportProviderData = {
|
const providerData: ZipExportProviderData = {
|
||||||
getNoteTargetUrl,
|
getNoteTargetUrl,
|
||||||
metaFile,
|
|
||||||
archive,
|
archive,
|
||||||
rootMeta: rootMeta!,
|
|
||||||
branch,
|
branch,
|
||||||
rewriteFn
|
rewriteFn
|
||||||
};
|
};
|
||||||
@ -94,36 +92,14 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
|
|||||||
}
|
}
|
||||||
|
|
||||||
let existingExtension = path.extname(fileName).toLowerCase();
|
let existingExtension = path.extname(fileName).toLowerCase();
|
||||||
let newExtension;
|
const newExtension = provider.mapExtension(type, mime, existingExtension, format);
|
||||||
|
|
||||||
// the following two are handled specifically since we always want to have these extensions no matter the automatic detection
|
|
||||||
// and/or existing detected extensions in the note name
|
|
||||||
if (type === "text" && format === "markdown") {
|
|
||||||
newExtension = "md";
|
|
||||||
} else if (type === "text" && format === "html") {
|
|
||||||
newExtension = "html";
|
|
||||||
} else if (mime === "application/x-javascript" || mime === "text/javascript") {
|
|
||||||
newExtension = "js";
|
|
||||||
} else if (type === "canvas" || mime === "application/json") {
|
|
||||||
newExtension = "json";
|
|
||||||
} else if (existingExtension.length > 0) {
|
|
||||||
// if the page already has an extension, then we'll just keep it
|
|
||||||
newExtension = null;
|
|
||||||
} else {
|
|
||||||
if (mime?.toLowerCase()?.trim() === "image/jpg") {
|
|
||||||
newExtension = "jpg";
|
|
||||||
} else if (mime?.toLowerCase()?.trim() === "text/mermaid") {
|
|
||||||
newExtension = "txt";
|
|
||||||
} else {
|
|
||||||
newExtension = mimeTypes.extension(mime) || "dat";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the note is already named with the extension (e.g. "image.jpg"), then it's silly to append the exact same extension again
|
// if the note is already named with the extension (e.g. "image.jpg"), then it's silly to append the exact same extension again
|
||||||
if (newExtension && existingExtension !== `.${newExtension.toLowerCase()}`) {
|
if (newExtension && existingExtension !== `.${newExtension.toLowerCase()}`) {
|
||||||
fileName += `.${newExtension}`;
|
fileName += `.${newExtension}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return getUniqueFilename(existingFileNames, fileName);
|
return getUniqueFilename(existingFileNames, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,9 +384,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
|
|||||||
files: [rootMeta]
|
files: [rootMeta]
|
||||||
};
|
};
|
||||||
|
|
||||||
const provider= buildProvider();
|
provider.prepareMeta(metaFile);
|
||||||
|
|
||||||
provider.prepareMeta();
|
|
||||||
|
|
||||||
for (const noteMeta of Object.values(noteIdToMeta)) {
|
for (const noteMeta of Object.values(noteIdToMeta)) {
|
||||||
// filter out relations which are not inside this export
|
// filter out relations which are not inside this export
|
||||||
@ -442,7 +416,7 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
|
|||||||
|
|
||||||
saveNote(rootMeta, "");
|
saveNote(rootMeta, "");
|
||||||
|
|
||||||
provider.afterDone();
|
provider.afterDone(rootMeta);
|
||||||
|
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Archiver } from "archiver";
|
|||||||
import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js";
|
import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js";
|
||||||
import type BNote from "../../../becca/entities/bnote.js";
|
import type BNote from "../../../becca/entities/bnote.js";
|
||||||
import type BBranch from "../../../becca/entities/bbranch.js";
|
import type BBranch from "../../../becca/entities/bbranch.js";
|
||||||
|
import mimeTypes from "mime-types";
|
||||||
|
|
||||||
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
|
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
|
||||||
|
|
||||||
@ -24,8 +25,6 @@ export interface AdvancedExportOptions {
|
|||||||
export interface ZipExportProviderData {
|
export interface ZipExportProviderData {
|
||||||
branch: BBranch;
|
branch: BBranch;
|
||||||
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
||||||
metaFile: NoteMetaFile;
|
|
||||||
rootMeta: NoteMeta;
|
|
||||||
archive: Archiver;
|
archive: Archiver;
|
||||||
zipExportOptions?: AdvancedExportOptions;
|
zipExportOptions?: AdvancedExportOptions;
|
||||||
rewriteFn: RewriteLinksFn;
|
rewriteFn: RewriteLinksFn;
|
||||||
@ -33,24 +32,46 @@ export interface ZipExportProviderData {
|
|||||||
|
|
||||||
export abstract class ZipExportProvider {
|
export abstract class ZipExportProvider {
|
||||||
branch: BBranch;
|
branch: BBranch;
|
||||||
metaFile: NoteMetaFile;
|
|
||||||
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
|
||||||
rootMeta: NoteMeta;
|
|
||||||
archive: Archiver;
|
archive: Archiver;
|
||||||
zipExportOptions?: AdvancedExportOptions;
|
zipExportOptions?: AdvancedExportOptions;
|
||||||
rewriteFn: RewriteLinksFn;
|
rewriteFn: RewriteLinksFn;
|
||||||
|
|
||||||
constructor(data: ZipExportProviderData) {
|
constructor(data: ZipExportProviderData) {
|
||||||
this.branch = data.branch;
|
this.branch = data.branch;
|
||||||
this.metaFile = data.metaFile;
|
|
||||||
this.getNoteTargetUrl = data.getNoteTargetUrl;
|
this.getNoteTargetUrl = data.getNoteTargetUrl;
|
||||||
this.rootMeta = data.rootMeta;
|
|
||||||
this.archive = data.archive;
|
this.archive = data.archive;
|
||||||
this.zipExportOptions = data.zipExportOptions;
|
this.zipExportOptions = data.zipExportOptions;
|
||||||
this.rewriteFn = data.rewriteFn;
|
this.rewriteFn = data.rewriteFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract prepareMeta(): void;
|
abstract prepareMeta(metaFile: NoteMetaFile): void;
|
||||||
abstract prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer;
|
abstract prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer;
|
||||||
abstract afterDone(): void;
|
abstract afterDone(rootMeta: NoteMeta): void;
|
||||||
|
|
||||||
|
mapExtension(type: string | null, mime: string, existingExtension: string, format: string) {
|
||||||
|
// the following two are handled specifically since we always want to have these extensions no matter the automatic detection
|
||||||
|
// and/or existing detected extensions in the note name
|
||||||
|
if (type === "text" && format === "markdown") {
|
||||||
|
return "md";
|
||||||
|
} else if (type === "text" && format === "html") {
|
||||||
|
return "html";
|
||||||
|
} else if (mime === "application/x-javascript" || mime === "text/javascript") {
|
||||||
|
return "js";
|
||||||
|
} else if (type === "canvas" || mime === "application/json") {
|
||||||
|
return "json";
|
||||||
|
} else if (existingExtension.length > 0) {
|
||||||
|
// if the page already has an extension, then we'll just keep it
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (mime?.toLowerCase()?.trim() === "image/jpg") {
|
||||||
|
return "jpg";
|
||||||
|
} else if (mime?.toLowerCase()?.trim() === "text/mermaid") {
|
||||||
|
return "txt";
|
||||||
|
} else {
|
||||||
|
return mimeTypes.extension(mime) || "dat";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,27 +10,24 @@ export default class HtmlExportProvider extends ZipExportProvider {
|
|||||||
private indexMeta: NoteMeta | null = null;
|
private indexMeta: NoteMeta | null = null;
|
||||||
private cssMeta: NoteMeta | null = null;
|
private cssMeta: NoteMeta | null = null;
|
||||||
|
|
||||||
prepareMeta() {
|
prepareMeta(metaFile) {
|
||||||
this.navigationMeta = {
|
this.navigationMeta = {
|
||||||
noImport: true,
|
noImport: true,
|
||||||
dataFileName: "navigation.html"
|
dataFileName: "navigation.html"
|
||||||
};
|
};
|
||||||
|
metaFile.files.push(this.navigationMeta);
|
||||||
this.metaFile.files.push(this.navigationMeta);
|
|
||||||
|
|
||||||
this.indexMeta = {
|
this.indexMeta = {
|
||||||
noImport: true,
|
noImport: true,
|
||||||
dataFileName: "index.html"
|
dataFileName: "index.html"
|
||||||
};
|
};
|
||||||
|
metaFile.files.push(this.indexMeta);
|
||||||
this.metaFile.files.push(this.indexMeta);
|
|
||||||
|
|
||||||
this.cssMeta = {
|
this.cssMeta = {
|
||||||
noImport: true,
|
noImport: true,
|
||||||
dataFileName: "style.css"
|
dataFileName: "style.css"
|
||||||
};
|
};
|
||||||
|
metaFile.files.push(this.cssMeta);
|
||||||
this.metaFile.files.push(this.cssMeta);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
||||||
@ -72,23 +69,23 @@ export default class HtmlExportProvider extends ZipExportProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
afterDone() {
|
afterDone(rootMeta: NoteMeta) {
|
||||||
if (!this.navigationMeta || !this.indexMeta || !this.cssMeta) {
|
if (!this.navigationMeta || !this.indexMeta || !this.cssMeta) {
|
||||||
throw new Error("Missing meta.");
|
throw new Error("Missing meta.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#saveNavigation(this.rootMeta, this.navigationMeta);
|
this.#saveNavigation(rootMeta, this.navigationMeta);
|
||||||
this.#saveIndex(this.rootMeta, this.indexMeta);
|
this.#saveIndex(rootMeta, this.indexMeta);
|
||||||
this.#saveCss(this.rootMeta, this.cssMeta);
|
this.#saveCss(rootMeta, this.cssMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
#saveNavigationInner(meta: NoteMeta) {
|
#saveNavigationInner(rootMeta: NoteMeta, meta: NoteMeta) {
|
||||||
let html = "<li>";
|
let html = "<li>";
|
||||||
|
|
||||||
const escapedTitle = escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ""}${meta.title}`);
|
const escapedTitle = escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ""}${meta.title}`);
|
||||||
|
|
||||||
if (meta.dataFileName && meta.noteId) {
|
if (meta.dataFileName && meta.noteId) {
|
||||||
const targetUrl = this.getNoteTargetUrl(meta.noteId, this.rootMeta);
|
const targetUrl = this.getNoteTargetUrl(meta.noteId, rootMeta);
|
||||||
|
|
||||||
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
||||||
} else {
|
} else {
|
||||||
@ -99,7 +96,7 @@ export default class HtmlExportProvider extends ZipExportProvider {
|
|||||||
html += "<ul>";
|
html += "<ul>";
|
||||||
|
|
||||||
for (const child of meta.children) {
|
for (const child of meta.children) {
|
||||||
html += this.#saveNavigationInner(child);
|
html += this.#saveNavigationInner(rootMeta, child);
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</ul>";
|
html += "</ul>";
|
||||||
@ -119,7 +116,7 @@ export default class HtmlExportProvider extends ZipExportProvider {
|
|||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="style.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ul>${this.#saveNavigationInner(rootMeta)}</ul>
|
<ul>${this.#saveNavigationInner(rootMeta, rootMeta)}</ul>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
const prettyHtml = fullHtml.length < 100_000 ? html.prettyPrint(fullHtml, { indent_size: 2 }) : fullHtml;
|
const prettyHtml = fullHtml.length < 100_000 ? html.prettyPrint(fullHtml, { indent_size: 2 }) : fullHtml;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import NoteMeta from "../../meta/note_meta";
|
import NoteMeta, { NoteMetaFile } from "../../meta/note_meta";
|
||||||
import { ZipExportProvider } from "./abstract_provider";
|
import { ZipExportProvider } from "./abstract_provider";
|
||||||
import { RESOURCE_DIR } from "../../resource_dir";
|
import { RESOURCE_DIR } from "../../resource_dir";
|
||||||
import { getResourceDir, isDev } from "../../utils";
|
import { getResourceDir, isDev } from "../../utils";
|
||||||
@ -13,7 +13,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
|
|||||||
private assetsMeta: NoteMeta[] = [];
|
private assetsMeta: NoteMeta[] = [];
|
||||||
private indexMeta: NoteMeta | null = null;
|
private indexMeta: NoteMeta | null = null;
|
||||||
|
|
||||||
prepareMeta(): void {
|
prepareMeta(metaFile: NoteMetaFile): void {
|
||||||
const assets = [
|
const assets = [
|
||||||
"style.css",
|
"style.css",
|
||||||
"script.js",
|
"script.js",
|
||||||
@ -32,7 +32,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
|
|||||||
dataFileName: asset
|
dataFileName: asset
|
||||||
};
|
};
|
||||||
this.assetsMeta.push(assetMeta);
|
this.assetsMeta.push(assetMeta);
|
||||||
this.metaFile.files.push(assetMeta);
|
metaFile.files.push(assetMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.indexMeta = {
|
this.indexMeta = {
|
||||||
@ -40,7 +40,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
|
|||||||
dataFileName: "index.html"
|
dataFileName: "index.html"
|
||||||
};
|
};
|
||||||
|
|
||||||
this.metaFile.files.push(this.indexMeta);
|
metaFile.files.push(this.indexMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer {
|
prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta, note: BNote | undefined, branch: BBranch): string | Buffer {
|
||||||
@ -58,18 +58,22 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
afterDone(): void {
|
afterDone(rootMeta: NoteMeta): void {
|
||||||
this.#saveAssets(this.rootMeta, this.assetsMeta);
|
this.#saveAssets(rootMeta, this.assetsMeta);
|
||||||
this.#saveIndex();
|
this.#saveIndex(rootMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
#saveIndex() {
|
mapExtension(_type: string | null, _mime: string, _existingExtension: string, _format: string): string | null {
|
||||||
|
return "html";
|
||||||
|
}
|
||||||
|
|
||||||
|
#saveIndex(rootMeta: NoteMeta) {
|
||||||
if (!this.indexMeta?.dataFileName) {
|
if (!this.indexMeta?.dataFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const note = this.branch.getNote();
|
const note = this.branch.getNote();
|
||||||
const fullHtml = this.prepareContent(this.rootMeta.title ?? "", note.getContent(), this.rootMeta, note, this.branch);
|
const fullHtml = this.prepareContent(rootMeta.title ?? "", note.getContent(), rootMeta, note, this.branch);
|
||||||
this.archive.append(fullHtml, { name: this.indexMeta.dataFileName });
|
this.archive.append(fullHtml, { name: this.indexMeta.dataFileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user