feat(export/zip): get CSS to load

This commit is contained in:
Elian Doran 2025-06-13 23:47:04 +03:00
parent f189deb415
commit 4d5e866db6
No known key found for this signature in database
3 changed files with 60 additions and 20 deletions

View File

@ -2,11 +2,11 @@
import html from "html"; import html from "html";
import dateUtils from "../date_utils.js"; import dateUtils from "../date_utils.js";
import path from "path"; import path, { join } from "path";
import mimeTypes from "mime-types"; import mimeTypes from "mime-types";
import mdService from "./markdown.js"; import mdService from "./markdown.js";
import packageInfo from "../../../package.json" with { type: "json" }; import packageInfo from "../../../package.json" with { type: "json" };
import { getContentDisposition, escapeHtml, getResourceDir } from "../utils.js"; import { getContentDisposition, escapeHtml, getResourceDir, isDev } from "../utils.js";
import protectedSessionService from "../protected_session.js"; import protectedSessionService from "../protected_session.js";
import sanitize from "sanitize-filename"; import sanitize from "sanitize-filename";
import fs from "fs"; import fs from "fs";
@ -22,7 +22,7 @@ import type BBranch from "../../becca/entities/bbranch.js";
import type BNote from "../../becca/entities/bnote.js"; import type BNote from "../../becca/entities/bnote.js";
import type { Response } from "express"; import type { Response } from "express";
import type { NoteMetaFile } from "../meta/note_meta.js"; import type { NoteMetaFile } from "../meta/note_meta.js";
import cssContent from "@triliumnext/ckeditor5/content.css"; //import cssContent from "@triliumnext/ckeditor5/content.css";
import { renderNoteForExport } from "../../share/content_renderer.js"; import { renderNoteForExport } from "../../share/content_renderer.js";
type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string; type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string;
@ -328,12 +328,13 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
throw new Error("Missing note path."); throw new Error("Missing note path.");
} }
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`; const basePath = "../".repeat(noteMeta.notePath.length - 1);
const htmlTitle = escapeHtml(title); const htmlTitle = escapeHtml(title);
if (note) { if (note) {
content = renderNoteForExport(note, branch); content = renderNoteForExport(note, branch, basePath);
} else { } else {
const cssUrl = basePath + "style.css";
// <base> element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809 // <base> element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809
content = `<html> content = `<html>
<head> <head>
@ -518,6 +519,7 @@ ${markdownContent}`;
return; return;
} }
let cssContent = getShareThemeAssets("css");
archive.append(cssContent, { name: cssMeta.dataFileName }); archive.append(cssContent, { name: cssMeta.dataFileName });
} }
@ -629,6 +631,19 @@ async function exportToZipFile(noteId: string, format: "markdown" | "html", zipF
log.info(`Exported '${noteId}' with format '${format}' to '${zipFilePath}'`); log.info(`Exported '${noteId}' with format '${format}' to '${zipFilePath}'`);
} }
function getShareThemeAssets(extension: string) {
let path: string | undefined;
if (isDev) {
path = join(getResourceDir(), "..", "..", "client", "dist", "src", `share.${extension}`);
}
if (!path) {
throw new Error("Not yet defined.");
}
return fs.readFileSync(path, "utf-8");
}
export default { export default {
exportToZip, exportToZip,
exportToZipFile exportToZipFile

View File

@ -16,6 +16,8 @@ import log from "../services/log.js";
import { join } from "path"; import { join } from "path";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
const shareAdjustedAssetPath = isDev ? assetPath : `../${assetPath}`;
/** /**
* Represents the output of the content renderer. * Represents the output of the content renderer.
*/ */
@ -58,20 +60,50 @@ function getSharedSubTreeRoot(note: SNote | BNote | undefined): Subroot {
return getSharedSubTreeRoot(parentBranch.getParentNote()); return getSharedSubTreeRoot(parentBranch.getParentNote());
} }
export function renderNoteForExport(note: BNote, parentBranch: BBranch) { export function renderNoteForExport(note: BNote, parentBranch: BBranch, basePath: string) {
const subRoot: Subroot = { const subRoot: Subroot = {
branch: parentBranch, branch: parentBranch,
note: parentBranch.getNote() note: parentBranch.getNote()
}; };
return renderNoteContentInternal(note, subRoot, note.getParentNotes()[0].noteId);
return renderNoteContentInternal(note, {
subRoot,
rootNoteId: note.getParentNotes()[0].noteId,
cssToLoad: [
`${basePath}style.css`
]
});
} }
export function renderNoteContent(note: SNote) { export function renderNoteContent(note: SNote) {
const subRoot = getSharedSubTreeRoot(note); const subRoot = getSharedSubTreeRoot(note);
return renderNoteContentInternal(note, subRoot, "_share");
// Determine CSS to load.
const cssToLoad: string[] = [];
if (!isDev && !note.isLabelTruthy("shareOmitDefaultCss")) {
cssToLoad.push(`${shareAdjustedAssetPath}/src/share.css`);
cssToLoad.push(`${shareAdjustedAssetPath}/src/boxicons.css`);
}
// Support custom CSS too.
for (const cssRelation of note.getRelations("shareCss")) {
cssToLoad.push(`api/notes/${cssRelation.value}/download`);
}
return renderNoteContentInternal(note, {
subRoot,
rootNoteId: "_share",
cssToLoad
});
} }
function renderNoteContentInternal(note: SNote | BNote, subRoot: Subroot, rootNoteId: string) { interface RenderArgs {
subRoot: Subroot;
rootNoteId: string;
cssToLoad: string[];
}
function renderNoteContentInternal(note: SNote | BNote, renderArgs: RenderArgs) {
const { header, content, isEmpty } = getContent(note); const { header, content, isEmpty } = getContent(note);
const showLoginInShareTheme = options.getOption("showLoginInShareTheme"); const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
const opts = { const opts = {
@ -79,14 +111,13 @@ function renderNoteContentInternal(note: SNote | BNote, subRoot: Subroot, rootNo
header, header,
content, content,
isEmpty, isEmpty,
subRoot, assetPath: shareAdjustedAssetPath,
assetPath: isDev ? assetPath : `../${assetPath}`,
assetUrlFragment, assetUrlFragment,
appPath: isDev ? app_path : `../${app_path}`, appPath: isDev ? app_path : `../${app_path}`,
showLoginInShareTheme, showLoginInShareTheme,
t, t,
isDev, isDev,
rootNoteId ...renderArgs
}; };
// Check if the user has their own template. // Check if the user has their own template.

View File

@ -6,14 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="<% if (note.hasRelation("shareFavicon")) { %>api/notes/<%= note.getRelation("shareFavicon").value %>/download<% } else { %>../favicon.ico<% } %>"> <link rel="shortcut icon" href="<% if (note.hasRelation("shareFavicon")) { %>api/notes/<%= note.getRelation("shareFavicon").value %>/download<% } else { %>../favicon.ico<% } %>">
<script src="<%= appPath %>/share.js" type="module"></script> <% for (const url of cssToLoad) { %>
<% if (!isDev && !note.isLabelTruthy("shareOmitDefaultCss")) { %> <link href="<%= url %>" rel="stylesheet">
<link href="<%= assetPath %>/src/share.css" rel="stylesheet">
<link href="<%= assetPath %>/src/boxicons.css" rel="stylesheet">
<% } %>
<% for (const cssRelation of note.getRelations("shareCss")) { %>
<link href="api/notes/<%= cssRelation.value %>/download" rel="stylesheet">
<% } %> <% } %>
<% for (const jsRelation of note.getRelations("shareJs")) { %> <% for (const jsRelation of note.getRelations("shareJs")) { %>
<script type="module" src="api/notes/<%= jsRelation.value %>/download"></script> <script type="module" src="api/notes/<%= jsRelation.value %>/download"></script>