From c0593707f2d4677a2faef95d62f0c1a58dbf804e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 26 Mar 2026 21:41:11 +0200 Subject: [PATCH] refactor(core): use own path replacement --- .../trilium-core/src/services/utils/index.ts | 11 +++-- .../src/services/utils/path.spec.ts | 41 +++++++++++++++++++ .../trilium-core/src/services/utils/path.ts | 18 ++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 packages/trilium-core/src/services/utils/path.spec.ts create mode 100644 packages/trilium-core/src/services/utils/path.ts diff --git a/packages/trilium-core/src/services/utils/index.ts b/packages/trilium-core/src/services/utils/index.ts index b2f1188ee1..cd76e0325b 100644 --- a/packages/trilium-core/src/services/utils/index.ts +++ b/packages/trilium-core/src/services/utils/index.ts @@ -4,7 +4,7 @@ import { encodeBase64 } from "./binary"; import { extensions as mimeToExt, types as extToMime } from "mime-types"; import escape from "escape-html"; import unescape from "unescape"; -import path from "path"; +import { basename, extname } from "./path"; import { NoteMeta } from "../../meta"; // TODO: Implement platform detection. @@ -161,8 +161,7 @@ export function formatDownloadTitle(fileName: string, type: string | null, mime: if (mimeLc === "application/octet-stream") return ""; // if fileName has an extension matching the mime already - reuse it - const dotIdx = fileName.lastIndexOf("."); - const ext = dotIdx !== -1 ? fileName.substring(dotIdx + 1).toLowerCase() : ""; + const ext = extname(fileName).toLowerCase().replace(".", ""); if (ext && extToMime[ext] === mimeLc) return ""; // as last resort try to get extension from mimeType @@ -207,7 +206,7 @@ export function escapeRegExp(str: string) { } export function removeFileExtension(filePath: string, mime?: string) { - const extension = path.extname(filePath).toLowerCase(); + const extension = extname(filePath).toLowerCase(); if (mime?.startsWith("video/") || mime?.startsWith("audio/")) { return filePath.substring(0, filePath.length - extension.length); @@ -233,8 +232,8 @@ export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boo const trimmedNoteMeta = noteMeta?.title?.trim(); if (trimmedNoteMeta) return trimmedNoteMeta; - const basename = path.basename(removeFileExtension(filePath, noteMeta?.mime)); - return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename; + const fileBasename = basename(removeFileExtension(filePath, noteMeta?.mime)); + return replaceUnderscoresWithSpaces ? fileBasename.replace(/_/g, " ").trim() : fileBasename; } // try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined diff --git a/packages/trilium-core/src/services/utils/path.spec.ts b/packages/trilium-core/src/services/utils/path.spec.ts new file mode 100644 index 0000000000..8543617def --- /dev/null +++ b/packages/trilium-core/src/services/utils/path.spec.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from "vitest"; +import { extname, basename } from "./path"; + +describe("#extname", () => { + const testCases: [input: string, expected: string][] = [ + ["file.txt", ".txt"], + ["file.tar.gz", ".gz"], + ["file", ""], + [".hidden", ""], + [".hidden.txt", ".txt"], + ["no-ext.", "."], + ["path/to/file.ts", ".ts"], + ["path\\to\\file.ts", ".ts"], + ["path/to/.gitignore", ""], + ["", ""], + ]; + + testCases.forEach(([input, expected]) => { + it(`'${input}' should return '${expected}'`, () => { + expect(extname(input)).toBe(expected); + }); + }); +}); + +describe("#basename", () => { + const testCases: [input: string, expected: string][] = [ + ["path/to/file.txt", "file.txt"], + ["path\\to\\file.txt", "file.txt"], + ["file.txt", "file.txt"], + ["/root/file", "file"], + ["C:\\Users\\test\\file.md", "file.md"], + ["path/to/dir/", ""], + ["", ""], + ]; + + testCases.forEach(([input, expected]) => { + it(`'${input}' should return '${expected}'`, () => { + expect(basename(input)).toBe(expected); + }); + }); +}); diff --git a/packages/trilium-core/src/services/utils/path.ts b/packages/trilium-core/src/services/utils/path.ts new file mode 100644 index 0000000000..d470b5dc16 --- /dev/null +++ b/packages/trilium-core/src/services/utils/path.ts @@ -0,0 +1,18 @@ +/** + * Browser-safe path utilities that don't depend on Node's `path` module. + * Handles both forward slashes and backslashes. + */ + +/** Returns the extension of a file path (e.g. ".txt"), or "" if none. */ +export function extname(filePath: string): string { + const base = basename(filePath); + const dotIdx = base.lastIndexOf("."); + if (dotIdx <= 0) return ""; + return base.substring(dotIdx); +} + +/** Returns the last component of a file path. */ +export function basename(filePath: string): string { + const lastSlash = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")); + return filePath.substring(lastSlash + 1); +}