Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop

This commit is contained in:
Elian Doran 2024-11-01 23:14:41 +02:00
commit 293db6962e
No known key found for this signature in database
53 changed files with 1030 additions and 548 deletions

View File

@ -1,5 +1,7 @@
# TriliumNext Notes
![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/notes) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/notes/total)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.

View File

@ -1,15 +0,0 @@
{
"src": "dist/trilium-linux-x64",
"dest": "dist/",
"compression": "xz",
"name": "trilium",
"productName": "Trilium Notes",
"genericName": "Note taker",
"description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
"sections": "misc",
"maintainer": "zadam.apps@gmail.com",
"homepage": "https://github.com/zadam/trilium",
"bin": "trilium",
"icon": "dist/trilium-linux-x64/icon.png",
"categories": [ "Office" ]
}

View File

@ -0,0 +1,12 @@
[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(';') %>;
<% } %>

View File

@ -20,13 +20,20 @@ module.exports = {
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
const extraResources = getExtraResourcesForPlatform();
for (const resource of extraResources) {
const baseName = path.basename(resource);
let sourcePath;
if (platform === 'darwin') {
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', path.basename(resource));
sourcePath = path.join(buildPath, `${APP_NAME}.app`, 'Contents', 'Resources', baseName);
} else {
sourcePath = path.join(buildPath, 'resources', path.basename(resource));
sourcePath = path.join(buildPath, 'resources', baseName);
}
let destPath;
if (baseName !== "256x256.png") {
destPath = path.join(buildPath, baseName);
} else {
destPath = path.join(buildPath, "icon.png");
}
const destPath = path.join(buildPath, path.basename(resource));
// Copy files from resources folder to root
fs.move(sourcePath, destPath)
@ -44,6 +51,7 @@ module.exports = {
config: {
options: {
icon: "./images/app-icons/png/128x128.png",
desktopTemplate: path.resolve("./bin/electron-forge/desktop.ejs")
}
}
},
@ -95,6 +103,7 @@ function getExtraResourcesForPlatform() {
case 'darwin':
break;
case 'linux':
resources.push("images/app-icons/png/256x256.png")
for (const script of scripts) {
resources.push(`./bin/tpl/${script}.sh`)
}

177
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "trilium",
"version": "0.90.8",
"version": "0.90.9-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.90.8",
"version": "0.90.9-beta",
"license": "AGPL-3.0-only",
"dependencies": {
"@braintree/sanitize-url": "7.1.0",
@ -23,11 +23,11 @@
"cls-hooked": "4.2.2",
"codemirror": "5.65.18",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"cookie-parser": "1.4.7",
"csurf": "1.11.0",
"dayjs": "1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.1.1",
"debounce": "2.2.0",
"ejs": "3.1.10",
"electron-debug": "4.0.1",
"electron-dl": "4.0.0",
@ -35,18 +35,18 @@
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint": "9.10.0",
"express": "4.21.0",
"express": "4.21.1",
"express-partial-content": "1.0.2",
"express-rate-limit": "7.4.0",
"express-session": "1.18.0",
"force-graph": "1.43.5",
"express-rate-limit": "7.4.1",
"express-session": "1.18.1",
"force-graph": "1.45.0",
"fs-extra": "11.2.0",
"helmet": "7.1.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.5",
"i18next": "23.16.0",
"i18next": "23.16.2",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2",
"image-type": "4.1.0",
@ -63,10 +63,10 @@
"katex": "0.16.11",
"knockout": "3.5.1",
"mark.js": "8.11.1",
"marked": "14.1.2",
"marked": "14.1.3",
"mermaid": "11.3.0",
"mime-types": "2.1.35",
"mind-elixir": "4.2.0",
"mind-elixir": "4.2.3",
"multer": "1.4.5-lts.1",
"node-abi": "3.67.0",
"normalize-strings": "1.1.1",
@ -79,7 +79,7 @@
"request": "2.88.2",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.13.0",
"sanitize-html": "2.13.1",
"sax": "1.4.1",
"semver": "7.6.3",
"serve-favicon": "2.5.0",
@ -92,7 +92,7 @@
"tree-kill": "1.2.2",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.2",
"vanilla-js-wheel-zoom": "9.0.4",
"ws": "8.18.0",
"xml2js": "0.6.2",
"yauzl": "3.1.3"
@ -126,7 +126,7 @@
"@types/jsdom": "21.1.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.5.4",
"@types/node": "22.7.8",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
@ -145,14 +145,14 @@
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "1.7.0",
"jasmine": "5.3.1",
"jasmine": "5.4.0",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "3.1.7",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"tslib": "2.7.0",
"tslib": "2.8.0",
"tsx": "4.19.1",
"typescript": "5.6.3",
"webpack": "5.95.0",
@ -3360,9 +3360,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.43",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz",
"integrity": "sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==",
"dev": true,
"dependencies": {
"@types/node": "*",
@ -3380,6 +3380,18 @@
"@types/express": "*"
}
},
"node_modules/@types/express/node_modules/@types/express-serve-static-core": {
"version": "4.19.6",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz",
"integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==",
"dev": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
@ -3528,9 +3540,9 @@
}
},
"node_modules/@types/node": {
"version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"version": "22.7.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.8.tgz",
"integrity": "sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==",
"dependencies": {
"undici-types": "~6.19.2"
}
@ -5319,9 +5331,9 @@
]
},
"node_modules/canvas-color-tracker": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.2.1.tgz",
"integrity": "sha512-i5clg2pEdaWqHuEM/B74NZNLkHh5+OkXbA/T4iaBiaNDagkOCXkLNrhqUfdUugsRwuaNRU20e/OygzxWRor3yg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/canvas-color-tracker/-/canvas-color-tracker-1.3.1.tgz",
"integrity": "sha512-eNycxGS7oQ3IS/9QQY41f/aQjiO9Y/MtedhCgSdsbLSxC9EyUD8L3ehl/Q3Kfmvt8um79S45PBV+5Rxm5ztdSw==",
"dependencies": {
"tinycolor2": "^1.6.0"
},
@ -5926,11 +5938,11 @@
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": {
"cookie": "0.4.1",
"cookie": "0.7.2",
"cookie-signature": "1.0.6"
},
"engines": {
@ -5938,9 +5950,9 @@
}
},
"node_modules/cookie-parser/node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
@ -6753,9 +6765,9 @@
"integrity": "sha512-ExERH5o3oo6jFOdkvMP3gytTCQ9Ksi5PtylclJWghr7k7m3o2U5QrwtdiJkOxLOH4ghr0EKhpqGefzGz1VvVJg=="
},
"node_modules/debounce": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-2.1.1.tgz",
"integrity": "sha512-+xRWxgel9LgTC4PwKlm7TJUK6B6qsEK77NaiNvXmeQ7Y3e6OVVsBC4a9BSptS/mAYceyAz37Oa8JTTuPRft7uQ==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz",
"integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==",
"engines": {
"node": ">=18"
},
@ -8229,16 +8241,16 @@
}
},
"node_modules/express": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -8278,9 +8290,9 @@
}
},
"node_modules/express-rate-limit": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.0.tgz",
"integrity": "sha512-v1204w3cXu5gCDmAvgvzI6qjzZzoMWKnyVDk3ACgfswTQLYiGen+r8w0VnXnGMmzEN/g8fwIQ4JrFFd4ZP6ssg==",
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz",
"integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==",
"engines": {
"node": ">= 16"
},
@ -8292,11 +8304,11 @@
}
},
"node_modules/express-session": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
"integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
"dependencies": {
"cookie": "0.6.0",
"cookie": "0.7.2",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
@ -8310,9 +8322,9 @@
}
},
"node_modules/express-session/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
@ -8358,9 +8370,9 @@
]
},
"node_modules/express/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@ -8835,14 +8847,14 @@
}
},
"node_modules/force-graph": {
"version": "1.43.5",
"resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.43.5.tgz",
"integrity": "sha512-HveLELh9yhZXO/QOfaFS38vlwJZ/3sKu+jarfXzRmbmihSOH/BbRWnUvmg8wLFiYy6h4HlH4lkRfZRccHYmXgA==",
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.45.0.tgz",
"integrity": "sha512-QM/J72Vji5D3ug+TDu8wH+qne0zEKE9Cn7m9ocH/1RtaVY0BBqZQ4Mn6MiwNRyxwl28lsUd0F54kDpINnagvOA==",
"dependencies": {
"@tweenjs/tween.js": "18 - 23",
"@tweenjs/tween.js": "18 - 25",
"accessor-fn": "1",
"bezier-js": "3 - 6",
"canvas-color-tracker": "1",
"canvas-color-tracker": "^1.3",
"d3-array": "1 - 3",
"d3-drag": "2 - 3",
"d3-force-3d": "2 - 3",
@ -9715,9 +9727,9 @@
}
},
"node_modules/i18next": {
"version": "23.16.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.0.tgz",
"integrity": "sha512-Ni3CG6c14teOogY19YNRl+kYaE/Rb59khy0VyHVn4uOZ97E2E/Yziyi6r3C3s9+wacjdLZiq/LLYyx+Cgd+FCw==",
"version": "23.16.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.2.tgz",
"integrity": "sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==",
"funding": [
{
"type": "individual",
@ -10828,22 +10840,22 @@
}
},
"node_modules/jasmine": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.3.1.tgz",
"integrity": "sha512-3zeUCfr3d1iga3s+NgDpggCP+ex5sdbNgqNn+Tq4yw/QfnwGrWC/ZvXX1IRm5deSIZ1LnvoeGY55F/ztbVOXPQ==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-5.4.0.tgz",
"integrity": "sha512-E2u4ylX5tgGYvbynImU6EUBKKrSVB1L72FEPjGh4M55ov1VsxR26RA2JU91L9YSPFgcjo4mCLyKn/QXvEYGBkA==",
"dev": true,
"dependencies": {
"glob": "^10.2.2",
"jasmine-core": "~5.3.0"
"jasmine-core": "~5.4.0"
},
"bin": {
"jasmine": "bin/jasmine.js"
}
},
"node_modules/jasmine-core": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.3.0.tgz",
"integrity": "sha512-zsOmeBKESky4toybvWEikRiZ0jHoBEu79wNArLfMdSnlLMZx3Xcp6CSm2sUcYyoJC+Uyj8LBJap/MUbVSfJ27g==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.4.0.tgz",
"integrity": "sha512-T4fio3W++llLd7LGSGsioriDHgWyhoL6YTu4k37uwJLF7DzOzspz7mNxRoM3cQdLWtL/ebazQpIf/yZGJx/gzg==",
"dev": true
},
"node_modules/jasmine/node_modules/brace-expansion": {
@ -11817,9 +11829,9 @@
}
},
"node_modules/marked": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz",
"integrity": "sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==",
"version": "14.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.3.tgz",
"integrity": "sha512-ZibJqTULGlt9g5k4VMARAktMAjXoVnnr+Y3aCqW1oDftcV4BA3UmrBifzXoZyenHRk75csiPu9iwsTj4VNBT0g==",
"bin": {
"marked": "bin/marked.js"
},
@ -12028,9 +12040,9 @@
}
},
"node_modules/mind-elixir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.0.tgz",
"integrity": "sha512-FBmTri+lfuScRtaBQGh6WNFU1Bbqp2IDq6dF9coxv5aj34wuTsacR8fwyTaK79Wh8A5mUL/D/HO/KvSMBKJBqg=="
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/mind-elixir/-/mind-elixir-4.2.3.tgz",
"integrity": "sha512-o/t9/mrJkRu0PE5UjXBv8ZZuhwSdm6C1Hw65v/+bIlB2CS2MOGZ/GNPvU3U4kPDu6LnCZ0kw0L7hoVfHhrZLtw=="
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
@ -14321,9 +14333,9 @@
}
},
"node_modules/sanitize-html": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz",
"integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==",
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz",
"integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==",
"dependencies": {
"deepmerge": "^4.2.2",
"escape-string-regexp": "^4.0.0",
@ -15647,9 +15659,9 @@
}
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA=="
},
"node_modules/tsscmp": {
"version": "1.0.6",
@ -16032,10 +16044,9 @@
"integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="
},
"node_modules/vanilla-js-wheel-zoom": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/vanilla-js-wheel-zoom/-/vanilla-js-wheel-zoom-9.0.2.tgz",
"integrity": "sha512-GleJm/qTDcfQC7gdFH4BT5vCZmnz2LOTgLAQ5CCCwftFC/zrBURRWr5Y7jRPi3W3rRf8bTIAN3hLZYFqvK5jwA==",
"license": "MIT"
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/vanilla-js-wheel-zoom/-/vanilla-js-wheel-zoom-9.0.4.tgz",
"integrity": "sha512-OjmS9ihEKBCRw2OQ7IiIdQGXdC5gTEEmtcAWZcPeNAJaYiS61KCd02Z72YMtIoXLGN5TZP+wliBMylLAsr6wow=="
},
"node_modules/vary": {
"version": "1.1.2",

View File

@ -2,7 +2,7 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.90.8",
"version": "0.90.9-beta",
"license": "AGPL-3.0-only",
"main": "./dist/electron-main.js",
"author": {
@ -63,11 +63,11 @@
"cls-hooked": "4.2.2",
"codemirror": "5.65.18",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"cookie-parser": "1.4.7",
"csurf": "1.11.0",
"dayjs": "1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.1.1",
"debounce": "2.2.0",
"ejs": "3.1.10",
"electron-debug": "4.0.1",
"electron-dl": "4.0.0",
@ -75,18 +75,18 @@
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint": "9.10.0",
"express": "4.21.0",
"express": "4.21.1",
"express-partial-content": "1.0.2",
"express-rate-limit": "7.4.0",
"express-session": "1.18.0",
"force-graph": "1.43.5",
"express-rate-limit": "7.4.1",
"express-session": "1.18.1",
"force-graph": "1.45.0",
"fs-extra": "11.2.0",
"helmet": "7.1.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.5",
"i18next": "23.16.0",
"i18next": "23.16.2",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2",
"image-type": "4.1.0",
@ -103,10 +103,10 @@
"katex": "0.16.11",
"knockout": "3.5.1",
"mark.js": "8.11.1",
"marked": "14.1.2",
"marked": "14.1.3",
"mermaid": "11.3.0",
"mime-types": "2.1.35",
"mind-elixir": "4.2.0",
"mind-elixir": "4.2.3",
"multer": "1.4.5-lts.1",
"node-abi": "3.67.0",
"normalize-strings": "1.1.1",
@ -119,7 +119,7 @@
"request": "2.88.2",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.13.0",
"sanitize-html": "2.13.1",
"sax": "1.4.1",
"semver": "7.6.3",
"serve-favicon": "2.5.0",
@ -132,7 +132,7 @@
"tree-kill": "1.2.2",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "9.0.2",
"vanilla-js-wheel-zoom": "9.0.4",
"ws": "8.18.0",
"xml2js": "0.6.2",
"yauzl": "3.1.3"
@ -163,7 +163,7 @@
"@types/jsdom": "21.1.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.5.4",
"@types/node": "22.7.8",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
@ -182,14 +182,14 @@
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "1.7.0",
"jasmine": "5.3.1",
"jasmine": "5.4.0",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "3.1.7",
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"tslib": "2.7.0",
"tslib": "2.8.0",
"tsx": "4.19.1",
"typescript": "5.6.3",
"webpack": "5.95.0",

View File

@ -9,6 +9,7 @@ import ws from "../services/ws.js";
import bundleService from "../services/bundle.js";
import froca from "../services/froca.js";
import linkService from "../services/link.js";
import { t } from "../services/i18n.js";
export default class Entrypoints extends Component {
constructor() {
@ -172,13 +173,13 @@ export default class Entrypoints extends Component {
const resp = await server.post(`sql/execute/${note.noteId}`);
if (!resp.success) {
toastService.showError(`Error occurred while executing SQL query: ${resp.error}`);
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
}
await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});
}
toastService.showMessage("Note executed");
toastService.showMessage(t("entrypoints.note-executed"));
}
hideAllPopups() {
@ -200,6 +201,6 @@ export default class Entrypoints extends Component {
await server.post(`notes/${noteId}/revision`);
toastService.showMessage("Note revision has been created.");
toastService.showMessage(t("entrypoints.note-revision-created"));
}
}

View File

@ -560,6 +560,18 @@ export default class TabManager extends Component {
}
}
async closeRightTabsCommand({ntxId}) {
const ntxIds = this.mainNoteContexts.map(nc => nc.ntxId);
const index = ntxIds.indexOf(ntxId);
if (index !== -1) {
const idsToRemove = ntxIds.slice(index + 1);
for (const ntxIdToRemove of idsToRemove) {
await this.removeNoteContext(ntxIdToRemove);
}
}
}
async closeTabCommand({ntxId}) {
await this.removeNoteContext(ntxId);
}

View File

@ -9,9 +9,9 @@ import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
bundleService.getWidgetBundlesByParent().then(async widgetBundles => {
await appContext.earlyInit();
await appContext.earlyInit();
bundleService.getWidgetBundlesByParent().then(async widgetBundles => {
// A dynamic import is required for layouts since they initialize components which require translations.
const DesktopLayout = (await import("./layouts/desktop_layout.js")).default;

View File

@ -3,6 +3,7 @@ import froca from "../services/froca.js";
import contextMenu from "./context_menu.js";
import dialogService from "../services/dialog.js";
import server from "../services/server.js";
import { t } from '../services/i18n.js';
export default class LauncherContextMenu {
/**
@ -33,29 +34,27 @@ export default class LauncherContextMenu {
const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
const isItem = isVisibleItem || isAvailableItem;
const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();;
const canBeReset = !canBeDeleted && note.isLaunchBarConfig();
return [
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null,
(isVisibleRoot || isAvailableRoot) ? { title: "----" } : null,
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
{ title: 'Reset', command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
{ title: `${t("launcher_context_menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted },
{ title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset},
{ title: "----" },
isAvailableItem ? { title: 'Move to visible launchers', command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: 'Move to available launchers', command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
{ title: `Duplicate launcher <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null,
isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null,
{ title: `${t("launcher_context_menu.duplicate-launcher")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
enabled: isItem }
].filter(row => row !== null);
}
async selectMenuItemHandler({command}) {
if (command === 'resetLauncher') {
const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"?
All data / settings in this note (and its children) will be lost
and the launcher will be returned to its original location.`);
const confirmed = await dialogService.confirm(t("launcher_context_menu.reset_launcher_confirm", { title: this.node.title }));
if (confirmed) {
await server.post(`special-notes/launchers/${this.node.data.noteId}/reset`);

View File

@ -136,7 +136,7 @@ export default class TreeContextMenu {
this.treeWidget.triggerCommand("openNewNoteSplit", {ntxId, notePath});
}
else if (command === 'convertNoteToAttachment') {
if (!await dialogService.confirm(`Are you sure you want to convert note selected notes into attachments of their parent notes?`)) {
if (!await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm"))) {
return;
}
@ -154,7 +154,7 @@ export default class TreeContextMenu {
}
}
toastService.showMessage(`${converted} notes have been converted to attachments.`);
toastService.showMessage(t("tree-context-menu.converted-to-attachments", { count: converted }));
}
else if (command === 'copyNotePathToClipboard') {
navigator.clipboard.writeText('#' + notePath);

View File

@ -5,6 +5,7 @@ import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import { t } from './i18n.js';
async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
branchIdsToMove = filterRootNote(branchIdsToMove);
@ -13,7 +14,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
const beforeBranch = froca.getBranch(beforeBranchId);
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@ -42,7 +43,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
];
if (forbiddenNoteIds.includes(afterNote.noteId)) {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@ -62,7 +63,7 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) {
const newParentBranch = froca.getBranch(newParentBranchId);
if (newParentBranch.noteId === '_lbRoot') {
toastService.showError('Cannot move notes here.');
toastService.showError(t("branches.cannot-move-notes-here"));
return;
}
@ -192,7 +193,7 @@ function filterRootNote(branchIds) {
function makeToast(id, message) {
return {
id: id,
title: "Delete status",
title: t("branches.delete-status"),
message: message,
icon: "trash"
};
@ -207,9 +208,9 @@ ws.subscribeToMessages(async message => {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message.taskId, `Delete notes in progress: ${message.progressCount}`));
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message.taskId, "Delete finished successfully.");
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);
@ -225,9 +226,9 @@ ws.subscribeToMessages(async message => {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message.taskId, `Undeleting notes in progress: ${message.progressCount}`));
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
toast.closeAfter = 5000;
toastService.showPersistent(toast);

View File

@ -3,6 +3,7 @@ import server from "./server.js";
import toastService from "./toast.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
async function getAndExecuteBundle(noteId, originEntity = null, script = null, params = null) {
const bundle = await server.post(`script/bundle/${noteId}`, {
@ -75,9 +76,23 @@ async function getWidgetBundlesByParent() {
try {
widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
}
catch (e) {
} catch (e) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
title: t("toast.bundle-error.title"),
icon: "alert",
message: t("toast.bundle-error.message", {
id: noteId,
title: note.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
}

View File

@ -78,7 +78,7 @@ async function copy(branchIds) {
clipboard.writeHTML(links.join(', '));
}
toastService.showMessage("Note(s) have been copied into clipboard.");
toastService.showMessage(t("clipboard.copied"));
}
function cut(branchIds) {
@ -87,7 +87,7 @@ function cut(branchIds) {
if (clipboardBranchIds.length > 0) {
clipboardMode = 'cut';
toastService.showMessage("Note(s) have been cut into clipboard.");
toastService.showMessage(t("clipboard.cut"));
}
}

View File

@ -217,8 +217,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
*/
this.runOnBackend = async (func, params = []) => {
if (func?.constructor.name === "AsyncFunction" || func?.startsWith?.("async ")) {
toastService.showError("You're passing an async function to api.runOnBackend() which will likely not work as you intended. "
+ "Either make the function synchronous (by removing 'async' keyword), or use api.runAsyncOnBackendWithManualTransactionHandling()");
toastService.showError(t("frontend_script_api.async_warning"));
}
return await this.__runOnBackendInner(func, params, true);
@ -240,8 +239,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
*/
this.runAsyncOnBackendWithManualTransactionHandling = async (func, params = []) => {
if (func?.constructor.name === "Function" || func?.startsWith?.("function")) {
toastService.showError("You're passing a synchronous function to api.runAsyncOnBackendWithManualTransactionHandling(), " +
"while you should likely use api.runOnBackend() instead.");
toastService.showError(t("frontend_script_api.sync_warning"));
}
return await this.__runOnBackendInner(func, params, false);

View File

@ -26,21 +26,6 @@ function setupGlobs() {
// for CKEditor integration (button on block toolbar)
window.glob.importMarkdownInline = async () => appContext.triggerCommand("importMarkdownInline");
window.glob.SEARCH_HELP_TEXT = `
<strong>Search tips</strong> - also see <button class="btn btn-sm" type="button" data-help-page="search.html">complete help on search</button>
<p>
<ul>
<li>Just enter any text for full text search</li>
<li><code>#abc</code> - returns notes with label abc</li>
<li><code>#year = 2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li>
<li><code>#rock #pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li>
<li><code>#rock or #pop</code> - only one of the labels must be present</li>
<li><code>#year &lt;= 2000</code> - numerical comparison (also &gt;, &gt;=, &lt;).</li>
<li><code>note.dateCreated >= MONTH-1</code> - notes created in the last month</li>
<li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li>
</ul>
</p>`;
window.onerror = function (msg, url, lineNo, columnNo, error) {
const string = msg.toLowerCase();
@ -64,6 +49,28 @@ function setupGlobs() {
return false;
};
window.addEventListener("unhandledrejection", (e) => {
const string = e?.reason?.message?.toLowerCase();
let message = "Uncaught error: ";
if (string?.includes("script error")) {
message += 'No details available';
} else {
message += [
`Message: ${e.reason.message}`,
`Line: ${e.reason.lineNumber}`,
`Column: ${e.reason.columnNumber}`,
`Error object: ${JSON.stringify(e.reason)}`,
`Stack: ${e.reason && e.reason.stack}`
].join(', ');
}
ws.logError(message);
return false;
});
for (const appCssNoteId of glob.appCssNoteIds || []) {
libraryLoader.requireCss(`api/notes/download/${appCssNoteId}`, false);
}

View File

@ -2,6 +2,7 @@ import appContext from "../components/app_context.js";
import treeService from "./tree.js";
import dialogService from "./dialog.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
function getHoistedNoteId() {
const activeNoteContext = appContext.tabManager.getActiveContext();
@ -53,7 +54,7 @@ async function checkNoteAccess(notePath, noteContext) {
const hoistedNote = await froca.getNote(hoistedNoteId);
if ((!hoistedNote.hasAncestor('_hidden') || resolvedNotePath.includes('_lbBookmarks'))
&& !await dialogService.confirm(`Requested note '${requestedNote.title}' is outside of hoisted note '${hoistedNote.title}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?`)) {
&& !await dialogService.confirm(t("hoisted_note.confirm_unhoisting", { requestedNote: requestedNote.title, hoistedNote: hoistedNote.title }))) {
return false;
}

View File

@ -8,9 +8,9 @@ function copyImageReferenceToClipboard($imageWrapper) {
const success = document.execCommand('copy');
if (success) {
toastService.showMessage("A reference to the image has been copied to clipboard. This can be pasted in any text note.");
toastService.showMessage(t("image.copied-to-clipboard"));
} else {
toastService.showAndLogError("Could not copy the image reference to clipboard.");
toastService.showAndLogError(t("image.cannot-copy"));
}
}
finally {

View File

@ -36,7 +36,7 @@ export async function uploadFiles(entityType, parentNoteId, files, options) {
type: 'POST',
timeout: 60 * 60 * 1000,
error: function (xhr) {
toastService.showError(`Import failed: ${xhr.responseText}`);
toastService.showError(t("import.failed", { message: xhr.responseText }));
},
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false, // NEEDED, DON'T REMOVE THIS

View File

@ -5,6 +5,7 @@ import ws from "./ws.js";
import froca from "./froca.js";
import treeService from "./tree.js";
import toastService from "./toast.js";
import { t } from "./i18n.js";
async function createNote(parentNotePath, options = {}) {
options = Object.assign({
@ -119,7 +120,7 @@ async function duplicateSubtree(noteId, parentNotePath) {
activeNoteContext.setNote(`${parentNotePath}/${note.noteId}`);
const origNote = await froca.getNote(noteId);
toastService.showMessage(`Note "${origNote.title}" has been duplicated`);
toastService.showMessage(t("note_create.duplicated", { title: origNote.title }));
}
export default {

View File

@ -6,6 +6,7 @@ import appContext from "../components/app_context.js";
import froca from "./froca.js";
import utils from "./utils.js";
import options from "./options.js";
import { t } from './i18n.js';
let protectedSessionDeferred = null;
@ -50,7 +51,7 @@ async function setupProtectedSession(password) {
const response = await server.post('login/protected', { password: password });
if (!response.success) {
toastService.showError("Wrong password.", 3000);
toastService.showError(t("protected_session.wrong_password"), 3000);
return;
}
@ -72,7 +73,7 @@ ws.subscribeToMessages(async message => {
protectedSessionDeferred = null;
}
toastService.showMessage("Protected session has been started.");
toastService.showMessage(t("protected_session.started"));
}
else if (message.type === 'protectedSessionLogout') {
utils.reloadFrontendApp(`Protected session logout`);
@ -85,10 +86,10 @@ async function protectNote(noteId, protect, includingSubtree) {
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
}
function makeToast(message, protectingLabel, text) {
function makeToast(message, title, text) {
return {
id: message.taskId,
title: `${protectingLabel} status`,
title,
message: text,
icon: message.data.protect ? "check-shield" : "shield"
};
@ -99,15 +100,19 @@ ws.subscribeToMessages(async message => {
return;
}
const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting";
const isProtecting = message.data.protect;
const title = isProtecting ? t("protected_session.protecting-title") : t("protected_session.unprotecting-title");
if (message.type === 'taskError') {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'taskProgressCount') {
toastService.showPersistent(makeToast(message, protectingLabel,`${protectingLabel} in progress: ${message.progressCount}`));
const count = message.progressCount;
const text = ( isProtecting ? t("protected_session.protecting-in-progress", { count }) : t("protected_session.unprotecting-in-progress-count", { count }));
toastService.showPersistent(makeToast(message, title, text));
} else if (message.type === 'taskSucceeded') {
const toast = makeToast(message, protectingLabel, `${protectingLabel} finished successfully.`);
const text = (isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully"))
const toast = makeToast(message, title, text);
toast.closeAfter = 3000;
toastService.showPersistent(toast);

View File

@ -5,7 +5,7 @@ async function syncNow(ignoreNotConfigured = false) {
const result = await server.post('sync/now');
if (result.success) {
toastService.showMessage("Sync finished successfully.");
toastService.showMessage(t("sync.finished-successfully"));
}
else {
if (result.message.length > 200) {
@ -13,7 +13,7 @@ async function syncNow(ignoreNotConfigured = false) {
}
if (!ignoreNotConfigured || result.errorCode !== 'NOT_CONFIGURED') {
toastService.showError(`Sync failed: ${result.message}`);
toastService.showError(t("sync.failed", { message: result.message }));
}
}
}

View File

@ -16,7 +16,7 @@ function toast(options) {
);
$toast.find('.toast-title').text(options.title);
$toast.find('.toast-body').text(options.message);
$toast.find('.toast-body').html(options.message);
if (options.id) {
$toast.attr("id", `toast-${options.id}`);

View File

@ -114,10 +114,10 @@ async function handleMessage(event) {
await executeFrontendUpdate(message.data.entityChanges);
}
else if (message.type === 'sync-hash-check-failed') {
toastService.showError("Sync check failed!", 60000);
toastService.showError(t("ws.sync-check-failed"), 60000);
}
else if (message.type === 'consistency-checks-failed') {
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
toastService.showError(t("ws.consistency-checks-failed"), 50 * 60000);
}
else if (message.type === 'api-log-messages') {
appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages});
@ -189,7 +189,7 @@ async function consumeFrontendUpdateData() {
else {
console.log("nonProcessedEntityChanges causing the timeout", nonProcessedEntityChanges);
toastService.showError(`Encountered error "${e.message}", check out the console.`);
toastService.showError(t("ws.encountered-error", { message: e.message }));
}
}

View File

@ -1,4 +1,5 @@
import Component from "../components/component.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import toastService from "../services/toast.js";
@ -85,14 +86,7 @@ class BasicWidget extends Component {
try {
this.doRender();
} catch (e) {
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message", {
title: this.widgetTitle,
message: e.message
})
});
this.logRenderingError(e);
}
this.$widget.attr('data-component-id', this.componentId);
@ -131,6 +125,35 @@ class BasicWidget extends Component {
return this.$widget;
}
logRenderingError(e) {
console.log("Got issue in widget ", this);
console.error(e);
let noteId = this._noteId;
if (this._noteId) {
froca.getNote(noteId, true).then((note) => {
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message-custom", {
id: noteId,
title: note.title,
message: e.message
})
});
});
return;
}
toastService.showPersistent({
title: t("toast.widget-error.title"),
icon: "alert",
message: t("toast.widget-error.message-unknown", {
message: e.message
})
});
}
/**
* Indicates if the widget is enabled. Widgets are enabled by default. Generally setting this to `false` will cause the widget not to be displayed, however it will still be available on the DOM but hidden.
* @returns

View File

@ -127,18 +127,18 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
}
async convertNoteIntoAttachmentCommand() {
if (!await dialogService.confirm(`Are you sure you want to convert note '${this.note.title}' into an attachment of the parent note?`)) {
if (!await dialogService.confirm(t("note_actions.convert_into_attachment_prompt", { title: this.note.title }))) {
return;
}
const {attachment: newAttachment} = await server.post(`notes/${this.noteId}/convert-to-attachment`);
if (!newAttachment) {
toastService.showMessage(`Converting note '${this.note.title}' failed.`);
toastService.showMessage(t("note_actions.convert_into_attachment_failed", { title: this.note.title }));
return;
}
toastService.showMessage(`Note '${newAttachment.title}' has been converted to attachment.`);
toastService.showMessage(t("note_actions.convert_into_attachment_successful", { title: newAttachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newAttachment.ownerId, {
viewScope: {

View File

@ -8,7 +8,11 @@ export default class Container extends BasicWidget {
renderChildren() {
for (const widget of this.children) {
try {
this.$widget.append(widget.render());
} catch (e) {
widget.logRenderingError(e);
}
}
}
}

View File

@ -666,9 +666,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
const node = this.prepareNode(branch);
if (node) {
noteList.push(node);
}
}
return noteList;
}
@ -709,7 +710,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const note = branch.getNoteFromCache();
if (!note) {
throw new Error(`Branch '${branch.branchId}' has no child note '${branch.noteId}'`);
console.warn(`Branch '${branch.branchId}' has no child note '${branch.noteId}'`);
return null;
}
const title = `${branch.prefix ? (`${branch.prefix} - `) : ""}${note.title}`;
@ -1031,7 +1033,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
activeNode.load(true);
activeNode.setExpanded(true, {noAnimation: true});
toastService.showMessage("Saved search note refreshed.");
toastService.showMessage(t("note_tree.saved-search-note-refreshed"));
}
async batchUpdate(cb) {
@ -1075,7 +1077,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
node.setExpanded(false);
if (noneCollapsedYet) {
toastService.showMessage("Auto collapsing notes after inactivity...");
toastService.showMessage(t("note_tree.auto-collapsing-notes-after-inactivity"));
noneCollapsedYet = false;
}
}

View File

@ -159,7 +159,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
return true;
}
return await dialogService.confirm("It is not recommended to change note type when note content is not empty. Do you want to continue anyway?");
return await dialogService.confirm(t("note_types.confirm-change"));
}
async entitiesReloadedEvent({ loadResults }) {

View File

@ -42,6 +42,10 @@ const TPL = `
word-break:keep-all;
white-space: nowrap;
}
.promoted-attribute-cell input[type="checkbox"] {
height: 1.5em;
}
</style>
<div class="promoted-attributes-container"></div>

View File

@ -1,4 +1,6 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import toastService from "../services/toast.js";
import { t } from "../services/i18n.js";
const WIDGET_TPL = `
<div class="card widget">
@ -54,7 +56,9 @@ class RightPanelWidget extends NoteContextAwareWidget {
this.$buttons.append(buttonWidget.render());
}
this.initialized = this.doRenderBody();
this.initialized = this.doRenderBody().catch(e => {
this.logRenderingError(e);
});
}
/**

View File

@ -61,9 +61,9 @@ export default class SearchString extends AbstractSearchOption {
await this.setAttribute('label', 'searchString', searchString);
if (this.note.title.startsWith('Search: ')) {
if (this.note.title.startsWith(t("search_string.search_prefix"))) {
await server.put(`notes/${this.note.noteId}/title`, {
title: `Search: ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}`}`
title: `${t("search_string.search_prefix")} ${searchString.length < 30 ? searchString : `${searchString.substr(0, 30)}`}`
});
}
}, 1000);

View File

@ -260,8 +260,10 @@ export default class TabRowWidget extends BasicWidget {
y: e.pageY,
items: [
{title: t('tab_row.close'), command: "closeTab", uiIcon: "bx bx-x"},
{title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-x"},
{title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-x"},
{title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.length !== 1},
{title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId},
{title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-empty"},
{ title: "----" },
{title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"}
],
selectMenuItemHandler: ({command}) => {

View File

@ -0,0 +1,120 @@
import TypeWidget from "./type_widget.js";
import libraryLoader from "../../services/library_loader.js";
import options from "../../services/options.js";
/**
* An abstract {@link TypeWidget} which implements the CodeMirror editor, meant to be used as a parent for
* widgets requiring the editor.
*
* The widget handles the loading and initialization of the CodeMirror editor, as well as some common
* actions.
*
* The derived class must:
*
* - Define `$editor` in the constructor.
* - Call `super.doRender()` in the extended class.
* - Call `this._update(note, content)` in `#doRefresh(note)`.
*/
export default class AbstractCodeTypeWidget extends TypeWidget {
doRender() {
this.initialized = this.#initEditor();
}
async #initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
// these conflict with backward/forward navigation shortcuts
delete CodeMirror.keyMap.default["Alt-Left"];
delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"];
this.codeEditor = CodeMirror(this.$editor[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
matchBrackets: true,
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
lineNumbers: true,
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
...this.getExtraOpts()
});
this.onEditorInitialized();
}
/**
* Can be extended in derived classes to add extra options to the CodeMirror constructor. The options are appended
* at the end, so it is possible to override the default values introduced by the abstract editor as well.
*
* @returns the extra options to be passed to the CodeMirror constructor.
*/
getExtraOpts() {
return {};
}
/**
* Called as soon as the CodeMirror library has been loaded and the editor was constructed. Can be extended in
* derived classes to add additional functionality or to register event handlers.
*
* By default, it does nothing.
*/
onEditorInitialized() {
// Do nothing by default.
}
/**
* Must be called by the derived classes in `#doRefresh(note)` in order to react to changes.
*
* @param {*} note the note that was changed.
* @param {*} content the new content of the note.
*/
_update(note, content) {
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback
this.codeEditor.setValue(content || "");
this.codeEditor.clearHistory();
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
};
show() {
this.$widget.show();
if (this.codeEditor) { // show can be called before render
this.codeEditor.refresh();
}
}
focus() {
this.$editor.focus();
this.codeEditor.focus();
}
scrollToEnd() {
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
this.codeEditor.focus();
}
cleanup() {
if (this.codeEditor) {
this.spacedUpdate.allowUpdateWithoutChange(() => {
this.codeEditor.setValue('');
});
}
}
}

View File

@ -1,8 +1,7 @@
import { t } from "../../services/i18n.js";
import libraryLoader from "../../services/library_loader.js";
import TypeWidget from "./type_widget.js";
import keyboardActionService from "../../services/keyboard_actions.js";
import options from "../../services/options.js";
import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
const TPL = `
<div class="note-detail-code note-detail-printable">
@ -21,53 +20,31 @@ const TPL = `
<div class="note-detail-code-editor"></div>
</div>`;
export default class EditableCodeTypeWidget extends TypeWidget {
export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
static getType() { return "editableCode"; }
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$editor = this.$widget.find('.note-detail-code-editor');
keyboardActionService.setupActionsForElement('code-detail', this.$widget, this);
super.doRender();
this.initialized = this.initEditor();
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
// these conflict with backward/forward navigation shortcuts
delete CodeMirror.keyMap.default["Alt-Left"];
delete CodeMirror.keyMap.default["Alt-Right"];
CodeMirror.modeURL = `${window.glob.assetPath}/node_modules/codemirror/mode/%N/%N.js`;
CodeMirror.modeInfo.find(mode=>mode.name === "JavaScript").mimes.push(...["application/javascript;env=frontend", "application/javascript;env=backend"]);
CodeMirror.modeInfo.find(mode=>mode.name === "SQLite").mimes=["text/x-sqlite", "text/x-sqlite;schema=trilium"];
this.codeEditor = CodeMirror(this.$editor[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
matchBrackets: true,
getExtraOpts() {
return {
keyMap: options.is('vimKeymapEnabled') ? "vim": "default",
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
lint: true,
gutters: ["CodeMirror-lint-markers"],
lineNumbers: true,
tabindex: 300,
// we line wrap partly also because without it horizontal scrollbar displays only when you scroll
// all the way to the bottom of the note. With line wrap, there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
placeholder: t('editable_code.placeholder'),
});
};
}
onEditorInitialized() {
this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate());
}
@ -75,57 +52,18 @@ export default class EditableCodeTypeWidget extends TypeWidget {
const blob = await this.note.getBlob();
await this.spacedUpdate.allowUpdateWithoutChange(() => {
// CodeMirror breaks pretty badly on null, so even though it shouldn't happen (guarded by a consistency check)
// we provide fallback
this.codeEditor.setValue(blob.content || "");
this.codeEditor.clearHistory();
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
this._update(note, blob.content);
});
this.show();
}
show() {
this.$widget.show();
if (this.codeEditor) { // show can be called before render
this.codeEditor.refresh();
}
}
getData() {
return {
content: this.codeEditor.getValue()
};
}
focus() {
this.$editor.focus();
this.codeEditor.focus();
}
scrollToEnd() {
this.codeEditor.setCursor(this.codeEditor.lineCount(), 0);
this.codeEditor.focus();
}
cleanup() {
if (this.codeEditor) {
this.spacedUpdate.allowUpdateWithoutChange(() => {
this.codeEditor.setValue('');
});
}
}
async executeWithCodeEditorEvent({resolve, ntxId}) {
if (!this.isNoteContext(ntxId)) {
return;

View File

@ -18,7 +18,6 @@ const TPL = `
width: 130px;
text-align: center;
margin: 10px;
padding; 10px;
border: 1px transparent solid;
}
@ -27,20 +26,35 @@ const TPL = `
border: 1px solid var(--main-border-color);
}
.note-detail-empty-results .aa-dropdown-menu {
max-height: 50vh;
overflow: scroll;
border: var(--bs-border-width) solid var(--bs-border-color);
border-top: 0;
}
.empty-tab-search .note-autocomplete-input {
border-bottom-left-radius: 0;
}
.empty-tab-search .input-clearer-button {
border-bottom-right-radius: 0;
}
.workspace-icon {
text-align: center;
font-size: 500%;
}
</style>
<div class="form-group">
<div class="workspace-notes"></div>
<div class="form-group empty-tab-search">
<label>${t('empty.open_note_instruction')}</label>
<div class="input-group">
<div class="input-group mt-1">
<input class="form-control note-autocomplete" placeholder="${t('empty.search_placeholder')}">
</div>
</div>
<div class="workspace-notes"></div>
<div class="note-detail-empty-results"></div>
</div>`;
export default class EmptyTypeWidget extends TypeWidget {
@ -51,10 +65,12 @@ export default class EmptyTypeWidget extends TypeWidget {
this.$widget = $(TPL);
this.$autoComplete = this.$widget.find(".note-autocomplete");
this.$results = this.$widget.find(".note-detail-empty-results");
noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, {
hideGoToSelectedNoteButton: true,
allowCreatingNotes: true
allowCreatingNotes: true,
container: this.$results
})
.on('autocomplete:noteselected', function(event, suggestion, dataset) {
if (!suggestion.notePath) {
@ -66,6 +82,7 @@ export default class EmptyTypeWidget extends TypeWidget {
this.$workspaceNotes = this.$widget.find('.workspace-notes');
noteAutocompleteService.showRecentNotes(this.$autoComplete);
super.doRender();
}

View File

@ -1,4 +1,4 @@
import TypeWidget from "./type_widget.js";
import AbstractCodeTypeWidget from "./abstract_code_type_widget.js";
const TPL = `
<div class="note-detail-readonly-code note-detail-printable">
@ -16,12 +16,13 @@ const TPL = `
<pre class="note-detail-readonly-code-content"></pre>
</div>`;
export default class ReadOnlyCodeTypeWidget extends TypeWidget {
export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget {
static getType() { return "readOnlyCode"; }
doRender() {
this.$widget = $(TPL);
this.$content = this.$widget.find('.note-detail-readonly-code-content');
this.contentSized();
this.$editor = this.$widget.find('.note-detail-readonly-code-content');
super.doRender();
}
@ -33,7 +34,14 @@ export default class ReadOnlyCodeTypeWidget extends TypeWidget {
content = this.format(content);
}
this.$content.text(content);
this._update(note, content);
this.show();
}
getExtraOpts() {
return {
readOnly: true
};
}
async executeWithContentElementEvent({resolve, ntxId}) {

View File

@ -70,30 +70,10 @@ textarea,
background: var(--input-background-color) url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='%23ffffff' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>") right .75rem center/15px 20px no-repeat;
}
/* Hide number input arrows */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox browser */
input[type="number"] {
-moz-appearance: textfield;
}
/* Show number input arrows when focus or hover */
input[type="number"]:focus::-webkit-inner-spin-button,
input[type="number"]:focus::-webkit-outer-spin-button,
input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:hover::-webkit-outer-spin-button {
-webkit-appearance: inner-spin-button;
}
/* Restore default apperance */
input[type="number"]:focus,
input[type="number"]:hover {
appearance: auto;
input[type="number"],
input[type="checkbox"] {
appearance: auto !important;
}
#left-pane input,

View File

@ -15,8 +15,7 @@
"message": "发生了严重错误,导致客户端应用程序无法启动:\n\n{{message}}\n\n这很可能是由于脚本以意外的方式失败引起的。请尝试以安全模式启动应用程序并解决问题。"
},
"widget-error": {
"title": "小部件初始化失败",
"message": "标题为 \"{{title}}\" 的小部件由于以下原因无法初始化:\n\n{{message}}"
"title": "小部件初始化失败"
}
},
"add_link": {

View File

@ -16,7 +16,12 @@
},
"widget-error": {
"title": "Failed to initialize a widget",
"message": "Widget with title \"{{title}}\" could not be initialized due to:\n\n{{message}}"
"message-custom": "Custom widget from note with ID \"{{id}}\", titled \"{{title}}\" could not be initialized due to:\n\n{{message}}",
"message-unknown": "Unknown widget could not be initialized due to:\n\n{{message}}"
},
"bundle-error": {
"title": "Failed to load a custom script",
"message": "Script from note with ID \"{{id}}\", titled \"{{title}}\" could not be executed due to:\n\n{{message}}"
}
},
"add_link": {
@ -165,7 +170,8 @@
"textImportedAsText": "Import HTML, Markdown and TXT as text notes if it's unclear from metadata",
"codeImportedAsCode": "Import recognized code files (e.g. <code>.json</code>) as code notes if it's unclear from metadata",
"replaceUnderscoresWithSpaces": "Replace underscores with spaces in imported note names",
"import": "Import"
"import": "Import",
"failed": "Import failed: {{message}}."
},
"include_note": {
"dialog_title": "Include note",
@ -631,7 +637,10 @@
"export_note": "Export note",
"delete_note": "Delete note",
"print_note": "Print note",
"save_revision": "Save revision"
"save_revision": "Save revision",
"convert_into_attachment_failed": "Converting note '{{title}}' failed.",
"convert_into_attachment_successful": "Note '{{title}}' has been converted to attachment.",
"convert_into_attachment_prompt": "Are you sure you want to convert note '{{title}}' into an attachment of the parent note?"
},
"onclick_button": {
"no_click_handler": "Button widget '{{componentId}}' has no defined click handler"
@ -886,7 +895,8 @@
"label_rock_or_pop": "only one of the labels must be present",
"label_year_comparison": "numerical comparison (also >, >=, <).",
"label_date_created": "notes created in the last month",
"error": "Search error: {{error}}"
"error": "Search error: {{error}}",
"search_prefix": "Search:"
},
"attachment_detail": {
"open_help_page": "Open help page on attachments",
@ -920,7 +930,15 @@
},
"protected_session": {
"enter_password_instruction": "Showing protected note requires entering your password:",
"start_session_button": "Start protected session"
"start_session_button": "Start protected session",
"started": "Protected session has been started.",
"wrong_password": "Wrong password.",
"protecting-finished-successfully": "Protecting finished successfully.",
"unprotecting-finished-successfully": "Unprotecting finished successfully.",
"protecting-in-progress": "Protecting in progress: {{count}}",
"unprotecting-in-progress-count": "Unprotecting in progress: {{count}}",
"protecting-title": "Protecting status",
"unprotecting-title": "Unprotecting status"
},
"relation_map": {
"open_in_new_tab": "Open in new tab",
@ -975,7 +993,7 @@
"error_creating_anonymized_database": "Could not create anonymized database, check backend logs for details",
"successfully_created_fully_anonymized_database": "Created fully anonymized database in {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Created lightly anonymized database in {{anonymizedFilePath}}",
"no_anonymized_database_yet": "no anonymized database yet"
"no_anonymized_database_yet": "No anonymized database yet."
},
"database_integrity_check": {
"title": "Database Integrity Check",
@ -991,7 +1009,9 @@
"fill_entity_changes_button": "Fill entity changes records",
"full_sync_triggered": "Full sync triggered",
"filling_entity_changes": "Filling entity changes rows...",
"sync_rows_filled_successfully": "Sync rows filled successfully"
"sync_rows_filled_successfully": "Sync rows filled successfully",
"finished-successfully": "Sync finished successfully.",
"failed": "Sync failed: {{message}}"
},
"vacuum_database": {
"title": "Vacuum Database",
@ -1198,7 +1218,7 @@
"password": {
"heading": "Password",
"alert_message": "Please take care to remember your new password. Password is used for logging into the web interface and to encrypt protected notes. If you forget your password, then all your protected notes are forever lost.",
"reset_link": "click here to reset it.",
"reset_link": "Click here to reset it.",
"old_password": "Old password",
"new_password": "New password",
"new_password_confirmation": "New password confirmation",
@ -1310,7 +1330,9 @@
"duplicate-subtree": "Duplicate subtree",
"export": "Export",
"import-into-note": "Import into note",
"apply-bulk-actions": "Apply bulk actions"
"apply-bulk-actions": "Apply bulk actions",
"converted-to-attachments": "{{count}} notes have been converted to attachments.",
"convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?"
},
"shared_info": {
"shared_publicly": "This note is shared publicly on",
@ -1333,7 +1355,8 @@
"image": "Image",
"launcher": "Launcher",
"doc": "Doc",
"widget": "Widget"
"widget": "Widget",
"confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?"
},
"protect_note": {
"toggle-on": "Protect the note",
@ -1379,7 +1402,9 @@
"hide-archived-notes": "Hide archived notes",
"automatically-collapse-notes": "Automatically collapse notes",
"automatically-collapse-notes-title": "Notes will be collapsed after period of inactivity to declutter the tree.",
"save-changes": "Save & apply changes"
"save-changes": "Save & apply changes",
"auto-collapsing-notes-after-inactivity": "Auto collapsing notes after inactivity...",
"saved-search-note-refreshed": "Saved search note refreshed."
},
"title_bar_buttons": {
"window-on-top": "Keep this window on top."
@ -1408,6 +1433,7 @@
"add_new_tab": "Add new tab",
"close": "Close",
"close_other_tabs": "Close other tabs",
"close_right_tabs": "Close tabs to the right",
"close_all_tabs": "Close all tabs",
"move_tab_to_new_window": "Move this tab to a new window",
"new_tab": "New tab"
@ -1423,5 +1449,53 @@
},
"app_context": {
"please_wait_for_save": "Please wait for a couple of seconds for the save to finish, then you can try again."
},
"note_create": {
"duplicated": "Note \"{{title}}\" has been duplicated."
},
"image": {
"copied-to-clipboard": "A reference to the image has been copied to clipboard. This can be pasted in any text note.",
"cannot-copy": "Could not copy the image reference to clipboard."
},
"clipboard": {
"cut": "Note(s) have been cut into clipboard.",
"copied": "Note(s) have been copied into clipboard."
},
"entrypoints": {
"note-revision-created": "Note revision has been created.",
"note-executed": "Note executed.",
"sql-error": "Error occurred while executing SQL query: {{message}}"
},
"branches": {
"cannot-move-notes-here": "Cannot move notes here.",
"delete-status": "Delete status",
"delete-notes-in-progress": "Delete notes in progress: {{count}}",
"delete-finished-successfully": "Delete finished successfully.",
"undeleting-notes-in-progress": "Undeleting notes in progress: {{count}}",
"undeleting-notes-finished-successfully": "Undeleting notes finished successfully."
},
"frontend_script_api": {
"async_warning": "You're passing an async function to `api.runOnBackend()` which will likely not work as you intended.\\nEither make the function synchronous (by removing `async` keyword), or use `api.runAsyncOnBackendWithManualTransactionHandling()`.",
"sync_warning": "You're passing a synchronous function to `api.runAsyncOnBackendWithManualTransactionHandling()`,\\nwhile you should likely use `api.runOnBackend()` instead."
},
"ws": {
"sync-check-failed": "Sync check failed!",
"consistency-checks-failed": "Consistency checks failed! See logs for details.",
"encountered-error": "Encountered error \"{{message}}\", check out the console."
},
"hoisted_note": {
"confirm_unhoisting": "Requested note '{{requestedNote}}' is outside of hoisted note '{{hoistedNote}}' subtree and you must unhoist to access the note. Do you want to proceed with unhoisting?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Do you really want to reset \"{{title}}\"? All data / settings in this note (and its children) will be lost and the launcher will be returned to its original location.",
"add-note-launcher": "Add a note launcher",
"add-script-launcher": "Add a script launcher",
"add-custom-widget": "Add a custom widget",
"add-spacer": "Add spacer",
"delete": "Delete",
"reset": "Reset",
"move-to-visible-launchers": "Move to visible launchers",
"move-to-available-launchers": "Move to available launchers",
"duplicate-launcher": "Duplicate launcher"
}
}

View File

@ -16,7 +16,12 @@
},
"widget-error": {
"title": "No se pudo inicializar un widget",
"message": "Widget con título \"{{title}}\" no pudo ser inicializado debido a:\n\n{{message}}"
"message-custom": "El widget personalizado de la nota con ID \"{{id}}\", titulada \"{{title}}\" no pudo ser inicializado debido a:\n\n{{message}}",
"message-unknown": "Un widget no pudo ser inicializado debido a:\n\n{{message}}"
},
"bundle-error": {
"title": "Hubo un fallo al cargar un script personalizado",
"message": "El script de la nota con ID \"{{id}}\", titulado \"{{title}}\" no pudo ser ejecutado debido a:\n\n{{message}}"
}
},
"add_link": {
@ -165,7 +170,8 @@
"textImportedAsText": "Importar HTML, Markdown y TXT como notas de texto si no está claro en los metadatos",
"codeImportedAsCode": "Importar archivos de código reconocidos (por ejemplo, <code>.json</code>) como notas de código si no están claros en los metadatos",
"replaceUnderscoresWithSpaces": "Reemplazar guiones bajos con espacios en nombres de notas importadas",
"import": "Importar"
"import": "Importar",
"failed": "La importación falló: {{message}}."
},
"include_note": {
"dialog_title": "Incluir nota",
@ -329,9 +335,9 @@
"run_on_instance": "Definir en qué instancia de Trilium se debe ejecutar esto. Predeterminado para todas las instancias.",
"run_at_hour": "¿A qué hora debería funcionar? Debe usarse junto con <code>#run=hourly</code>. Se puede definir varias veces para varias ejecuciones durante el día.",
"disable_inclusion": "los scripts con esta etiqueta no se incluirán en la ejecución del script principal.",
"sorted": "mantiene las notas hijo ordenadas alfabéticamente por título",
"sorted": "mantiene las subnotas ordenadas alfabéticamente por título",
"sort_direction": "ASC (el valor predeterminado) o DESC",
"sort_folders_first": "Las carpetas (notas con hijos) deben ordenarse en la parte superior",
"sort_folders_first": "Las carpetas (notas con subnotas) deben ordenarse en la parte superior",
"top": "mantener la nota dada en la parte superior de su padre (se aplica solo en padres ordenados)",
"hide_promoted_attributes": "Ocultar atributos promovidos en esta nota",
"read_only": "el editor está en modo de sólo lectura. Funciona sólo para notas de texto y código.",
@ -349,9 +355,9 @@
"workspace_tab_background_color": "color CSS utilizado en la pestaña de nota cuando se ancla a esta nota",
"workspace_calendar_root": "Define la raíz del calendario por cada espacio de trabajo",
"workspace_template": "Esta nota aparecerá en la selección de plantillas disponibles al crear una nueva nota, pero solo cuando se levante a un espacio de trabajo que contenga esta plantilla",
"search_home": "se crearán nuevas notas de búsqueda como hijas de esta nota",
"workspace_search_home": "se crearán nuevas notas de búsqueda como hijo de esta nota cuando se anclan a algún antecesor de esta nota del espacio de trabajo",
"inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas - cuando crea una nota usando el botón \"nueva nota\" en la barra lateral, las notas serán creadas como notas hijo de la nota marcada con la etiqueta <code>#inbox</code>.",
"search_home": "se crearán nuevas notas de búsqueda como subnotas de esta nota",
"workspace_search_home": "se crearán nuevas notas de búsqueda como subnotas de esta nota cuando se anclan a algún antecesor de esta nota del espacio de trabajo",
"inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas - cuando crea una nota usando el botón \"nueva nota\" en la barra lateral, las notas serán creadas como subnotas de la nota marcada con la etiqueta <code>#inbox</code>.",
"workspace_inbox": "ubicación predeterminada de la bandeja de entrada para nuevas notas cuando se anclan a algún antecesor de esta nota del espacio de trabajo",
"sql_console_home": "ubicación predeterminada de las notas de la consola SQL",
"bookmark_folder": "la nota con esta etiqueta aparecerá en los marcadores como carpeta (permitiendo el acceso a sus elementos hijos).",
@ -367,7 +373,7 @@
"share_index": "tenga en cuenta que con esto esta etiqueta enumerará todas las raíces de las notas compartidas",
"display_relations": "nombres de relaciones delimitados por comas que deben mostrarse. Todos los demás estarán ocultos.",
"hide_relations": "nombres de relaciones delimitados por comas que deben ocultarse. Se mostrarán todos los demás.",
"title_template": "título por defecto de notas creadas como hijo de esta nota. El valor es evaluado como una cadena de JavaScript \n y por lo tanto puede ser enriquecida con contenido dinámico vía las variables inyectadas <code>now</code> y <code>parentNote</code>. Ejemplos:\n \n <ul>\n <li><code>trabajos literarios de ${parentNote.getLabelValue('authorName')}</code></li>\n <li><code>Registro para ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Consulte la <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki para obtener más detalles</a>, documentación de la API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> y <a href=\"https://day.js.org/docs/en/display/format\">now</a> para más detalles.",
"title_template": "título por defecto de notas creadas como subnota de esta nota. El valor es evaluado como una cadena de JavaScript \n y por lo tanto puede ser enriquecida con contenido dinámico vía las variables inyectadas <code>now</code> y <code>parentNote</code>. Ejemplos:\n \n <ul>\n <li><code>trabajos literarios de ${parentNote.getLabelValue('authorName')}</code></li>\n <li><code>Registro para ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Consulte la <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki para obtener más detalles</a>, documentación de la API para <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> y <a href=\"https://day.js.org/docs/en/display/format\">now</a> para más detalles.",
"template": "Esta nota aparecerá en la selección de plantillas disponibles al crear una nueva nota",
"toc": "<code>#toc</code> o <code>#toc=show</code> forzará que se muestre la tabla de contenido, <code>#toc=hide</code> forzará a ocultarla. Si la etiqueta no existe, se observa la configuración global",
"color": "define el color de la nota en el árbol de notas, enlaces, etc. Utilice cualquier valor de color CSS válido como 'red' o #a13d5f",
@ -631,7 +637,10 @@
"export_note": "Exportar nota",
"delete_note": "Eliminar nota",
"print_note": "Imprimir nota",
"save_revision": "Guardar revisión"
"save_revision": "Guardar revisión",
"convert_into_attachment_failed": "La conversión de nota '{{title}}' falló.",
"convert_into_attachment_successful": "La nota '{{title}}' ha sido convertida a un archivo adjunto.",
"convert_into_attachment_prompt": "¿Está seguro que desea convertir la nota '{{title}}' en un archivo adjunto de la nota padre?"
},
"onclick_button": {
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
@ -669,7 +678,7 @@
"button_title": "Exportar diagrama como SVG"
},
"relation_map_buttons": {
"create_child_note_title": "Crear una nueva nota hijo y agregarla a este mapa de relaciones",
"create_child_note_title": "Crear una nueva subnota y agregarla a este mapa de relaciones",
"reset_pan_zoom_title": "Restablecer la panorámica y el zoom a las coordenadas y ampliación iniciales",
"zoom_in_title": "Acercar",
"zoom_out_title": "Alejar"
@ -680,7 +689,7 @@
"relation": "relación"
},
"mobile_detail_menu": {
"insert_child_note": "Insertar nota hijo",
"insert_child_note": "Insertar subnota",
"delete_this_note": "Eliminar esta nota",
"error_cannot_get_branch_id": "No se puede obtener el branchID del notePath '{{notePath}}'",
"error_unrecognized_command": "Comando no reconocido {{command}}"
@ -701,7 +710,7 @@
"grid": "Cuadrícula",
"list": "Lista",
"collapse_all_notes": "Contraer todas las notas",
"expand_all_children": "Ampliar todos los hijos",
"expand_all_children": "Ampliar todas las subnotas",
"collapse": "Colapsar",
"expand": "Expandir",
"book_properties": "Propiedades del libro",
@ -855,7 +864,7 @@
"content_and_attachments_size": "Tenga en cuenta el tamaño del contenido, incluidos los archivos adjuntos",
"content_and_attachments_and_revisions_size": "Tenga en cuenta el tamaño del contenido, incluidos los archivos adjuntos y las revisiones",
"revision_count": "Número de revisiones",
"children_count": "Notas sobre el número de hijos",
"children_count": "Número de subnotas",
"parent_count": "Número de clones",
"owned_label_count": "Número de etiquetas",
"owned_relation_count": "Número de relaciones",
@ -891,7 +900,7 @@
"attachment_detail": {
"open_help_page": "Abrir página de ayuda en archivos adjuntos",
"owning_note": "Nota dueña: ",
"you_can_also_open": ", también puedes abri el ",
"you_can_also_open": ", también puede abrir el ",
"list_of_all_attachments": "Lista de todos los archivos adjuntos",
"attachment_deleted": "Este archivo adjunto ha sido eliminado."
},
@ -902,7 +911,7 @@
"no_attachments": "Esta nota no tiene archivos adjuntos."
},
"book": {
"no_children_help": "Esta nota de tipo libro no tieneninguna nota hijo así que no hay nada que mostrar. Véa la <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para más detalles."
"no_children_help": "Esta nota de tipo libro no tieneninguna subnota así que no hay nada que mostrar. Véa la <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> para más detalles."
},
"editable_code": {
"placeholder": "Escriba el contenido de su nota de código aquí..."
@ -920,7 +929,15 @@
},
"protected_session": {
"enter_password_instruction": "Para mostrar una nota protegida es necesario ingresar su contraseña:",
"start_session_button": "Iniciar sesión protegida"
"start_session_button": "Iniciar sesión protegida",
"started": "La sesión protegida ha iniciado.",
"wrong_password": "Contraseña incorrecta.",
"protecting-finished-successfully": "La protección finalizó exitosamente.",
"unprotecting-finished-successfully": "La desprotección finalizó exitosamente.",
"protecting-in-progress": "Protección en progreso: {{count}}",
"unprotecting-in-progress-count": "Desprotección en progreso: {{count}}",
"protecting-title": "Estado de protección",
"unprotecting-title": "Estado de desprotección"
},
"relation_map": {
"open_in_new_tab": "Abrir en nueva pestaña",
@ -975,7 +992,7 @@
"error_creating_anonymized_database": "No se pudo crear una base de datos anónima; consulte los registros de backend para obtener más detalles",
"successfully_created_fully_anonymized_database": "Se creó una base de datos completamente anónima en {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Se creó una base de datos ligeramente anónima en {{anonymizedFilePath}}",
"no_anonymized_database_yet": "aún no hay base de datos anónima"
"no_anonymized_database_yet": "Aún no hay base de datos anónima."
},
"database_integrity_check": {
"title": "Verificación de integridad de la base de datos",
@ -991,7 +1008,9 @@
"fill_entity_changes_button": "Llenar registros de cambios de entidad",
"full_sync_triggered": "Sincronización completa activada",
"filling_entity_changes": "Rellenar filas de cambios de entidad...",
"sync_rows_filled_successfully": "Sincronizar filas completadas correctamente"
"sync_rows_filled_successfully": "Sincronizar filas completadas correctamente",
"finished-successfully": "La sincronización finalizó exitosamente.",
"failed": "La sincronización falló: {{message}}"
},
"vacuum_database": {
"title": "Limpiar base de datos",
@ -1137,7 +1156,7 @@
},
"table_of_contents": {
"title": "Tabla de contenido",
"description": "La tabla de contenido aparecerá en las notas de texto cuando la nota tenga más de un número definido de títulos. Puedes personalizar este número:",
"description": "La tabla de contenido aparecerá en las notas de texto cuando la nota tenga más de un número definido de títulos. Puede personalizar este número:",
"disable_info": "También puede utilizar esta opción para desactivar la TDC (TOC) de forma efectiva estableciendo un número muy alto.",
"shortcut_info": "Puede configurar un atajo de teclado para alternar rápidamente el panel derecho (incluido el TDC) en Opciones -> Atajos (nombre 'toggleRightPane')."
},
@ -1286,7 +1305,7 @@
"open-in-a-new-tab": "Abrir en nueva pestaña",
"open-in-a-new-split": "Abrir en nueva división",
"insert-note-after": "Insertar nota después de",
"insert-child-note": "Insertar nota hijo",
"insert-child-note": "Insertar subnota",
"delete": "Eliminar",
"search-in-subtree": "Buscar en subárbol",
"hoist-note": "Anclar nota",
@ -1310,7 +1329,9 @@
"duplicate-subtree": "Duplicar subárbol",
"export": "Exportar",
"import-into-note": "Importar a nota",
"apply-bulk-actions": "Aplicar acciones en lote"
"apply-bulk-actions": "Aplicar acciones en lote",
"converted-to-attachments": "{{count}} notas han sido convertidas en archivos adjuntos.",
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres?"
},
"shared_info": {
"shared_publicly": "Esta nota está compartida públicamente en",
@ -1333,7 +1354,8 @@
"image": "Imagen",
"launcher": "Lanzador",
"doc": "Doc",
"widget": "Widget"
"widget": "Widget",
"confirm-change": "No es recomendado cambiar el tipo de nota cuando el contenido de la nota no está vacío. ¿Desea continuar de cualquier manera?"
},
"protect_note": {
"toggle-on": "Proteger la nota",
@ -1379,7 +1401,9 @@
"hide-archived-notes": "Ocultar notas archivadas",
"automatically-collapse-notes": "Colapsar notas automaticamente",
"automatically-collapse-notes-title": "Las notas serán colapsadas después de un periodo de inactividad para despejar el árbol.",
"save-changes": "Guardar y aplicar cambios"
"save-changes": "Guardar y aplicar cambios",
"auto-collapsing-notes-after-inactivity": "Colapsando notas automáticamente después de inactividad...",
"saved-search-note-refreshed": "La nota de búsqueda guardada fue recargada."
},
"title_bar_buttons": {
"window-on-top": "Mantener esta ventana en la parte superior."
@ -1423,5 +1447,53 @@
},
"app_context": {
"please_wait_for_save": "Por favor espere algunos segundos a que se termine de guardar, después intente de nuevo."
},
"note_create": {
"duplicated": "La nota \"{{title}}\" ha sido duplicada."
},
"image": {
"copied-to-clipboard": "Una referencia a la imagen ha sido copiada al portapapeles. Esta puede ser pegada en cualquier nota de texto.",
"cannot-copy": "No se pudo copiar la referencia de imagen al portapapeles."
},
"clipboard": {
"cut": "La(s) notas(s) han sido cortadas al portapapeles.",
"copied": "La(s) notas(s) han sido copiadas al portapapeles."
},
"entrypoints": {
"note-revision-created": "Una revisión de nota ha sido creada.",
"note-executed": "Nota ejecutada.",
"sql-error": "Ocurrió un error al ejecutar la consulta SQL: {{message}}"
},
"branches": {
"cannot-move-notes-here": "No se pueden mover notas aquí.",
"delete-status": "Estado de eliminación",
"delete-notes-in-progress": "Eliminación de notas en progreso: {{count}}",
"delete-finished-successfully": "La eliminación finalizó exitosamente.",
"undeleting-notes-in-progress": "Recuperación de notas en progreso: {{count}}",
"undeleting-notes-finished-successfully": "La recuperación de notas finalizó exitosamente."
},
"frontend_script_api": {
"async_warning": "Está pasando una función asíncrona a `api.runOnBackend ()` que probablemente no funcionará como pretendía.",
"sync_warning": "Estás pasando una función sincrónica a `api.runasynconbackendwithmanualTransactionHandling ()`, \\ n while debería usar `api.runonbackend ()` en su lugar."
},
"ws": {
"sync-check-failed": "¡La comprobación de sincronización falló!",
"consistency-checks-failed": "¡Las comprobaciones de consistencia fallaron! Vea los registros para más detalles.",
"encountered-error": "Error encontrado \"{{message}}\", compruebe la consola."
},
"hoisted_note": {
"confirm_unhoisting": "La nota requerida '{{requestedNote}}' está fuera del subárbol de la nota anclada '{{hoistedNote}}' y debe desanclarla para acceder a la nota. ¿Desea proceder con el desanclaje?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "¿Realmente desea restaurar \"{{title}}\"? Todos los datos / ajustes en esta nota (y sus subnotas) se van a perder y el lanzador regresará a su ubicación original.",
"add-note-launcher": "Agregar un lanzador de nota",
"add-script-launcher": "Agregar un lanzador de script",
"add-custom-widget": "Agregar un widget personalizado",
"add-spacer": "Agregar espaciador",
"delete": "Eliminar",
"reset": "Restaurar",
"move-to-visible-launchers": "Mover a lanzadores visibles",
"move-to-available-launchers": "Mover a lanzadores disponibles",
"duplicate-launcher": "Duplicar lanzador"
}
}

View File

@ -15,8 +15,7 @@
"message": "Une erreur critique s'est produite qui empêche l'application client de démarrer :\n\n{{message}}\n\nCeci est probablement dû à un échec inattendu d'un script. Essayez de démarrer l'application en mode sans échec et de résoudre le problème."
},
"widget-error": {
"title": "Impossible d'initialiser un widget",
"message": "Le widget portant le titre \"{{title}}\" n'a pas pu être initialisé en raison de :\n\n{{message}}"
"title": "Impossible d'initialiser un widget"
}
},
"add_link": {
@ -975,7 +974,7 @@
"error_creating_anonymized_database": "Impossible de créer une base de données anonymisée, vérifiez les journaux backend pour plus de détails",
"successfully_created_fully_anonymized_database": "Base de données entièrement anonymisée crée dans {{anonymizedFilePath}}",
"successfully_created_lightly_anonymized_database": "Base de données partiellement anonymisée crée dans {{anonymizedFilePath}}",
"no_anonymized_database_yet": "aucune base de données anonymisée"
"no_anonymized_database_yet": "Aucune base de données anonymisée"
},
"database_integrity_check": {
"title": "Vérification de l'intégrité de la base de données",

View File

@ -381,7 +381,7 @@
"full_anonymization_description": "Această acțiune va crea o nouă copie a bazei de date și o va anonimiza (se șterge conținutul tuturor notițelor și se menține doar structura și câteva metainformații neconfidențiale) pentru a putea fi partajate online cu scopul de a depana anumite probleme fără a risca expunerea datelor personale.",
"light_anonymization": "Anonimizare parțială",
"light_anonymization_description": "Această acțiune va crea o copie a bazei de date și o va anonimiza parțial - mai exact se va șterge conținutul tuturor notițelor, dar titlurile și atributele vor rămâne. De asemenea, script-urile de front-end sau back-end și widget-urile personalizate vor rămâne și ele. Acest lucru oferă mai mult context pentru a depana probleme.",
"no_anonymized_database_yet": "încă nu există nicio bază de date anonimizată",
"no_anonymized_database_yet": "Încă nu există nicio bază de date anonimizată.",
"save_fully_anonymized_database": "Salvează bază de date complet anonimizată",
"save_lightly_anonymized_database": "Salvează bază de date parțial anonimizată",
"successfully_created_fully_anonymized_database": "S-a creat cu succes o bază de date complet anonimizată în {{anonymizedFilePath}}",
@ -553,7 +553,7 @@
"open_sql_console": "Deschide consola SQL",
"open_sql_console_history": "Deschide istoricul consolei SQL",
"options": "Opțiuni",
"reload_frontend": "Reîncarcă interfață",
"reload_frontend": "Reîncarcă interfața",
"reload_hint": "Reîncărcarea poate ajuta atunci când există ceva probleme vizuale fără a trebui repornită întreaga aplicație.",
"reset_zoom_level": "Resetează nivelul de zoom",
"show_backend_log": "Afișează log-ul din backend",
@ -684,7 +684,8 @@
"safeImportTooltip": "Fișierele de Trilium exportate în format <code>.zip</code> pot conține scripturi executabile ce pot avea un comportament malițios. Importarea sigură va dezactiva execuția automată a tuturor scripturilor importate. Debifați „Importare sigură” dacă arhiva importată conține scripturi executabile dorite și aveți încredere deplină în conținutul acestora.",
"shrinkImages": "Micșorare imagini",
"shrinkImagesTooltip": "<p>Dacă bifați această opțiune, Trilium va încerca să micșoreze imaginea importată prin scalarea și importarea ei, aspect ce poate afecta calitatea aparentă a imaginii. Dacă nu este bifat, imaginile vor fi importate fără nicio modificare.</p><p>Acest lucru nu se aplică la importuri de tip <code>.zip</code> cu metainformații deoarece se asumă că aceste fișiere sunt deja optimizate.</p>",
"textImportedAsText": "Importă HTML, Markdown și TXT ca notițe de tip text dacă este neclar din metainformații"
"textImportedAsText": "Importă HTML, Markdown și TXT ca notițe de tip text dacă este neclar din metainformații",
"failed": "Eroare la importare: {{message}}."
},
"include_archived_notes": {
"include_archived_notes": "Include notițele arhivate"
@ -784,7 +785,10 @@
"print_note": "Imprimare notiță",
"re_render_note": "Reinterpretare notiță",
"save_revision": "Salvează o nouă revizie",
"search_in_note": "Caută în notiță"
"search_in_note": "Caută în notiță",
"convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.",
"convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.",
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?"
},
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
@ -902,7 +906,15 @@
},
"protected_session": {
"enter_password_instruction": "Afișarea notițelor protejate necesită introducerea parolei:",
"start_session_button": "Deschide sesiunea protejată"
"start_session_button": "Deschide sesiunea protejată",
"started": "Sesiunea protejată este activă.",
"wrong_password": "Parolă greșită.",
"protecting-finished-successfully": "Protejarea a avut succes.",
"protecting-in-progress": "Protejare în curs: {{count}}",
"protecting-title": "Stare protejare",
"unprotecting-title": "Stare deprotejare",
"unprotecting-finished-successfully": "Deprotejarea a avut succes.",
"unprotecting-in-progress-count": "Deprotejare în curs: {{count}}"
},
"protected_session_password": {
"close_label": "Închide",
@ -1081,7 +1093,8 @@
"label_year_comparison": "comparații numerice (de asemenea >, >=, <).",
"placeholder": "cuvinte cheie pentru căutarea în conținut, #etichetă = valoare...",
"search_syntax": "Sintaxa de căutare",
"title_column": "Textul de căutat:"
"title_column": "Textul de căutat:",
"search_prefix": "Căutare:"
},
"shortcuts": {
"action_name": "Denumirea acțiunii",
@ -1133,7 +1146,9 @@
"force_full_sync_button": "Forțează sincronizare completă",
"full_sync_triggered": "S-a activat o sincronizare completă",
"sync_rows_filled_successfully": "Rândurile de sincronizare s-au completat cu succes",
"title": "Sincronizare"
"title": "Sincronizare",
"failed": "Eroare la sincronizare: {{message}}",
"finished-successfully": "Sincronizarea a avut succes."
},
"sync_2": {
"config_title": "Configurația sincronizării",
@ -1174,8 +1189,13 @@
"title": "Eroare critică"
},
"widget-error": {
"message": "Widget-ul intitulat „{{title}}” nu a putut fi inițializat din cauza:\n\n{{message}}",
"title": "Eroare la inițializarea unui widget"
"title": "Eroare la inițializarea unui widget",
"message-custom": "Widget-ul personalizat din notița cu ID-ul „{{id}}”, întitulată ”{{title}}” nu a putut fi inițializată din cauza:\n\n{{message}}",
"message-unknown": "Un widget necunoscut nu a putut fi inițializat din cauza:\n\n{{message}}"
},
"bundle-error": {
"title": "Eroare la încărcarea unui script personalizat",
"message": "Scriptul din notița cu ID-ul „{{id}}”, întitulată „{{title}}” nu a putut fi executată din cauza:\n\n{{message}}"
}
},
"tray": {
@ -1280,7 +1300,9 @@
"sort-by": "Ordonare după...",
"unprotect-subtree": "Deprotejează ierarhia",
"hoist-note": "Focalizează notița",
"unhoist-note": "Defocalizează notița"
"unhoist-note": "Defocalizează notița",
"converted-to-attachments": "{{count}} notițe au fost convertite în atașamente.",
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte?"
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
@ -1303,7 +1325,8 @@
"file": "Fișier",
"image": "Imagine",
"launcher": "Scurtătură",
"widget": "Widget"
"widget": "Widget",
"confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?"
},
"protect_note": {
"toggle-off": "Deprotejează notița",
@ -1369,7 +1392,9 @@
"hide-archived-notes": "Ascunde notițele arhivate",
"save-changes": "Salvează și aplică modificările",
"scroll-active-title": "Mergi la notița activă",
"tree-settings-title": "Setări ale ierarhiei notițelor"
"tree-settings-title": "Setări ale ierarhiei notițelor",
"auto-collapsing-notes-after-inactivity": "Se minimizează notițele după inactivitate...",
"saved-search-note-refreshed": "Notița de căutare salvată a fost reîmprospătată."
},
"title_bar_buttons": {
"window-on-top": "Menține fereastra mereu vizibilă"
@ -1413,7 +1438,8 @@
"close_other_tabs": "Închide celelalte taburi",
"close_tab": "Închide tab",
"move_tab_to_new_window": "Mută acest tab în altă fereastră",
"new_tab": "Tab nou"
"new_tab": "Tab nou",
"close_right_tabs": "Închide taburile din dreapta"
},
"toc": {
"options": "Setări",
@ -1423,5 +1449,53 @@
"file_last_modified": "Fișierul <code class=\"file-path\"></code> a fost ultima oară modificat la data de <span class=\"file-last-modified\"></span>.",
"ignore_this_change": "Ignoră această schimbare",
"upload_modified_file": "Încarcă fișier modificat"
},
"clipboard": {
"copied": "Notițele au fost copiate în clipboard.",
"cut": "Notițele au fost decupate în clipboard."
},
"entrypoints": {
"note-executed": "Notița a fost executată.",
"note-revision-created": "S-a creat o revizie a notiței.",
"sql-error": "A apărut o eroare la executarea interogării SQL: {{message}}"
},
"image": {
"cannot-copy": "Nu s-a putut copia în clipboard referința către imagine.",
"copied-to-clipboard": "S-a copiat o referință către imagine în clipboard. Aceasta se poate lipi în orice notiță text."
},
"note_create": {
"duplicated": "Notița „{{title}}” a fost dublificată."
},
"branches": {
"cannot-move-notes-here": "Nu se pot muta notițe aici.",
"delete-finished-successfully": "Ștergerea a avut succes.",
"delete-notes-in-progress": "Ștergere în curs: {{count}}",
"delete-status": "Starea ștergerii",
"undeleting-notes-finished-successfully": "Restaurarea notițelor a avut succes.",
"undeleting-notes-in-progress": "Restaurare notițe în curs: {{count}}"
},
"frontend_script_api": {
"async_warning": "Ați trimis o funcție asincronă metodei `api.runOnBackend()` și este posibil să nu se comporte așa cum vă așteptați.\\nFie faceți metoda sincronă (prin ștergerea cuvântului-cheie `async`), sau folosiți `api.runAsyncOnBackendWithManualTransactionHandling()`.",
"sync_warning": "Ați trimis o funcție sincronă funcției `api.runAsyncOnBackendWithManualTransactionHandling()`,\\ndar cel mai probabil trebuie folosit `api.runOnBackend()` în schimb."
},
"ws": {
"consistency-checks-failed": "Au fost identificate erori de consistență! Vedeți mai multe detalii în loguri.",
"encountered-error": "A fost întâmpinată o eroare: „{{message}}”. Vedeți în loguri pentru mai multe detalii.",
"sync-check-failed": "Verificările de sincronizare au eșuat!"
},
"hoisted_note": {
"confirm_unhoisting": "Notița dorită „{{requestedNote}}” este în afara ierarhiei notiței focalizate „{{hoistedNote}}”. Doriți defocalizarea pentru a accesa notița?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Doriți resetarea lansatorului „{{title}}”? Toate datele și setările din această notiță (și subnotițele ei) vor fi pierdute, iar lansatorul va fi resetat în poziția lui originală.",
"add-custom-widget": "Adaugă un widget personalizat",
"add-note-launcher": "Adaugă un lansator de notiță",
"add-script-launcher": "Adaugă un lansator de script",
"add-spacer": "Adaugă un separator",
"delete": "Șterge",
"duplicate-launcher": "Dublifică lansatorul",
"move-to-available-launchers": "Mută în Lansatoare disponibile",
"move-to-visible-launchers": "Mută în Lansatoare vizibile",
"reset": "Resetează"
}
}

View File

@ -5,6 +5,7 @@ import log from "../../services/log.js";
import searchService from "../../services/search/services/search.js";
import ValidationError from "../../errors/validation_error.js";
import { Request } from 'express';
import { changeLanguage } from "../../services/i18n.js";
// options allowed to be updated directly in the Options dialog
const ALLOWED_OPTIONS = new Set([
@ -108,6 +109,11 @@ function update(name: string, value: string) {
optionService.setOption(name, value);
if (name === "locale") {
// This runs asynchronously, so it's not perfect, but it does the trick for now.
changeLanguage(value);
}
return true;
}

View File

@ -23,13 +23,10 @@ function register(app: Application) {
// error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
if (err && err.message && (
(err.message.includes("Router not found for request") && err.message.includes(".js.map"))
|| (err.message.includes("Router not found for request") && err.message.includes(".css.map"))
)) {
// ignore
} else {
if (err.status !== 404) {
log.info(err);
} else {
log.info(`${err.status} ${req.method} ${req.url}`);
}
res.status(err.status || 500);

View File

@ -9,14 +9,20 @@ import searchService from "../services/search/services/search.js";
import SearchContext from "../services/search/search_context.js";
import hoistedNoteService from "./hoisted_note.js";
import BNote from "../becca/entities/bnote.js";
import { t } from "i18next";
const CALENDAR_ROOT_LABEL = 'calendarRoot';
const YEAR_LABEL = 'yearNote';
const MONTH_LABEL = 'monthNote';
const DATE_LABEL = 'dateNote';
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const WEEKDAY_TRANSLATION_IDS = [
"weekdays.sunday", "weekdays.monday", "weekdays.tuesday", "weekdays.wednesday", "weekdays.thursday", "weekdays.friday", "weekdays.saturday", "weekdays.sunday"
];
const MONTH_TRANSLATION_IDS = [
"months.january", "months.february", "months.march", "months.april", "months.may", "months.june", "months.july", "months.august", "months.september", "months.october", "months.november", "months.december"
];
type StartOfWeek = "monday" | "sunday";
@ -92,7 +98,7 @@ function getYearNote(dateStr: string, _rootNote: BNote | null = null): BNote {
function getMonthNoteTitle(rootNote: BNote, monthNumber: string, dateObj: Date) {
const pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
const monthName = MONTHS[dateObj.getMonth()];
const monthName = t(MONTH_TRANSLATION_IDS[dateObj.getMonth()]);
return pattern
.replace(/{shortMonth3}/g, monthName.slice(0,3))
@ -138,7 +144,7 @@ function getMonthNote(dateStr: string, _rootNote: BNote | null = null): BNote {
function getDayNoteTitle(rootNote: BNote, dayNumber: string, dateObj: Date) {
const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
const weekDay = DAYS[dateObj.getDay()];
const weekDay = t(WEEKDAY_TRANSLATION_IDS[dateObj.getDay()]);
return pattern
.replace(/{ordinal}/g, ordinal(parseInt(dayNumber)))

View File

@ -58,8 +58,16 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
let fileName = baseFileName.trim();
// Crop fileName to avoid its length exceeding 30 and prevent cutting into the extension.
if (fileName.length > 30) {
fileName = fileName.substr(0, 30).trim();
// We use regex to match the extension to preserve multiple dots in extensions (e.g. .tar.gz).
let match = fileName.match(/(\.[a-zA-Z0-9_.!#-]+)$/);
let ext = match ? match[0] : '';
// Crop the extension if extension length exceeds 30
const croppedExt = ext.slice(-30);
// Crop the file name section and append the cropped extension
fileName = fileName.slice(0, 30 - croppedExt.length) + croppedExt;
}
let existingExtension = path.extname(fileName).toLowerCase();
@ -76,6 +84,9 @@ async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "h
else if (mime === 'application/x-javascript' || mime === 'text/javascript') {
newExtension = 'js';
}
else if (type === 'canvas' || mime === 'application/json') {
newExtension = 'json';
}
else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it
newExtension = null;
}

View File

@ -42,3 +42,7 @@ function getCurrentLanguage() {
return language;
}
export function changeLanguage(locale: string) {
return i18next.changeLanguage(locale);
}

View File

@ -8,6 +8,7 @@ import hoistedNoteService from "./hoisted_note.js";
import searchService from "./search/services/search.js";
import SearchContext from "./search/search_context.js";
import hiddenSubtree from "./hidden_subtree.js";
import { t } from "i18next";
const { LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT } = hiddenSubtree;
function getInboxNote(date: string) {
@ -75,7 +76,7 @@ function saveSqlConsole(sqlConsoleNoteId: string) {
function createSearchNote(searchString: string, ancestorNoteId: string) {
const {note} = noteService.createNewNote({
parentNoteId: getMonthlyParentNoteId('_search', 'search'),
title: `Search: ${searchString}`,
title: `${t("special_notes.search_prefix")} ${searchString}`,
content: "",
type: 'search',
mime: 'application/json'

View File

@ -219,10 +219,13 @@ function formatDownloadTitle(fileName: string, type: string | null, mime: string
function removeTextFileExtension(filePath: string) {
const extension = path.extname(filePath).toLowerCase();
if (extension === '.md' || extension === '.markdown' || extension === '.html') {
switch (extension) {
case ".md":
case ".markdown":
case ".html":
case ".htm":
return filePath.substr(0, filePath.length - extension.length);
}
else {
default:
return filePath;
}
}

View File

@ -157,5 +157,31 @@
"clipped-from": "This note was originally clipped from {{- url}}",
"child-notes": "Child notes:",
"no-content": "This note has no content."
},
"weekdays": {
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday"
},
"months": {
"january": "January",
"february": "February",
"march": "March",
"april": "April",
"may": "May",
"june": "June",
"july": "July",
"august": "August",
"september": "September",
"october": "October",
"november": "November",
"december": "December"
},
"special_notes": {
"search_prefix": "Search:"
}
}

View File

@ -5,7 +5,7 @@
"expand-subtree": "Expandir el subárbol de la nota actual",
"collapse-tree": "Colapsa el árbol de notas completo",
"collapse-subtree": "Colapsa el subárbol de la nota actual",
"sort-child-notes": "Ordenar notas hijas",
"sort-child-notes": "Ordenar subnotas",
"creating-and-moving-notes": "Creando y moviendo notas",
"create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día",
"delete-note": "Eliminar nota",
@ -155,7 +155,30 @@
"share_page": {
"parent": "padre:",
"clipped-from": "Esta nota fue recortada originalmente de {{- url}}",
"child-notes": "Notas hijo:",
"child-notes": "Subnotas:",
"no-content": "Esta nota no tiene contenido."
},
"weekdays": {
"monday": "Lunes",
"tuesday": "Martes",
"wednesday": "Miércoles",
"thursday": "Jueves",
"friday": "Viernes",
"saturday": "Sábado",
"sunday": "Domingo"
},
"months": {
"january": "Enero",
"february": "Febrero",
"march": "Marzo",
"april": "Abril",
"may": "Mayo",
"june": "Junio",
"july": "Julio",
"august": "Agosto",
"september": "Septiembre",
"october": "Octubre",
"november": "Noviembre",
"december": "Diciembre"
}
}

View File

@ -157,5 +157,31 @@
"clipped-from": "Această notiță a fost decupată inițial de pe {{- url}}",
"no-content": "Această notiță nu are conținut.",
"parent": "părinte:"
},
"weekdays": {
"monday": "Luni",
"tuesday": "Marți",
"wednesday": "Miercuri",
"thursday": "Joi",
"friday": "Vineri",
"saturday": "Sâmbătă",
"sunday": "Duminică"
},
"months": {
"january": "Ianuarie",
"february": "Februarie",
"march": "Martie",
"april": "Aprilie",
"may": "Mai",
"june": "Iunie",
"july": "Iulie",
"august": "August",
"september": "Septembrie",
"october": "Octombrie",
"november": "Noiembrie",
"december": "Decembrie"
},
"special_notes": {
"search_prefix": "Căutare:"
}
}