Merge remote-tracking branch 'origin/main' into standalone

This commit is contained in:
Elian Doran
2026-04-09 12:57:06 +03:00
40 changed files with 514 additions and 345 deletions

View File

@@ -10,6 +10,7 @@ import helmet from "helmet";
import { t } from "i18next";
import path from "path";
import favicon from "serve-favicon";
import type serveStatic from "serve-static";
import assets from "./routes/assets.js";
import custom from "./routes/custom.js";
@@ -22,6 +23,9 @@ import openID from "./services/open_id.js";
import { RESOURCE_DIR } from "./services/resource_dir.js";
import utils, { getResourceDir, isDev } from "./services/utils.js";
// Allow serving assets even if the installation path contains a hidden (dot-prefixed) directory.
const STATIC_OPTIONS: serveStatic.ServeStaticOptions = { dotfiles: "allow" };
export default async function buildApp() {
const app = express();
@@ -97,10 +101,10 @@ export default async function buildApp() {
// localhost-only guard and does not require Trilium authentication.
mcpRoutes.register(app);
app.use(express.static(path.join(publicDir, "root")));
app.use(`/manifest.webmanifest`, express.static(path.join(publicAssetsDir, "manifest.webmanifest")));
app.use(`/robots.txt`, express.static(path.join(publicAssetsDir, "robots.txt")));
app.use(`/icon.png`, express.static(path.join(publicAssetsDir, "icon.png")));
app.use(express.static(path.join(publicDir, "root"), STATIC_OPTIONS));
app.use(`/manifest.webmanifest`, express.static(path.join(publicAssetsDir, "manifest.webmanifest"), STATIC_OPTIONS));
app.use(`/robots.txt`, express.static(path.join(publicAssetsDir, "robots.txt"), STATIC_OPTIONS));
app.use(`/icon.png`, express.static(path.join(publicAssetsDir, "icon.png"), STATIC_OPTIONS));
const { default: sessionParser, startSessionCleanup } = await import("./routes/session_parser.js");
app.use(sessionParser);

View File

@@ -365,7 +365,10 @@
"last-updated": "Last updated on {{- date}}",
"subpages": "Subpages:",
"on-this-page": "On This Page",
"expand": "Expand"
"expand": "Expand",
"toggle-navigation": "Toggle Navigation",
"toggle-toc": "Toggle Table of Contents",
"logo-alt": "Logo"
},
"hidden_subtree_templates": {
"text-snippet": "Text Snippet",

View File

@@ -9,6 +9,9 @@ import auth from "../services/auth.js";
import { getResourceDir, isDev } from "../services/utils.js";
import { doubleCsrfProtection as csrfMiddleware } from "./csrf_protection.js";
// Allow serving assets even if the installation path contains a hidden (dot-prefixed) directory.
const STATIC_OPTIONS: serveStatic.ServeStaticOptions = { dotfiles: "allow" };
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
if (!isDev) {
options = {
@@ -16,7 +19,7 @@ const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOp
...options
};
}
return express.static(root, options);
return express.static(root, { ...STATIC_OPTIONS, ...options });
};
async function register(app: express.Application) {
@@ -66,7 +69,7 @@ async function register(app: express.Application) {
// broken when closing the browser and coming back in to the page.
// The page is restored from cache, but the API call fail.
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.sendFile(path.join(publicDir, "index.html"));
res.sendFile(path.join(publicDir, "index.html"), STATIC_OPTIONS);
});
app.use("/assets", persistentCacheStatic(path.join(publicDir, "assets")));
app.use(`/src`, persistentCacheStatic(path.join(publicDir, "src")));
@@ -76,14 +79,14 @@ async function register(app: express.Application) {
app.use(`/${assetUrlFragment}/translations/`, persistentCacheStatic(path.join(publicDir, "translations")));
app.use(`/node_modules/`, persistentCacheStatic(path.join(publicDir, "node_modules")));
}
app.use(`/share/assets/fonts/`, express.static(path.join(getClientDir(), "fonts")));
app.use(`/share/assets/`, express.static(getShareThemeAssetDir()));
app.use(`/share/assets/fonts/`, express.static(path.join(getClientDir(), "fonts"), STATIC_OPTIONS));
app.use(`/share/assets/`, express.static(getShareThemeAssetDir(), STATIC_OPTIONS));
app.use(`/pdfjs/`, persistentCacheStatic(getPdfjsAssetDir()));
app.use(`/${assetUrlFragment}/images`, persistentCacheStatic(path.join(resourceDir, "assets", "images")));
app.use(`/${assetUrlFragment}/doc_notes`, persistentCacheStatic(path.join(resourceDir, "assets", "doc_notes")));
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts")));
app.use(`/assets/vX/images`, express.static(path.join(srcRoot, "..", "images")));
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets")));
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, "public/fonts"), STATIC_OPTIONS));
app.use(`/assets/vX/images`, express.static(path.join(srcRoot, "..", "images"), STATIC_OPTIONS));
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, "public/stylesheets"), STATIC_OPTIONS));
}
export function getShareThemeAssetDir() {

View File

@@ -148,8 +148,8 @@ export function renderNoteContent(note: SNote) {
isStatic: false,
faviconUrl: note.hasRelation("shareFavicon") ? `api/notes/${note.getRelationValue("shareFavicon")}/download` : `../favicon.ico`,
iconPackCss: iconPacks.map(p => iconPackService.generateCss(p, p.builtin
? `/share/assets/fonts/${p.fontAttachmentId}.${iconPackService.MIME_TO_EXTENSION_MAPPINGS[p.fontMime]}`
: `/share/api/attachments/${p.fontAttachmentId}/download`
? `assets/fonts/${p.fontAttachmentId}.${iconPackService.MIME_TO_EXTENSION_MAPPINGS[p.fontMime]}`
: `api/attachments/${p.fontAttachmentId}/download`
))
.filter(Boolean)
.join("\n\n"),

View File

@@ -56,13 +56,20 @@ export default class NodejsZipProvider implements ZipProvider {
processEntry: (entry: ZipEntry, readContent: () => Promise<Uint8Array>) => Promise<void>
): Promise<void> {
return new Promise<void>((res, rej) => {
yauzl.fromBuffer(Buffer.from(buffer), { lazyEntries: true, validateEntrySizes: false }, (err, zipfile) => {
yauzl.fromBuffer(Buffer.from(buffer), { lazyEntries: true, validateEntrySizes: false, decodeStrings: false }, (err, zipfile) => {
if (err) { rej(err); return; }
if (!zipfile) { rej(new Error("Unable to read zip file.")); return; }
zipfile.readEntry();
zipfile.on("entry", async (entry: yauzl.Entry) => {
try {
// yauzl with decodeStrings: false returns fileName as a Buffer.
// We decode as UTF-8 to handle ZIP files that use UTF-8 filenames
// without setting the general purpose bit flag 11 (language encoding flag).
const fileName = Buffer.isBuffer(entry.fileName)
? (entry.fileName as Buffer).toString("utf-8")
: entry.fileName;
const readContent = () => new Promise<Uint8Array>((res, rej) => {
zipfile.openReadStream(entry, (err, readStream) => {
if (err) { rej(err); return; }
@@ -71,7 +78,7 @@ export default class NodejsZipProvider implements ZipProvider {
});
});
await processEntry({ fileName: entry.fileName }, readContent);
await processEntry({ fileName }, readContent);
} catch (e) {
rej(e);
}