From 668cc7e1a41ef43faafed64ff2d94d0cfd7f56ce Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 22 Jan 2025 07:38:25 +0100 Subject: [PATCH 001/110] test(import/utils): prepare spec file --- src/services/utils.spec.ts | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/services/utils.spec.ts diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts new file mode 100644 index 000000000..c2712ccc9 --- /dev/null +++ b/src/services/utils.spec.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from "vitest"; +import utils from "./utils.js"; + +describe.todo("#newEntityId", () => {}); + +describe.todo("#randomString", () => {}); + +describe.todo("#randomSecureToken", () => {}); + +describe.todo("#md5", () => {}); + +describe.todo("#hashedBlobId", () => {}); + +describe.todo("#toBase64", () => {}); + +describe.todo("#fromBase64", () => {}); + +describe.todo("#hmac", () => {}); + +describe.todo("#isElectron", () => {}); + +describe.todo("#hash", () => {}); + +describe.todo("#isEmptyOrWhitespace", () => {}); + +describe.todo("#sanitizeSqlIdentifier", () => {}); + +describe.todo("#escapeHtml", () => {}); + +describe.todo("#unescapeHtml", () => {}); + +describe.todo("#toObject", () => {}); + +describe.todo("#stripTags", () => {}); + +describe.todo("#union", () => {}); + +describe.todo("#escapeRegExp", () => {}); + +describe.todo("#crash", () => {}); + +describe.todo("#sanitizeFilenameForHeader", () => {}); + +describe.todo("#getContentDisposition", () => {}); + +describe.todo("#isStringNote", () => {}); + +describe.todo("#quoteRegex", () => {}); + +describe.todo("#replaceAll", () => {}); + +// TriliumNextTODO move existing formatDownloadTitle in here +// describe.todo("#formatDownloadTitle", () => {}); + +describe.todo("#removeTextFileExtension", () => {}); + +describe.todo("#getNoteTitle", () => {}); + +describe.todo("#timeLimit", () => {}); + +describe.todo("#deferred", () => {}); + +describe.todo("#removeDiacritic", () => {}); + +describe.todo("#normalize", () => {}); + +describe.todo("#toMap", () => {}); + +describe.todo("#isString", () => {}); + +describe.todo("#getResourceDir", () => {}); + +describe.todo("#isMac", () => {}); + +describe.todo("#isWindows", () => {}); \ No newline at end of file From e6f5321444933a86e14da55b346141ff48c1cc23 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 22 Jan 2025 08:23:44 +0100 Subject: [PATCH 002/110] test(server/utils): move formatDownloadTitle tests to spec file --- .../utils.formatDownloadTitle.spec.ts | 61 --------- src/services/utils.spec.ts | 116 +++++++++++++++++- 2 files changed, 115 insertions(+), 62 deletions(-) delete mode 100644 src/services/utils.formatDownloadTitle.spec.ts diff --git a/src/services/utils.formatDownloadTitle.spec.ts b/src/services/utils.formatDownloadTitle.spec.ts deleted file mode 100644 index 0cc259e16..000000000 --- a/src/services/utils.formatDownloadTitle.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { expect, describe, it } from "vitest"; -import { formatDownloadTitle } from "./utils.js"; - -const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [ - // empty fileName tests - [["", "text", ""], "untitled.html"], - - [["", "canvas", ""], "untitled.json"], - - [["", null, ""], "untitled"], - - // json extension from type tests - [["test_file", "canvas", ""], "test_file.json"], - - [["test_file", "relationMap", ""], "test_file.json"], - - [["test_file", "search", ""], "test_file.json"], - - // extension based on mime type - [["test_file", null, "text/csv"], "test_file.csv"], - - [["test_file_wo_ext", "image", "image/svg+xml"], "test_file_wo_ext.svg"], - - [["test_file_wo_ext", "file", "application/json"], "test_file_wo_ext.json"], - - [["test_file_w_fake_ext.ext", "image", "image/svg+xml"], "test_file_w_fake_ext.ext.svg"], - - [["test_file_w_correct_ext.svg", "image", "image/svg+xml"], "test_file_w_correct_ext.svg"], - - [["test_file_w_correct_ext.svgz", "image", "image/svg+xml"], "test_file_w_correct_ext.svgz"], - - [["test_file.zip", "file", "application/zip"], "test_file.zip"], - - [["test_file", "file", "application/zip"], "test_file.zip"], - - // application/octet-stream tests - [["test_file", "file", "application/octet-stream"], "test_file"], - - [["test_file.zip", "file", "application/octet-stream"], "test_file.zip"], - - [["test_file.unknown", null, "application/octet-stream"], "test_file.unknown"], - - // sanitized filename tests - [["test/file", null, "application/octet-stream"], "testfile"], - - [["test:file.zip", "file", "application/zip"], "testfile.zip"], - - [[":::", "file", "application/zip"], ".zip"], - - [[":::a", "file", "application/zip"], "a.zip"] -]; - -describe("utils/formatDownloadTitle unit tests", () => { - testCases.forEach((testCase) => { - return it(`With args '${JSON.stringify(testCase[0])}' it should return '${testCase[1]}'`, () => { - const [value, expected] = testCase; - const actual = formatDownloadTitle(...value); - expect(actual).toStrictEqual(expected); - }); - }); -}); \ No newline at end of file diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index c2712ccc9..9986f5dc2 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -72,4 +72,118 @@ describe.todo("#getResourceDir", () => {}); describe.todo("#isMac", () => {}); -describe.todo("#isWindows", () => {}); \ No newline at end of file +describe.todo("#isWindows", () => {}); + + +describe("#formatDownloadTitle", () => { + + //prettier-ignore + const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [ + + // empty fileName tests + [ + ["", "text", ""], + "untitled.html" + ], + [ + ["", "canvas", ""], + "untitled.json" + ], + [ + ["", null, ""], + "untitled" + ], + + + // json extension from type tests + [ + ["test_file", "canvas", ""], + "test_file.json" + ], + [ + ["test_file", "relationMap", ""], + "test_file.json" + ], + [ + ["test_file", "search", ""], + "test_file.json" + ], + + + // extension based on mime type + [ + ["test_file", null, "text/csv"], + "test_file.csv" + ], + [ + ["test_file_wo_ext", "image", "image/svg+xml"], + "test_file_wo_ext.svg" + ], + [ + ["test_file_wo_ext", "file", "application/json"], + "test_file_wo_ext.json" + ], + [ + ["test_file_w_fake_ext.ext", "image", "image/svg+xml"], + "test_file_w_fake_ext.ext.svg" + ], + [ + ["test_file_w_correct_ext.svg", "image", "image/svg+xml"], + "test_file_w_correct_ext.svg" + ], + [ + ["test_file_w_correct_ext.svgz", "image", "image/svg+xml"], + "test_file_w_correct_ext.svgz" + ], + [ + ["test_file.zip", "file", "application/zip"], + "test_file.zip" + ], + [ + ["test_file", "file", "application/zip"], + "test_file.zip" + ], + + + // application/octet-stream tests + [ + ["test_file", "file", "application/octet-stream"], + "test_file" + ], + [ + ["test_file.zip", "file", "application/octet-stream"], + "test_file.zip" + ], + [ + ["test_file.unknown", null, "application/octet-stream"], + "test_file.unknown" + ], + + + // sanitized filename tests + [ + ["test/file", null, "application/octet-stream"], + "testfile" + ], + [ + ["test:file.zip", "file", "application/zip"], + "testfile.zip" + ], + [ + [":::", "file", "application/zip"], + ".zip" + ], + [ + [":::a", "file", "application/zip"], + "a.zip" + ] + ]; + + testCases.forEach((testCase) => { + const [fnParams, expected] = testCase; + return it(`With args '${JSON.stringify(fnParams)}', it should return '${expected}'`, () => { + const actual = utils.formatDownloadTitle(...fnParams); + expect(actual).toStrictEqual(expected); + }); + }); +}); \ No newline at end of file From 8546fe2333ec9ff40675dd09ba448601a869aa77 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 16:54:49 +0100 Subject: [PATCH 003/110] test(server/utils): add tests for isEmptyOrWhitespace --- src/services/utils.spec.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 9986f5dc2..6ead22576 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -21,7 +21,27 @@ describe.todo("#isElectron", () => {}); describe.todo("#hash", () => {}); -describe.todo("#isEmptyOrWhitespace", () => {}); +describe("#isEmptyOrWhitespace", () => { + + const testCases: TestCase[] = [ + ["w/ 'null' it should return true", [null], true], + ["w/ 'null' it should return true", [null], true], + ["w/ undefined it should return true", [undefined], true], + ["w/ empty string '' it should return true", [""], true], + ["w/ single whitespace string ' ' it should return true", [" "], true], + ["w/ multiple whitespace string ' ' it should return true", [" "], true], + ["w/ non-empty string ' t ' it should return false", [" t "], false], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.isEmptyOrWhitespace(...fnParams); + expect(result).toStrictEqual(expected); + }) + }) + +}); describe.todo("#sanitizeSqlIdentifier", () => {}); From 03c1128a726b6b05a4411899d1c7d9c133ab2882 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 17:07:48 +0100 Subject: [PATCH 004/110] fix(isEmptyOrWhitespace): avoid exception throwing when passed value is undefined the req.body value from "routes/api/branches" actually seems to never get parsed into a JS object, but arrives as text string, so req.body.prefix could be undefined, which of course would cause an error to be thrown, when trying to call "match" on undefined. --- src/routes/api/branches.ts | 1 + src/services/utils.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/api/branches.ts b/src/routes/api/branches.ts index 746ebb92a..b9c5f751d 100644 --- a/src/routes/api/branches.ts +++ b/src/routes/api/branches.ts @@ -216,6 +216,7 @@ function deleteBranch(req: Request) { function setPrefix(req: Request) { const branchId = req.params.branchId; + //TriliumNextTODO: req.body arrives as string, so req.body.prefix will be undefined – did the code below ever even work? const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix; const branch = becca.getBranchOrThrow(branchId); diff --git a/src/services/utils.ts b/src/services/utils.ts index b00c1e488..f101f6064 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -71,8 +71,9 @@ export function hash(text: string) { return crypto.createHash("sha1").update(text).digest("base64"); } -export function isEmptyOrWhitespace(str: string) { - return str === null || str.match(/^ *$/) !== null; +export function isEmptyOrWhitespace(str: string | null | undefined) { + if (!str) return true; + return str.match(/^ *$/) !== null; } export function sanitizeSqlIdentifier(str: string) { From 33346e0cee95c14714038a3642bc306def86ccdd Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 17:28:46 +0100 Subject: [PATCH 005/110] test(server/utils): add tests for sanitizeSqlIdentifier --- src/services/utils.spec.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 6ead22576..57f658b5b 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -43,7 +43,28 @@ describe("#isEmptyOrWhitespace", () => { }); -describe.todo("#sanitizeSqlIdentifier", () => {}); +describe("#sanitizeSqlIdentifier", () => { + + const testCases: TestCase[] = [ + ["w/ 'test' it should not strip anything", ["test"], "test"], + ["w/ 'test123' it should not strip anything", ["test123"], "test123"], + ["w/ 'tEst_TeSt' it should not strip anything", ["tEst_TeSt"], "tEst_TeSt"], + ["w/ 'test_test' it should not strip '_'", ["test_test"], "test_test"], + ["w/ 'test-' it should strip the '-'", ["test-"], "test"], + ["w/ 'test-test' it should strip the '-'", ["test-test"], "testtest"], + ["w/ 'test; --test' it should strip the '; --'", ["test; --test"], "testtest"], + ["w/ 'test test' it should strip the ' '", ["test test"], "testtest"], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.sanitizeSqlIdentifier(...fnParams); + expect(result).toStrictEqual(expected); + }) + }); + +}); describe.todo("#escapeHtml", () => {}); From 05a0acbdb518483dc7b37772ebd84ffae2b7dc15 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 19:05:13 +0100 Subject: [PATCH 006/110] test(server/utils): add tests for newEntityId and randomString --- src/services/utils.spec.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 57f658b5b..36eae4e82 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -1,9 +1,28 @@ import { describe, it, expect } from "vitest"; import utils from "./utils.js"; -describe.todo("#newEntityId", () => {}); +type TestCase any> = [desc: string, fnParams: Parameters, expected: ReturnType]; -describe.todo("#randomString", () => {}); +describe("#newEntityId", () => { + + it("should return a string with a length of 12", () => { + const result = utils.newEntityId(); + expect(result).toBeTypeOf("string"); + expect(result).toHaveLength(12); + }); + +}); + +describe("#randomString", () => { + + it("should return a string with a length as per argument", () => { + const stringLength = 5; + const result = utils.randomString(stringLength); + expect(result).toBeTypeOf("string"); + expect(result).toHaveLength(stringLength); + }); + +}); describe.todo("#randomSecureToken", () => {}); From 9689222fd430de776b6081e95fb0de32d79eaf45 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 19:15:21 +0100 Subject: [PATCH 007/110] test(server/utils): add tests for removeTextFileExtension --- src/services/utils.spec.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 36eae4e82..92c70268d 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -112,7 +112,24 @@ describe.todo("#replaceAll", () => {}); // TriliumNextTODO move existing formatDownloadTitle in here // describe.todo("#formatDownloadTitle", () => {}); -describe.todo("#removeTextFileExtension", () => {}); +describe("#removeTextFileExtension", () => { + 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"], + ["w/ 'test.htm' it should strip '.htm'", ["test.htm"], "test"], + ["w/ 'test.zip' it should NOT strip '.zip'", ["test.zip"], "test.zip"], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.removeTextFileExtension(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); + +}); describe.todo("#getNoteTitle", () => {}); From 41c96fb2023fad91ec7a18d83a396798cfe2653b Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 19:23:09 +0100 Subject: [PATCH 008/110] test(server/utils): add tests for envToBoolean --- src/services/utils.spec.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 92c70268d..190bfabf4 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -145,6 +145,34 @@ describe.todo("#toMap", () => {}); describe.todo("#isString", () => {}); +describe("#envToBoolean", () => { + const testCases: TestCase[] = [ + ["w/ 'true' it should return boolean 'true'", ["true"], true], + ["w/ 'True' it should return boolean 'true'", ["True"], true], + ["w/ 'TRUE' it should return boolean 'true'", ["TRUE"], true], + ["w/ 'true ' it should return boolean 'true'", ["true "], true], + ["w/ 'false' it should return boolean 'false'", ["false"], false], + ["w/ 'False' it should return boolean 'false'", ["False"], false], + ["w/ 'FALSE' it should return boolean 'false'", ["FALSE"], false], + ["w/ 'false ' it should return boolean 'false'", ["false "], false], + ["w/ 'whatever' (non-boolean string) it should return undefined", ["whatever"], undefined], + ["w/ '-' (non-boolean string) it should return undefined", ["-"], undefined], + ["w/ '' (empty string) it should return undefined", [""], undefined], + ["w/ ' ' (white space string) it should return undefined", [" "], undefined], + ["w/ undefined it should return undefined", [undefined], undefined], + //@ts-expect-error - pass wrong type as param + ["w/ number 1 it should return undefined", [1], undefined], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.envToBoolean(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + describe.todo("#getResourceDir", () => {}); describe.todo("#isMac", () => {}); From 92123e17615eefaf24465d7b200290cc6f20df6b Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 19:39:26 +0100 Subject: [PATCH 009/110] refactor(server/utils): get rid of isString MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit let's use typeof x === "string" → works exactly the same and at the same speed as this custom isString fn --- src/services/notes.ts | 4 ++-- src/services/utils.spec.ts | 2 -- src/services/utils.ts | 5 ----- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/services/notes.ts b/src/services/notes.ts index d0a699984..0faf08d74 100644 --- a/src/services/notes.ts +++ b/src/services/notes.ts @@ -6,7 +6,7 @@ import eventService from "./events.js"; import cls from "../services/cls.js"; import protectedSessionService from "../services/protected_session.js"; import log from "../services/log.js"; -import { newEntityId, isString, unescapeHtml, quoteRegex, toMap } from "../services/utils.js"; +import { newEntityId, unescapeHtml, quoteRegex, toMap } from "../services/utils.js"; import revisionService from "./revisions.js"; import request from "./request.js"; import path from "path"; @@ -884,7 +884,7 @@ async function asyncPostProcessContent(note: BNote, content: string | Buffer) { return; } - if (note.hasStringContent() && !isString(content)) { + if (note.hasStringContent() && typeof content !== "string") { content = content.toString(); } diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 190bfabf4..3ede44aa5 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -143,8 +143,6 @@ describe.todo("#normalize", () => {}); describe.todo("#toMap", () => {}); -describe.todo("#isString", () => {}); - describe("#envToBoolean", () => { const testCases: TestCase[] = [ ["w/ 'true' it should return boolean 'true'", ["true"], true], diff --git a/src/services/utils.ts b/src/services/utils.ts index f101f6064..a1a7cc05c 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -295,10 +295,6 @@ export function toMap>(list: T[], key: keyof T): R return map; } -export function isString(x: any) { - return Object.prototype.toString.call(x) === "[object String]"; -} - // try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined export function envToBoolean(val: string | undefined) { if (val === undefined || typeof val !== "string") return undefined; @@ -357,7 +353,6 @@ export default { normalize, hashedBlobId, toMap, - isString, getResourceDir, isMac, isWindows, From 738436061c0a66089b6a1e47554d8da63a41c699 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 20:37:00 +0100 Subject: [PATCH 010/110] test(server/utils): add tests for removeDiacritic and normalize --- src/services/utils.spec.ts | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 3ede44aa5..49adcc0f4 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -137,9 +137,45 @@ describe.todo("#timeLimit", () => {}); describe.todo("#deferred", () => {}); -describe.todo("#removeDiacritic", () => {}); +describe("#removeDiacritic", () => { -describe.todo("#normalize", () => {}); + const testCases: TestCase[] = [ + ["w/ 'Äpfel' it should replace the 'Ä'", ["Äpfel"], "Apfel"], + ["w/ 'Été' it should replace the 'É' and 'é'", ["Été"], "Ete"], + ["w/ 'Fête' it should replace the 'ê'", ["Fête"], "Fete"], + ["w/ 'Αλφαβήτα' it should replace the 'ή'", ["Αλφαβήτα"], "Αλφαβητα"], + ["w/ '' (empty string) it should return empty string", [""], ""], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.removeDiacritic(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + + +describe("#normalize", () => { + + const testCases: TestCase[] = [ + ["w/ 'Äpfel' it should replace the 'Ä' and return lowercased", ["Äpfel"], "apfel"], + ["w/ 'Été' it should replace the 'É' and 'é' and return lowercased", ["Été"], "ete"], + ["w/ 'FêTe' it should replace the 'ê' and return lowercased", ["FêTe"], "fete"], + ["w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", ["ΑλΦαβήΤα"], "αλφαβητα"], + ["w/ '' (empty string) it should return empty string", [""], ""], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.normalize(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); + +}); describe.todo("#toMap", () => {}); From 45cf0334f18e6908ebe8b9543264b08ad3d059b6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 21:30:53 +0100 Subject: [PATCH 011/110] test(server/utils): add tests for stripTags --- src/services/utils.spec.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 49adcc0f4..0b54d6d95 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -91,7 +91,28 @@ describe.todo("#unescapeHtml", () => {}); describe.todo("#toObject", () => {}); -describe.todo("#stripTags", () => {}); +describe("#stripTags", () => { + + //pre + const htmlWithNewlines = +`

abc +def

+

ghi

`; + + const testCases: TestCase[] = [ + ["should strip all tags and only return the content, leaving new lines and spaces in tact", [htmlWithNewlines], "abc\ndef\nghi"], + //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text? + ["should strip all tags and only return the content", ["

abc

def

"], "abcdef"], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.stripTags(...fnParams); + expect(result).toStrictEqual(expected); + }) + }); +}); describe.todo("#union", () => {}); From 81db6817329c5b2fe589e9bd9b7df87da4a3da9e Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 21:34:47 +0100 Subject: [PATCH 012/110] refactor(server/utils): remove unused union function is not used at all anywhere --- src/services/utils.spec.ts | 2 -- src/services/utils.ts | 24 ------------------------ 2 files changed, 26 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 0b54d6d95..06eccab7c 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -114,8 +114,6 @@ def

}); }); -describe.todo("#union", () => {}); - describe.todo("#escapeRegExp", () => {}); describe.todo("#crash", () => {}); diff --git a/src/services/utils.ts b/src/services/utils.ts index a1a7cc05c..6ebc93ccc 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -104,29 +104,6 @@ export function stripTags(text: string) { return text.replace(/<(?:.|\n)*?>/gm, ""); } -export function union(a: T[], b: T[]): T[] { - const obj: Record = {} as Record; // TODO: unsafe? - - for (let i = a.length - 1; i >= 0; i--) { - obj[a[i]] = a[i]; - } - - for (let i = b.length - 1; i >= 0; i--) { - obj[b[i]] = b[i]; - } - - const res: T[] = []; - - for (const k in obj) { - if (obj.hasOwnProperty(k)) { - // <-- optional - res.push(obj[k]); - } - } - - return res; -} - export function escapeRegExp(str: string) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } @@ -337,7 +314,6 @@ export default { unescapeHtml, toObject, stripTags, - union, escapeRegExp, crash, getContentDisposition, From de5ffb591d38dd8967650a7152f07f57d53918b0 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 21:45:14 +0100 Subject: [PATCH 013/110] test(server/utils): add tests for isMac/isWindows/isElectron --- src/services/utils.spec.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 06eccab7c..ce1e83392 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -36,8 +36,6 @@ describe.todo("#fromBase64", () => {}); describe.todo("#hmac", () => {}); -describe.todo("#isElectron", () => {}); - describe.todo("#hash", () => {}); describe("#isEmptyOrWhitespace", () => { @@ -228,10 +226,23 @@ describe("#envToBoolean", () => { describe.todo("#getResourceDir", () => {}); -describe.todo("#isMac", () => {}); +describe("#isElectron", () => { + it("should export a boolean", () => { + expect(utils.isElectron).toBeTypeOf("boolean"); + }); +}); -describe.todo("#isWindows", () => {}); +describe("#isMac", () => { + it("should export a boolean", () => { + expect(utils.isMac).toBeTypeOf("boolean"); + }); +}); +describe("#isWindows", () => { + it("should export a boolean", () => { + expect(utils.isWindows).toBeTypeOf("boolean"); + }); +}); describe("#formatDownloadTitle", () => { From 283b19c716b2eb038728818b93908d488959aa5d Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 21:53:18 +0100 Subject: [PATCH 014/110] fix(server/utils): add missed export of isDev in default export object --- src/services/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/utils.ts b/src/services/utils.ts index 6ebc93ccc..21649b5b2 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -332,5 +332,6 @@ export default { getResourceDir, isMac, isWindows, + isDev, envToBoolean }; From 5c20a6d5eebf3ec8d29d575fbae8595469cf793c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 21:53:31 +0100 Subject: [PATCH 015/110] test(server/utils): add tests for isDev --- src/services/utils.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index ce1e83392..3e1766f48 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -244,6 +244,12 @@ describe("#isWindows", () => { }); }); +describe("#isDev", () => { + it("should export a boolean", () => { + expect(utils.isDev).toBeTypeOf("boolean"); + }); +}); + describe("#formatDownloadTitle", () => { //prettier-ignore From 3094b1779e6856c18a96202b42be6a61e73671dd Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 22:58:30 +0100 Subject: [PATCH 016/110] test(server/utils): add tests for isStringNote --- src/services/utils.spec.ts | 44 +++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 3e1766f48..f765eea5b 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -120,7 +120,49 @@ describe.todo("#sanitizeFilenameForHeader", () => {}); describe.todo("#getContentDisposition", () => {}); -describe.todo("#isStringNote", () => {}); +describe("#isStringNote", () => { + + const testCases: TestCase[] = [ + [ + "w/ 'undefined' note type, but a string mime type, it should return true", + [undefined, "application/javascript"], + true + ], + [ + "w/ non-string note type, it should return false", + ["image", "image/jpeg"], + false + ], + [ + "w/ string note type (text), it should return true", + ["text", "text/html"], + true + ], + [ + "w/ string note type (code), it should return true", + ["code", "application/json"], + true + ], + [ + "w/ non-string note type (file), but string mime type, it should return true", + ["file", "application/json"], + true + ], + [ + "w/ non-string note type (file), but mime type starting with 'text/', it should return true", + ["file", "text/html"], + true + ], + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.isStringNote(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); describe.todo("#quoteRegex", () => {}); From 440dbfd4d4672bc76ab65ee018282cc22cd6da80 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Wed, 29 Jan 2025 23:19:08 +0100 Subject: [PATCH 017/110] refactor(server/utils): use Set for isStringNote --- src/services/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 21649b5b2..67c415f9f 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -132,11 +132,12 @@ export function getContentDisposition(filename: string) { return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`; } +// render and book are string note in the sense that they are expected to contain empty string +const STRING_NOTE_TYPES = new Set(["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"]); const STRING_MIME_TYPES = new Set(["application/javascript", "application/x-javascript", "application/json", "application/x-sql", "image/svg+xml"]); export function isStringNote(type: string | undefined, mime: string) { - // render and book are string note in the sense that they are expected to contain empty string - return (type && ["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas"].includes(type)) || mime.startsWith("text/") || STRING_MIME_TYPES.has(mime); + return (type && STRING_NOTE_TYPES.has(type)) || mime.startsWith("text/") || STRING_MIME_TYPES.has(mime); } export function quoteRegex(url: string) { From 9eeedc827ce152b46e676eda40c4f1629c4d4ec3 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 07:39:33 +0100 Subject: [PATCH 018/110] test(server/utils): add tests for timeLimit --- src/services/utils.spec.ts | 59 +++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index f765eea5b..d7a2e27bb 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -192,7 +192,64 @@ describe("#removeTextFileExtension", () => { describe.todo("#getNoteTitle", () => {}); -describe.todo("#timeLimit", () => {}); +describe("#timeLimit", () => { + + it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => { + const resolvedValue = `resolved: ${new Date().toISOString()}`; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res(resolvedValue); + }, 200); + //rej("rejected!"); + }); + expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue); + }); + + it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => { + const rejectedValue = `rejected: ${new Date().toISOString()}`; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + //return res("resolved"); + rej(rejectedValue); + }, 100); + }); + expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue) + }); + + it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res("resolved"); + }, 500); + //rej("rejected!"); + }); + expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`) + }); + + it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { + const customErrorMsg = "Custom Error"; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res("resolved"); + }, 500); + //rej("rejected!"); + }); + expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg) + }); + + // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? + it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => { + //@ts-expect-error - passing in illegal type 'undefined' + expect(utils.timeLimit(undefined, 200)).toBe(undefined) + }); + + // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? + it("when the passed promise is not a promise, it should return the passed value", async () => { + //@ts-expect-error - passing in illegal type 'object' + expect(utils.timeLimit({test: 1}, 200)).toStrictEqual({test: 1}) + }); + +}); describe.todo("#deferred", () => {}); From 9a8a27c02c2226d4fc1ed216875aa07433fd5f22 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 07:43:01 +0100 Subject: [PATCH 019/110] refactor(server/utils): avoid same variable name for error in timeLimit rename the error created in timeLimit to `errorTimeLimit` to differentiate it from the error that is caught inside the promise makes it a bit easier to quickly distinguish these --- src/services/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 67c415f9f..c9636f16f 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -209,7 +209,7 @@ export function timeLimit(promise: Promise, limitMs: number, errorMessage? } // better stack trace if created outside of promise - const error = new Error(errorMessage || `Process exceeded time limit ${limitMs}`); + const errorTimeLimit = new Error(errorMessage || `Process exceeded time limit ${limitMs}`); return new Promise((res, rej) => { let resolved = false; @@ -224,7 +224,7 @@ export function timeLimit(promise: Promise, limitMs: number, errorMessage? setTimeout(() => { if (!resolved) { - rej(error); + rej(errorTimeLimit); } }, limitMs); }); From ef66d330eca0a19a083b6c71b3330338ccae35ee Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 07:43:25 +0100 Subject: [PATCH 020/110] chore(server/utils): timeLimit - add TODO comment --- src/services/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/services/utils.ts b/src/services/utils.ts index c9636f16f..aec55305d 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -9,6 +9,7 @@ import mimeTypes from "mime-types"; import path from "path"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; +//import type { NoteType } from "../rows.js"; const randtoken = generator({ source: "crypto" }); @@ -203,6 +204,7 @@ export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boo } export function timeLimit(promise: Promise, limitMs: number, errorMessage?: string): Promise { + // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? if (!promise || !promise.then) { // it's not actually a promise return promise; From df1d47972023d6ee76754320c6e009d025171131 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 07:59:12 +0100 Subject: [PATCH 021/110] chore(server/utils): sort exports alphabetically --- src/services/utils.ts | 62 +++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index aec55305d..d7eef0fca 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -302,39 +302,39 @@ export function getResourceDir() { } export default { - randomSecureToken, - randomString, + crash, + deferred, + envToBoolean, + escapeHtml, + escapeRegExp, + formatDownloadTitle, + fromBase64, + getContentDisposition, + getNoteTitle, + getResourceDir, + hash, + hashedBlobId, + hmac, + isDev, + isElectron, + isEmptyOrWhitespace, + isMac, + isStringNote, + isWindows, md5, newEntityId, - toBase64, - fromBase64, - hmac, - isElectron, - hash, - isEmptyOrWhitespace, - sanitizeSqlIdentifier, - escapeHtml, - unescapeHtml, - toObject, - stripTags, - escapeRegExp, - crash, - getContentDisposition, - isStringNote, - quoteRegex, - replaceAll, - getNoteTitle, - removeTextFileExtension, - formatDownloadTitle, - timeLimit, - deferred, - removeDiacritic, normalize, - hashedBlobId, + quoteRegex, + randomSecureToken, + randomString, + removeDiacritic, + removeTextFileExtension, + replaceAll, + sanitizeSqlIdentifier, + stripTags, + timeLimit, + toBase64, toMap, - getResourceDir, - isMac, - isWindows, - isDev, - envToBoolean + toObject, + unescapeHtml }; From acb7098bb8c6be6acd65b6b0e8be58f59f4188d6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:09:54 +0100 Subject: [PATCH 022/110] test(server/utils): add tests for sanitizeFilenameForHeader --- src/services/utils.spec.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index d7a2e27bb..39c276717 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -116,7 +116,25 @@ describe.todo("#escapeRegExp", () => {}); describe.todo("#crash", () => {}); -describe.todo("#sanitizeFilenameForHeader", () => {}); +describe("#sanitizeFilenameForHeader", () => { + // only test our own code, not the sanitize-filename side of things + const testCases: TestCase[] = [ + ["when passed filename is empty, it should fallback to default value 'file'", [" "], "file"], + ["when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", [".."], "file"], + // COM1 is a Windows specific "illegal filename" that sanitize filename strips away + ["when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", ["COM1"], "file"], + ["sanitized passed filename should be returned URIEncoded", ["test file.csv"], "test%20file.csv"] + ] + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.sanitizeFilenameForHeader(...fnParams); + expect(result).toStrictEqual(expected); + }) + }); + +}); describe.todo("#getContentDisposition", () => {}); From 6ab9a3979d37613c410591353fe1a5f003212f25 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:12:11 +0100 Subject: [PATCH 023/110] refactor(server/utils): simplify sanitizeFilenameForHeader since "" is falsy, we can just use "||" here --- src/services/utils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index d7eef0fca..126e07f76 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -118,12 +118,7 @@ export async function crash() { } export function sanitizeFilenameForHeader(filename: string) { - let sanitizedFilename = sanitize(filename); - - if (sanitizedFilename.trim().length === 0) { - sanitizedFilename = "file"; - } - + const sanitizedFilename = sanitize(filename).trim() || "file"; return encodeURIComponent(sanitizedFilename); } From 29b1befd602eeb7822d6aa6c327788552bfb594f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:12:42 +0100 Subject: [PATCH 024/110] fix(server/utils): missing export for sanitizeFilenameForHeader --- src/services/utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/utils.ts b/src/services/utils.ts index 126e07f76..9af0c20fc 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -325,6 +325,7 @@ export default { removeDiacritic, removeTextFileExtension, replaceAll, + sanitizeFilenameForHeader, sanitizeSqlIdentifier, stripTags, timeLimit, From fedaec6c79ebec062e1c2175e54e385b2aa0e6a9 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:33:59 +0100 Subject: [PATCH 025/110] refactor(server/utils): merge sanitizeFilenameForHeader into getContentDisposition sanitizeFilenameForHeader is not used anywhere else and is tiny, so let's merge it --- src/services/utils.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 9af0c20fc..174929334 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -117,15 +117,10 @@ export async function crash() { } } -export function sanitizeFilenameForHeader(filename: string) { - const sanitizedFilename = sanitize(filename).trim() || "file"; - return encodeURIComponent(sanitizedFilename); -} - export function getContentDisposition(filename: string) { - const sanitizedFilename = sanitizeFilenameForHeader(filename); - - return `file; filename="${sanitizedFilename}"; filename*=UTF-8''${sanitizedFilename}`; + const sanitizedFilename = sanitize(filename).trim() || "file"; + const uriEncodedFilename = encodeURIComponent(sanitizedFilename); + return `file; filename="${uriEncodedFilename}"; filename*=UTF-8''${uriEncodedFilename}`; } // render and book are string note in the sense that they are expected to contain empty string @@ -325,7 +320,6 @@ export default { removeDiacritic, removeTextFileExtension, replaceAll, - sanitizeFilenameForHeader, sanitizeSqlIdentifier, stripTags, timeLimit, From b812e67794aa44956e3295cec1575cace8f6d942 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:35:18 +0100 Subject: [PATCH 026/110] test(server/utils): adapt tests for getContentDisposition since we merged these functions, adapt the previous tests of sanitizeFilenameForHeader and use them for the newly merged function getContentDisposition --- src/services/utils.spec.ts | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 39c276717..16bdbccc5 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -116,28 +116,42 @@ describe.todo("#escapeRegExp", () => {}); describe.todo("#crash", () => {}); -describe("#sanitizeFilenameForHeader", () => { - // only test our own code, not the sanitize-filename side of things - const testCases: TestCase[] = [ - ["when passed filename is empty, it should fallback to default value 'file'", [" "], "file"], - ["when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", [".."], "file"], +describe("#getContentDisposition", () => { + + const defaultFallBackDisposition = `file; filename="file"; filename*=UTF-8''file`; + const testCases: TestCase[] = [ + [ + "when passed filename is empty, it should fallback to default value 'file'", + [" "], + defaultFallBackDisposition + ], + [ + "when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", + [".."], + defaultFallBackDisposition + ], // COM1 is a Windows specific "illegal filename" that sanitize filename strips away - ["when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", ["COM1"], "file"], - ["sanitized passed filename should be returned URIEncoded", ["test file.csv"], "test%20file.csv"] + [ + "when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", + ["COM1"], + defaultFallBackDisposition + ], + [ + "sanitized passed filename should be returned URIEncoded", + ["test file.csv"], + `file; filename="test%20file.csv"; filename*=UTF-8''test%20file.csv` + ] ] testCases.forEach(testCase => { const [desc, fnParams, expected] = testCase; it(desc, () => { - const result = utils.sanitizeFilenameForHeader(...fnParams); - expect(result).toStrictEqual(expected); + const result = utils.getContentDisposition(...fnParams); + expect(result).toStrictEqual(expected); }) }); - }); -describe.todo("#getContentDisposition", () => {}); - describe("#isStringNote", () => { const testCases: TestCase[] = [ From c15e46bf2556fa44b0452bff48dd091e9713117a Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 08:46:56 +0100 Subject: [PATCH 027/110] chore(server/utils): improve types for getNoteTitle --- src/services/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 174929334..a24698a1c 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -9,7 +9,7 @@ import mimeTypes from "mime-types"; import path from "path"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; -//import type { NoteType } from "../rows.js"; +import type NoteMeta from "./meta/note_meta.js"; const randtoken = generator({ source: "crypto" }); @@ -181,7 +181,7 @@ export function removeTextFileExtension(filePath: string) { } } -export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: { title?: string }) { +export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) { if (noteMeta?.title) { return noteMeta.title; } else { From c3587ad5361416f29d808eda89b502c7f65489e4 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 21:32:52 +0100 Subject: [PATCH 028/110] test(server/utils): add tests for getNoteTitle --- src/services/utils.spec.ts | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 16bdbccc5..9fbb6c545 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -222,7 +222,66 @@ describe("#removeTextFileExtension", () => { }); -describe.todo("#getNoteTitle", () => {}); +describe("#getNoteTitle", () => { + const testCases: TestCase[] = [ + [ + "when file has no spaces, and no special file extension, it should return the filename unaltered", + ["test.json", true, undefined], + "test.json" + ], + [ + "when replaceUnderscoresWithSpaces is false, it should keep the underscores in the title", + ["test_file.json", false, undefined], + "test_file.json" + ], + [ + "when replaceUnderscoresWithSpaces is true, it should replace the underscores in the title", + ["test_file.json", true, undefined], + "test file.json" + ], + [ + "when filePath ends with one of the extra handled endings (.md), it should strip the file extension from the title", + ["test_file.md", false, undefined], + "test_file" + ], + [ + "when filePath ends with one of the extra handled endings (.md) and replaceUnderscoresWithSpaces is true, it should strip the file extension from the title and replace underscores", + ["test_file.md", true, undefined], + "test file" + ], + [ + "when filepath contains a full path, it should only return the basename of the file", + ["Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.zip", true, undefined], + "template.zip" + ], + [ + "when filepath contains a full path and has extra handled ending (.html), it should only return the basename of the file and strip the file extension", + ["Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.html", true, undefined], + "template" + ], + [ + "when a noteMeta object is passed, it should use the title from the noteMeta, if present", + //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here + ["test_file.md", true, { title: "some other title"}], + "some other title" + ], + [ + "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", + //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here + ["test_file.md", true, { title: ""}], + "test file" + ] + ]; + + testCases.forEach(testCase => { + const [desc, fnParams, expected] = testCase; + it(desc, () => { + const result = utils.getNoteTitle(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); + +}); describe("#timeLimit", () => { From 65be2cf04876da50f561b62c3e25bc5c8179f772 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 21:38:23 +0100 Subject: [PATCH 029/110] refactor(server/utils): simplify getNoteTitle --- src/services/utils.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index a24698a1c..b2204b337 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -182,15 +182,9 @@ export function removeTextFileExtension(filePath: string) { } export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) { - if (noteMeta?.title) { - return noteMeta.title; - } else { - const basename = path.basename(removeTextFileExtension(filePath)); - if (replaceUnderscoresWithSpaces) { - return basename.replace(/_/g, " ").trim(); - } - return basename; - } + if (noteMeta?.title) return noteMeta.title; + const basename = path.basename(removeTextFileExtension(filePath)); + return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename; } export function timeLimit(promise: Promise, limitMs: number, errorMessage?: string): Promise { From 46f28f4f09ed12f69f0379bbc6e7f21b37c2b185 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 21:39:42 +0100 Subject: [PATCH 030/110] test(server/utils): add edge case test for getNoteTitle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit when the noteMeta title consists of just spaces, it should fall back to "normal" handling again → currently this fails --- src/services/utils.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 9fbb6c545..68cb940cf 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -270,6 +270,12 @@ describe("#getNoteTitle", () => { //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here ["test_file.md", true, { title: ""}], "test file" + ], + [ + "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", + //@ts-expect-error - passing in incomplete noteMeta - but we only care about the title prop here + ["test_file.json", false, { title: " "}], + "test_file.json" ] ]; From 6e5e6989ed72db0b8391f930e161eb07394fd3e9 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 21:52:26 +0100 Subject: [PATCH 031/110] fix(server/utils): fix potentially "empty looking" title from getNoteTitle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit when the noteMeta title consists of just spaces, it will fall back to "normal" handling again → instead of showing " " as title, which would be perceived as "empty" --- src/services/utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index b2204b337..964f0708b 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -182,7 +182,9 @@ export function removeTextFileExtension(filePath: string) { } export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) { - if (noteMeta?.title) return noteMeta.title; + const trimmedNoteMeta = noteMeta?.title?.trim(); + if (trimmedNoteMeta) return trimmedNoteMeta; + const basename = path.basename(removeTextFileExtension(filePath)); return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename; } From a4ce2ddd5e494cdee2967b002ae90f283312f6c6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 21:55:37 +0100 Subject: [PATCH 032/110] refactor(server/utils): simplify getResourceDir get rid of unnecessary else branch here --- src/services/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index 964f0708b..bc35fe10f 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -280,11 +280,8 @@ export function envToBoolean(val: string | undefined) { * @returns the resource dir. */ export function getResourceDir() { - if (isElectron && !isDev) { - return process.resourcesPath; - } else { - return join(dirname(fileURLToPath(import.meta.url)), "..", ".."); - } + if (isElectron && !isDev) return process.resourcesPath; + return join(dirname(fileURLToPath(import.meta.url)), "..", ".."); } export default { From 9f2dd21865cb20bfe9cc209bafbd1d0e568e8a8a Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 22:55:58 +0100 Subject: [PATCH 033/110] test(server/utils): add tests for randomSecureToken (bit ugly I have to say, as we are essentially partially testing "crypto" module here, probably should be instead replaced by a version that mocks crypto module and checks, if the called functions match the expectations) --- src/services/utils.spec.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 68cb940cf..272a03b8c 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -24,7 +24,25 @@ describe("#randomString", () => { }); -describe.todo("#randomSecureToken", () => {}); +describe("#randomSecureToken", () => { + // base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity + // https://stackoverflow.com/a/13378842 + const byteToBase64Length = (bytes: number) => 4 * (bytes / 3); + + it("should return a string and use 32 bytes by default", () => { + const result = utils.randomSecureToken(); + expect(result).toBeTypeOf("string"); + expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(32)); + }); + + it("should return a string and use passed byte length", () => { + const bytes = 16; + const result = utils.randomSecureToken(bytes); + expect(result).toBeTypeOf("string"); + expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(bytes)); + expect(result.length).toBeLessThan(44); // default argument uses 32 bytes -> which translates to 44 base64 legal chars + }); +}); describe.todo("#md5", () => {}); From 72f0de6b78938f5550327d08e69579038f38f434 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 23:23:07 +0100 Subject: [PATCH 034/110] test(server/utils): add todo remarks --- src/services/utils.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 272a03b8c..9669acfd0 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -24,6 +24,7 @@ describe("#randomString", () => { }); +// TriliumNextTODO: should use mocks and assert that functions get called describe("#randomSecureToken", () => { // base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity // https://stackoverflow.com/a/13378842 @@ -44,16 +45,22 @@ describe("#randomSecureToken", () => { }); }); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#md5", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#hashedBlobId", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#toBase64", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#fromBase64", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#hmac", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#hash", () => {}); describe("#isEmptyOrWhitespace", () => { @@ -101,8 +108,10 @@ describe("#sanitizeSqlIdentifier", () => { }); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#escapeHtml", () => {}); +// TriliumNextTODO: should use mocks and assert that functions get called describe.todo("#unescapeHtml", () => {}); describe.todo("#toObject", () => {}); From d71e127828f7d9bc29c2d6f1e47b7dd435b24920 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 23:32:44 +0100 Subject: [PATCH 035/110] refactor(server/utils): re-export escape/unescape instead of wrapping them in function -> since the functions did not do *anything* other than calling the escape/unescape module -> let's just re-export them directly --- src/services/utils.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/services/utils.ts b/src/services/utils.ts index bc35fe10f..ba9751965 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -81,13 +81,9 @@ export function sanitizeSqlIdentifier(str: string) { return str.replace(/[^A-Za-z0-9_]/g, ""); } -export function escapeHtml(str: string) { - return escape(str); -} +export const escapeHtml = escape; -export function unescapeHtml(str: string) { - return unescape(str); -} +export const unescapeHtml = unescape; export function toObject(array: T[], fn: (item: T) => [K, V]): Record { const obj: Record = {} as Record; // TODO: unsafe? From f0ba056bb755c42ffe5bf2a690905775559fc4f5 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Fri, 31 Jan 2025 23:47:55 +0100 Subject: [PATCH 036/110] test(server/utils): add tests for escapeHtml & unescapeHtml --- src/services/utils.spec.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 9669acfd0..ad5c0841c 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -108,11 +108,17 @@ describe("#sanitizeSqlIdentifier", () => { }); -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#escapeHtml", () => {}); +describe("#escapeHtml", () => { + it("should re-export 'escape-html' npm module as escapeHtml", () => { + expect(utils.escapeHtml).toBeTypeOf("function"); + }); +}); -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#unescapeHtml", () => {}); +describe("#unescapeHtml", () => { + it("should re-export 'unescape' npm module as unescapeHtml", () => { + expect(utils.unescapeHtml).toBeTypeOf("function"); + }); +}); describe.todo("#toObject", () => {}); From ab0c84a57eea12e970ab4c6ae312b08ee5c9fd94 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 14:11:17 +0100 Subject: [PATCH 037/110] refactor(server/utils): use a "real" Map for toMap --- src/services/notes.ts | 6 +++--- src/services/utils.ts | 12 +++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/services/notes.ts b/src/services/notes.ts index 0faf08d74..26e51675e 100644 --- a/src/services/notes.ts +++ b/src/services/notes.ts @@ -731,13 +731,13 @@ function updateNoteData(noteId: string, content: string, attachments: Attachment note.setContent(newContent, { forceFrontendReload }); if (attachments?.length > 0) { - const existingAttachmentsByTitle = toMap(note.getAttachments({ includeContentLength: false }), "title"); + const existingAttachmentsByTitle = toMap(note.getAttachments({ includeContentLength: false }), "title"); for (const { attachmentId, role, mime, title, position, content } of attachments) { - if (attachmentId || !(title in existingAttachmentsByTitle)) { + const existingAttachment = existingAttachmentsByTitle.get(title); + if (attachmentId || !existingAttachment) { note.saveAttachment({ attachmentId, role, mime, title, content, position }); } else { - const existingAttachment = existingAttachmentsByTitle[title]; existingAttachment.role = role; existingAttachment.mime = mime; existingAttachment.position = position; diff --git a/src/services/utils.ts b/src/services/utils.ts index ba9751965..09e3964e5 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -247,13 +247,15 @@ export function normalize(str: string) { return removeDiacritic(str).toLowerCase(); } -export function toMap>(list: T[], key: keyof T): Record { - const map: Record = {}; - +export function toMap>(list: T[], key: keyof T) { + const map = new Map(); for (const el of list) { - map[el[key]] = el; + const keyForMap = el[key]; + if (!keyForMap) continue; + // TriliumNextTODO: do we need to handle the case when the same key is used? + // currently this will overwrite the existing entry in the map + map.set(keyForMap, el); } - return map; } From e1795a0ad18732c52d227f2a00ea396ede45280c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 14:18:54 +0100 Subject: [PATCH 038/110] test(server/utils): add tests for toMap --- src/services/utils.spec.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index ad5c0841c..db4b94165 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -423,7 +423,28 @@ describe("#normalize", () => { }); -describe.todo("#toMap", () => {}); +describe("#toMap", () => { + it("should return an instace of Map, with the correct size and keys, when supplied with a list and existing keys", () => { + const testList = [{title: "test", propA: "text", propB: 123 }, {title: "test2", propA: "prop2", propB: 456 }]; + const result = utils.toMap(testList, "title"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(2); + expect(Array.from(result.keys())).toStrictEqual(["test", "test2"]); + }); + it("should return an instace of Map, with an empty size, when the supplied list does not contain the supplied key", () => { + const testList = [{title: "test", propA: "text", propB: 123 }, {title: "test2", propA: "prop2", propB: 456 }]; + //@ts-expect-error - key is non-existing on supplied list type + const result = utils.toMap(testList, "nonExistingKey"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(0); + }); + it.fails("should correctly handle duplicate keys? (currently it will overwrite the entry, so returned size will be 1 instead of 2)", () => { + const testList = [{title: "testDupeTitle", propA: "text", propB: 123 }, {title: "testDupeTitle", propA: "prop2", propB: 456 }]; + const result = utils.toMap(testList, "title"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(2); + }); +}); describe("#envToBoolean", () => { const testCases: TestCase[] = [ From 4917296d96fb31dd49cb6f08812d8c593096ad0b Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 14:33:47 +0100 Subject: [PATCH 039/110] test(server/utils): add basic test for deferred this needs to be expanded, but I don't fully understand what the exact purpose is of this deferred fn --- src/services/utils.spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index db4b94165..9c1fd045b 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -381,7 +381,13 @@ describe("#timeLimit", () => { }); -describe.todo("#deferred", () => {}); +describe("#deferred", () => { + it("should return a promise", () => { + const result = utils.deferred(); + expect(result).toBeInstanceOf(Promise) + }) + // TriliumNextTODO: Add further tests! +}); describe("#removeDiacritic", () => { From 5c904f100a3fabb0a0eddcce6fe7a70a83ac7ef2 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 14:36:24 +0100 Subject: [PATCH 040/110] test(server/utils): add prettier ignore statement to avoid newlines getting mangled --- src/services/utils.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 9c1fd045b..ef4745517 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -124,7 +124,7 @@ describe.todo("#toObject", () => {}); describe("#stripTags", () => { - //pre + //prettier-ignore const htmlWithNewlines = `

abc def

From 13e72c5e0a2aad8228eab8dcd4e15d6a96ae1f20 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 15:00:47 +0100 Subject: [PATCH 041/110] test(server/utils): add basic test for toObject --- src/services/utils.spec.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index ef4745517..98cc5bfab 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -120,7 +120,20 @@ describe("#unescapeHtml", () => { }); }); -describe.todo("#toObject", () => {}); +describe("#toObject", () => { + it("should return an object with keys and value being set from the supplied Function", () => { + type TestListEntry = { testPropA: string, testPropB: string }; + type TestListFn = (testListEntry: TestListEntry) => [string, string]; + const testList: [TestListEntry, TestListEntry] = [{ testPropA: "keyA", testPropB: "valueA" }, { testPropA: "keyB", testPropB: "valueB" }]; + const fn: TestListFn = (testListEntry: TestListEntry) => [testListEntry.testPropA + "_fn", testListEntry.testPropB + "_fn"]; + + const result = utils.toObject(testList, fn); + expect(result).toStrictEqual({ + "keyA_fn": "valueA_fn", + "keyB_fn": "valueB_fn" + }); + }); +}); describe("#stripTags", () => { From 08c32da0d2c11014178bd59ed144be2a34b992a6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 15:06:07 +0100 Subject: [PATCH 042/110] test(server/utils): fix warnings and explicitly await timeLimit tests --- src/services/utils.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 98cc5bfab..9198ce9c7 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -345,7 +345,7 @@ describe("#timeLimit", () => { }, 200); //rej("rejected!"); }); - expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue); + await expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue); }); it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => { @@ -356,7 +356,7 @@ describe("#timeLimit", () => { rej(rejectedValue); }, 100); }); - expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue) + await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue) }); it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { @@ -366,7 +366,7 @@ describe("#timeLimit", () => { }, 500); //rej("rejected!"); }); - expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`) + await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`) }); it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { @@ -377,7 +377,7 @@ describe("#timeLimit", () => { }, 500); //rej("rejected!"); }); - expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg) + await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg) }); // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? From e710409134ae20879ca6b25591654f444a96533f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 1 Feb 2025 15:10:31 +0100 Subject: [PATCH 043/110] test(server/utils): remove completed TODO --- src/services/utils.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/utils.spec.ts b/src/services/utils.spec.ts index 9198ce9c7..3451a881d 100644 --- a/src/services/utils.spec.ts +++ b/src/services/utils.spec.ts @@ -246,9 +246,6 @@ describe.todo("#quoteRegex", () => {}); describe.todo("#replaceAll", () => {}); -// TriliumNextTODO move existing formatDownloadTitle in here -// describe.todo("#formatDownloadTitle", () => {}); - describe("#removeTextFileExtension", () => { const testCases: TestCase[] = [ ["w/ 'test.md' it should strip '.md'", ["test.md"], "test"], From 512440684e379cc8fc6cba56f453a5920bfe3584 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sun, 2 Feb 2025 10:19:21 -0800 Subject: [PATCH 044/110] remove `mac_init` functionality as it was overriding paste (and other) functionality, and is no longer needed --- src/public/app/desktop.ts | 3 --- src/public/app/services/mac_init.ts | 14 -------------- src/public/app/setup.ts | 3 --- 3 files changed, 20 deletions(-) diff --git a/src/public/app/desktop.ts b/src/public/app/desktop.ts index 4e9930d34..b78074126 100644 --- a/src/public/app/desktop.ts +++ b/src/public/app/desktop.ts @@ -4,7 +4,6 @@ import noteTooltipService from "./services/note_tooltip.js"; import bundleService from "./services/bundle.js"; import toastService from "./services/toast.js"; import noteAutocompleteService from "./services/note_autocomplete.js"; -import macInit from "./services/mac_init.js"; import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; @@ -35,8 +34,6 @@ if (utils.isElectron()) { initOnElectron(); } -macInit.init(); - noteTooltipService.setupGlobalTooltip(); noteAutocompleteService.init(); diff --git a/src/public/app/services/mac_init.ts b/src/public/app/services/mac_init.ts index c864b3cad..46a59aeea 100644 --- a/src/public/app/services/mac_init.ts +++ b/src/public/app/services/mac_init.ts @@ -5,20 +5,6 @@ import utils from "./utils.js"; import shortcutService from "./shortcuts.js"; function init() { - if (utils.isElectron() && utils.isMac()) { - shortcutService.bindGlobalShortcut("meta+c", () => exec("copy")); - shortcutService.bindGlobalShortcut("meta+v", () => exec("paste")); - shortcutService.bindGlobalShortcut("meta+x", () => exec("cut")); - shortcutService.bindGlobalShortcut("meta+a", () => exec("selectAll")); - shortcutService.bindGlobalShortcut("meta+z", () => exec("undo")); - shortcutService.bindGlobalShortcut("meta+y", () => exec("redo")); - } -} - -function exec(cmd: string) { - document.execCommand(cmd); - - return false; } export default { diff --git a/src/public/app/setup.ts b/src/public/app/setup.ts index ec389c793..1970c110a 100644 --- a/src/public/app/setup.ts +++ b/src/public/app/setup.ts @@ -1,9 +1,6 @@ import utils from "./services/utils.js"; -import macInit from "./services/mac_init.js"; import ko from "knockout"; -macInit.init(); - // TriliumNextTODO: properly make use of below types // type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; // type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop"; From 363360f0d555ad8a94d0fd2887deca30c24287a0 Mon Sep 17 00:00:00 2001 From: perfectra1n Date: Sun, 2 Feb 2025 10:21:59 -0800 Subject: [PATCH 045/110] we can just remove mac_init now since we're not using it --- src/public/app/services/mac_init.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/public/app/services/mac_init.ts diff --git a/src/public/app/services/mac_init.ts b/src/public/app/services/mac_init.ts deleted file mode 100644 index 46a59aeea..000000000 --- a/src/public/app/services/mac_init.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Mac specific initialization - */ -import utils from "./utils.js"; -import shortcutService from "./shortcuts.js"; - -function init() { -} - -export default { - init -}; From 61e56023d42dcb92ae0742ea973dca307a2a6c6f Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sun, 2 Feb 2025 12:47:27 +0100 Subject: [PATCH 046/110] refactor(electron-forge/deb): make desktop.ejs easier to read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in theory we could use variables in ejs, but unfortunately electron uses lodash template to create the file, which itself DOES NOT like any let/const/var assignments – wasted a bit of time finding this out, before coming up with this solution --- bin/electron-forge/desktop.ejs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/bin/electron-forge/desktop.ejs b/bin/electron-forge/desktop.ejs index f803f37b2..32430cd2a 100644 --- a/bin/electron-forge/desktop.ejs +++ b/bin/electron-forge/desktop.ejs @@ -1,12 +1,17 @@ [Desktop Entry] -<% if (productName) { %>Name=<%= productName %> -<% } %><% if (description) { %>Comment=<%= description %> -<% } %><% if (genericName) { %>GenericName=<%= genericName %> -<% } %><% if (name) { %>Exec=<%= name %> %U -Icon=<%= name %> -<% } %>Type=Application -StartupNotify=true -<% if (productName) { %>StartupWMClass=<%= productName %> -<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>; -<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>; -<% } %> \ No newline at end of file +<%= +Object.entries({ + "Name": productName, + "Comment": description, + "GenericName": genericName, + "Exec": name ? `${name} %U` : undefined, + "Icon": name, + "Type": "Application", + "StartupNotify": "true", + "StartupWMClass": productName, + "Categories": categories?.length ? `${categories.join(";")};` : undefined, + "MimeType": mimeType?.length ? `${mimeType.join(";")};` : undefined +}) +.map(line => line[1] ? line.join("=") : undefined) +.filter(line => !!line) +.join("\n")%> \ No newline at end of file From ccbd47e03df415608f71431bef01024132109a64 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 01:09:01 +0200 Subject: [PATCH 047/110] desktop app: use a custom user agent string --- electron.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electron.ts b/electron.ts index 154ec38d4..dc93e6688 100644 --- a/electron.ts +++ b/electron.ts @@ -26,6 +26,8 @@ electronDl({ saveAs: true }); // needed for excalidraw export https://github.com/zadam/trilium/issues/4271 electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features"); +electron.app.userAgentFallback = `${electron.app.getName()} ${electron.app.getVersion()}`; + // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. From 528fe1904cabdb73bdfd046e3343515dee22582f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Feb 2025 01:09:17 +0000 Subject: [PATCH 048/110] chore(deps): update dependency @playwright/test to v1.50.1 --- package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3aac1a975..bc3f77601 100644 --- a/package-lock.json +++ b/package-lock.json @@ -113,7 +113,7 @@ "@electron-forge/maker-zip": "7.6.1", "@electron-forge/plugin-auto-unpack-natives": "7.6.1", "@electron/rebuild": "3.7.1", - "@playwright/test": "1.50.0", + "@playwright/test": "1.50.1", "@types/archiver": "6.0.3", "@types/better-sqlite3": "7.6.12", "@types/bootstrap": "5.2.10", @@ -2880,13 +2880,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.0.tgz", - "integrity": "sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.50.0" + "playwright": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -13320,13 +13320,13 @@ } }, "node_modules/playwright": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", - "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.50.0" + "playwright-core": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -13339,9 +13339,9 @@ } }, "node_modules/playwright-core": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", - "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index fa88fd4fd..6ec4319d9 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@electron-forge/maker-zip": "7.6.1", "@electron-forge/plugin-auto-unpack-natives": "7.6.1", "@electron/rebuild": "3.7.1", - "@playwright/test": "1.50.0", + "@playwright/test": "1.50.1", "@types/archiver": "6.0.3", "@types/better-sqlite3": "7.6.12", "@types/bootstrap": "5.2.10", From fd7f3ef2ff48c19795ba49538ddc394b8cf62a40 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 3 Feb 2025 00:49:45 +0200 Subject: [PATCH 049/110] Merge pull request #1101 from TriliumNext/bugfix/mac-paste Remove `mac_init` as it was breaking MacOS --- src/public/app/desktop.ts | 3 --- src/public/app/services/mac_init.ts | 26 -------------------------- src/public/app/setup.ts | 3 --- 3 files changed, 32 deletions(-) delete mode 100644 src/public/app/services/mac_init.ts diff --git a/src/public/app/desktop.ts b/src/public/app/desktop.ts index e6eca440d..d8fad16ef 100644 --- a/src/public/app/desktop.ts +++ b/src/public/app/desktop.ts @@ -4,7 +4,6 @@ import noteTooltipService from "./services/note_tooltip.js"; import bundleService from "./services/bundle.js"; import toastService from "./services/toast.js"; import noteAutocompleteService from "./services/note_autocomplete.js"; -import macInit from "./services/mac_init.js"; import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; @@ -35,8 +34,6 @@ if (utils.isElectron()) { initOnElectron(); } -macInit.init(); - noteTooltipService.setupGlobalTooltip(); noteAutocompleteService.init(); diff --git a/src/public/app/services/mac_init.ts b/src/public/app/services/mac_init.ts deleted file mode 100644 index c864b3cad..000000000 --- a/src/public/app/services/mac_init.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Mac specific initialization - */ -import utils from "./utils.js"; -import shortcutService from "./shortcuts.js"; - -function init() { - if (utils.isElectron() && utils.isMac()) { - shortcutService.bindGlobalShortcut("meta+c", () => exec("copy")); - shortcutService.bindGlobalShortcut("meta+v", () => exec("paste")); - shortcutService.bindGlobalShortcut("meta+x", () => exec("cut")); - shortcutService.bindGlobalShortcut("meta+a", () => exec("selectAll")); - shortcutService.bindGlobalShortcut("meta+z", () => exec("undo")); - shortcutService.bindGlobalShortcut("meta+y", () => exec("redo")); - } -} - -function exec(cmd: string) { - document.execCommand(cmd); - - return false; -} - -export default { - init -}; diff --git a/src/public/app/setup.ts b/src/public/app/setup.ts index ec389c793..1970c110a 100644 --- a/src/public/app/setup.ts +++ b/src/public/app/setup.ts @@ -1,9 +1,6 @@ import utils from "./services/utils.js"; -import macInit from "./services/mac_init.js"; import ko from "knockout"; -macInit.init(); - // TriliumNextTODO: properly make use of below types // type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; // type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop"; From 438f28b5b097f74f3c1d2374a260ac6fd48a4879 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 3 Feb 2025 09:48:06 +0200 Subject: [PATCH 050/110] chore(release): prepare for 0.91.6 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index da8a3c313..ef7966f30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.91.5", + "version": "0.91.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.91.5", + "version": "0.91.6", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "7.1.1", diff --git a/package.json b/package.json index c84735495..7c4a41e3e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes", - "version": "0.91.5", + "version": "0.91.6", "license": "AGPL-3.0-only", "main": "./dist/electron-main.js", "author": { From aa446f7bd644525103b8e199b534cf96ebd498d6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sun, 2 Feb 2025 22:59:27 +0100 Subject: [PATCH 051/110] deps: add @electron-forge/maker-rpm --- package-lock.json | 223 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 224 insertions(+) diff --git a/package-lock.json b/package-lock.json index 3aac1a975..67bdada0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,6 +109,7 @@ "@electron-forge/cli": "7.6.1", "@electron-forge/maker-deb": "7.6.1", "@electron-forge/maker-dmg": "7.6.1", + "@electron-forge/maker-rpm": "7.6.1", "@electron-forge/maker-squirrel": "7.6.1", "@electron-forge/maker-zip": "7.6.1", "@electron-forge/plugin-auto-unpack-natives": "7.6.1", @@ -782,6 +783,23 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/@electron-forge/maker-rpm": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-7.6.1.tgz", + "integrity": "sha512-BShfmto+XTSA01pkZp10r2ktyruVfI24sGC+y4az1vbqkmX2qN9j0Xr+G/ZMHsm76XHju0N/e1Y2pqqu2JM8/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "7.6.1", + "@electron-forge/shared-types": "7.6.1" + }, + "engines": { + "node": ">= 16.4.0" + }, + "optionalDependencies": { + "electron-installer-redhat": "^3.2.0" + } + }, "node_modules/@electron-forge/maker-squirrel": { "version": "7.6.1", "resolved": "https://registry.npmjs.org/@electron-forge/maker-squirrel/-/maker-squirrel-7.6.1.tgz", @@ -8029,6 +8047,211 @@ "appdmg": "^0.6.4" } }, + "node_modules/electron-installer-redhat": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-3.4.0.tgz", + "integrity": "sha512-gEISr3U32Sgtj+fjxUAlSDo3wyGGq6OBx7rF5UdpIgbnpUvMN4W5uYb0ThpnAZ42VEJh/3aODQXHbFS4f5J3Iw==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-redhat": "src/cli.js" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-installer-redhat/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/electron-installer-redhat/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/electron-installer-redhat/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/electron-installer-redhat/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-redhat/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-installer-redhat/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-installer-redhat/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/electron-installer-redhat/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-installer-redhat/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/electron-is-accelerator": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz", diff --git a/package.json b/package.json index fa88fd4fd..40b658a57 100644 --- a/package.json +++ b/package.json @@ -151,6 +151,7 @@ "@electron-forge/cli": "7.6.1", "@electron-forge/maker-deb": "7.6.1", "@electron-forge/maker-dmg": "7.6.1", + "@electron-forge/maker-rpm": "7.6.1", "@electron-forge/maker-squirrel": "7.6.1", "@electron-forge/maker-zip": "7.6.1", "@electron-forge/plugin-auto-unpack-natives": "7.6.1", From 1cd49130dd99206de45c09289ae0071f6ebd150c Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sun, 2 Feb 2025 23:00:22 +0100 Subject: [PATCH 052/110] build(electron): add rpm config --- forge.config.cjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/forge.config.cjs b/forge.config.cjs index 429bd5abe..1b2e06f22 100644 --- a/forge.config.cjs +++ b/forge.config.cjs @@ -58,6 +58,15 @@ module.exports = { } } }, + { + name: "@electron-forge/maker-rpm", + config: { + options: { + icon: "./images/app-icons/png/128x128.png", + desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs") + } + } + }, { name: "@electron-forge/maker-squirrel", config: { From e3c7a72eee3bf2eb920c5489f8e9da02a81d5911 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 3 Feb 2025 08:34:17 +0100 Subject: [PATCH 053/110] build(release): add rpm build for Linux --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8ea938236..fdaad4469 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,7 @@ jobs: extension: dmg - name: linux image: ubuntu-latest - extension: deb + extension: [deb, rpm] - name: windows image: windows-latest extension: exe @@ -37,6 +37,9 @@ jobs: - name: Set up Python for appdmg to be installed if: ${{ matrix.os.name == 'macos' }} run: brew install python-setuptools + - name: Install rpm on Ubuntu for RPM package building + if: ${{ matrix.os.name == 'linux' }} + run: sudo apt install rpm - name: Install dependencies run: npm ci - name: Update build info From 0ce3ae9476ee0ff6e13f388d0d2e2bdaf8f6e1e6 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Mon, 3 Feb 2025 09:21:31 +0100 Subject: [PATCH 054/110] build(release): fix Unix artefact preparation --- .github/workflows/release.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fdaad4469..8257fe571 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,10 +20,10 @@ jobs: os: - name: macos image: macos-latest - extension: dmg + extension: [dmg, zip] - name: linux image: ubuntu-latest - extension: [deb, rpm] + extension: [deb, rpm, zip] - name: windows image: windows-latest extension: exe @@ -49,11 +49,12 @@ jobs: - name: Prepare artifacts (Unix) if: runner.os != 'windows' run: | - mkdir -p upload - file=$(find out/make -name '*.zip' -print -quit) - cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip" - file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit) - cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}" + mkdir -p upload; + for ext in ${{ join(matrix.os.extension, ' ') }}; + do + file=$(find out/make -name "*.$ext" -print -quit); + cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.$ext"; + done - name: Prepare artifacts (Windows) if: runner.os == 'windows' run: | From c832eed0d7a16195d64522ff9147abf1f8a6d54e Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 16:37:27 +0200 Subject: [PATCH 055/110] style(next): fix the combo box style being overridden in some particular cases --- src/public/stylesheets/theme-next/forms.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/public/stylesheets/theme-next/forms.css b/src/public/stylesheets/theme-next/forms.css index f6d8b47e8..5c92497f2 100644 --- a/src/public/stylesheets/theme-next/forms.css +++ b/src/public/stylesheets/theme-next/forms.css @@ -273,6 +273,7 @@ input::selection, select, select.form-select, +select.form-control, .select-button.dropdown-toggle.btn { outline: 3px solid transparent; outline-offset: 6px; @@ -285,6 +286,7 @@ select.form-select, select:hover, select.form-select:hover, +select.form-control:hover, .select-button.dropdown-toggle.btn:hover { background-color: var(--input-hover-background); color: var(--input-hover-color); @@ -297,6 +299,7 @@ button.select-button.dropdown-toggle.btn:active { select:focus, select.form-select:focus, +select.form-control:focus, .select-button.dropdown-toggle.btn:focus { box-shadow: unset; outline: 3px solid var(--input-focus-outline-color); From 9a84dec222c106ca59e105add350feb02ab3ae35 Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 16:44:22 +0200 Subject: [PATCH 056/110] client: "Label detail" dialog: use a consistent checkbox format --- .../app/widgets/attribute_widgets/attribute_detail.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.ts b/src/public/app/widgets/attribute_widgets/attribute_detail.ts index cccb70075..69c518768 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.ts +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.ts @@ -149,8 +149,13 @@ const TPL = ` - ${t("attribute_detail.inheritable")} - + + + + From 8a51f05bf4ef0f85273a9d35afb582d885a64cba Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 16:52:15 +0200 Subject: [PATCH 057/110] client: mark some links to use the new style --- src/public/app/widgets/type_widgets/attachment_detail.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/public/app/widgets/type_widgets/attachment_detail.js b/src/public/app/widgets/type_widgets/attachment_detail.js index 2a797fba5..15932d63b 100644 --- a/src/public/app/widgets/type_widgets/attachment_detail.js +++ b/src/public/app/widgets/type_widgets/attachment_detail.js @@ -15,18 +15,18 @@ const TPL = ` display: flex; flex-direction: column; } - + .attachment-detail .links-wrapper { font-size: larger; padding: 0 0 16px 0; } - + .attachment-detail .attachment-wrapper { flex-grow: 1; } - +
`; @@ -57,7 +57,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { this.$linksWrapper.empty().append( t("attachment_detail.owning_note"), - await linkService.createLink(this.noteId), + (await linkService.createLink(this.noteId)), t("attachment_detail.you_can_also_open"), await linkService.createLink(this.noteId, { title: t("attachment_detail.list_of_all_attachments"), From 4dafb47a36d4d4ae70a048541fc3d32de0dccccb Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 17:00:01 +0200 Subject: [PATCH 058/110] client: add some gap between protected session password prompt form elements --- src/public/app/widgets/type_widgets/protected_session.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/public/app/widgets/type_widgets/protected_session.js b/src/public/app/widgets/type_widgets/protected_session.js index 620946012..3caafb1b7 100644 --- a/src/public/app/widgets/type_widgets/protected_session.js +++ b/src/public/app/widgets/type_widgets/protected_session.js @@ -9,6 +9,11 @@ const TPL = ` width: 300px; margin: 30px auto auto; } + + input, button { + margin-top: 12px; + } +
From 3ec24231f2173c9dfdb2d1714c048333e9c44a6b Mon Sep 17 00:00:00 2001 From: Adorian Doran Date: Mon, 3 Feb 2025 17:06:04 +0200 Subject: [PATCH 059/110] client: "Include note" dialog: use a consistent format for radio buttons --- src/public/app/widgets/dialogs/include_note.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/public/app/widgets/dialogs/include_note.js b/src/public/app/widgets/dialogs/include_note.js index 5778306eb..f3ff29714 100644 --- a/src/public/app/widgets/dialogs/include_note.js +++ b/src/public/app/widgets/dialogs/include_note.js @@ -25,16 +25,22 @@ const TPL = ` ${t("include_note.box_size_prompt")}
- - +
- - +
- - +