diff --git a/bin/electron-forge/desktop.ejs b/bin/electron-forge/desktop.ejs index f803f37b2..32430cd2a 100644 --- a/bin/electron-forge/desktop.ejs +++ b/bin/electron-forge/desktop.ejs @@ -1,12 +1,17 @@ [Desktop Entry] -<% if (productName) { %>Name=<%= productName %> -<% } %><% if (description) { %>Comment=<%= description %> -<% } %><% if (genericName) { %>GenericName=<%= genericName %> -<% } %><% if (name) { %>Exec=<%= name %> %U -Icon=<%= name %> -<% } %>Type=Application -StartupNotify=true -<% if (productName) { %>StartupWMClass=<%= productName %> -<% } if (categories && categories.length) { %>Categories=<%= categories.join(';') %>; -<% } %><% if (mimeType && mimeType.length) { %>MimeType=<%= mimeType.join(';') %>; -<% } %> \ No newline at end of file +<%= +Object.entries({ + "Name": productName, + "Comment": description, + "GenericName": genericName, + "Exec": name ? `${name} %U` : undefined, + "Icon": name, + "Type": "Application", + "StartupNotify": "true", + "StartupWMClass": productName, + "Categories": categories?.length ? `${categories.join(";")};` : undefined, + "MimeType": mimeType?.length ? `${mimeType.join(";")};` : undefined +}) +.map(line => line[1] ? line.join("=") : undefined) +.filter(line => !!line) +.join("\n")%> \ No newline at end of file diff --git a/electron.ts b/electron.ts index 154ec38d4..dc93e6688 100644 --- a/electron.ts +++ b/electron.ts @@ -26,6 +26,8 @@ electronDl({ saveAs: true }); // needed for excalidraw export https://github.com/zadam/trilium/issues/4271 electron.app.commandLine.appendSwitch("enable-experimental-web-platform-features"); +electron.app.userAgentFallback = `${electron.app.getName()} ${electron.app.getVersion()}`; + // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. diff --git a/package-lock.json b/package-lock.json index 49e028734..bd31ed854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": "*" }, diff --git a/package.json b/package.json index 4216a3d26..de2300213 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/public/app/components/app_context.ts b/src/public/app/components/app_context.ts index dbfe78cd0..2be0d986b 100644 --- a/src/public/app/components/app_context.ts +++ b/src/public/app/components/app_context.ts @@ -83,7 +83,7 @@ export type CommandMappings = { }; showExportDialog: CommandData & { notePath: string; - defaultType: "single"; + defaultType: "single" | "subtree"; }; showDeleteNotesDialog: CommandData & { branchIdsToDelete: string[]; diff --git a/src/public/app/desktop.ts b/src/public/app/desktop.ts index 4e9930d34..b78074126 100644 --- a/src/public/app/desktop.ts +++ b/src/public/app/desktop.ts @@ -4,7 +4,6 @@ import noteTooltipService from "./services/note_tooltip.js"; import bundleService from "./services/bundle.js"; import toastService from "./services/toast.js"; import noteAutocompleteService from "./services/note_autocomplete.js"; -import macInit from "./services/mac_init.js"; import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; @@ -35,8 +34,6 @@ if (utils.isElectron()) { initOnElectron(); } -macInit.init(); - noteTooltipService.setupGlobalTooltip(); noteAutocompleteService.init(); diff --git a/src/public/app/services/mac_init.ts b/src/public/app/services/mac_init.ts deleted file mode 100644 index c864b3cad..000000000 --- a/src/public/app/services/mac_init.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Mac specific initialization - */ -import utils from "./utils.js"; -import shortcutService from "./shortcuts.js"; - -function init() { - if (utils.isElectron() && utils.isMac()) { - shortcutService.bindGlobalShortcut("meta+c", () => exec("copy")); - shortcutService.bindGlobalShortcut("meta+v", () => exec("paste")); - shortcutService.bindGlobalShortcut("meta+x", () => exec("cut")); - shortcutService.bindGlobalShortcut("meta+a", () => exec("selectAll")); - shortcutService.bindGlobalShortcut("meta+z", () => exec("undo")); - shortcutService.bindGlobalShortcut("meta+y", () => exec("redo")); - } -} - -function exec(cmd: string) { - document.execCommand(cmd); - - return false; -} - -export default { - init -}; diff --git a/src/public/app/setup.ts b/src/public/app/setup.ts index ec389c793..1970c110a 100644 --- a/src/public/app/setup.ts +++ b/src/public/app/setup.ts @@ -1,9 +1,6 @@ import utils from "./services/utils.js"; -import macInit from "./services/mac_init.js"; import ko from "knockout"; -macInit.init(); - // TriliumNextTODO: properly make use of below types // type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | ""; // type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop"; diff --git a/src/public/app/types.d.ts b/src/public/app/types.d.ts index b1750afb4..2af1e098d 100644 --- a/src/public/app/types.d.ts +++ b/src/public/app/types.d.ts @@ -39,6 +39,7 @@ interface CustomGlobals { maxEntityChangeIdAtLoad: number; maxEntityChangeSyncIdAtLoad: number; assetPath: string; + appPath: string; instanceName: string; appCssNoteIds: string[]; triliumVersion: string; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.ts b/src/public/app/widgets/attribute_widgets/attribute_detail.ts index 7ea7061a9..c5c56102e 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.ts +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.ts @@ -67,6 +67,10 @@ const TPL = ` .attr-detail input[readonly] { background-color: var(--accented-background-color) !important; } + + .attr-edit-table td { + padding: 4px 0; + }
@@ -97,8 +101,13 @@ const TPL = ` - ${t("attribute_detail.promoted")} - + + + + ${t("attribute_detail.promoted_alias")} @@ -149,8 +158,13 @@ const TPL = ` - ${t("attribute_detail.inheritable")} - + + + + @@ -242,7 +256,9 @@ const ATTR_HELP: Record> = { 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"), diff --git a/src/public/app/widgets/attribute_widgets/attribute_editor.ts b/src/public/app/widgets/attribute_widgets/attribute_editor.ts index 1547de085..e7df24327 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_editor.ts +++ b/src/public/app/widgets/attribute_widgets/attribute_editor.ts @@ -77,8 +77,8 @@ const TPL = `
-
-
+
+
diff --git a/src/public/app/widgets/dialogs/export.js b/src/public/app/widgets/dialogs/export.ts similarity index 81% rename from src/public/app/widgets/dialogs/export.js rename to src/public/app/widgets/dialogs/export.ts index 5bc9b3707..8f1fe5306 100644 --- a/src/public/app/widgets/dialogs/export.js +++ b/src/public/app/widgets/dialogs/export.ts @@ -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 = ` + +
+ +
`; export default class ExportDialog extends BasicWidget { + + private taskId: string; + private branchId: string | null; + private modal?: bootstrap.Modal; + private $form!: JQuery; + private $noteTitle!: JQuery; + private $subtreeFormats!: JQuery; + private $singleFormats!: JQuery; + private $subtreeType!: JQuery; + private $singleType!: JQuery; + private $exportButton!: JQuery; + private $opmlVersions!: JQuery; + 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; diff --git a/src/public/app/widgets/dialogs/include_note.js b/src/public/app/widgets/dialogs/include_note.js index 5778306eb..f3ff29714 100644 --- a/src/public/app/widgets/dialogs/include_note.js +++ b/src/public/app/widgets/dialogs/include_note.js @@ -25,16 +25,22 @@ const TPL = ` ${t("include_note.box_size_prompt")}
- - +
- - +
- - +
-
- 0 - / - 0 -
+
diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 21f84dbc1..7c1e4f1d9 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -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") }); } diff --git a/src/public/app/widgets/type_widgets/attachment_detail.js b/src/public/app/widgets/type_widgets/attachment_detail.js index 2a797fba5..15932d63b 100644 --- a/src/public/app/widgets/type_widgets/attachment_detail.js +++ b/src/public/app/widgets/type_widgets/attachment_detail.js @@ -15,18 +15,18 @@ const TPL = ` display: flex; flex-direction: column; } - + .attachment-detail .links-wrapper { font-size: larger; padding: 0 0 16px 0; } - + .attachment-detail .attachment-wrapper { flex-grow: 1; } - +
`; @@ -57,7 +57,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget { this.$linksWrapper.empty().append( t("attachment_detail.owning_note"), - await linkService.createLink(this.noteId), + (await linkService.createLink(this.noteId)), t("attachment_detail.you_can_also_open"), await linkService.createLink(this.noteId, { title: t("attachment_detail.list_of_all_attachments"), diff --git a/src/public/app/widgets/type_widgets/doc.js b/src/public/app/widgets/type_widgets/doc.ts similarity index 90% rename from src/public/app/widgets/type_widgets/doc.js rename to src/public/app/widgets/type_widgets/doc.ts index 4fd2d76f0..433abccda 100644 --- a/src/public/app/widgets/type_widgets/doc.js +++ b/src/public/app/widgets/type_widgets/doc.ts @@ -1,3 +1,4 @@ +import type FNote from "../../entities/fnote.js"; import TypeWidget from "./type_widget.js"; const TPL = `
@@ -5,7 +6,7 @@ const TPL = `
.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 = `
border-radius: 5px; } - +
`; export default class DocTypeWidget extends TypeWidget { + + private $content!: JQuery; + 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) { diff --git a/src/public/app/widgets/type_widgets/geo_map.ts b/src/public/app/widgets/type_widgets/geo_map.ts index 60166cd41..fdca64cbc 100644 --- a/src/public/app/widgets/type_widgets/geo_map.ts +++ b/src/public/app/widgets/type_widgets/geo_map.ts @@ -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; - 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; + private currentTrackData: Record; + 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: `\ - - - - ${childNote.title}`, - 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(`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: `\ + + + + ${title}`, + 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(); diff --git a/src/public/app/widgets/type_widgets/protected_session.js b/src/public/app/widgets/type_widgets/protected_session.js index 620946012..a8726de74 100644 --- a/src/public/app/widgets/type_widgets/protected_session.js +++ b/src/public/app/widgets/type_widgets/protected_session.js @@ -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; + } +
diff --git a/src/public/stylesheets/print.css b/src/public/stylesheets/print.css index 34521d733..e172ece25 100644 --- a/src/public/stylesheets/print.css +++ b/src/public/stylesheets/print.css @@ -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; } \ No newline at end of file diff --git a/src/public/stylesheets/theme-next/base.css b/src/public/stylesheets/theme-next/base.css index 7d030b320..99b1263f7 100644 --- a/src/public/stylesheets/theme-next/base.css +++ b/src/public/stylesheets/theme-next/base.css @@ -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); +} \ No newline at end of file diff --git a/src/public/stylesheets/theme-next/forms.css b/src/public/stylesheets/theme-next/forms.css index 9e5702514..87e793e37 100644 --- a/src/public/stylesheets/theme-next/forms.css +++ b/src/public/stylesheets/theme-next/forms.css @@ -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); diff --git a/src/public/stylesheets/theme-next/notes/empty.css b/src/public/stylesheets/theme-next/notes/empty.css new file mode 100644 index 000000000..fcdf07198 --- /dev/null +++ b/src/public/stylesheets/theme-next/notes/empty.css @@ -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; +} \ No newline at end of file diff --git a/src/public/stylesheets/theme-next/notes/text.css b/src/public/stylesheets/theme-next/notes/text.css index 56a12e1c4..76cc6e395 100644 --- a/src/public/stylesheets/theme-next/notes/text.css +++ b/src/public/stylesheets/theme-next/notes/text.css @@ -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; +} \ No newline at end of file diff --git a/src/public/stylesheets/theme-next/shell.css b/src/public/stylesheets/theme-next/shell.css index 1f80fcb0f..0bb24d775 100644 --- a/src/public/stylesheets/theme-next/shell.css +++ b/src/public/stylesheets/theme-next/shell.css @@ -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; } \ No newline at end of file diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index c0d7d0eb1..958df556b 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -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 online)", @@ -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: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger." }, "attribute_editor": { "help_text_body1": "To add label, just type e.g. #rock or if you want to add also value then e.g. #year = 2020", diff --git a/src/routes/assets.ts b/src/routes/assets.ts index 959de788b..e76f0fc88 100644 --- a/src/routes/assets.ts +++ b/src/routes/assets.ts @@ -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"))); diff --git a/src/services/builtin_attributes.ts b/src/services/builtin_attributes.ts index 30c0b6ce5..1caaecd63 100644 --- a/src/services/builtin_attributes.ts +++ b/src/services/builtin_attributes.ts @@ -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" }, diff --git a/src/services/export/pdf.ts b/src/services/export/pdf.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/services/search/expressions/note_content_fulltext.spec.ts b/src/services/search/expressions/note_content_fulltext.spec.ts new file mode 100644 index 000000000..9ef4a7d78 --- /dev/null +++ b/src/services/search/expressions/note_content_fulltext.spec.ts @@ -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(""); + }); +}); diff --git a/src/services/search/expressions/note_content_fulltext.ts b/src/services/search/expressions/note_content_fulltext.ts index 083fd3d0e..b0a97da76 100644 --- a/src/services/search/expressions/note_content_fulltext.ts +++ b/src/services/search/expressions/note_content_fulltext.ts @@ -131,52 +131,7 @@ class NoteContentFulltextExp extends Expression { content = content.replace(/ /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; // 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; // 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; diff --git a/src/services/utils.ts b/src/services/utils.ts index 09e3964e5..289e1af6b 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -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, diff --git a/src/services/window.ts b/src/services/window.ts index 01047a628..30781a416 100644 --- a/src/services/window.ts +++ b/src/services/window.ts @@ -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: `
`, footerTemplate: ` -
- +
` }); diff --git a/src/views/setup.ejs b/src/views/setup.ejs index 13c01a1bf..1f010d990 100644 --- a/src/views/setup.ejs +++ b/src/views/setup.ejs @@ -56,18 +56,26 @@
-
- -
-
- -
-
- -
+
+ +
+ +
+ +
+ +
+ +
diff --git a/src/www.ts b/src/www.ts index 258f0f272..d442ba35f 100644 --- a/src/www.ts +++ b/src/www.ts @@ -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); }