feat(export/share): render non-text note types

This commit is contained in:
Elian Doran
2025-06-23 20:00:40 +03:00
parent 35622a2122
commit b475037127
4 changed files with 60 additions and 64 deletions

View File

@@ -2,6 +2,7 @@ import { Archiver } from "archiver";
import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js";
import type BNote from "../../../becca/entities/bnote.js";
import type BBranch from "../../../becca/entities/bbranch.js";
import mimeTypes from "mime-types";
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
@@ -24,8 +25,6 @@ export interface AdvancedExportOptions {
export interface ZipExportProviderData {
branch: BBranch;
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
metaFile: NoteMetaFile;
rootMeta: NoteMeta;
archive: Archiver;
zipExportOptions?: AdvancedExportOptions;
rewriteFn: RewriteLinksFn;
@@ -33,24 +32,46 @@ export interface ZipExportProviderData {
export abstract class ZipExportProvider {
branch: BBranch;
metaFile: NoteMetaFile;
getNoteTargetUrl: (targetNoteId: string, sourceMeta: NoteMeta) => string | null;
rootMeta: NoteMeta;
archive: Archiver;
zipExportOptions?: AdvancedExportOptions;
rewriteFn: RewriteLinksFn;
constructor(data: ZipExportProviderData) {
this.branch = data.branch;
this.metaFile = data.metaFile;
this.getNoteTargetUrl = data.getNoteTargetUrl;
this.rootMeta = data.rootMeta;
this.archive = data.archive;
this.zipExportOptions = data.zipExportOptions;
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 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";
}
}
}
}

View File

@@ -10,27 +10,24 @@ export default class HtmlExportProvider extends ZipExportProvider {
private indexMeta: NoteMeta | null = null;
private cssMeta: NoteMeta | null = null;
prepareMeta() {
prepareMeta(metaFile) {
this.navigationMeta = {
noImport: true,
dataFileName: "navigation.html"
};
this.metaFile.files.push(this.navigationMeta);
metaFile.files.push(this.navigationMeta);
this.indexMeta = {
noImport: true,
dataFileName: "index.html"
};
this.metaFile.files.push(this.indexMeta);
metaFile.files.push(this.indexMeta);
this.cssMeta = {
noImport: true,
dataFileName: "style.css"
};
this.metaFile.files.push(this.cssMeta);
metaFile.files.push(this.cssMeta);
}
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) {
throw new Error("Missing meta.");
}
this.#saveNavigation(this.rootMeta, this.navigationMeta);
this.#saveIndex(this.rootMeta, this.indexMeta);
this.#saveCss(this.rootMeta, this.cssMeta);
this.#saveNavigation(rootMeta, this.navigationMeta);
this.#saveIndex(rootMeta, this.indexMeta);
this.#saveCss(rootMeta, this.cssMeta);
}
#saveNavigationInner(meta: NoteMeta) {
#saveNavigationInner(rootMeta: NoteMeta, meta: NoteMeta) {
let html = "<li>";
const escapedTitle = escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ""}${meta.title}`);
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>`;
} else {
@@ -99,7 +96,7 @@ export default class HtmlExportProvider extends ZipExportProvider {
html += "<ul>";
for (const child of meta.children) {
html += this.#saveNavigationInner(child);
html += this.#saveNavigationInner(rootMeta, child);
}
html += "</ul>";
@@ -119,7 +116,7 @@ export default class HtmlExportProvider extends ZipExportProvider {
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul>${this.#saveNavigationInner(rootMeta)}</ul>
<ul>${this.#saveNavigationInner(rootMeta, rootMeta)}</ul>
</body>
</html>`;
const prettyHtml = fullHtml.length < 100_000 ? html.prettyPrint(fullHtml, { indent_size: 2 }) : fullHtml;

View File

@@ -1,5 +1,5 @@
import { join } from "path";
import NoteMeta from "../../meta/note_meta";
import NoteMeta, { NoteMetaFile } from "../../meta/note_meta";
import { ZipExportProvider } from "./abstract_provider";
import { RESOURCE_DIR } from "../../resource_dir";
import { getResourceDir, isDev } from "../../utils";
@@ -13,7 +13,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
private assetsMeta: NoteMeta[] = [];
private indexMeta: NoteMeta | null = null;
prepareMeta(): void {
prepareMeta(metaFile: NoteMetaFile): void {
const assets = [
"style.css",
"script.js",
@@ -32,7 +32,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
dataFileName: asset
};
this.assetsMeta.push(assetMeta);
this.metaFile.files.push(assetMeta);
metaFile.files.push(assetMeta);
}
this.indexMeta = {
@@ -40,7 +40,7 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
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 {
@@ -58,18 +58,22 @@ export default class ShareThemeExportProvider extends ZipExportProvider {
return content;
}
afterDone(): void {
this.#saveAssets(this.rootMeta, this.assetsMeta);
this.#saveIndex();
afterDone(rootMeta: NoteMeta): void {
this.#saveAssets(rootMeta, this.assetsMeta);
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) {
return;
}
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 });
}