diff --git a/apps/server/src/services/import/single.ts b/apps/server/src/services/import/single.ts
index 5abb6008a..7200d17d5 100644
--- a/apps/server/src/services/import/single.ts
+++ b/apps/server/src/services/import/single.ts
@@ -1,5 +1,4 @@
import type { NoteType } from "@triliumnext/commons";
-import { extname } from "path";
import type BNote from "../../becca/entities/bnote.js";
import imageService from "../../services/image.js";
@@ -55,13 +54,14 @@ function importImage(file: File, parentNote: BNote, taskContext: TaskContext<"im
function importFile(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) {
const originalName = file.originalname;
+ const mime = mimeService.getMime(originalName) || file.mimetype;
const { note } = noteService.createNewNote({
parentNoteId: parentNote.noteId,
- title: removeFileExtension(originalName),
+ title: getNoteTitle(originalName, mime === "application/pdf"),
content: file.buffer,
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
type: "file",
- mime: mimeService.getMime(originalName) || file.mimetype
+ mime
});
note.addLabel("originalFileName", originalName);
@@ -71,17 +71,6 @@ function importFile(taskContext: TaskContext<"importNotes">, file: File, parentN
return note;
}
-function removeFileExtension(filename: string) {
- const extension = extname(filename).toLowerCase();
-
- switch (extension) {
- case ".pdf":
- return filename.substring(0, filename.length - extension.length);
- default:
- return filename;
- }
-}
-
function importCodeNote(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) {
const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
const content = processStringOrBuffer(file.buffer);
diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts
index c1ac90b91..2d2251cb3 100644
--- a/apps/server/src/services/import/zip.ts
+++ b/apps/server/src/services/import/zip.ts
@@ -1,26 +1,27 @@
-"use strict";
-import BAttribute from "../../becca/entities/battribute.js";
-import { removeTextFileExtension, newEntityId, getNoteTitle, processStringOrBuffer, unescapeHtml } from "../../services/utils.js";
-import log from "../../services/log.js";
-import noteService from "../../services/notes.js";
-import attributeService from "../../services/attributes.js";
-import BBranch from "../../becca/entities/bbranch.js";
+
+import { ALLOWED_NOTE_TYPES, type NoteType } from "@triliumnext/commons";
import path from "path";
-import protectedSessionService from "../protected_session.js";
-import mimeService from "./mime.js";
-import treeService from "../tree.js";
+import type { Stream } from "stream";
import yauzl from "yauzl";
-import htmlSanitizer from "../html_sanitizer.js";
+
import becca from "../../becca/becca.js";
import BAttachment from "../../becca/entities/battachment.js";
-import markdownService from "./markdown.js";
-import type TaskContext from "../task_context.js";
+import BAttribute from "../../becca/entities/battribute.js";
+import BBranch from "../../becca/entities/bbranch.js";
import type BNote from "../../becca/entities/bnote.js";
-import type NoteMeta from "../meta/note_meta.js";
+import attributeService from "../../services/attributes.js";
+import log from "../../services/log.js";
+import noteService from "../../services/notes.js";
+import { getNoteTitle, newEntityId, processStringOrBuffer, removeFileExtension, unescapeHtml } from "../../services/utils.js";
+import htmlSanitizer from "../html_sanitizer.js";
import type AttributeMeta from "../meta/attribute_meta.js";
-import type { Stream } from "stream";
-import { ALLOWED_NOTE_TYPES, type NoteType } from "@triliumnext/commons";
+import type NoteMeta from "../meta/note_meta.js";
+import protectedSessionService from "../protected_session.js";
+import type TaskContext from "../task_context.js";
+import treeService from "../tree.js";
+import markdownService from "./markdown.js";
+import mimeService from "./mime.js";
interface MetaFile {
files: NoteMeta[];
@@ -108,7 +109,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
dataFileName: ""
};
- let parent: NoteMeta | undefined = undefined;
+ let parent: NoteMeta | undefined;
for (let segment of pathSegments) {
if (!cursor?.children?.length) {
@@ -161,7 +162,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
// in case we lack metadata, we treat e.g. "Programming.html" and "Programming" as the same note
// (one data file, the other directory for children)
- const filePathNoExt = removeTextFileExtension(filePath);
+ const filePathNoExt = removeFileExtension(filePath);
if (filePathNoExt in createdPaths) {
return createdPaths[filePathNoExt];
@@ -241,10 +242,10 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
}
const { note } = noteService.createNewNote({
- parentNoteId: parentNoteId,
+ parentNoteId,
title: noteTitle || "",
content: "",
- noteId: noteId,
+ noteId,
type: resolveNoteType(noteMeta?.type),
mime: noteMeta ? noteMeta.mime : "text/html",
prefix: noteMeta?.prefix || "",
@@ -294,12 +295,12 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
noteId: getNewNoteId(noteMeta.noteId)
};
- } else {
- // don't check for noteMeta since it's not mandatory for notes
- return {
- noteId: getNoteId(noteMeta, absUrl)
- };
- }
+ }
+ // don't check for noteMeta since it's not mandatory for notes
+ return {
+ noteId: getNoteId(noteMeta, absUrl)
+ };
+
}
function processTextNoteContent(content: string, noteTitle: string, filePath: string, noteMeta?: NoteMeta) {
@@ -312,9 +313,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
content = content.replace(/
([^<]*)<\/h1>/gi, (match, text) => {
if (noteTitle.trim() === text.trim()) {
return ""; // remove whole H1 tag
- } else {
- return `${text}
`;
- }
+ }
+ return `${text}
`;
+
});
if (taskContext.data?.safeImport) {
@@ -347,9 +348,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
return `src="api/attachments/${target.attachmentId}/image/${path.basename(url)}"`;
} else if (target.noteId) {
return `src="api/images/${target.noteId}/${path.basename(url)}"`;
- } else {
- return match;
- }
+ }
+ return match;
+
});
content = content.replace(/href="([^"]*)"/g, (match, url) => {
@@ -373,9 +374,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
return `href="#root/${target.noteId}?viewMode=attachments&attachmentId=${target.attachmentId}"`;
} else if (target.noteId) {
return `href="#root/${target.noteId}"`;
- } else {
- return match;
- }
+ }
+ return match;
+
});
if (noteMeta) {
@@ -525,9 +526,9 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
}
({ note } = noteService.createNewNote({
- parentNoteId: parentNoteId,
+ parentNoteId,
title: noteTitle || "",
- content: content,
+ content,
noteId,
type,
mime,
@@ -536,7 +537,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu
// root notePosition should be ignored since it relates to the original document
// now import root should be placed after existing notes into new parent
notePosition: noteMeta && firstNote ? noteMeta.notePosition : undefined,
- isProtected: isProtected
+ isProtected
}));
createdNoteIds.add(note.noteId);
@@ -648,7 +649,7 @@ function streamToBuffer(stream: Stream): Promise {
export function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise {
return new Promise((res, rej) => {
- zipfile.openReadStream(entry, function (err, readStream) {
+ zipfile.openReadStream(entry, (err, readStream) => {
if (err) rej(err);
if (!readStream) throw new Error("Unable to read content.");
@@ -659,7 +660,7 @@ export function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise
export function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => Promise) {
return new Promise((res, rej) => {
- yauzl.fromBuffer(buffer, { lazyEntries: true, validateEntrySizes: false }, function (err, zipfile) {
+ yauzl.fromBuffer(buffer, { lazyEntries: true, validateEntrySizes: false }, (err, zipfile) => {
if (err) rej(err);
if (!zipfile) throw new Error("Unable to read zip file.");
@@ -691,9 +692,9 @@ function resolveNoteType(type: string | undefined): NoteType {
if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) {
return type as NoteType;
- } else {
- return "text";
- }
+ }
+ return "text";
+
}
export function removeTriliumTags(content: string) {
@@ -702,7 +703,7 @@ export function removeTriliumTags(content: string) {
"([^<]*)<\/title>"
];
for (const tag of tagsToRemove) {
- let re = new RegExp(tag, "gi");
+ const re = new RegExp(tag, "gi");
content = content.replace(re, "");
}
diff --git a/apps/server/src/services/utils.spec.ts b/apps/server/src/services/utils.spec.ts
index d767d2d25..1a69d7dd1 100644
--- a/apps/server/src/services/utils.spec.ts
+++ b/apps/server/src/services/utils.spec.ts
@@ -1,4 +1,5 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect,it } from "vitest";
+
import utils from "./utils.js";
type TestCase any> = [desc: string, fnParams: Parameters, expected: ReturnType];
@@ -120,7 +121,7 @@ describe("#toObject", () => {
{ testPropA: "keyA", testPropB: "valueA" },
{ testPropA: "keyB", testPropB: "valueB" }
];
- const fn: TestListFn = (testListEntry: TestListEntry) => [ testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn" ];
+ const fn: TestListFn = (testListEntry: TestListEntry) => [ `${testListEntry.testPropA }_fn`, `${testListEntry.testPropB }_fn` ];
const result = utils.toObject(testList, fn);
expect(result).toStrictEqual({
@@ -240,8 +241,8 @@ describe.todo("#quoteRegex", () => {});
describe.todo("#replaceAll", () => {});
-describe("#removeTextFileExtension", () => {
- const testCases: TestCase[] = [
+describe("#removeFileExtension", () => {
+ const testCases: TestCase[] = [
[ "w/ 'test.md' it should strip '.md'", [ "test.md" ], "test" ],
[ "w/ 'test.markdown' it should strip '.markdown'", [ "test.markdown" ], "test" ],
[ "w/ 'test.html' it should strip '.html'", [ "test.html" ], "test" ],
@@ -252,7 +253,7 @@ describe("#removeTextFileExtension", () => {
testCases.forEach((testCase) => {
const [ desc, fnParams, expected ] = testCase;
it(desc, () => {
- const result = utils.removeTextFileExtension(...fnParams);
+ const result = utils.removeFileExtension(...fnParams);
expect(result).toStrictEqual(expected);
});
});
diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts
index 370f9297f..a97b84a6c 100644
--- a/apps/server/src/services/utils.ts
+++ b/apps/server/src/services/utils.ts
@@ -1,18 +1,19 @@
-"use strict";
+
import chardet from "chardet";
-import stripBom from "strip-bom";
import crypto from "crypto";
-import { generator } from "rand-token";
-import unescape from "unescape";
import escape from "escape-html";
-import sanitize from "sanitize-filename";
-import mimeTypes from "mime-types";
-import path from "path";
-import type NoteMeta from "./meta/note_meta.js";
-import log from "./log.js";
import { t } from "i18next";
+import mimeTypes from "mime-types";
import { release as osRelease } from "os";
+import path from "path";
+import { generator } from "rand-token";
+import sanitize from "sanitize-filename";
+import stripBom from "strip-bom";
+import unescape from "unescape";
+
+import log from "./log.js";
+import type NoteMeta from "./meta/note_meta.js";
const osVersion = osRelease().split('.').map(Number);
@@ -204,7 +205,7 @@ export function formatDownloadTitle(fileName: string, type: string | null, mime:
return `${fileNameBase}${getExtension()}`;
}
-export function removeTextFileExtension(filePath: string) {
+export function removeFileExtension(filePath: string) {
const extension = path.extname(filePath).toLowerCase();
switch (extension) {
@@ -216,6 +217,7 @@ export function removeTextFileExtension(filePath: string) {
case ".excalidraw":
case ".mermaid":
case ".mmd":
+ case ".pdf":
return filePath.substring(0, filePath.length - extension.length);
default:
return filePath;
@@ -226,7 +228,7 @@ export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boo
const trimmedNoteMeta = noteMeta?.title?.trim();
if (trimmedNoteMeta) return trimmedNoteMeta;
- const basename = path.basename(removeTextFileExtension(filePath));
+ const basename = path.basename(removeFileExtension(filePath));
return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename;
}
@@ -467,28 +469,28 @@ export function normalizeCustomHandlerPattern(pattern: string | null | undefined
// If already ends with slash, create both versions
if (basePattern.endsWith('/')) {
- const withoutSlash = basePattern.slice(0, -1) + '$';
+ const withoutSlash = `${basePattern.slice(0, -1) }$`;
const withSlash = pattern;
return [withoutSlash, withSlash];
- } else {
- // Add optional trailing slash
- const withSlash = basePattern + '/?$';
- return [withSlash];
}
+ // Add optional trailing slash
+ const withSlash = `${basePattern }/?$`;
+ return [withSlash];
+
}
// For patterns without $, add both versions
if (pattern.endsWith('/')) {
const withoutSlash = pattern.slice(0, -1);
return [withoutSlash, pattern];
- } else {
- const withSlash = pattern + '/';
- return [pattern, withSlash];
}
+ const withSlash = `${pattern }/`;
+ return [pattern, withSlash];
+
}
export function formatUtcTime(time: string) {
- return time.replace("T", " ").substring(0, 19)
+ return time.replace("T", " ").substring(0, 19);
}
// TODO: Deduplicate with client utils
@@ -501,9 +503,9 @@ export function formatSize(size: number | null | undefined) {
if (size < 1024) {
return `${size} KiB`;
- } else {
- return `${Math.round(size / 102.4) / 10} MiB`;
}
+ return `${Math.round(size / 102.4) / 10} MiB`;
+
}
function slugify(text: string) {
@@ -544,7 +546,7 @@ export default {
randomSecureToken,
randomString,
removeDiacritic,
- removeTextFileExtension,
+ removeFileExtension,
replaceAll,
safeExtractMessageAndStackFromError,
sanitizeSqlIdentifier,