mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
Merge branch 'develop' into develop
This commit is contained in:
commit
9d6caa84cd
64
package-lock.json
generated
64
package-lock.json
generated
@ -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",
|
||||
@ -171,7 +171,7 @@
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"nodemon": "3.1.9",
|
||||
"postcss-loader": "8.1.1",
|
||||
"prettier": "3.5.1",
|
||||
"prettier": "3.5.2",
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"sass": "1.85.0",
|
||||
@ -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": {
|
||||
@ -8367,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"
|
||||
@ -14432,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",
|
||||
@ -14451,7 +14469,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"nanoid": "^3.3.8",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
@ -14638,9 +14656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz",
|
||||
"integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==",
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
|
||||
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@ -18017,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"
|
||||
|
12
package.json
12
package.json
@ -28,7 +28,7 @@
|
||||
"server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
|
||||
"electron:start": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./electron-main.ts --inspect=5858 .",
|
||||
"electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
|
||||
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev nix-shell -p electron_34 --run \"electron ./electron-main.ts --inspect=5858 .\"",
|
||||
"electron:start-nix-no-dir": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
|
||||
"electron:start-prod": "npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
|
||||
"electron:start-prod-no-dir": "npm run build:prepare-dist && cross-env TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||
@ -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",
|
||||
@ -218,7 +218,7 @@
|
||||
"mini-css-extract-plugin": "2.9.2",
|
||||
"nodemon": "3.1.9",
|
||||
"postcss-loader": "8.1.1",
|
||||
"prettier": "3.5.1",
|
||||
"prettier": "3.5.2",
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"sass": "1.85.0",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -288,6 +288,10 @@ type EventMappings = {
|
||||
showHighlightsListWidget: {
|
||||
noteId: string;
|
||||
};
|
||||
showSearchError: {
|
||||
error: string;
|
||||
};
|
||||
searchRefreshed: { ntxId?: string | null };
|
||||
hoistedNoteChanged: {
|
||||
noteId: string;
|
||||
ntxId: string | null;
|
||||
@ -365,6 +369,8 @@ class AppContext extends Component {
|
||||
layout?: Layout;
|
||||
noteTreeWidget?: NoteTreeWidget;
|
||||
|
||||
lastSearchString?: string;
|
||||
|
||||
constructor(isMainWindow: boolean) {
|
||||
super();
|
||||
|
||||
|
@ -19,18 +19,18 @@ export interface FAttachmentRow {
|
||||
class FAttachment {
|
||||
private froca: Froca;
|
||||
attachmentId!: string;
|
||||
private ownerId!: string;
|
||||
ownerId!: string;
|
||||
role!: string;
|
||||
mime!: string;
|
||||
title!: string;
|
||||
isProtected!: boolean; // TODO: Is this used?
|
||||
private dateModified!: string;
|
||||
utcDateModified!: string;
|
||||
private utcDateScheduledForErasureSince!: string;
|
||||
utcDateScheduledForErasureSince!: string;
|
||||
/**
|
||||
* optionally added to the entity
|
||||
*/
|
||||
private contentLength!: number;
|
||||
contentLength!: number;
|
||||
|
||||
constructor(froca: Froca, row: FAttachmentRow) {
|
||||
/** @type {Froca} */
|
||||
|
@ -24,7 +24,8 @@ interface Options {
|
||||
|
||||
const CODE_MIME_TYPES = new Set(["application/json"]);
|
||||
|
||||
async function getRenderedContent(this: {} | { ctx: string }, entity: FNote, options: Options = {}) {
|
||||
async function getRenderedContent(this: {} | { ctx: string }, entity: FNote | FAttachment, options: Options = {}) {
|
||||
|
||||
options = Object.assign(
|
||||
{
|
||||
tooltip: false
|
||||
@ -47,7 +48,7 @@ async function getRenderedContent(this: {} | { ctx: string }, entity: FNote, opt
|
||||
renderFile(entity, type, $renderedContent);
|
||||
} else if (type === "mermaid") {
|
||||
await renderMermaid(entity, $renderedContent);
|
||||
} else if (type === "render") {
|
||||
} else if (type === "render" && entity instanceof FNote) {
|
||||
const $content = $("<div>");
|
||||
|
||||
await renderService.render(entity, $content);
|
||||
@ -79,7 +80,7 @@ async function getRenderedContent(this: {} | { ctx: string }, entity: FNote, opt
|
||||
};
|
||||
}
|
||||
|
||||
async function renderText(note: FNote, $renderedContent: JQuery<HTMLElement>) {
|
||||
async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
|
||||
// entity must be FNote
|
||||
const blob = await note.getBlob();
|
||||
|
||||
@ -102,7 +103,7 @@ async function renderText(note: FNote, $renderedContent: JQuery<HTMLElement>) {
|
||||
}
|
||||
|
||||
await applySyntaxHighlight($renderedContent);
|
||||
} else {
|
||||
} else if (note instanceof FNote) {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
}
|
||||
}
|
||||
@ -110,7 +111,7 @@ async function renderText(note: FNote, $renderedContent: JQuery<HTMLElement>) {
|
||||
/**
|
||||
* Renders a code note, by displaying its content and applying syntax highlighting based on the selected MIME type.
|
||||
*/
|
||||
async function renderCode(note: FNote, $renderedContent: JQuery<HTMLElement>) {
|
||||
async function renderCode(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
|
||||
const blob = await note.getBlob();
|
||||
|
||||
const $codeBlock = $("<code>");
|
||||
@ -208,7 +209,7 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
|
||||
async function renderMermaid(note: FNote, $renderedContent: JQuery<HTMLElement>) {
|
||||
async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
|
||||
const blob = await note.getBlob();
|
||||
|
@ -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");
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { TaskRow } from "../../../becca/entities/rows.js";
|
||||
import type { TaskRow, AttachmentRow } from "../../../becca/entities/rows.js";
|
||||
import type { AttributeType } from "../entities/fattribute.js";
|
||||
import type { EntityChange } from "../server_types.js";
|
||||
|
||||
@ -37,8 +37,6 @@ interface ContentNoteIdToComponentIdRow {
|
||||
componentId: string;
|
||||
}
|
||||
|
||||
interface AttachmentRow {}
|
||||
|
||||
interface OptionRow {}
|
||||
|
||||
interface NoteReorderingRow {}
|
||||
|
@ -7,6 +7,8 @@ import imageService from "../services/image.js";
|
||||
import linkService from "../services/link.js";
|
||||
import contentRenderer from "../services/content_renderer.js";
|
||||
import toastService from "../services/toast.js";
|
||||
import type FAttachment from "../entities/fattachment.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail-widget">
|
||||
@ -96,7 +98,12 @@ const TPL = `
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentDetailWidget extends BasicWidget {
|
||||
constructor(attachment, isFullDetail) {
|
||||
attachment: FAttachment;
|
||||
attachmentActionsWidget: AttachmentActionsWidget;
|
||||
isFullDetail: boolean;
|
||||
$wrapper!: JQuery<HTMLElement>;
|
||||
|
||||
constructor(attachment: FAttachment, isFullDetail: boolean) {
|
||||
super();
|
||||
|
||||
this.contentSized();
|
||||
@ -140,7 +147,8 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
this.$wrapper.addClass("scheduled-for-deletion");
|
||||
|
||||
const scheduledSinceTimestamp = utils.parseDate(utcDateScheduledForErasureSince)?.getTime();
|
||||
const intervalMs = options.getInt("eraseUnusedAttachmentsAfterSeconds") * 1000;
|
||||
// use default value (30 days in seconds) from options_init as fallback, in case getInt returns null
|
||||
const intervalMs = options.getInt("eraseUnusedAttachmentsAfterSeconds") || 2592000 * 1000;
|
||||
const deletionTimestamp = scheduledSinceTimestamp + intervalMs;
|
||||
const willBeDeletedInMs = deletionTimestamp - Date.now();
|
||||
|
||||
@ -185,7 +193,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
const attachmentRow = loadResults.getAttachmentRows().find((att) => att.attachmentId === this.attachment.attachmentId);
|
||||
|
||||
if (attachmentRow) {
|
@ -8,6 +8,9 @@ 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";
|
||||
import type AttachmentDetailWidget from "../attachment_detail.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown attachment-actions">
|
||||
@ -16,11 +19,11 @@ const TPL = `
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
|
||||
.attachment-actions .dropdown-menu {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
|
||||
.attachment-actions .dropdown-item .bx {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
@ -35,7 +38,7 @@ const TPL = `
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
|
||||
style="position: relative; top: 3px;"></button>
|
||||
|
||||
@ -43,17 +46,17 @@ const TPL = `
|
||||
|
||||
<li data-trigger-command="openAttachment" class="dropdown-item"
|
||||
title="${t("attachments_actions.open_externally_title")}"><span class="bx bx-file-find"></span> ${t("attachments_actions.open_externally")}</li>
|
||||
|
||||
|
||||
<li data-trigger-command="openAttachmentCustom" class="dropdown-item"
|
||||
title="${t("attachments_actions.open_custom_title")}"><span class="bx bx-customize"></span> ${t("attachments_actions.open_custom")}</li>
|
||||
|
||||
|
||||
<li data-trigger-command="downloadAttachment" class="dropdown-item">
|
||||
<span class="bx bx-download"></span> ${t("attachments_actions.download")}</li>
|
||||
|
||||
<li data-trigger-command="copyAttachmentLinkToClipboard" class="dropdown-item"><span class="bx bx-link">
|
||||
</span> ${t("attachments_actions.copy_link_to_clipboard")}</li>
|
||||
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
@ -68,18 +71,23 @@ const TPL = `
|
||||
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
||||
|
||||
|
||||
<li data-trigger-command="convertAttachmentIntoNote" class="dropdown-item"><span class="bx bx-note">
|
||||
</span> ${t("attachments_actions.convert_attachment_into_note")}</li>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<input type="file" class="attachment-upload-new-revision-input" style="display: none">
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentActionsWidget extends BasicWidget {
|
||||
constructor(attachment, isFullDetail) {
|
||||
$uploadNewRevisionInput!: JQuery<HTMLInputElement>;
|
||||
attachment: FAttachment;
|
||||
isFullDetail: boolean;
|
||||
dropdown!: Dropdown;
|
||||
|
||||
constructor(attachment: FAttachment, isFullDetail: boolean) {
|
||||
super();
|
||||
|
||||
this.attachment = attachment;
|
||||
@ -92,20 +100,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 +131,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
|
||||
$openAttachmentCustomButton.addClass("disabled").append($('<span class="bx bx-info-circle disabled-tooltip" />').attr("title", t("attachments_actions.open_custom_client_only")));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async openAttachmentCommand() {
|
||||
@ -141,7 +151,9 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
async copyAttachmentLinkToClipboardCommand() {
|
||||
this.parent.copyAttachmentLinkToClipboard();
|
||||
if (this.parent && "copyAttachmentLinkToClipboard" in this.parent) {
|
||||
(this.parent as AttachmentDetailWidget).copyAttachmentLinkToClipboard();
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAttachmentCommand() {
|
||||
@ -158,7 +170,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<ReturnType<typeof attachmentsApiRoute.convertAttachmentToNote>>(`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);
|
@ -137,6 +137,7 @@ const TPL = `
|
||||
<li class="dropdown-item" data-trigger-command="toggleZenMode">
|
||||
<span class="bx bxs-yin-yang"></span>
|
||||
${t("global_menu.toggle-zen-mode")}
|
||||
<kbd data-command="toggleZenMode"></kbd>
|
||||
</li>
|
||||
|
||||
<div class="dropdown-divider"></div>
|
||||
|
@ -1,9 +1,12 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
|
||||
const TPL = `\
|
||||
<div class="close-zen-container">
|
||||
<button class="button-widget bx icon-action bxs-yin-yang"
|
||||
data-trigger-command="toggleZenMode" />
|
||||
data-trigger-command="toggleZenMode"
|
||||
title="${t("zen_mode.button_exit")}"
|
||||
/>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
|
@ -12,7 +12,7 @@ import type FNote from "../entities/fnote.js";
|
||||
|
||||
const esc = utils.escapeHtml;
|
||||
|
||||
const TPL = `<div class="note-map-widget" style="position: relative;">
|
||||
const TPL = `<div class="note-map-widget">
|
||||
<style>
|
||||
.note-detail-note-map {
|
||||
height: 100%;
|
||||
@ -34,60 +34,76 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
|
||||
/* Style Ui Element to Drag Nodes */
|
||||
.fixnodes-type-switcher {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 45%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
z-index: 10; /* should be below dropdown (note actions) */
|
||||
border-radius:0.2rem;
|
||||
border-radius: .2rem;
|
||||
}
|
||||
|
||||
.fixnodes-type-switcher button.toggled {
|
||||
background: var(--active-item-background-color);
|
||||
color: var(--active-item-text-color);
|
||||
}
|
||||
|
||||
/* Start of styling the slider */
|
||||
input[type="range"] {
|
||||
.fixnodes-type-switcher input[type="range"] {
|
||||
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
margin-left: 15px;
|
||||
width:50%
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Changing slider tracker */
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
height: 6px;
|
||||
background: #ccc;
|
||||
border-radius: 16px;
|
||||
.fixnodes-type-switcher input[type="range"]::-webkit-slider-runnable-track {
|
||||
height: 4px;
|
||||
background-color: var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Changing Slider Thumb */
|
||||
.fixnodes-type-switcher input[type="range"]::-webkit-slider-thumb {
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-top:-5px;
|
||||
background-color: var(--accented-background-color);
|
||||
border: 1px solid var(--main-text-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Changing Slider Thumb*/
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin-top:-4px;
|
||||
background-color: #661822;
|
||||
border-radius: 50%;
|
||||
.fixnodes-type-switcher input[type="range"]::-moz-range-track {
|
||||
background-color: var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.fixnodes-type-switcher input[type="range"]::-moz-range-thumb {
|
||||
background-color: var(--accented-background-color);
|
||||
border-color: var(--main-text-color);
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/* End of styling the slider */
|
||||
|
||||
</style>
|
||||
|
||||
<div class="btn-group btn-group-sm map-type-switcher" role="group">
|
||||
<button type="button" class="btn bx bx-network-chart" title="${t("note-map.button-link-map")}" data-type="link"></button>
|
||||
<button type="button" class="btn bx bx-sitemap" title="${t("note-map.button-tree-map")}" data-type="tree"></button>
|
||||
<button type="button" class="btn bx bx-network-chart tn-tool-button" title="${t("note-map.button-link-map")}" data-type="link"></button>
|
||||
<button type="button" class="btn bx bx-sitemap tn-tool-button" title="${t("note-map.button-tree-map")}" data-type="tree"></button>
|
||||
</div>
|
||||
|
||||
<! UI for dragging Notes and link force >
|
||||
|
||||
<div class=" btn-group-sm fixnodes-type-switcher" role="group">
|
||||
<button type="button" class="btn bx bx-expand" title="${t("note_map.fix-nodes")}" data-type="moveable"></button>
|
||||
<input type="range" class=" slider" min="1" title="${t("note_map.link-distance")}" max="100" value="40" >
|
||||
|
||||
<div class=" btn-group-sm fixnodes-type-switcher" role="group">
|
||||
<button type="button" data-toggle="button" class="btn bx bx-lock-alt tn-tool-button" title="${t("note_map.fix-nodes")}" data-type="moveable"></button>
|
||||
<input type="range" class="slider" min="1" title="${t("note_map.link-distance")}" max="100" value="40" >
|
||||
</div>
|
||||
|
||||
<div class="style-resolver"></div>
|
||||
@ -163,6 +179,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
|
||||
private themeStyle!: string;
|
||||
private $container!: JQuery<HTMLElement>;
|
||||
private $styleResolver!: JQuery<HTMLElement>;
|
||||
private $fixNodesButton!: JQuery<HTMLElement>;
|
||||
graph!: ForceGraph;
|
||||
private noteIdToSizeMap!: Record<string, number>;
|
||||
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();
|
||||
|
@ -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 = `
|
||||
<div class="search-definition-widget">
|
||||
<style>
|
||||
<style>
|
||||
.search-setting-table {
|
||||
margin-top: 0;
|
||||
margin-bottom: 7px;
|
||||
@ -28,28 +30,28 @@ const TPL = `
|
||||
border-collapse: separate;
|
||||
border-spacing: 10px;
|
||||
}
|
||||
|
||||
|
||||
.search-setting-table div {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.search-setting-table .button-column {
|
||||
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
||||
width: 50px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
.search-setting-table .title-column {
|
||||
/* minimal width so that table remains static sized and most space remains for middle column with settings */
|
||||
width: 50px;
|
||||
white-space: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
.search-setting-table .button-column .dropdown-menu {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
|
||||
.attribute-list hr {
|
||||
height: 1px;
|
||||
border-color: var(--main-border-color);
|
||||
@ -58,7 +60,7 @@ const TPL = `
|
||||
margin-top: 5px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
||||
.search-definition-widget input:invalid {
|
||||
border: 3px solid red;
|
||||
}
|
||||
@ -66,7 +68,7 @@ const TPL = `
|
||||
.add-search-option button {
|
||||
margin-top: 5px; /* to give some spacing when buttons overflow on the next line */
|
||||
}
|
||||
|
||||
|
||||
.dropdown-header {
|
||||
background-color: var(--accented-background-color);
|
||||
}
|
||||
@ -78,47 +80,47 @@ const TPL = `
|
||||
<td class="title-column">${t("search_definition.add_search_option")}</td>
|
||||
<td colspan="2" class="add-search-option">
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="searchString">
|
||||
<span class="bx bx-text"></span>
|
||||
<span class="bx bx-text"></span>
|
||||
${t("search_definition.search_string")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="searchScript">
|
||||
<span class="bx bx-code"></span>
|
||||
<span class="bx bx-code"></span>
|
||||
${t("search_definition.search_script")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="ancestor">
|
||||
<span class="bx bx-filter-alt"></span>
|
||||
<span class="bx bx-filter-alt"></span>
|
||||
${t("search_definition.ancestor")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="fastSearch"
|
||||
title="${t("search_definition.fast_search_description")}">
|
||||
<span class="bx bx-run"></span>
|
||||
${t("search_definition.fast_search")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="includeArchivedNotes"
|
||||
title="${t("search_definition.include_archived_notes_description")}">
|
||||
<span class="bx bx-archive"></span>
|
||||
${t("search_definition.include_archived")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="orderBy">
|
||||
<span class="bx bx-arrow-from-top"></span>
|
||||
${t("search_definition.order_by")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="limit" title="${t("search_definition.limit_description")}">
|
||||
<span class="bx bx-stop"></span>
|
||||
${t("search_definition.limit")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm" data-search-option-add="debug" title="${t("search_definition.debug_description")}">
|
||||
<span class="bx bx-bug"></span>
|
||||
${t("search_definition.debug")}
|
||||
</button>
|
||||
|
||||
|
||||
<div class="dropdown" style="display: inline-block;">
|
||||
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="bx bxs-zap"></span>
|
||||
@ -138,12 +140,12 @@ const TPL = `
|
||||
<span class="bx bx-search"></span>
|
||||
${t("search_definition.search_button")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm search-and-execute-button">
|
||||
<span class="bx bxs-zap"></span>
|
||||
${t("search_definition.search_execute")}
|
||||
</button>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-sm save-to-note-button">
|
||||
<span class="bx bx-save"></span>
|
||||
${t("search_definition.save_to_note")}
|
||||
@ -158,7 +160,21 @@ const TPL = `
|
||||
|
||||
const OPTION_CLASSES = [SearchString, SearchScript, Ancestor, FastSearch, IncludeArchivedNotes, OrderBy, Limit, Debug];
|
||||
|
||||
// TODO: Deduplicate with server
|
||||
interface SaveSearchNoteResponse {
|
||||
notePath: string;
|
||||
}
|
||||
|
||||
export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $component!: JQuery<HTMLElement>;
|
||||
private $actionList!: JQuery<HTMLElement>;
|
||||
private $searchOptions!: JQuery<HTMLElement>;
|
||||
private $searchButton!: JQuery<HTMLElement>;
|
||||
private $searchAndExecuteButton!: JQuery<HTMLElement>;
|
||||
private $saveToNoteButton!: JQuery<HTMLElement>;
|
||||
private $actionOptions!: JQuery<HTMLElement>;
|
||||
|
||||
get name() {
|
||||
return "searchDefinition";
|
||||
}
|
||||
@ -194,7 +210,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
const searchOptionName = $(event.target).attr("data-search-option-add");
|
||||
const clazz = OPTION_CLASSES.find((SearchOptionClass) => SearchOptionClass.optionName === searchOptionName);
|
||||
|
||||
if (clazz) {
|
||||
if (clazz && this.noteId) {
|
||||
await clazz.create(this.noteId);
|
||||
} else {
|
||||
logError(t("search_definition.unknown_search_option", { searchOptionName }));
|
||||
@ -204,11 +220,13 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
});
|
||||
|
||||
this.$widget.on("click", "[data-action-add]", async (event) => {
|
||||
Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle"));
|
||||
Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle")[0]);
|
||||
|
||||
const actionName = $(event.target).attr("data-action-add");
|
||||
|
||||
await bulkActionService.addAction(this.noteId, actionName);
|
||||
if (this.noteId && actionName) {
|
||||
await bulkActionService.addAction(this.noteId, actionName);
|
||||
}
|
||||
|
||||
this.refresh();
|
||||
});
|
||||
@ -224,7 +242,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$saveToNoteButton = this.$widget.find(".save-to-note-button");
|
||||
this.$saveToNoteButton.on("click", async () => {
|
||||
const { notePath } = await server.post("special-notes/save-search-note", { searchNoteId: this.noteId });
|
||||
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: this.noteId });
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
@ -236,24 +254,32 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async refreshResultsCommand() {
|
||||
try {
|
||||
const { error } = await froca.loadSearchNote(this.noteId);
|
||||
if (!this.noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.handleEvent("showSearchError", { error });
|
||||
try {
|
||||
const result = await froca.loadSearchNote(this.noteId);
|
||||
|
||||
if (result && result.error) {
|
||||
this.handleEvent("showSearchError", { error: result.error });
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
toastService.showError(e.message);
|
||||
}
|
||||
|
||||
this.triggerEvent("searchRefreshed", { ntxId: this.noteContext.ntxId });
|
||||
this.triggerEvent("searchRefreshed", { ntxId: this.noteContext?.ntxId });
|
||||
}
|
||||
|
||||
async refreshSearchDefinitionCommand() {
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
async refreshWithNote(note: FNote) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$component.show();
|
||||
|
||||
this.$saveToNoteButton.toggle(note.isHiddenCompletely());
|
||||
@ -263,7 +289,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
for (const OptionClass of OPTION_CLASSES) {
|
||||
const { attributeType, optionName } = OptionClass;
|
||||
|
||||
const attr = this.note.getAttribute(attributeType, optionName);
|
||||
const attr = this.note.getAttribute(attributeType as AttributeType, optionName);
|
||||
|
||||
this.$widget.find(`[data-search-option-add='${optionName}'`).toggle(!attr);
|
||||
|
||||
@ -271,14 +297,19 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
const searchOption = new OptionClass(attr, this.note).setParent(this);
|
||||
this.child(searchOption);
|
||||
|
||||
this.$searchOptions.append(searchOption.render());
|
||||
const renderedEl = searchOption.render();
|
||||
if (renderedEl) {
|
||||
this.$searchOptions.append(renderedEl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = bulkActionService.parseActions(this.note);
|
||||
const renderedEls = actions
|
||||
.map((action) => action.render())
|
||||
.filter((e) => e) as JQuery<HTMLElement>[];
|
||||
|
||||
this.$actionOptions.empty().append(...actions.map((action) => action.render()));
|
||||
|
||||
this.$actionOptions.empty().append(...renderedEls);
|
||||
this.$searchAndExecuteButton.css("visibility", actions.length > 0 ? "visible" : "_hidden");
|
||||
}
|
||||
|
||||
@ -294,7 +325,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
|
||||
toastService.showMessage(t("search_definition.actions_executed"), 3000);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }) {
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// only refreshing deleted attrs, otherwise components update themselves
|
||||
if (loadResults.getAttributeRows().find((attrRow) => attrRow.type === "label" && attrRow.name === "action" && attrRow.isDeleted)) {
|
||||
this.refresh();
|
@ -2,24 +2,32 @@ import server from "../../services/server.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import Component from "../../components/component.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { t } from "../../services/i18n.js"; // 新增的导入
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type FAttribute from "../../entities/fattribute.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { AttributeType } from "../../entities/fattribute.js";
|
||||
|
||||
export default class AbstractSearchOption extends Component {
|
||||
constructor(attribute, note) {
|
||||
export default abstract class AbstractSearchOption extends Component {
|
||||
|
||||
private attribute: FAttribute;
|
||||
protected note: FNote;
|
||||
|
||||
constructor(attribute: FAttribute, note: FNote) {
|
||||
super();
|
||||
|
||||
this.attribute = attribute;
|
||||
this.note = note;
|
||||
}
|
||||
|
||||
static async setAttribute(noteId, type, name, value = "") {
|
||||
static async setAttribute(noteId: string, type: AttributeType, name: string, value: string = "") {
|
||||
await server.put(`notes/${noteId}/set-attribute`, { type, name, value });
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
}
|
||||
|
||||
async setAttribute(type, name, value = "") {
|
||||
await this.constructor.setAttribute(this.note.noteId, type, name, value);
|
||||
async setAttribute(type: AttributeType, name: string, value: string = "") {
|
||||
// TODO: Find a better pattern.
|
||||
await (this.constructor as any).setAttribute(this.note.noteId, type, name, value);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -29,29 +37,29 @@ export default class AbstractSearchOption extends Component {
|
||||
$rendered
|
||||
.find(".search-option-del")
|
||||
.on("click", () => this.deleteOption())
|
||||
.attr("title", t("abstract_search_option.remove_this_search_option")); // 使用 t 函数处理 i18n 字符串
|
||||
.attr("title", t("abstract_search_option.remove_this_search_option"));
|
||||
|
||||
utils.initHelpDropdown($rendered);
|
||||
|
||||
return $rendered;
|
||||
} catch (e) {
|
||||
logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack })); // 使用 t 函数处理 i18n 字符串
|
||||
} catch (e: any) {
|
||||
logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack }));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// to be overridden
|
||||
doRender() {}
|
||||
abstract doRender(): JQuery<HTMLElement>;
|
||||
|
||||
async deleteOption() {
|
||||
await this.deleteAttribute(this.constructor.attributeType, this.constructor.optionName);
|
||||
// TODO: Find a better pattern.
|
||||
await this.deleteAttribute((this.constructor as any).attributeType, (this.constructor as any).optionName);
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
await this.triggerCommand("refreshSearchDefinition");
|
||||
}
|
||||
|
||||
async deleteAttribute(type, name) {
|
||||
async deleteAttribute(type: AttributeType, name: string) {
|
||||
for (const attr of this.note.getOwnedAttributes()) {
|
||||
if (attr.type === type && attr.name === name) {
|
||||
await server.remove(`notes/${this.note.noteId}/attributes/${attr.attributeId}`);
|
@ -2,7 +2,7 @@ import AbstractSearchOption from "./abstract_search_option.js";
|
||||
import SpacedUpdate from "../../services/spaced_update.js";
|
||||
import server from "../../services/server.js";
|
||||
import shortcutService from "../../services/shortcuts.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { Tooltip } from "bootstrap";
|
||||
|
||||
@ -10,7 +10,7 @@ const TPL = `
|
||||
<tr>
|
||||
<td class="title-column">${t("search_string.title_column")}</td>
|
||||
<td>
|
||||
<textarea class="form-control search-string" placeholder="${t("search_string.placeholder")}"></textarea>
|
||||
<textarea class="form-control search-string" placeholder="${t("search_string.placeholder")}" autofocus></textarea>
|
||||
</td>
|
||||
<td class="button-column">
|
||||
<div class="dropdown help-dropdown">
|
||||
@ -29,12 +29,16 @@ const TPL = `
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<span class="bx bx-x icon-action search-option-del"></span>
|
||||
</td>
|
||||
</tr>`;
|
||||
|
||||
export default class SearchString extends AbstractSearchOption {
|
||||
|
||||
private $searchString!: JQuery<HTMLElement>;
|
||||
private spacedUpdate!: SpacedUpdate;
|
||||
|
||||
static get optionName() {
|
||||
return "searchString";
|
||||
}
|
||||
@ -42,7 +46,7 @@ export default class SearchString extends AbstractSearchOption {
|
||||
return "label";
|
||||
}
|
||||
|
||||
static async create(noteId) {
|
||||
static async create(noteId: string) {
|
||||
await AbstractSearchOption.setAttribute(noteId, "label", "searchString");
|
||||
}
|
||||
|
||||
@ -61,7 +65,7 @@ export default class SearchString extends AbstractSearchOption {
|
||||
});
|
||||
|
||||
this.spacedUpdate = new SpacedUpdate(async () => {
|
||||
const searchString = this.$searchString.val();
|
||||
const searchString = String(this.$searchString.val());
|
||||
appContext.lastSearchString = searchString;
|
||||
|
||||
await this.setAttribute("label", "searchString", searchString);
|
||||
@ -73,13 +77,13 @@ export default class SearchString extends AbstractSearchOption {
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.$searchString.val(this.note.getLabelValue("searchString"));
|
||||
this.$searchString.val(this.note.getLabelValue("searchString") ?? "");
|
||||
|
||||
return $option;
|
||||
}
|
||||
|
||||
showSearchErrorEvent({ error }) {
|
||||
let tooltip = new Tooltip(this.$searchString, {
|
||||
showSearchErrorEvent({ error }: EventData<"showSearchError">) {
|
||||
let tooltip = new Tooltip(this.$searchString[0], {
|
||||
trigger: "manual",
|
||||
title: `${t("search_string.error", { error })}`,
|
||||
placement: "bottom"
|
||||
@ -92,7 +96,7 @@ export default class SearchString extends AbstractSearchOption {
|
||||
|
||||
focusOnSearchDefinitionEvent() {
|
||||
this.$searchString
|
||||
.val(this.$searchString.val().trim() || appContext.lastSearchString)
|
||||
.val(String(this.$searchString.val()).trim() ?? appContext.lastSearchString)
|
||||
.focus()
|
||||
.select();
|
||||
this.spacedUpdate.scheduleUpdate();
|
@ -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;
|
@ -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 = `
|
||||
<div class="attachment-detail note-detail-printable">
|
||||
@ -32,6 +34,9 @@ const TPL = `
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
$wrapper!: JQuery<HTMLElement>;
|
||||
$linksWrapper!: JQuery<HTMLElement>;
|
||||
|
||||
static getType() {
|
||||
return "attachmentDetail";
|
||||
}
|
||||
@ -44,7 +49,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
async doRefresh(note: Parameters<TypeWidget["doRefresh"]>[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("<strong>" + t("attachment_detail.attachment_deleted") + "</strong>");
|
||||
@ -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;
|
||||
}
|
||||
}
|
@ -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 = `
|
||||
<div class="attachment-list note-detail-printable">
|
||||
@ -27,6 +28,10 @@ const TPL = `
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
$list!: JQuery<HTMLElement>;
|
||||
$linksWrapper!: JQuery<HTMLElement>;
|
||||
renderedAttachmentIds!: Set<string>;
|
||||
|
||||
static getType() {
|
||||
return "attachmentList";
|
||||
}
|
||||
@ -39,7 +44,10 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
async doRefresh(note: Parameters<TypeWidget["doRefresh"]>[0]) {
|
||||
// TriliumNextTODO: do we need to handle an undefined/null note?
|
||||
if (!note) return false;
|
||||
|
||||
const $helpButton = $(`
|
||||
<button class="attachment-help-button icon-action bx bx-help-circle"
|
||||
type="button" data-help-page="attachments.html"
|
||||
@ -56,7 +64,11 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
$(`<div class="attachment-actions-toolbar">`).append(
|
||||
$('<button class="btn btn-sm">')
|
||||
.text(t("attachment_list.upload_attachments"))
|
||||
.on("click", () => this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId })),
|
||||
.on("click", () => {
|
||||
if (this.noteId) {
|
||||
this.triggerCommand("showUploadAttachmentsDialog", { noteId: this.noteId })
|
||||
}
|
||||
}),
|
||||
$helpButton
|
||||
)
|
||||
);
|
||||
@ -83,9 +95,9 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// updates and deletions are handled by the detail, for new attachments the whole list has to be refreshed
|
||||
const attachmentsAdded = loadResults.getAttachmentRows().some((att) => !this.renderedAttachmentIds.has(att.attachmentId));
|
||||
const attachmentsAdded = loadResults.getAttachmentRows().some((att) => att.attachmentId && !this.renderedAttachmentIds.has(att.attachmentId));
|
||||
|
||||
if (attachmentsAdded) {
|
||||
this.refresh();
|
@ -57,11 +57,14 @@ const TPL = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
function buildTask(task: FTask) {
|
||||
return `\
|
||||
<li class="task">
|
||||
<input type="checkbox" class="check" data-task-id="${task.taskId}" ${task.isDone ? "checked" : ""} /> ${task.title}
|
||||
</li>`;
|
||||
function buildTasks(tasks: FTask[]) {
|
||||
let html = '';
|
||||
|
||||
for (const task of tasks) {
|
||||
html += `<li class="task"><input type="checkbox" class="check" data-task-id="${task.taskId}" ${task.isDone ? "checked" : ""} />${task.title}</li>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export default class TaskListWidget extends TypeWidget {
|
||||
@ -79,6 +82,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("");
|
||||
}
|
||||
});
|
||||
|
||||
@ -112,12 +116,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">) {
|
||||
|
@ -45,9 +45,18 @@ const TPL = `
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.calendar-container a.fc-event {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.calendar-container .fc-button {
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
|
||||
.calendar-container .promoted-attribute {
|
||||
font-size: 0.85em;
|
||||
opacity: 0.85;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="calendar-container">
|
||||
@ -119,13 +128,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 += `<span class="${iconClass}"></span> `;
|
||||
}
|
||||
|
||||
html += utils.escapeHtml(e.event.title);
|
||||
|
||||
// Promoted attributes
|
||||
if (promotedAttributes) {
|
||||
for (const [ name, value ] of Object.entries(promotedAttributes)) {
|
||||
html += `\
|
||||
<div class="promoted-attribute">
|
||||
<span class="promoted-attribute-name">${name}</span>: <span class="promoted-attribute-value">${value}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
return { html };
|
||||
}),
|
||||
dateClick: async (e) => {
|
||||
@ -279,7 +299,7 @@ export default class CalendarView extends ViewMode {
|
||||
const events: EventSourceInput = [];
|
||||
|
||||
for (const note of notes) {
|
||||
let startDate = note.getLabelValue("startDate");
|
||||
const startDate = note.getLabelValue("startDate");
|
||||
|
||||
if (note.hasChildren()) {
|
||||
const childrenEventData = await this.#buildEvents(note.getChildNoteIds());
|
||||
@ -304,6 +324,13 @@ 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[] = [];
|
||||
|
||||
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,
|
||||
@ -311,7 +338,8 @@ export default class CalendarView extends ViewMode {
|
||||
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);
|
||||
@ -323,6 +351,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<string, string> = {};
|
||||
|
||||
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<string[]> {
|
||||
if (customTitleValue) {
|
||||
const attributeName = customTitleValue.substring(1);
|
||||
|
@ -79,7 +79,9 @@ button.btn.btn-success kbd {
|
||||
*/
|
||||
|
||||
:root .icon-action:not(.global-menu-button),
|
||||
:root .tn-tool-button {
|
||||
:root .tn-tool-button,
|
||||
:root .btn-group .tn-tool-button:not(:last-child),
|
||||
:root .btn-group .tn-tool-button:last-child {
|
||||
width: var(--icon-button-size);
|
||||
height: var(--icon-button-size);
|
||||
border: unset !important;
|
||||
@ -89,6 +91,10 @@ button.btn.btn-success kbd {
|
||||
color: var(--icon-button-color);
|
||||
}
|
||||
|
||||
.btn-group .tn-tool-button + .tn-tool-button {
|
||||
margin-left: 4px !important;
|
||||
}
|
||||
|
||||
/* The "x" icon button */
|
||||
:root .icon-action.bx-x,
|
||||
:root .tn-tool-button.bx-x {
|
||||
@ -620,4 +626,12 @@ a.tn-link:hover[href^="https://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a:hover[href^="http://"]:not(.no-arrow)::after,
|
||||
.use-tn-links a:hover[href^="https://"]:not(.no-arrow)::after {
|
||||
animation: link-arrow-blink 500ms linear alternate infinite;
|
||||
}
|
||||
|
||||
/*
|
||||
* Range inputs (sliders)
|
||||
*/
|
||||
|
||||
input[type="range"] {
|
||||
background: transparent;
|
||||
}
|
@ -1319,6 +1319,14 @@ body .calendar-dropdown-widget .calendar-body a:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note Map
|
||||
*/
|
||||
|
||||
.note-detail-note-map .fixnodes-type-switcher .tn-tool-button.toggled {
|
||||
color: var(--tab-close-button-hover-background);
|
||||
}
|
||||
|
||||
/*
|
||||
* Recent changes list
|
||||
*/
|
||||
|
@ -645,6 +645,9 @@
|
||||
"show-cheatsheet": "Show Cheatsheet",
|
||||
"toggle-zen-mode": "Zen Mode"
|
||||
},
|
||||
"zen_mode": {
|
||||
"button_exit": "Exit Zen Mode"
|
||||
},
|
||||
"sync_status": {
|
||||
"unknown": "<p>Sync status will be known once the next sync attempt starts.</p><p>Click to trigger sync now.</p>",
|
||||
"connected_with_changes": "<p>Connected to the sync server. <br>There are some outstanding changes yet to be synced.</p><p>Click to trigger sync.</p>",
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user