Merge develop into test_server-utils

This commit is contained in:
Elian Doran 2025-02-04 21:28:27 +02:00
commit 892734bce3
No known key found for this signature in database
35 changed files with 722 additions and 308 deletions

View File

@ -1,12 +1,17 @@
[Desktop Entry]
<% if (productName) { %>Name=<%= productName %>
<% } %><% if (description) { %>Comment=<%= description %>
<% } %><% if (genericName) { %>GenericName=<%= genericName %>
<% } %><% if (name) { %>Exec=<%= name %> %U
Icon=<%= name %>
<% } %>Type=Application
StartupNotify=true
<% if (productName) { %>StartupWMClass=<%= productName %>
<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>;
<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>;
<% } %>
<%=
Object.entries({
"Name": productName,
"Comment": description,
"GenericName": genericName,
"Exec": name ? `${name} %U` : undefined,
"Icon": name,
"Type": "Application",
"StartupNotify": "true",
"StartupWMClass": productName,
"Categories": categories?.length ? `${categories.join(";")};` : undefined,
"MimeType": mimeType?.length ? `${mimeType.join(";")};` : undefined
})
.map(line => line[1] ? line.join("=") : undefined)
.filter(line => !!line)
.join("\n")%>

View File

@ -26,6 +26,8 @@ electronDl({ saveAs: true });
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features");
electron.app.userAgentFallback = `${electron.app.getName()} ${electron.app.getVersion()}`;
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.

165
package-lock.json generated
View File

@ -69,6 +69,7 @@
"katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
"marked": "15.0.6",
"mermaid": "11.4.1",
@ -85,7 +86,6 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
"semver": "7.7.0",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
@ -112,7 +112,7 @@
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1",
"@electron/rebuild": "3.7.1",
"@playwright/test": "1.50.0",
"@playwright/test": "1.50.1",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@ -132,14 +132,14 @@
"@types/jasmine": "5.1.5",
"@types/jquery": "3.5.32",
"@types/jsdom": "21.1.7",
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.12.0",
"@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
"@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
@ -149,7 +149,7 @@
"@types/ws": "8.5.14",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.4",
"@vitest/coverage-v8": "3.0.5",
"cross-env": "7.0.3",
"electron": "34.0.2",
"esm": "3.2.25",
@ -164,7 +164,7 @@
"tsx": "4.19.2",
"typedoc": "0.27.6",
"typescript": "5.7.3",
"vitest": "3.0.4",
"vitest": "3.0.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"
@ -2878,13 +2878,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.50.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.0.tgz",
"integrity": "sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz",
"integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.50.0"
"playwright": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@ -3839,6 +3839,16 @@
"@types/geojson": "*"
}
},
"node_modules/@types/leaflet-gpx": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/@types/leaflet-gpx/-/leaflet-gpx-1.3.7.tgz",
"integrity": "sha512-IDshIOLZ7dUUjRiCE3DuJcAGavgUCw0xQ93dc/3YvsA6jrFc+nx8eXr0tqZIf2SaWMgqiDj/n7N24WWNh/898g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/leaflet": "*"
}
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@ -3897,9 +3907,9 @@
}
},
"node_modules/@types/node": {
"version": "22.12.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz",
"integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
"version": "22.13.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz",
"integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@ -3990,13 +4000,6 @@
"@types/node": "*"
}
},
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
@ -4151,9 +4154,9 @@
}
},
"node_modules/@vitest/coverage-v8": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.4.tgz",
"integrity": "sha512-f0twgRCHgbs24Dp8cLWagzcObXMcuKtAwgxjJV/nnysPAJJk1JiKu/W0gIehZLmkljhJXU/E0/dmuQzsA/4jhA==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.5.tgz",
"integrity": "sha512-zOOWIsj5fHh3jjGwQg+P+J1FW3s4jBu1Zqga0qW60yutsBtqEqNEJKWYh7cYn1yGD+1bdPsPdC/eL4eVK56xMg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4174,8 +4177,8 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "3.0.4",
"vitest": "3.0.4"
"@vitest/browser": "3.0.5",
"vitest": "3.0.5"
},
"peerDependenciesMeta": {
"@vitest/browser": {
@ -4184,14 +4187,14 @@
}
},
"node_modules/@vitest/expect": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz",
"integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.5.tgz",
"integrity": "sha512-nNIOqupgZ4v5jWuQx2DSlHLEs7Q4Oh/7AYwNyE+k0UQzG7tSmjPXShUikn1mpNGzYEN2jJbTvLejwShMitovBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.0.4",
"@vitest/utils": "3.0.4",
"@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.5",
"chai": "^5.1.2",
"tinyrainbow": "^2.0.0"
},
@ -4200,13 +4203,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz",
"integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.5.tgz",
"integrity": "sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.0.4",
"@vitest/spy": "3.0.5",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.17"
},
@ -4227,9 +4230,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz",
"integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.5.tgz",
"integrity": "sha512-CjUtdmpOcm4RVtB+up8r2vVDLR16Mgm/bYdkGFe3Yj/scRfCpbSi2W/BDSDcFK7ohw8UXvjMbOp9H4fByd/cOA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4240,13 +4243,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz",
"integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.5.tgz",
"integrity": "sha512-BAiZFityFexZQi2yN4OX3OkJC6scwRo8EhRB0Z5HIGGgd2q+Nq29LgHU/+ovCtd0fOfXj5ZI6pwdlUmC5bpi8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "3.0.4",
"@vitest/utils": "3.0.5",
"pathe": "^2.0.2"
},
"funding": {
@ -4261,13 +4264,13 @@
"license": "MIT"
},
"node_modules/@vitest/snapshot": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz",
"integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.5.tgz",
"integrity": "sha512-GJPZYcd7v8QNUJ7vRvLDmRwl+a1fGg4T/54lZXe+UOGy47F9yUfE18hRCtXL5aHN/AONu29NGzIXSVFh9K0feA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.0.4",
"@vitest/pretty-format": "3.0.5",
"magic-string": "^0.30.17",
"pathe": "^2.0.2"
},
@ -4283,9 +4286,9 @@
"license": "MIT"
},
"node_modules/@vitest/spy": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz",
"integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.5.tgz",
"integrity": "sha512-5fOzHj0WbUNqPK6blI/8VzZdkBlQLnT25knX0r4dbZI9qoZDf3qAdjoMmDcLG5A83W6oUUFJgUd0EYBc2P5xqg==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -4296,13 +4299,13 @@
}
},
"node_modules/@vitest/utils": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz",
"integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.5.tgz",
"integrity": "sha512-N9AX0NUoUtVwKwy21JtwzaqR5L5R5A99GAbrHfCCXK1lp593i/3AZAXhSP43wRQuxYsflrdzEfXZFo1reR1Nkg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.0.4",
"@vitest/pretty-format": "3.0.5",
"loupe": "^3.1.2",
"tinyrainbow": "^2.0.0"
},
@ -11431,6 +11434,12 @@
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
},
"node_modules/leaflet-gpx": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/leaflet-gpx/-/leaflet-gpx-2.1.2.tgz",
"integrity": "sha512-lKoEPlAWel9KXn9keg6Dmyt7gmj5IYyD8CKuxivN+77GpZr2bpKliwFvZJxLUHmNu4fICmCySyxhm5qjZuvvQg==",
"license": "BSD-2-Clause"
},
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
@ -13302,13 +13311,13 @@
}
},
"node_modules/playwright": {
"version": "1.50.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz",
"integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz",
"integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.50.0"
"playwright-core": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@ -13321,9 +13330,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.50.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz",
"integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz",
"integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -14703,9 +14712,9 @@
}
},
"node_modules/semver": {
"version": "7.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
"integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -16838,9 +16847,9 @@
}
},
"node_modules/vite-node": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz",
"integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.5.tgz",
"integrity": "sha512-02JEJl7SbtwSDJdYS537nU6l+ktdvcREfLksk/NDAqtdKWGqHl+joXzEubHROmS3E6pip+Xgu2tFezMu75jH7A==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -17332,19 +17341,19 @@
}
},
"node_modules/vitest": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz",
"integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.5.tgz",
"integrity": "sha512-4dof+HvqONw9bvsYxtkfUp2uHsTN9bV2CZIi1pWgoFpL1Lld8LA1ka9q/ONSsoScAKG7NVGf2stJTI7XRkXb2Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "3.0.4",
"@vitest/mocker": "3.0.4",
"@vitest/pretty-format": "^3.0.4",
"@vitest/runner": "3.0.4",
"@vitest/snapshot": "3.0.4",
"@vitest/spy": "3.0.4",
"@vitest/utils": "3.0.4",
"@vitest/expect": "3.0.5",
"@vitest/mocker": "3.0.5",
"@vitest/pretty-format": "^3.0.5",
"@vitest/runner": "3.0.5",
"@vitest/snapshot": "3.0.5",
"@vitest/spy": "3.0.5",
"@vitest/utils": "3.0.5",
"chai": "^5.1.2",
"debug": "^4.4.0",
"expect-type": "^1.1.0",
@ -17356,7 +17365,7 @@
"tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.0.4",
"vite-node": "3.0.5",
"why-is-node-running": "^2.3.0"
},
"bin": {
@ -17372,8 +17381,8 @@
"@edge-runtime/vm": "*",
"@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.0.4",
"@vitest/ui": "3.0.4",
"@vitest/browser": "3.0.5",
"@vitest/ui": "3.0.5",
"happy-dom": "*",
"jsdom": "*"
},

View File

@ -114,6 +114,7 @@
"katex": "0.16.21",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.1.2",
"mark.js": "8.11.1",
"marked": "15.0.6",
"mermaid": "11.4.1",
@ -130,7 +131,6 @@
"sanitize-filename": "1.6.3",
"sanitize-html": "2.14.0",
"sax": "1.4.1",
"semver": "7.7.0",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "0.5.21",
@ -154,7 +154,7 @@
"@electron-forge/maker-zip": "7.6.1",
"@electron-forge/plugin-auto-unpack-natives": "7.6.1",
"@electron/rebuild": "3.7.1",
"@playwright/test": "1.50.0",
"@playwright/test": "1.50.1",
"@types/archiver": "6.0.3",
"@types/better-sqlite3": "7.6.12",
"@types/bootstrap": "5.2.10",
@ -174,14 +174,14 @@
"@types/jasmine": "5.1.5",
"@types/jquery": "3.5.32",
"@types/jsdom": "21.1.7",
"@types/leaflet-gpx": "1.3.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.12.0",
"@types/node": "22.13.1",
"@types/react": "18.3.18",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
"@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
@ -191,7 +191,7 @@
"@types/ws": "8.5.14",
"@types/xml2js": "0.4.14",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.0.4",
"@vitest/coverage-v8": "3.0.5",
"cross-env": "7.0.3",
"electron": "34.0.2",
"esm": "3.2.25",
@ -206,7 +206,7 @@
"tsx": "4.19.2",
"typedoc": "0.27.6",
"typescript": "5.7.3",
"vitest": "3.0.4",
"vitest": "3.0.5",
"webpack": "5.97.1",
"webpack-cli": "6.0.1",
"webpack-dev-middleware": "7.4.2"

View File

@ -83,7 +83,7 @@ export type CommandMappings = {
};
showExportDialog: CommandData & {
notePath: string;
defaultType: "single";
defaultType: "single" | "subtree";
};
showDeleteNotesDialog: CommandData & {
branchIdsToDelete: string[];

View File

@ -4,7 +4,6 @@ import noteTooltipService from "./services/note_tooltip.js";
import bundleService from "./services/bundle.js";
import toastService from "./services/toast.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import macInit from "./services/mac_init.js";
import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
@ -35,8 +34,6 @@ if (utils.isElectron()) {
initOnElectron();
}
macInit.init();
noteTooltipService.setupGlobalTooltip();
noteAutocompleteService.init();

View File

@ -1,26 +0,0 @@
/**
* Mac specific initialization
*/
import utils from "./utils.js";
import shortcutService from "./shortcuts.js";
function init() {
if (utils.isElectron() && utils.isMac()) {
shortcutService.bindGlobalShortcut("meta+c", () => exec("copy"));
shortcutService.bindGlobalShortcut("meta+v", () => exec("paste"));
shortcutService.bindGlobalShortcut("meta+x", () => exec("cut"));
shortcutService.bindGlobalShortcut("meta+a", () => exec("selectAll"));
shortcutService.bindGlobalShortcut("meta+z", () => exec("undo"));
shortcutService.bindGlobalShortcut("meta+y", () => exec("redo"));
}
}
function exec(cmd: string) {
document.execCommand(cmd);
return false;
}
export default {
init
};

View File

@ -1,9 +1,6 @@
import utils from "./services/utils.js";
import macInit from "./services/mac_init.js";
import ko from "knockout";
macInit.init();
// TriliumNextTODO: properly make use of below types
// type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | "";
// type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop";

View File

@ -39,6 +39,7 @@ interface CustomGlobals {
maxEntityChangeIdAtLoad: number;
maxEntityChangeSyncIdAtLoad: number;
assetPath: string;
appPath: string;
instanceName: string;
appCssNoteIds: string[];
triliumVersion: string;

View File

@ -67,6 +67,10 @@ const TPL = `
.attr-detail input[readonly] {
background-color: var(--accented-background-color) !important;
}
.attr-edit-table td {
padding: 4px 0;
}
</style>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
@ -97,8 +101,13 @@ const TPL = `
</tr>
<tr class="attr-row-promoted"
title="${t("attribute_detail.promoted_title")}">
<th>${t("attribute_detail.promoted")}</th>
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
<th></th>
<td>
<label class="tn-checkbox">
<input type="checkbox" class="attr-input-promoted form-check" />
${t("attribute_detail.promoted")}
</label>
</td>
</tr>
<tr class="attr-row-promoted-alias">
<th title="${t("attribute_detail.promoted_alias_title")}">${t("attribute_detail.promoted_alias")}</th>
@ -149,8 +158,13 @@ const TPL = `
</td>
</tr>
<tr title="${t("attribute_detail.inheritable_title")}">
<th>${t("attribute_detail.inheritable")}</th>
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
<th></th>
<td>
<label class="tn-checkbox">
<input type="checkbox" class="attr-input-inheritable form-check" />
${t("attribute_detail.inheritable")}
</label>
</td>
</tr>
</table>
@ -242,7 +256,9 @@ const ATTR_HELP: Record<string, Record<string, string>> = {
executeDescription: t("attribute_detail.execute_description"),
excludeFromNoteMap: t("attribute_detail.exclude_from_note_map"),
newNotesOnTop: t("attribute_detail.new_notes_on_top"),
hideHighlightWidget: t("attribute_detail.hide_highlight_widget")
hideHighlightWidget: t("attribute_detail.hide_highlight_widget"),
printLandscape: t("attribute_detail.print_landscape"),
printPageSize: t("attribute_detail.print_page_size")
},
relation: {
runOnNoteCreation: t("attribute_detail.run_on_note_creation"),

View File

@ -77,8 +77,8 @@ const TPL = `
<div class="attribute-list-editor" tabindex="200"></div>
<div class="bx bx-save save-attributes-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
<div class="bx bx-plus add-new-attribute-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
<div class="attribute-errors" style="display: none;"></div>
</div>

View File

@ -1,11 +1,12 @@
import treeService from "../../services/tree.js";
import utils from "../../services/utils.js";
import ws from "../../services/ws.js";
import toastService from "../../services/toast.js";
import toastService, { type ToastOptions } from "../../services/toast.js";
import froca from "../../services/froca.js";
import openService from "../../services/open.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js";
import type { EventData } from "../../components/app_context.js";
const TPL = `
<div class="export-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -105,6 +106,13 @@ const TPL = `
${t("export.format_markdown")}
</label>
</div>
<div class="form-check">
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="export-single-format" value="pdf">
${t("export.format_pdf")}
</label>
</div>
</div>
</div>
<div class="modal-footer">
@ -116,6 +124,19 @@ const TPL = `
</div>`;
export default class ExportDialog extends BasicWidget {
private taskId: string;
private branchId: string | null;
private modal?: bootstrap.Modal;
private $form!: JQuery<HTMLElement>;
private $noteTitle!: JQuery<HTMLElement>;
private $subtreeFormats!: JQuery<HTMLElement>;
private $singleFormats!: JQuery<HTMLElement>;
private $subtreeType!: JQuery<HTMLElement>;
private $singleType!: JQuery<HTMLElement>;
private $exportButton!: JQuery<HTMLElement>;
private $opmlVersions!: JQuery<HTMLElement>;
constructor() {
super();
@ -125,6 +146,8 @@ export default class ExportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
// Remove once bootstrap is fixed.
// @ts-ignore
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".export-form");
this.$noteTitle = this.$widget.find(".export-note-title");
@ -136,7 +159,7 @@ export default class ExportDialog extends BasicWidget {
this.$opmlVersions = this.$widget.find(".opml-versions");
this.$form.on("submit", () => {
this.modal.hide();
this.modal?.hide();
const exportType = this.$widget.find("input[name='export-type']:checked").val();
@ -149,13 +172,15 @@ export default class ExportDialog extends BasicWidget {
const exportVersion = exportFormat === "opml" ? this.$widget.find("input[name='opml-version']:checked").val() : "1.0";
this.exportBranch(this.branchId, exportType, exportFormat, exportVersion);
if (this.branchId) {
this.exportBranch(this.branchId, String(exportType), String(exportFormat), String(exportVersion));
}
return false;
});
this.$widget.find("input[name=export-type]").on("change", (e) => {
if (e.currentTarget.value === "subtree") {
if ((e.currentTarget as HTMLInputElement).value === "subtree") {
if (this.$widget.find("input[name=export-subtree-format]:checked").length === 0) {
this.$widget.find("input[name=export-subtree-format]:first").prop("checked", true);
}
@ -173,7 +198,7 @@ export default class ExportDialog extends BasicWidget {
});
this.$widget.find("input[name=export-subtree-format]").on("change", (e) => {
if (e.currentTarget.value === "opml") {
if ((e.currentTarget as HTMLInputElement).value === "opml") {
this.$opmlVersions.slideDown();
} else {
this.$opmlVersions.slideUp();
@ -181,7 +206,7 @@ export default class ExportDialog extends BasicWidget {
});
}
async showExportDialogEvent({ notePath, defaultType }) {
async showExportDialogEvent({ notePath, defaultType }: EventData<"showExportDialog">) {
this.taskId = "";
this.$exportButton.removeAttr("disabled");
@ -201,11 +226,15 @@ export default class ExportDialog extends BasicWidget {
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
this.branchId = await froca.getBranchId(parentNoteId, noteId);
this.$noteTitle.text(await treeService.getNoteTitle(noteId));
if (parentNoteId) {
this.branchId = await froca.getBranchId(parentNoteId, noteId);
}
if (noteId) {
this.$noteTitle.text(await treeService.getNoteTitle(noteId));
}
}
exportBranch(branchId, type, format, version) {
exportBranch(branchId: string, type: string, format: string, version: string) {
this.taskId = utils.randomString(10);
const url = openService.getUrlForDownload(`api/branches/${branchId}/export/${type}/${format}/${version}/${this.taskId}`);
@ -215,12 +244,14 @@ export default class ExportDialog extends BasicWidget {
}
ws.subscribeToMessages(async (message) => {
const makeToast = (id, message) => ({
id: id,
title: t("export.export_status"),
message: message,
icon: "arrow-square-up-right"
});
function makeToast(id: string, message: string): ToastOptions {
return {
id: id,
title: t("export.export_status"),
message: message,
icon: "arrow-square-up-right"
};
};
if (message.taskType !== "export") {
return;

View File

@ -25,16 +25,22 @@ const TPL = `
${t("include_note.box_size_prompt")}
<div class="form-check">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
<label class="form-check-label">${t("include_note.box_size_small")}</label>
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
${t("include_note.box_size_small")}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
<label class="form-check-label">${t("include_note.box_size_medium")}</label>
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
${t("include_note.box_size_medium")}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
<label class="form-check-label">${t("include_note.box_size_full")}</label>
<label class="form-check-label tn-radio">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
${t("include_note.box_size_full")}
</label>
</div>
</div>
<div class="modal-footer">

View File

@ -33,11 +33,15 @@ const TPL = `
}
.find-widget-found-wrapper {
font-weight: bold;
justify-content: center;
min-width: 60px;
padding: 0 4px;
font-size: .85em;
text-align: center;
}
.find-widget-search-term-input-group, .replace-widget-replacetext-input {
max-width: 300px;
max-width: 350px;
}
.find-widget-spacer {
@ -49,6 +53,13 @@ const TPL = `
<div class="input-group find-widget-search-term-input-group">
<input type="text" class="form-control find-widget-search-term-input" placeholder="${t("find.find_placeholder")}">
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
<div class="find-widget-found-wrapper input-group-text">
<span>
<span class="find-widget-current-found">0</span>
/
<span class="find-widget-total-found">0</span>
<span>
</div>
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div>
@ -66,11 +77,7 @@ const TPL = `
</label>
</div>
<div class="find-widget-found-wrapper">
<span class="find-widget-current-found">0</span>
/
<span class="find-widget-total-found">0</span>
</div>
<div class="find-widget-spacer"></div>

View File

@ -261,6 +261,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
const { ipcRenderer } = utils.dynamicRequire("electron");
ipcRenderer.send("export-as-pdf", {
title: this.note.title,
pageSize: this.note.getAttributeValue("label", "printPageSize") ?? "Letter",
landscape: this.note.hasAttribute("label", "printLandscape")
});
}

View File

@ -15,18 +15,18 @@ const TPL = `
display: flex;
flex-direction: column;
}
.attachment-detail .links-wrapper {
font-size: larger;
padding: 0 0 16px 0;
}
.attachment-detail .attachment-wrapper {
flex-grow: 1;
}
</style>
<div class="links-wrapper"></div>
<div class="links-wrapper use-tn-links"></div>
<div class="attachment-wrapper"></div>
</div>`;
@ -57,7 +57,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
this.$linksWrapper.empty().append(
t("attachment_detail.owning_note"),
await linkService.createLink(this.noteId),
(await linkService.createLink(this.noteId)),
t("attachment_detail.you_can_also_open"),
await linkService.createLink(this.noteId, {
title: t("attachment_detail.list_of_all_attachments"),

View File

@ -1,3 +1,4 @@
import type FNote from "../../entities/fnote.js";
import TypeWidget from "./type_widget.js";
const TPL = `<div class="note-detail-doc note-detail-printable">
@ -5,7 +6,7 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
.note-detail-doc-content {
padding: 15px;
}
.note-detail-doc-content pre {
background-color: var(--accented-background-color);
border: 1px solid var(--main-border-color);
@ -13,11 +14,14 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
border-radius: 5px;
}
</style>
<div class="note-detail-doc-content"></div>
</div>`;
export default class DocTypeWidget extends TypeWidget {
private $content!: JQuery<HTMLElement>;
static getType() {
return "doc";
}
@ -29,7 +33,7 @@ export default class DocTypeWidget extends TypeWidget {
super.doRender();
}
async doRefresh(note) {
async doRefresh(note: FNote) {
const docName = note.getLabelValue("docName");
if (docName) {

View File

@ -1,4 +1,4 @@
import { Marker, type LatLng, type LeafletMouseEvent } from "leaflet";
import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet";
import type FNote from "../../entities/fnote.js";
import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js";
import TypeWidget from "./type_widget.js"
@ -91,8 +91,6 @@ interface CreateChildResponse {
}
}
type MarkerData = Record<string, Marker>;
enum State {
Normal,
NewNote
@ -103,7 +101,9 @@ export default class GeoMapTypeWidget extends TypeWidget {
private geoMapWidget: GeoMapWidget;
private _state: State;
private L!: Leaflet;
private currentMarkerData: MarkerData;
private currentMarkerData: Record<string, Marker>;
private currentTrackData: Record<string, GPX>;
private gpxLoaded?: boolean;
static getType() {
return "geoMap";
@ -114,6 +114,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L));
this.currentMarkerData = {};
this.currentTrackData = {};
this._state = State.Normal;
this.child(this.geoMapWidget);
@ -159,9 +160,7 @@ export default class GeoMapTypeWidget extends TypeWidget {
}
async #reloadMarkers() {
const map = this.geoMapWidget.map;
if (!this.note || !map) {
if (!this.note) {
return;
}
@ -170,51 +169,91 @@ export default class GeoMapTypeWidget extends TypeWidget {
marker.remove();
}
// Delete all existing tracks
for (const track of Object.values(this.currentTrackData)) {
track.remove();
}
// Add the new markers.
this.currentMarkerData = {};
const childNotes = await this.note.getChildNotes();
const L = this.L;
for (const childNote of childNotes) {
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
if (!latLng) {
if (childNote.mime === "application/gpx+xml") {
this.#processNoteWithGpxTrack(childNote);
continue;
}
const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
const icon = L.divIcon({
html: `\
<img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" />
<img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" />
<span class="bx ${childNote.getIcon()}"></span>
<span class="title-label">${childNote.title}</span>`,
iconSize: [ 25, 41 ],
iconAnchor: [ 12, 41 ]
})
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
if (latLng) {
this.#processNoteWithMarker(childNote, latLng);
}
}
}
const marker = L.marker(L.latLng(lat, lng), {
icon,
draggable: true,
autoPan: true,
autoPanSpeed: 5,
})
.addTo(map)
.on("moveend", e => {
this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng());
});
async #processNoteWithGpxTrack(note: FNote) {
if (!this.L || !this.geoMapWidget.map) {
return;
}
marker.on("contextmenu", (e) => {
openContextMenu(childNote.noteId, e.originalEvent);
if (!this.gpxLoaded) {
await import("leaflet-gpx");
this.gpxLoaded = true;
}
// TODO: This is not very efficient as it's probably a string response that is parsed and then converted back to string and parsed again.
const xmlResponse = await server.get<XMLDocument>(`notes/${note.noteId}/open`);
const stringResponse = new XMLSerializer().serializeToString(xmlResponse);
const track = new this.L.GPX(stringResponse, {});
track.addTo(this.geoMapWidget.map);
this.currentTrackData[note.noteId] = track;
}
#processNoteWithMarker(note: FNote, latLng: string) {
const map = this.geoMapWidget.map;
if (!map) {
return;
}
const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
const L = this.L;
const icon = this.#buildIcon(note.getIcon(), note.title);
const marker = L.marker(L.latLng(lat, lng), {
icon,
draggable: true,
autoPan: true,
autoPanSpeed: 5,
})
.addTo(map)
.on("moveend", e => {
this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
});
const el = marker.getElement();
if (el) {
const $el = $(el);
$el.attr("data-href", `#${childNote.noteId}`);
note_tooltip.setupElementTooltip($($el));
}
marker.on("contextmenu", (e) => {
openContextMenu(note.noteId, e.originalEvent);
});
this.currentMarkerData[childNote.noteId] = marker;
const el = marker.getElement();
if (el) {
const $el = $(el);
$el.attr("data-href", `#${note.noteId}`);
note_tooltip.setupElementTooltip($($el));
}
this.currentMarkerData[note.noteId] = marker;
}
#buildIcon(bxIconClass: string, title: string) {
return this.L.divIcon({
html: `\
<img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" />
<img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" />
<span class="bx ${bxIconClass}"></span>
<span class="title-label">${title}</span>`,
iconSize: [ 25, 41 ],
iconAnchor: [ 12, 41 ]
})
}
#changeState(newState: State) {
@ -299,6 +338,14 @@ export default class GeoMapTypeWidget extends TypeWidget {
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
// If any of the children branches are altered.
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.noteId)) {
this.#reloadMarkers();
return;
}
// If any of note has its location attribute changed.
// TODO: Should probably filter by parent here as well.
const attributeRows = loadResults.getAttributeRows();
if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) {
this.#reloadMarkers();

View File

@ -9,6 +9,12 @@ const TPL = `
width: 300px;
margin: 30px auto auto;
}
.protected-session-password-component input,
.protected-session-password-component button {
margin-top: 12px;
}
</style>
<form class="protected-session-password-form">

View File

@ -4,6 +4,21 @@
--launcher-pane-background-color: var(--main-background-color);
--main-text-color: black;
--input-text-color: var(--main-text-color);
--print-font-size: 11pt;
}
@page {
margin: 2cm;
}
.ck-content {
font-size: var(--print-font-size);
text-align: justify;
}
.note-detail-readonly-text {
padding: 0 !important;
}
.no-print,
@ -141,13 +156,6 @@ span[style] {
outline: unset !important;
}
/* Fix visibility of checkbox checkmarks
see https://github.com/TriliumNext/Notes/issues/901 */
.ck-editor__editable.ck-content .todo-list .todo-list__label > span[contenteditable="false"] > input[checked]::after {
/* fallback to default ck-editor green */
border-color: hsl(126, 64%, 41%);
}
.include-note .include-note-content {
max-height: unset !important;
overflow: unset !important;
@ -159,4 +167,139 @@ span[style] {
.note-detail-code pre {
border: unset !important;
border-radius: unset !important;
}
/*
* Links
*/
.note-detail-printable a {
text-decoration: none;
}
.note-detail-printable a:not([href^="#root/"]) {
text-decoration: underline;
color: #374a75;
}
.note-detail-printable a::after {
/* Hide the external link trailing arrow */
display: none !important;
}
/*
* TODO list check boxes
*/
.note-detail-printable .todo-list__label * {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
@supports selector(.todo-list__label__description:has(*)) and (height: 1lh) {
.note-detail-printable .todo-list__label__description {
/* The percentage of the line height that the check box occupies */
--box-ratio: .75;
/* The size of the gap between the check box and the caption */
--box-text-gap: 0.25em;
--box-size: calc(1lh * var(--box-ratio));
--box-vert-offset: calc((1lh - var(--box-size)) / 2);
display: inline-block;
padding-left: calc(var(--box-size) + var(--box-text-gap));
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-blank-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5C3.89%2c3 3%2c3.89 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5C21%2c3.89 20.1%2c3 19%2c3M19%2c5V19H5V5H19Z' /%3e%3c/svg%3e");
background-position: 0 var(--box-vert-offset);
background-size: var(--box-size);
background-repeat: no-repeat;
}
.note-detail-printable .todo-list__label:has(input[type="checkbox"]:checked) .todo-list__label__description {
/* Source: https://pictogrammers.com/library/mdi/icon/checkbox-outline/ */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3e%3cpath d='M19%2c3H5A2%2c2 0 0%2c0 3%2c5V19A2%2c2 0 0%2c0 5%2c21H19A2%2c2 0 0%2c0 21%2c19V5A2%2c2 0 0%2c0 19%2c3M19%2c5V19H5V5H19M10%2c17L6%2c13L7.41%2c11.58L10%2c14.17L16.59%2c7.58L18%2c9' /%3e%3c/svg%3e");
}
.note-detail-printable .todo-list__label input[type="checkbox"] {
display: none !important;
}
}
/*
* Blockquotes
*/
.note-detail-printable blockquote {
box-shadow: unset;
}
/*
* Figures
*/
.note-detail-printable figcaption {
--accented-background-color: transparent;
font-style: italic;
}
/*
* Footnotes
*/
.note-detail-printable .footnote-reference a,
.footnote-back-link a {
text-decoration: none;
}
/* Make the "^" link cover the whole area of the footnote item */
.footnote-section {
clear: both;
}
.note-detail-printable li.footnote-item {
position: relative;
width: fit-content;
}
.note-detail-printable .footnote-back-link,
.note-detail-printable .footnote-back-link *,
.note-detail-printable .footnote-back-link a {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.note-detail-printable .footnote-back-link a {
color: transparent;
}
.note-detail-printable .footnote-content {
display: inline-block;
width: unset;
}
/*
* Widows and orphans
*/
p,
blockquote {
widows: 4;
orphans: 4;
}
pre > code {
widows: 6;
orphans: 6;
}
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
break-after: avoid;
}

View File

@ -1,6 +1,7 @@
@import url(./forms.css);
@import url(./shell.css);
@import url(./settings.css);
@import url(./notes/empty.css);
@import url(./notes/text.css);
@font-face {
@ -64,3 +65,29 @@
/* Theme capabilities */
--tab-note-icons: true;
}
/*
* Note search suggestions
*/
/* List body */
.jump-to-note-dialog .jump-to-note-results .aa-suggestions,
.note-detail-empty .aa-suggestions {
padding: 0;
}
/* List item */
.jump-to-note-dialog .aa-suggestions div,
.note-detail-empty .aa-suggestions div {
border-radius: 6px;
padding: 6px 12px;
color: var(--menu-text-color);
cursor: default;
}
/* Selected list item */
.jump-to-note-dialog .aa-suggestions div.aa-cursor,
.note-detail-empty .aa-suggestions div.aa-cursor {
background: var(--hover-item-background-color);
color: var(--hover-item-text-color);
}

View File

@ -78,10 +78,11 @@ button.btn.btn-success kbd {
* Icon buttons
*/
:root .icon-action:not(.global-menu-button) {
:root .icon-action:not(.global-menu-button),
:root .tn-tool-button {
width: var(--icon-button-size);
height: var(--icon-button-size);
border: unset;
border: unset !important;
border-radius: 8px;
padding: 0;
text-align: center;
@ -89,7 +90,8 @@ button.btn.btn-success kbd {
}
/* The "x" icon button */
:root .icon-action.bx-x {
:root .icon-action.bx-x,
:root .tn-tool-button.bx-x {
--icon-button-hover-background: var(--tab-close-button-hover-background);
--icon-button-hover-color: var(--tab-close-button-hover-color);
--icon-button-size: 24px;
@ -99,23 +101,28 @@ button.btn.btn-success kbd {
}
/* The icon */
:root .icon-action:not(.global-menu-button)::before {
:root .icon-action:not(.global-menu-button)::before,
:root .tn-tool-button::before {
display: block;
line-height: var(--icon-button-size);
font-size: calc(var(--icon-button-size) * var(--icon-button-icon-ratio));
}
:root .icon-action:not(.global-menu-button):hover,
:root .icon-action:not(.global-menu-button).show {
:root .icon-action:not(.global-menu-button).show,
:root .tn-tool-button:hover,
:root .tn-tool-button.show {
background: var(--icon-button-hover-background);
color: var(--icon-button-hover-color);
}
:root .icon-action:not(.global-menu-button):active::before {
:root .icon-action:not(.global-menu-button):active::before,
:root .tn-tool-button:active::before {
transform: scale(.85);
}
:root .icon-action:not(.global-menu-button):focus-visible {
:root .icon-action:not(.global-menu-button):focus-visible,
:root .tn-tool-button:focus-visible {
outline: 2px solid var(--input-focus-outline-color);
}
@ -216,6 +223,7 @@ input::selection,
.input-group button,
.input-group a {
display: flex;
align-items: center;
--bs-border-width: 0;
--accented-background-color: transparent;
background: transparent;
@ -228,15 +236,21 @@ input::selection,
.input-group button:hover,
.input-group a:hover {
--muted-text-color: var(--input-action-button-hover);
color: var(--input-action-button-hover);
}
.input-group button:focus-visible,
.input-group a:focus-visible {
box-shadow: unset;
outline: transparent;
border: transparent;
text-shadow: 0 0 3px var(--input-action-button-hover);
}
.input-group button:active {
background: transparent !important;
}
.input-group a.disabled {
opacity: .5;
/* Workaround to set the "background" property. */
@ -247,7 +261,7 @@ input::selection,
background: transparent !important;
}
.input-group .input-group-text {
.input-group .input-group-text:not(button):not(a) {
/* Background color hijack */
--accented-background-color: transparent;
@ -273,6 +287,7 @@ input::selection,
select,
select.form-select,
select.form-control,
.select-button.dropdown-toggle.btn {
outline: 3px solid transparent;
outline-offset: 6px;
@ -285,6 +300,7 @@ select.form-select,
select:hover,
select.form-select:hover,
select.form-control:hover,
.select-button.dropdown-toggle.btn:hover {
background-color: var(--input-hover-background);
color: var(--input-hover-color);
@ -297,6 +313,7 @@ button.select-button.dropdown-toggle.btn:active {
select:focus,
select.form-select:focus,
select.form-control:focus,
.select-button.dropdown-toggle.btn:focus {
box-shadow: unset;
outline: 3px solid var(--input-focus-outline-color);

View File

@ -0,0 +1,11 @@
/* The container */
div.note-detail-empty {
max-width: 70%;
margin: 50px auto;
}
/* The search results list */
.note-detail-empty span.aa-dropdown-menu {
margin-top: 1em;
border: unset;
}

View File

@ -126,3 +126,34 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
border: 0 !important;
border-top: 1px solid var(--main-border-color) !important;
}
/*
* Search in text panel
*/
.find-replace-widget > div {
padding: 8px;
}
.find-replace-widget > div + div {
padding-top: 0;
}
div.find-replace-widget div.find-widget-found-wrapper {
min-width: 50px;
font-style: normal;
font-weight: normal;
}
/* The up / down buttons of the "Find in text" input */
.find-replace-widget .input-group button {
font-size: 1.3em;
}
.find-replace-widget .form-check {
padding-left: 0;
}
.find-replace-widget .form-check .form-check-input {
margin-left: 0;
}

View File

@ -227,6 +227,12 @@ body.layout-horizontal > .horizontal {
--hover-item-background-color: transparent;
}
#launcher-pane.horizontal .global-menu-button .global-menu-button-update-available {
right: -23px;
bottom: -22px;
transform: scale(0.85);
}
.tooltip .tooltip-arrow {
display: none;
}
@ -1237,25 +1243,6 @@ body .calendar-dropdown-widget .calendar-body a:hover {
background: transparent !important;
}
/* List body */
.jump-to-note-dialog .jump-to-note-results .aa-suggestions {
padding: 0;
}
/* List item */
.jump-to-note-dialog .aa-suggestions div {
border-radius: 6px;
padding: 6px 12px;
color: var(--menu-text-color);
cursor: default;
}
/* Selected list item */
.jump-to-note-dialog .aa-suggestions div.aa-cursor {
background: var(--hover-item-background-color);
color: var(--hover-item-text-color);
}
/*
* Recent changes list
*/
@ -1655,4 +1642,9 @@ body .calendar-dropdown-widget .calendar-body a:hover {
border-radius: 8px;
padding-left: 12px;
}
/* Promoted attributes */
.promoted-attribute-cell div.input-group {
margin: 1px 0;
}

View File

@ -109,7 +109,8 @@
"choose_export_type": "Choose export type first please",
"export_status": "Export status",
"export_in_progress": "Export in progress: {{progressCount}}",
"export_finished_successfully": "Export finished successfully."
"export_finished_successfully": "Export finished successfully.",
"format_pdf": "PDF - for printing or sharing purposes."
},
"help": {
"fullDocumentation": "Help (full documentation is available <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
@ -437,7 +438,9 @@
"share_favicon": "Favicon note to be set in the shared page. Typically you want to set it to share root and make it inheritable. Favicon note must be in the shared sub-tree as well. Consider using 'share_hidden_from_tree'.",
"is_owned_by_note": "is owned by note",
"other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"",
"and_more": "... and {{count}} more."
"and_more": "... and {{count}} more.",
"print_landscape": "When exporting to PDF, changes the orientation of the page to landscape instead of portrait.",
"print_page_size": "When exporting to PDF, changes the size of the page. Supported values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
},
"attribute_editor": {
"help_text_body1": "To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code>",

View File

@ -31,6 +31,7 @@ async function register(app: express.Application) {
target: productionConfig.target
});
app.use(`/${assetPath}/app/doc_notes`, persistentCacheStatic(path.join(srcRoot, "public/app/doc_notes")));
app.use(`/${assetPath}/app`, webpackMiddleware(frontendCompiler));
} else {
app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(srcRoot, "public/app")));

View File

@ -72,6 +72,9 @@ export default [
{ type: "label", name: "webViewSrc", isDangerous: true },
{ type: "label", name: "hideHighlightWidget" },
{ type: "label", name: "printLandscape" },
{ type: "label", name: "printPageSize" },
// relation names
{ type: "relation", name: "internalLink" },
{ type: "relation", name: "imageLink" },

View File

View File

@ -0,0 +1,13 @@
import { describe, it, expect } from "vitest";
import { processMindmapContent } from "./note_content_fulltext.js";
describe("processMindmapContent", () => {
it("supports empty JSON", () => {
expect(processMindmapContent("{}")).toEqual("");
});
it("supports blank text / invalid JSON", () => {
expect(processMindmapContent("")).toEqual("");
expect(processMindmapContent(`{ "node": " }`)).toEqual("");
});
});

View File

@ -131,52 +131,7 @@ class NoteContentFulltextExp extends Expression {
content = content.replace(/&nbsp;/g, " ");
} else if (type === "mindMap" && mime === "application/json") {
let mindMapcontent = JSON.parse(content);
// Define interfaces for the JSON structure
interface MindmapNode {
id: string;
topic: string;
children: MindmapNode[]; // Recursive structure
direction?: number;
expanded?: boolean;
}
interface MindmapData {
nodedata: MindmapNode;
arrows: any[]; // If you know the structure, replace `any` with the correct type
summaries: any[];
direction: number;
theme: {
name: string;
type: string;
palette: string[];
cssvar: Record<string, string>; // Object with string keys and string values
};
}
// Recursive function to collect all topics
function collectTopics(node: MindmapNode): string[] {
// Collect the current node's topic
let topics = [node.topic];
// If the node has children, collect topics recursively
if (node.children && node.children.length > 0) {
for (const child of node.children) {
topics = topics.concat(collectTopics(child));
}
}
return topics;
}
// Start extracting from the root node
const topicsArray = collectTopics(mindMapcontent.nodedata);
// Combine topics into a single string
const topicsString = topicsArray.join(", ");
content = normalize(topicsString.toString());
content = processMindmapContent(content);
} else if (type === "canvas" && mime === "application/json") {
interface Element {
type: string;
@ -215,4 +170,63 @@ class NoteContentFulltextExp extends Expression {
}
}
export function processMindmapContent(content: string) {
let mindMapcontent;
try {
mindMapcontent = JSON.parse(content);
} catch (e) {
return "";
}
// Define interfaces for the JSON structure
interface MindmapNode {
id: string;
topic: string;
children: MindmapNode[]; // Recursive structure
direction?: number;
expanded?: boolean;
}
interface MindmapData {
nodedata: MindmapNode;
arrows: any[]; // If you know the structure, replace `any` with the correct type
summaries: any[];
direction: number;
theme: {
name: string;
type: string;
palette: string[];
cssvar: Record<string, string>; // Object with string keys and string values
};
}
// Recursive function to collect all topics
function collectTopics(node?: MindmapNode): string[] {
if (!node) {
return [];
}
// Collect the current node's topic
let topics = [node.topic];
// If the node has children, collect topics recursively
if (node.children && node.children.length > 0) {
for (const child of node.children) {
topics = topics.concat(collectTopics(child));
}
}
return topics;
}
// Start extracting from the root node
const topicsArray = collectTopics(mindMapcontent.nodedata);
// Combine topics into a single string
const topicsString = topicsArray.join(", ");
return normalize(topicsString.toString());
}
export default NoteContentFulltextExp;

View File

@ -282,7 +282,50 @@ export function getResourceDir() {
return join(dirname(fileURLToPath(import.meta.url)), "..", "..");
}
// TODO: Deduplicate with src/public/app/services/utils.ts
/**
* Compares two semantic version strings.
* Returns:
* 1 if v1 is greater than v2
* 0 if v1 is equal to v2
* -1 if v1 is less than v2
*
* @param v1 First version string
* @param v2 Second version string
* @returns
*/
function compareVersions(v1: string, v2: string): number {
// Remove 'v' prefix and everything after dash if present
v1 = v1.replace(/^v/, "").split("-")[0];
v2 = v2.replace(/^v/, "").split("-")[0];
const v1parts = v1.split(".").map(Number);
const v2parts = v2.split(".").map(Number);
// Pad shorter version with zeros
while (v1parts.length < 3) v1parts.push(0);
while (v2parts.length < 3) v2parts.push(0);
// Compare major version
if (v1parts[0] !== v2parts[0]) {
return v1parts[0] > v2parts[0] ? 1 : -1;
}
// Compare minor version
if (v1parts[1] !== v2parts[1]) {
return v1parts[1] > v2parts[1] ? 1 : -1;
}
// Compare patch version
if (v1parts[2] !== v2parts[2]) {
return v1parts[2] > v2parts[2] ? 1 : -1;
}
return 0;
}
export default {
compareVersions,
crash,
deferred,
envToBoolean,

View File

@ -51,6 +51,7 @@ ipcMain.on("create-extra-window", (event, arg) => {
interface ExportAsPdfOpts {
title: string;
landscape: boolean;
pageSize: "A0" | "A1" | "A2" | "A3" | "A4" | "A5" | "A6" | "Legal" | "Letter" | "Tabloid" | "Ledger";
}
ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => {
@ -76,11 +77,14 @@ ipcMain.on("export-as-pdf", async (e, opts: ExportAsPdfOpts) => {
try {
buffer = await browserWindow.webContents.printToPDF({
landscape: opts.landscape,
pageSize: opts.pageSize,
generateDocumentOutline: true,
generateTaggedPDF: true,
printBackground: true,
displayHeaderFooter: true,
headerTemplate: `<div></div>`,
footerTemplate: `
<div style="width: 100%; text-align: center; font-size: 10pt;">
<span class="pageNumber"></span>
<div class="pageNumber" style="width: 100%; text-align: center; font-size: 10pt;">
</div>
`
});

View File

@ -56,18 +56,26 @@
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
<form data-bind="submit: selectSetupType">
<div class="radio" style="margin-bottom: 15px;">
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType"">
<%= t("setup.new-document") %></label>
</div>
<div class="radio" style="margin-bottom: 15px;">
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
<%= t("setup.sync-from-desktop") %></label>
</div>
<div class="radio" style="margin-bottom: 15px;">
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType"">
<%= t("setup.sync-from-server") %></label>
</div>
<div class="radio" style="margin-bottom: 15px;">
<label class="tn-radio">
<input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType">
<%= t("setup.new-document") %>
</label>
</div>
<div class="radio" style="margin-bottom: 15px;">
<label class="tn-radio">
<input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
<%= t("setup.sync-from-desktop") %>
</label>
</div>
<div class="radio" style="margin-bottom: 15px;">
<label class="tn-radio">
<input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType">
<%= t("setup.sync-from-server") %>
</label>
</div>
<button type="submit" data-bind="disable: !setupTypeSelected()" class="btn btn-primary"><%= t("setup.next") %></button>
</form>

View File

@ -12,7 +12,8 @@ import ws from "./services/ws.js";
import utils from "./services/utils.js";
import port from "./services/port.js";
import host from "./services/host.js";
import semver from "semver";
const MINIMUM_NODE_VERSION = "22.0.0";
// setup basic error handling even before requiring dependencies, since those can produce errors as well
@ -32,8 +33,8 @@ function exit() {
process.on("SIGINT", exit);
process.on("SIGTERM", exit);
if (!semver.satisfies(process.version, ">=10.5.0")) {
console.error("Trilium only supports node.js 10.5 and later");
if (utils.compareVersions(process.version, MINIMUM_NODE_VERSION) < 0) {
console.error(`\nTrilium requires Node.js ${MINIMUM_NODE_VERSION} and later.\n`);
process.exit(1);
}