From 9fb95585f5b7a1dacc03b9f50d8cae1e71c1cf48 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Tue, 11 Feb 2025 21:58:29 +0100 Subject: [PATCH 01/38] chore(ts): port sync_status.ts --- .../widgets/{sync_status.js => sync_status.ts} | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) rename src/public/app/widgets/{sync_status.js => sync_status.ts} (90%) diff --git a/src/public/app/widgets/sync_status.js b/src/public/app/widgets/sync_status.ts similarity index 90% rename from src/public/app/widgets/sync_status.js rename to src/public/app/widgets/sync_status.ts index d17b7ae17..f5dc186b6 100644 --- a/src/public/app/widgets/sync_status.js +++ b/src/public/app/widgets/sync_status.ts @@ -72,6 +72,15 @@ const TPL = ` `; export default class SyncStatusWidget extends BasicWidget { + + syncState: "unknown" | "in-progress" | "connected" | "disconnected"; + allChangesPushed: boolean; + lastSyncedPush!: number; + settings: { + // TriliumNextTODO: narrow types and use TitlePlacement Type + titlePlacement: string; + } + constructor() { super(); @@ -91,13 +100,14 @@ export default class SyncStatusWidget extends BasicWidget { ws.subscribeToMessages((message) => this.processMessage(message)); } - showIcon(className) { + showIcon(className: string) { if (!options.get("syncServerHost")) { this.toggleInt(false); return; } - Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`), { + + Tooltip.getOrCreateInstance(this.$widget.find(`.sync-status-${className}`)[0], { html: true, placement: this.settings.titlePlacement, fallbackPlacements: [this.settings.titlePlacement] @@ -108,7 +118,8 @@ export default class SyncStatusWidget extends BasicWidget { this.$widget.find(`.sync-status-${className}`).show(); } - processMessage(message) { + // TriliumNextTODO: Use Type Message from "services/ws.ts" + processMessage(message: { type: string; lastSyncedPush: number; data: { lastSyncedPush: number } }) { if (message.type === "sync-pull-in-progress") { this.syncState = "in-progress"; this.lastSyncedPush = message.lastSyncedPush; From da69ee328591cc276551f3ba35d218764c11fdda Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 15:44:35 +0100 Subject: [PATCH 02/38] chore(types): move types to devDependencies --- package-lock.json | 32 +++++++++++++++++++++++++------- package.json | 8 ++++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc4286d18..b235bb301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.92.1-beta", + "version": "0.92.2-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.92.1-beta", + "version": "0.92.2-beta", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "7.1.1", @@ -20,10 +20,6 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.4", "@triliumnext/express-partial-content": "1.0.1", - "@types/js-yaml": "4.0.9", - "@types/leaflet": "1.9.16", - "@types/react-dom": "18.3.5", - "@types/swagger-ui-express": "4.1.8", "archiver": "7.0.1", "async-mutex": "0.5.0", "autocomplete.js": "0.38.1", @@ -141,12 +137,15 @@ "@types/html": "1.0.4", "@types/ini": "4.1.1", "@types/jquery": "3.5.32", + "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", + "@types/leaflet": "1.9.16", "@types/leaflet-gpx": "1.3.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", "@types/node": "22.13.5", "@types/react": "18.3.18", + "@types/react-dom": "18.3.5", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", @@ -154,6 +153,7 @@ "@types/session-file-store": "1.2.5", "@types/source-map-support": "0.5.10", "@types/stream-throttle": "0.1.4", + "@types/swagger-ui-express": "4.1.8", "@types/tmp": "0.2.6", "@types/turndown": "5.0.5", "@types/ws": "8.5.14", @@ -4009,6 +4009,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -4071,6 +4072,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -4397,6 +4399,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -4409,6 +4412,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -4483,6 +4487,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, "license": "MIT" }, "node_modules/@types/ini": { @@ -4505,7 +4510,9 @@ "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", - "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==" + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/jsdom": { "version": "21.1.7", @@ -4548,6 +4555,7 @@ "version": "1.9.16", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.16.tgz", "integrity": "sha512-wzZoyySUxkgMZ0ihJ7IaUIblG8Rdc8AbbZKLneyn+QjYsj5q1QU7TEKYqwTr10BGSzY5LI7tJk9Ifo+mEjdFRw==", + "dev": true, "license": "MIT", "dependencies": { "@types/geojson": "*" @@ -4592,6 +4600,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, "license": "MIT" }, "node_modules/@types/mime-types": { @@ -4632,24 +4641,28 @@ "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/qs": { "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4660,6 +4673,7 @@ "version": "18.3.5", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz", "integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" @@ -4715,6 +4729,7 @@ "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -4735,6 +4750,7 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -4784,6 +4800,7 @@ "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, "license": "MIT", "dependencies": { "@types/express": "*", @@ -7501,6 +7518,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/cytoscape": { diff --git a/package.json b/package.json index e6c9974d1..984636a8b 100644 --- a/package.json +++ b/package.json @@ -70,10 +70,6 @@ "@mermaid-js/layout-elk": "0.1.7", "@mind-elixir/node-menu": "1.0.4", "@triliumnext/express-partial-content": "1.0.1", - "@types/js-yaml": "4.0.9", - "@types/leaflet": "1.9.16", - "@types/react-dom": "18.3.5", - "@types/swagger-ui-express": "4.1.8", "archiver": "7.0.1", "async-mutex": "0.5.0", "autocomplete.js": "0.38.1", @@ -188,12 +184,15 @@ "@types/html": "1.0.4", "@types/ini": "4.1.1", "@types/jquery": "3.5.32", + "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", + "@types/leaflet": "1.9.16", "@types/leaflet-gpx": "1.3.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", "@types/node": "22.13.5", "@types/react": "18.3.18", + "@types/react-dom": "18.3.5", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", @@ -201,6 +200,7 @@ "@types/session-file-store": "1.2.5", "@types/source-map-support": "0.5.10", "@types/stream-throttle": "0.1.4", + "@types/swagger-ui-express": "4.1.8", "@types/tmp": "0.2.6", "@types/turndown": "5.0.5", "@types/ws": "8.5.14", From 49b52d312432504b172530982ec462cc26817f2d Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 15:47:30 +0100 Subject: [PATCH 03/38] chore: run `npm audit fix` 1 vuln fixed, 6 still open, but there is no fix for these yet https://github.com/advisories/GHSA-67mh-4wv8-2f99 --- package-lock.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index b235bb301..21ea55b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8385,9 +8385,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz", - "integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -14450,9 +14450,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "funding": [ { "type": "opencollective", @@ -14469,7 +14469,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -18035,15 +18035,15 @@ } }, "node_modules/vite": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", - "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.1.tgz", + "integrity": "sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", - "postcss": "^8.4.49", - "rollup": "^4.23.0" + "postcss": "^8.5.2", + "rollup": "^4.30.1" }, "bin": { "vite": "bin/vite.js" From 755b20bbab85622081464ef0e90a6a79b6af2685 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 16:01:49 +0100 Subject: [PATCH 04/38] fix(global_menu): add missing to zen mode --- src/public/app/widgets/buttons/global_menu.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/widgets/buttons/global_menu.ts b/src/public/app/widgets/buttons/global_menu.ts index 140d418d6..143f339bd 100644 --- a/src/public/app/widgets/buttons/global_menu.ts +++ b/src/public/app/widgets/buttons/global_menu.ts @@ -137,6 +137,7 @@ const TPL = ` From 4523307ead42188a9cd966628fd088d5f61428a8 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 16:12:12 +0100 Subject: [PATCH 05/38] i18n(close_zen_button): add a translatable title to the button previously it only displayed the keyboard shortcut, without any extra info --- src/public/app/widgets/close_zen_button.ts | 5 ++++- src/public/translations/en/translation.json | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/public/app/widgets/close_zen_button.ts b/src/public/app/widgets/close_zen_button.ts index 138fa6ed6..27e64183d 100644 --- a/src/public/app/widgets/close_zen_button.ts +++ b/src/public/app/widgets/close_zen_button.ts @@ -1,9 +1,12 @@ import BasicWidget from "./basic_widget.js"; +import { t } from "../services/i18n.js"; const TPL = `\
- + +
-
- - - +
+ +
@@ -163,6 +179,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { private themeStyle!: string; private $container!: JQuery; private $styleResolver!: JQuery; + private $fixNodesButton!: JQuery; graph!: ForceGraph; private noteIdToSizeMap!: Record; private zoomLevel!: number; @@ -182,6 +199,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget { this.$container = this.$widget.find(".note-map-container"); this.$styleResolver = this.$widget.find(".style-resolver"); + this.$fixNodesButton = this.$widget.find(".fixnodes-type-switcher > button"); new ResizeObserver(() => this.setDimensions()).observe(this.$container[0]); @@ -191,11 +209,11 @@ export default class NoteMapWidget extends NoteContextAwareWidget { await attributeService.setLabel(this.noteId ?? "", "mapType", type); }); - // Reading the status of the Drag nodes Ui element. Changing it´s color when activated. Reading Force value of the link distance. - - this.$widget.find(".fixnodes-type-switcher").on("click", async (event) => { + // Reading the status of the Drag nodes Ui element. Changing it´s color when activated. + // Reading Force value of the link distance. + this.$fixNodesButton.on("click", async (event) => { this.fixNodes = !this.fixNodes; - event.target.style.backgroundColor = this.fixNodes ? "#661822" : "transparent"; + this.$fixNodesButton.toggleClass("toggled", this.fixNodes); }); super.doRender(); From 31170744d1ee650a75d5ccf9526c8c806a42fc94 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 16:46:23 +0100 Subject: [PATCH 09/38] chore(ts): allow link.createLink notePath type to accept undefined it can also accept undefined -> it is even handled in that first if block. change required for upcoming port of attachment_*.js files --- src/public/app/services/link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/public/app/services/link.ts b/src/public/app/services/link.ts index 472f7d37a..9b3c779bb 100644 --- a/src/public/app/services/link.ts +++ b/src/public/app/services/link.ts @@ -70,7 +70,7 @@ interface CreateLinkOptions { viewScope?: ViewScope; } -async function createLink(notePath: string, options: CreateLinkOptions = {}) { +async function createLink(notePath: string | undefined, options: CreateLinkOptions = {}) { if (!notePath || !notePath.trim()) { logError("Missing note path"); From 70756fe7955a1ecbaf37d95b672ca510c9435ca7 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 16:58:55 +0100 Subject: [PATCH 10/38] chore(ts): start port of type_widgets/attachment_detail --- .../{attachment_detail.js => attachment_detail.ts} | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) rename src/public/app/widgets/type_widgets/{attachment_detail.js => attachment_detail.ts} (84%) diff --git a/src/public/app/widgets/type_widgets/attachment_detail.js b/src/public/app/widgets/type_widgets/attachment_detail.ts similarity index 84% rename from src/public/app/widgets/type_widgets/attachment_detail.js rename to src/public/app/widgets/type_widgets/attachment_detail.ts index 108c5da2e..5fc8e4201 100644 --- a/src/public/app/widgets/type_widgets/attachment_detail.js +++ b/src/public/app/widgets/type_widgets/attachment_detail.ts @@ -4,6 +4,8 @@ import linkService from "../../services/link.js"; import froca from "../../services/froca.js"; import utils from "../../services/utils.js"; import { t } from "../../services/i18n.js"; +import type FNote from "../../entities/fnote.js"; +import type { EventData } from "../../components/app_context.js"; const TPL = `
@@ -32,6 +34,9 @@ const TPL = `
`; export default class AttachmentDetailTypeWidget extends TypeWidget { + $wrapper!: JQuery; + $linksWrapper!: JQuery; + static getType() { return "attachmentDetail"; } @@ -44,7 +49,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { super.doRender(); } - async doRefresh(note) { + async doRefresh(note: Parameters[0]) { this.$wrapper.empty(); this.children = []; @@ -69,7 +74,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { $helpButton ); - const attachment = await froca.getAttachment(this.attachmentId, true); + const attachment = (this.attachmentId) ? await froca.getAttachment(this.attachmentId, true) : null; if (!attachment) { this.$wrapper.html("" + t("attachment_detail.attachment_deleted") + ""); @@ -82,7 +87,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { this.$wrapper.append(attachmentDetailWidget.render()); } - async entitiesReloadedEvent({ loadResults }) { + async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { const attachmentRow = loadResults.getAttachmentRows().find((att) => att.attachmentId === this.attachmentId); if (attachmentRow?.isDeleted) { @@ -91,6 +96,6 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { } get attachmentId() { - return this.noteContext.viewScope.attachmentId; + return this?.noteContext?.viewScope?.attachmentId; } } From bf15192b252158bb0d6b9dc6bb364bb3dd3bb086 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 17:39:30 +0100 Subject: [PATCH 11/38] chore(ts): start port of widgets/buttons/attachment_actions --- ...ents_actions.js => attachments_actions.ts} | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) rename src/public/app/widgets/buttons/{attachments_actions.js => attachments_actions.ts} (86%) diff --git a/src/public/app/widgets/buttons/attachments_actions.js b/src/public/app/widgets/buttons/attachments_actions.ts similarity index 86% rename from src/public/app/widgets/buttons/attachments_actions.js rename to src/public/app/widgets/buttons/attachments_actions.ts index 5ee51b9d9..f25e9c376 100644 --- a/src/public/app/widgets/buttons/attachments_actions.js +++ b/src/public/app/widgets/buttons/attachments_actions.ts @@ -8,6 +8,8 @@ import appContext from "../../components/app_context.js"; import openService from "../../services/open.js"; import utils from "../../services/utils.js"; import { Dropdown } from "bootstrap"; +import type attachmentsApiRoute from "../../../../routes/api/attachments.js" +import type FAttachment from "../../entities/fattachment.js"; const TPL = ` `; export default class AttachmentActionsWidget extends BasicWidget { - constructor(attachment, isFullDetail) { + $uploadNewRevisionInput!: JQuery; + attachment: FAttachment; + isFullDetail: boolean; + dropdown!: Dropdown; + + constructor(attachment: FAttachment, isFullDetail: boolean) { super(); this.attachment = attachment; @@ -92,20 +99,21 @@ export default class AttachmentActionsWidget extends BasicWidget { doRender() { this.$widget = $(TPL); - this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); + this.dropdown = Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")[0]); this.$widget.on("click", ".dropdown-item", () => this.dropdown.toggle()); this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input"); this.$uploadNewRevisionInput.on("change", async () => { - const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below + + const fileToUpload = this.$uploadNewRevisionInput[0].files?.item(0); // copy to allow reset below this.$uploadNewRevisionInput.val(""); - - const result = await server.upload(`attachments/${this.attachmentId}/file`, fileToUpload); - - if (result.uploaded) { - toastService.showMessage(t("attachments_actions.upload_success")); - } else { - toastService.showError(t("attachments_actions.upload_failed")); + if (fileToUpload) { + const result = await server.upload(`attachments/${this.attachmentId}/file`, fileToUpload); + if (result.uploaded) { + toastService.showMessage(t("attachments_actions.upload_success")); + } else { + toastService.showError(t("attachments_actions.upload_failed")); + } } }); @@ -122,6 +130,7 @@ export default class AttachmentActionsWidget extends BasicWidget { const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); $openAttachmentCustomButton.addClass("disabled").append($('').attr("title", t("attachments_actions.open_custom_client_only"))); } + } async openAttachmentCommand() { @@ -141,7 +150,8 @@ export default class AttachmentActionsWidget extends BasicWidget { } async copyAttachmentLinkToClipboardCommand() { - this.parent.copyAttachmentLinkToClipboard(); + //TriliumNextTODO: the parent here is AttachmentDetailWidget + this.parent?.copyAttachmentLinkToClipboard(); } async deleteAttachmentCommand() { @@ -158,7 +168,8 @@ export default class AttachmentActionsWidget extends BasicWidget { return; } - const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`); + + const { note: newNote } = await server.post>(`attachments/${this.attachmentId}/convert-to-note`); toastService.showMessage(t("attachments_actions.convert_success", { title: this.attachment.title })); await ws.waitForMaxKnownEntityChangeId(); await appContext.tabManager.getActiveContext().setNote(newNote.noteId); From f6785f7980ae9422087dc6262b0c4883a8c60008 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 18:38:00 +0100 Subject: [PATCH 12/38] chore(ts): add missing isDeleted and deleteId types to AttachmentRow --- src/becca/entities/rows.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/becca/entities/rows.ts b/src/becca/entities/rows.ts index ba9189190..b57801e1e 100644 --- a/src/becca/entities/rows.ts +++ b/src/becca/entities/rows.ts @@ -1,4 +1,5 @@ // TODO: Booleans should probably be numbers instead (as SQLite does not have booleans.); +// TODO: check against schema.sql which properties really are "optional" export interface AttachmentRow { attachmentId?: string; @@ -12,6 +13,8 @@ export interface AttachmentRow { dateModified?: string; utcDateModified?: string; utcDateScheduledForErasureSince?: string; + isDeleted?: boolean; + deleteId?: string; contentLength?: number; content?: Buffer | string; } From 8f643c62e3cf336b8db4c9f22f82ce8b9e24b025 Mon Sep 17 00:00:00 2001 From: Panagiotis Papadopoulos Date: Sat, 22 Feb 2025 18:48:46 +0100 Subject: [PATCH 13/38] chore(ts): port of type_widgets/attachment_list --- ...{attachment_list.js => attachment_list.ts} | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) rename src/public/app/widgets/type_widgets/{attachment_list.js => attachment_list.ts} (79%) diff --git a/src/public/app/widgets/type_widgets/attachment_list.js b/src/public/app/widgets/type_widgets/attachment_list.ts similarity index 79% rename from src/public/app/widgets/type_widgets/attachment_list.js rename to src/public/app/widgets/type_widgets/attachment_list.ts index f6eadb97e..3018f784b 100644 --- a/src/public/app/widgets/type_widgets/attachment_list.js +++ b/src/public/app/widgets/type_widgets/attachment_list.ts @@ -3,6 +3,7 @@ import AttachmentDetailWidget from "../attachment_detail.js"; import linkService from "../../services/link.js"; import utils from "../../services/utils.js"; import { t } from "../../services/i18n.js"; +import type { EventData } from "../../components/app_context.js"; const TPL = `
@@ -27,6 +28,10 @@ const TPL = `
`; export default class AttachmentListTypeWidget extends TypeWidget { + $list!: JQuery; + $linksWrapper!: JQuery; + renderedAttachmentIds!: Set; + static getType() { return "attachmentList"; } @@ -39,7 +44,10 @@ export default class AttachmentListTypeWidget extends TypeWidget { super.doRender(); } - async doRefresh(note) { + async doRefresh(note: Parameters[0]) { + // TriliumNextTODO: do we need to handle an undefined/null note? + if (!note) return false; + const $helpButton = $(` @@ -45,17 +46,17 @@ const TPL = ` - + - + - + @@ -70,13 +71,13 @@ const TPL = ` - + - +
- + `; @@ -150,10 +151,9 @@ export default class AttachmentActionsWidget extends BasicWidget { } async copyAttachmentLinkToClipboardCommand() { - //TriliumNextTODO: the parent here is AttachmentDetailWidget - //how can we pass that to the generic TypedComponent? - //@ts-ignore - TypedComponent - this.parent?.copyAttachmentLinkToClipboard(); + if (this.parent && "copyAttachmentLinkToClipboard" in this.parent) { + (this.parent as AttachmentDetailWidget).copyAttachmentLinkToClipboard(); + } } async deleteAttachmentCommand() { From 249c42e781889f3fc5cc125903fd32be6b85b8a4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 18:39:34 +0200 Subject: [PATCH 28/38] fix(view/calendar): guard condition breaking recursion --- src/public/app/widgets/view_widgets/calendar_view.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index fb375d346..d869b4b5d 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -280,9 +280,6 @@ export default class CalendarView extends ViewMode { for (const note of notes) { const startDate = note.getLabelValue("startDate"); - if (!startDate) { - continue; - } if (note.hasChildren()) { const childrenEventData = await this.#buildEvents(note.getChildNoteIds()); @@ -291,6 +288,9 @@ export default class CalendarView extends ViewMode { } } + if (!startDate) { + continue; + } const endDate = note.getAttributeValue("label", "endDate"); events.push(await CalendarView.#buildEvent(note, startDate, endDate)); From 07147bf857f5bc87e9be9bf3db2fa79e45365f04 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 19:14:09 +0200 Subject: [PATCH 29/38] fix(view/calendar): add basic support for promoted attributes --- .../app/widgets/view_widgets/calendar_view.ts | 70 +++++++++++++++---- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index d869b4b5d..0421fe4db 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -48,6 +48,11 @@ const TPL = ` .calendar-container .fc-button { padding: 0.2em 0.5em; } + + .calendar-container .promoted-attribute { + font-size: 0.85em; + opacity: 0.85; + }
@@ -119,13 +124,24 @@ export default class CalendarView extends ViewMode { height: "100%", eventContent: (e => { let html = ""; + const { iconClass, promotedAttributes } = e.event.extendedProps; - const iconClass = e.event.extendedProps.iconClass; + // Title and icon if (iconClass) { html += ` `; } - html += utils.escapeHtml(e.event.title); + + // Promoted attributes + if (promotedAttributes) { + for (const [ name, value ] of Object.entries(promotedAttributes)) { + html += `\ + `; + } + } + return { html }; }), dateClick: async (e) => { @@ -306,27 +322,22 @@ export default class CalendarView extends ViewMode { const events: EventInput[] = []; // the user can specify one or multiple attributes to be promoted onto the calendar view by setting `#calendar:promotedAttributes` at the note level // their values will then be rentered into the event title and appear as "[eventIcon] $eventTitle [#promotedAttributeX=valueX] [#promotedAttributeY=valueY]" - const promotedAttrs = note - .getAttributes() - .filter((attr) => attr.type == "label" && attr.name == "calendar:promotedAttribute") - .map((attr) => attr.value.substring(1)); - let titleExtended = ""; - if (promotedAttrs && promotedAttrs.length) { - const promotedValues = note - .getAttributes() - .filter((attr) => promotedAttrs.includes(attr.name)) - .map((attr) => [attr.name, attr.value]); - for (const defined of promotedValues) titleExtended = titleExtended + ` [#${defined[0]}="${defined[1]}"]`; + + const calendarPromotedAttributes = note.getLabelValue("calendar:promotedAttributes"); + let promotedAttributesData = null; + if (calendarPromotedAttributes) { + promotedAttributesData = await this.#buildPromotedAttributes(note, calendarPromotedAttributes); } for (const title of titles) { const eventData: EventInput = { - title: title + titleExtended, + title: title, start: startDate, url: `#${note.noteId}`, noteId: note.noteId, color: color ?? undefined, - iconClass: note.getLabelValue("iconClass") + iconClass: note.getLabelValue("iconClass"), + promotedAttributes: promotedAttributesData }; const endDateOffset = CalendarView.#offsetDate(endDate ?? startDate, 1); @@ -338,6 +349,35 @@ export default class CalendarView extends ViewMode { return events; } + static async #buildPromotedAttributes(note: FNote, calendarPromotedAttributes: string) { + const promotedAttributeNames = calendarPromotedAttributes.split(","); + const filteredPromotedAttributes = note.getPromotedDefinitionAttributes().filter((attr) => promotedAttributeNames.includes(attr.name)); + const result: Record = {}; + + for (const promotedAttribute of filteredPromotedAttributes) { + const [ type, name ] = promotedAttribute.name.split(":", 2); + const definition = promotedAttribute.getDefinition(); + + if (definition.multiplicity !== "single") { + // TODO: Add support for multiple definitions. + continue; + } + + // TODO: Add support for relations + if (type !== "label" || !note.hasLabel(name)) { + continue; + } + + const value = note.getLabelValue(name); + const friendlyName = definition.promotedAlias ?? name; + if (friendlyName && value) { + result[friendlyName] = value; + } + } + + return result; + } + static async #parseCustomTitle(customTitleValue: string | null, note: FNote, allowRelations = true): Promise { if (customTitleValue) { const attributeName = customTitleValue.substring(1); From 95e6919dcf6d0e4ad35f326fa36efeb39ca41855 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 19:15:33 +0200 Subject: [PATCH 30/38] chore(calendar/view): remove unnecessary comment --- src/public/app/widgets/view_widgets/calendar_view.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index 0421fe4db..e2987728b 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -320,8 +320,6 @@ export default class CalendarView extends ViewMode { const titles = await CalendarView.#parseCustomTitle(customTitle, note); const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color"); const events: EventInput[] = []; - // the user can specify one or multiple attributes to be promoted onto the calendar view by setting `#calendar:promotedAttributes` at the note level - // their values will then be rentered into the event title and appear as "[eventIcon] $eventTitle [#promotedAttributeX=valueX] [#promotedAttributeY=valueY]" const calendarPromotedAttributes = note.getLabelValue("calendar:promotedAttributes"); let promotedAttributesData = null; From d319eede1f3e12064816591b298f799cc2201ab8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 19:23:00 +0200 Subject: [PATCH 31/38] style(views/calendar): disable link underline --- src/public/app/widgets/view_widgets/calendar_view.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index e2987728b..9541bd0e6 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -45,6 +45,10 @@ const TPL = ` font-weight: normal; } + .calendar-container a.fc-event { + text-decoration: none; + } + .calendar-container .fc-button { padding: 0.2em 0.5em; } From 24c02e013b95b3d9930e302aa9c2bb418aeea68e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 19:58:43 +0200 Subject: [PATCH 32/38] fix(import/enex): "Missing or wrong content type for resource" (fixes #943) --- src/services/import/enex.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/services/import/enex.ts b/src/services/import/enex.ts index 9ac064109..ae248ab75 100644 --- a/src/services/import/enex.ts +++ b/src/services/import/enex.ts @@ -315,14 +315,10 @@ function importEnex(taskContext: TaskContext, file: File, parentNote: BNote): Pr resource.mime = resource.mime || "application/octet-stream"; const createFileNote = () => { - if (typeof resource.content !== "string") { - throw new Error("Missing or wrong content type for resource."); - } - const resourceNote = noteService.createNewNote({ parentNoteId: noteEntity.noteId, title: resource.title, - content: resource.content, + content: resource.content ?? "", type: "file", mime: resource.mime, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() From 31fcf7ea60647f2e5c96d171ff6c3c035a8fa5d9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 21:39:05 +0200 Subject: [PATCH 33/38] feat(tasks): clear text box when entering a task --- src/public/app/widgets/type_widgets/task_list.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/app/widgets/type_widgets/task_list.ts b/src/public/app/widgets/type_widgets/task_list.ts index 6c4f1aeda..b0e4ed9d2 100644 --- a/src/public/app/widgets/type_widgets/task_list.ts +++ b/src/public/app/widgets/type_widgets/task_list.ts @@ -79,6 +79,7 @@ export default class TaskListWidget extends TypeWidget { this.$addNewTask.on("keydown", (e) => { if (e.key === "Enter") { this.#createNewTask(String(this.$addNewTask.val())); + this.$addNewTask.val(""); } }); From 0ba4c9b9c78ee44889718e7b7d1b067961f3f757 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 21:54:58 +0200 Subject: [PATCH 34/38] fix(tasks): content flash when updating list of tasks --- .../app/widgets/type_widgets/task_list.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/public/app/widgets/type_widgets/task_list.ts b/src/public/app/widgets/type_widgets/task_list.ts index b0e4ed9d2..8211c5921 100644 --- a/src/public/app/widgets/type_widgets/task_list.ts +++ b/src/public/app/widgets/type_widgets/task_list.ts @@ -57,11 +57,17 @@ const TPL = `
`; -function buildTask(task: FTask) { - return `\ -
  • - ${task.title} -
  • `; +function buildTasks(tasks: FTask[]) { + let html = ''; + + for (const task of tasks) { + html += `\ +
  • + ${task.title} +
  • `; + } + + return html; } export default class TaskListWidget extends TypeWidget { @@ -113,12 +119,9 @@ export default class TaskListWidget extends TypeWidget { return; } - this.$taskContainer.html(""); - const tasks = await froca.getTasks(this.noteId); - for (const task of tasks) { - this.$taskContainer.append($(buildTask(task))); - } + const tasksHtml = buildTasks(tasks); + this.$taskContainer.html(tasksHtml); } entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { From b91b243432d7af7b1a37feac829f4fe99f2c91ef Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 23 Feb 2025 22:00:50 +0200 Subject: [PATCH 35/38] fix(tasks): selection contains spaces --- src/public/app/widgets/type_widgets/task_list.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/public/app/widgets/type_widgets/task_list.ts b/src/public/app/widgets/type_widgets/task_list.ts index 8211c5921..8a0131e16 100644 --- a/src/public/app/widgets/type_widgets/task_list.ts +++ b/src/public/app/widgets/type_widgets/task_list.ts @@ -61,10 +61,7 @@ function buildTasks(tasks: FTask[]) { let html = ''; for (const task of tasks) { - html += `\ -
  • - ${task.title} -
  • `; + html += `
  • ${task.title}
  • `; } return html; From 8ab0084e100f388db2c2d22b29467ee8677d8a08 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 24 Feb 2025 13:45:36 +0200 Subject: [PATCH 36/38] chore(client/ts): port search_definition --- src/public/app/components/app_context.ts | 4 + ...rch_definition.js => search_definition.ts} | 109 +++++++++++------- ...ch_option.js => abstract_search_option.ts} | 34 +++--- 3 files changed, 95 insertions(+), 52 deletions(-) rename src/public/app/widgets/ribbon_widgets/{search_definition.js => search_definition.ts} (83%) rename src/public/app/widgets/search_options/{abstract_search_option.js => abstract_search_option.ts} (55%) diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index f54fd88fa..25bdb1daa 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -288,6 +288,10 @@ type EventMappings = { showHighlightsListWidget: { noteId: string; }; + showSearchError: { + error: string; + }; + searchRefreshed: { ntxId?: string | null }; hoistedNoteChanged: { noteId: string; ntxId: string | null; diff --git a/src/public/app/widgets/ribbon_widgets/search_definition.js b/src/public/app/widgets/ribbon_widgets/search_definition.ts similarity index 83% rename from src/public/app/widgets/ribbon_widgets/search_definition.js rename to src/public/app/widgets/ribbon_widgets/search_definition.ts index 9f3bb5cb0..2328f996f 100644 --- a/src/public/app/widgets/ribbon_widgets/search_definition.js +++ b/src/public/app/widgets/ribbon_widgets/search_definition.ts @@ -14,13 +14,15 @@ import OrderBy from "../search_options/order_by.js"; import SearchScript from "../search_options/search_script.js"; import Limit from "../search_options/limit.js"; import Debug from "../search_options/debug.js"; -import appContext from "../../components/app_context.js"; +import appContext, { type EventData } from "../../components/app_context.js"; import bulkActionService from "../../services/bulk_action.js"; import { Dropdown } from "bootstrap"; +import type FNote from "../../entities/fnote.js"; +import type { AttributeType } from "../../entities/fattribute.js"; const TPL = `
    -