fix(server): unable to export as share

This commit is contained in:
Elian Doran
2026-03-27 23:52:19 +02:00
parent aee005b624
commit 8cd7e48e85
10 changed files with 71 additions and 27 deletions

View File

@@ -0,0 +1,16 @@
import { type ExportFormat, type ZipExportProviderData, ZipExportProvider } from "@triliumnext/core";
export async function standaloneZipExportProviderFactory(format: ExportFormat, data: ZipExportProviderData): Promise<ZipExportProvider> {
switch (format) {
case "html": {
const { default: HtmlExportProvider } = await import("@triliumnext/core/src/services/export/zip/html.js");
return new HtmlExportProvider(data);
}
case "markdown": {
const { default: MarkdownExportProvider } = await import("@triliumnext/core/src/services/export/zip/markdown.js");
return new MarkdownExportProvider(data);
}
default:
throw new Error(`Unsupported export format: '${format}'`);
}
}

View File

@@ -157,6 +157,7 @@ async function initialize(): Promise<void> {
executionContext: new BrowserExecutionContext(),
crypto: new BrowserCryptoProvider(),
zip: new BrowserZipProvider(),
zipExportProviderFactory: (await import("./lightweight/zip_export_provider_factory.js")).standaloneZipExportProviderFactory,
messaging: messagingProvider!,
request: new FetchRequestProvider(),
platform: new StandalonePlatformProvider(queryString),

View File

@@ -135,6 +135,7 @@ async function main() {
},
crypto: new NodejsCryptoProvider(),
zip: new NodejsZipProvider(),
zipExportProviderFactory: (await import("@triliumnext/server/src/services/export/zip/factory.js")).serverZipExportProviderFactory,
request: new NodeRequestProvider(),
executionContext: new ClsHookedExecutionContext(),
messaging: new WebSocketMessagingProvider(),

View File

@@ -2,6 +2,7 @@ import { beforeAll } from "vitest";
import { readFileSync } from "fs";
import { join } from "path";
import { initializeCore } from "@triliumnext/core";
import { serverZipExportProviderFactory } from "../src/services/export/zip/factory.js";
import ClsHookedExecutionContext from "../src/cls_provider.js";
import NodejsCryptoProvider from "../src/crypto_provider.js";
import NodejsZipProvider from "../src/zip_provider.js";
@@ -29,6 +30,7 @@ beforeAll(async () => {
},
crypto: new NodejsCryptoProvider(),
zip: new NodejsZipProvider(),
zipExportProviderFactory: serverZipExportProviderFactory,
executionContext: new ClsHookedExecutionContext(),
schema: readFileSync(require.resolve("@triliumnext/core/src/assets/schema.sql"), "utf-8"),
platform: new ServerPlatformProvider(),

View File

@@ -3,7 +3,7 @@
* are loaded later and will result in an empty string.
*/
import { getLog,initializeCore, sql_init } from "@triliumnext/core";
import { getLog, initializeCore, sql_init } from "@triliumnext/core";
import fs from "fs";
import { t } from "i18next";
import path from "path";
@@ -53,6 +53,7 @@ async function startApplication() {
},
crypto: new NodejsCryptoProvider(),
zip: new NodejsZipProvider(),
zipExportProviderFactory: (await import("./services/export/zip/factory.js")).serverZipExportProviderFactory,
request: new NodeRequestProvider(),
executionContext: new ClsHookedExecutionContext(),
messaging: new WebSocketMessagingProvider(),

View File

@@ -0,0 +1,20 @@
import { type ExportFormat, type ZipExportProviderData, ZipExportProvider } from "@triliumnext/core";
export async function serverZipExportProviderFactory(format: ExportFormat, data: ZipExportProviderData): Promise<ZipExportProvider> {
switch (format) {
case "html": {
const { default: HtmlExportProvider } = await import("@triliumnext/core/src/services/export/zip/html.js");
return new HtmlExportProvider(data);
}
case "markdown": {
const { default: MarkdownExportProvider } = await import("@triliumnext/core/src/services/export/zip/markdown.js");
return new MarkdownExportProvider(data);
}
case "share": {
const { default: ShareThemeExportProvider } = await import("./share_theme.js");
return new ShareThemeExportProvider(data);
}
default:
throw new Error(`Unsupported export format: '${format}'`);
}
}

View File

@@ -1,6 +1,5 @@
import { renderSpreadsheetToHtml } from "@triliumnext/commons";
import { sanitize } from "@triliumnext/core";
import { icon_packs as iconPackService } from "@triliumnext/core";
import { icon_packs as iconPackService, sanitize, utils } from "@triliumnext/core";
import { highlightAuto } from "@triliumnext/highlightjs";
import ejs from "ejs";
import escapeHtml from "escape-html";
@@ -16,7 +15,7 @@ import BNote from "../becca/entities/bnote.js";
import assetPath, { assetUrlFragment } from "../services/asset_path.js";
import log from "../services/log.js";
import options from "../services/options.js";
import utils, { getResourceDir, isDev, safeExtractMessageAndStackFromError } from "../services/utils.js";
import { getResourceDir, isDev } from "../services/utils.js";
import SAttachment from "./shaca/entities/sattachment.js";
import SBranch from "./shaca/entities/sbranch.js";
import type SNote from "./shaca/entities/snote.js";
@@ -224,7 +223,7 @@ function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs)
return ejs.render(content, opts, { includer });
}
} catch (e: unknown) {
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
const [errMessage, errStack] = utils.safeExtractMessageAndStackFromError(e);
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
}
}

View File

@@ -10,6 +10,7 @@ import { initSchema, initDemoArchive } from "./services/sql_init";
import appInfo from "./services/app_info";
import { type PlatformProvider, initPlatform } from "./services/platform";
import { type ZipProvider, initZipProvider } from "./services/zip_provider";
import { type ZipExportProviderFactory, initZipExportProviderFactory } from "./services/export/zip_export_provider_factory";
import markdown from "./services/import/markdown";
export { getLog } from "./services/log";
@@ -104,8 +105,9 @@ export * as routeHelpers from "./routes/helpers";
export { getZipProvider, type ZipArchive, type ZipProvider } from "./services/zip_provider";
export { default as zipImportService } from "./services/import/zip";
export { default as zipExportService } from "./services/export/zip";
export { type AdvancedExportOptions } from "./services/export/zip/abstract_provider";
export { type AdvancedExportOptions, type ZipExportProviderData } from "./services/export/zip/abstract_provider";
export { ZipExportProvider } from "./services/export/zip/abstract_provider";
export { type ZipExportProviderFactory } from "./services/export/zip_export_provider_factory";
export { type ExportFormat } from "./meta";
export * as becca_easy_mocking from "./test/becca_easy_mocking";
@@ -113,7 +115,7 @@ export * as becca_mocking from "./test/becca_mocking";
export { default as markdownImportService } from "./services/import/markdown";
export async function initializeCore({ dbConfig, executionContext, crypto, zip, translations, messaging, request, schema, extraAppInfo, platform, getDemoArchive }: {
export async function initializeCore({ dbConfig, executionContext, crypto, zip, zipExportProviderFactory, translations, messaging, request, schema, extraAppInfo, platform, getDemoArchive }: {
dbConfig: SqlServiceParams,
executionContext: ExecutionContext,
crypto: CryptoProvider,
@@ -121,6 +123,7 @@ export async function initializeCore({ dbConfig, executionContext, crypto, zip,
translations: TranslationProvider,
platform: PlatformProvider,
schema: string,
zipExportProviderFactory: ZipExportProviderFactory,
messaging?: MessagingProvider,
request?: RequestProvider,
getDemoArchive?: () => Promise<Uint8Array | null>,
@@ -134,6 +137,7 @@ export async function initializeCore({ dbConfig, executionContext, crypto, zip,
await initTranslations(translations);
initCrypto(crypto);
initZipProvider(zip);
initZipExportProviderFactory(zipExportProviderFactory);
initContext(executionContext);
initSql(new SqlService(dbConfig, getLog()));
initSchema(schema);

View File

@@ -11,27 +11,22 @@ import protectedSessionService from "../protected_session.js";
import TaskContext from "../task_context.js";
import { getZipProvider } from "../zip_provider.js";
import { getContentDisposition } from "../utils/index"
import { AdvancedExportOptions, ZipExportProvider, ZipExportProviderData } from "./zip/abstract_provider.js";
import HtmlExportProvider from "./zip/html.js";
import MarkdownExportProvider from "./zip/markdown.js";
import { AdvancedExportOptions, ZipExportProviderData } from "./zip/abstract_provider.js";
import { getZipExportProviderFactory } from "./zip_export_provider_factory.js";
import { AttachmentMeta, AttributeMeta, ExportFormat, NoteMeta, NoteMetaFile } from "../../meta";
import { ValidationError } from "../../errors";
import { extname } from "../utils/path";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch, format: ExportFormat, res: Record<string, any>, setHeaders = true, zipExportOptions?: AdvancedExportOptions) {
if (!["html", "markdown", "share"].includes(format)) {
throw new ValidationError(`Only 'html', 'markdown' and 'share' allowed as export format, '${format}' given`);
}
const archive = getZipProvider().createZipArchive();
const rewriteFn = (zipExportOptions?.customRewriteLinks ? zipExportOptions?.customRewriteLinks(rewriteLinks, getNoteTargetUrl) : rewriteLinks);
const provider = buildProvider();
const provider = await buildProvider();
const log = getLog();
const noteIdToMeta: Record<string, NoteMeta> = {};
function buildProvider(): ZipExportProvider {
async function buildProvider() {
const providerData: ZipExportProviderData = {
getNoteTargetUrl,
archive,
@@ -40,17 +35,7 @@ async function exportToZip(taskContext: TaskContext<"export">, branch: BBranch,
zipExportOptions
};
switch (format) {
case "html":
return new HtmlExportProvider(providerData);
case "markdown":
return new MarkdownExportProvider(providerData);
case "share":
// TODO: Reintroduce share format.
// return new ShareThemeExportProvider(providerData);
default:
throw new Error();
}
return getZipExportProviderFactory()(format, providerData);
}
function getUniqueFilename(existingFileNames: Record<string, number>, fileName: string) {

View File

@@ -0,0 +1,15 @@
import type { ExportFormat } from "../../meta.js";
import type { ZipExportProvider, ZipExportProviderData } from "./zip/abstract_provider.js";
export type ZipExportProviderFactory = (format: ExportFormat, data: ZipExportProviderData) => Promise<ZipExportProvider>;
let factory: ZipExportProviderFactory | null = null;
export function initZipExportProviderFactory(f: ZipExportProviderFactory) {
factory = f;
}
export function getZipExportProviderFactory(): ZipExportProviderFactory {
if (!factory) throw new Error("ZipExportProviderFactory not initialized.");
return factory;
}