Compare commits

..

571 Commits

Author SHA1 Message Date
perfectra1n
bc82f46c38 Revert "feat(fts5): get rid of search comparison code"
This reverts commit 41f6fedc61.
2026-02-08 21:17:04 -08:00
perfectra1n
9246a6907d feat(migrations): rice out fts5 migrations even more 2026-02-08 12:43:59 -08:00
perfectra1n
94df75e767 feat(migrations): optimize fts5 migration, take1 2026-02-08 11:51:47 -08:00
perfectra1n
680f5f83f4 fix(tests): resolve issue in test due to fts5 not being available in unit tests 2026-01-31 16:25:51 -08:00
perfectra1n
2b5920d140 fix(tests): resolve issue with search unit tests 2026-01-31 16:18:43 -08:00
perfectra1n
670bb57458 feat(fts5): update the document.db used in tests 2026-01-31 14:58:22 -08:00
Jon Fuller
fcd55996de Merge branch 'main' into feat/rice-searching-with-sqlite 2026-01-31 13:55:30 -08:00
perfectra1n
406db8d114 fix(ci): resolve issue in calendar test 2026-01-31 13:55:19 -08:00
Elian Doran
9142f2df4b Translations update from Hosted Weblate (#8569) 2026-01-31 22:46:57 +02:00
Elian Doran
eea4cbbd6c chore(ci): try to fix flaky "Merge manifest lists" 2026-01-31 22:34:14 +02:00
Ulices
bb31c20282 Translated using Weblate (Spanish)
Currently translated at 100.0% (1765 of 1765 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-31 21:14:06 +01:00
Elian Doran
f8ab206744 Mobile tabs v1 (#8568) 2026-01-31 22:13:58 +02:00
Elian Doran
4129b3a96e e2e(server): fix regressions after mobile tab switcher 2026-01-31 22:05:28 +02:00
Elian Doran
0de704bd3e fix(desktop): context menus no longer working 2026-01-31 21:42:39 +02:00
Elian Doran
5f20ce87a7 chore(mobile/tab_switcher): bypass weird regression in typecheck regarding React types 2026-01-31 21:17:08 +02:00
Elian Doran
ff80154fda chore(mobile/tab_switcher): address requested changes 2026-01-31 20:47:07 +02:00
Elian Doran
0d99cf9fb9 chore(mobile/tab_switcher): improve layout on tablet view 2026-01-31 20:41:53 +02:00
Elian Doran
a5306b2067 fix(mobile): modals on tablet view 2026-01-31 20:41:07 +02:00
Elian Doran
e9b826e498 chore(mobile/tab_switcher): stop auto-showing 2026-01-31 20:22:28 +02:00
Elian Doran
e7f356b87c feat(mobile/tab_switcher): display note splits 2026-01-31 20:21:11 +02:00
Elian Doran
d2abde714f chore(mobile/tab_switcher): enforce same height 2026-01-31 20:06:16 +02:00
Elian Doran
20eaa79079 fix(mobile): cover not working properly in modals 2026-01-31 20:04:00 +02:00
Elian Doran
3cb74bb844 fix(mobile): context menu won't dismiss due to missing cover 2026-01-31 20:00:56 +02:00
Elian Doran
a72d4f425a feat(mobile/tab_switcher): add context menu item for closing all tabs 2026-01-31 19:54:42 +02:00
Elian Doran
8f3545624e feat(mobile/tab_switcher): improve display of empty tabs 2026-01-31 19:46:15 +02:00
Elian Doran
8f6cfe8a04 feat(mobile/tab_switcher): improve display of collections 2026-01-31 19:43:53 +02:00
Elian Doran
bec7943e05 feat(mobile/tab_switcher): hide icon for new tab 2026-01-31 19:36:31 +02:00
Elian Doran
a486f5951e feat(mobile/tab_switcher): new tab button 2026-01-31 19:29:06 +02:00
Elian Doran
e8158aadec feat(mobile/tab_switcher): display number of tabs in launch bar 2026-01-31 19:19:39 +02:00
Elian Doran
b6f107b85b feat(mobile/tab_switcher): display number of tabs in modal title 2026-01-31 19:08:29 +02:00
Elian Doran
5abd27f252 chore(mobile/tab_switcher): improve modal fit when in browser 2026-01-31 19:04:47 +02:00
Elian Doran
39648b6df8 chore(mobile/tab_switcher): remove old tab bar 2026-01-31 19:03:15 +02:00
Elian Doran
0a7b2e3304 feat(mobile/tab_switcher): integrate into launch bar 2026-01-31 18:50:17 +02:00
Elian Doran
48db6e1756 feat(mobile/tab_switcher): button to close tab 2026-01-31 18:40:01 +02:00
Elian Doran
2a38af5db6 feat(mobile/tab_switcher): scroll to active tab 2026-01-31 18:25:01 +02:00
Elian Doran
740b1093d7 feat(mobile/tab_switcher): respect workspace background color 2026-01-31 18:09:04 +02:00
Elian Doran
6b70412f6e feat(mobile/tab_switcher): respect note color class 2026-01-31 17:50:52 +02:00
Elian Doran
43f147ec60 feat(mobile/tab_switcher): improve display of content widget & search 2026-01-31 17:31:30 +02:00
Elian Doran
bf0fc57493 feat(mobile/tab_switcher): display note icon 2026-01-31 17:29:24 +02:00
Elian Doran
325b8b886c fix(mobile/tab_switcher): clipped borders 2026-01-31 17:25:33 +02:00
Elian Doran
a02bbdc550 feat(mobile/tab_switcher): indicate active tab 2026-01-31 17:25:05 +02:00
Elian Doran
4285fd7708 feat(mobile/tab_switcher): click to activate 2026-01-31 17:14:54 +02:00
Elian Doran
96fa6eac44 fix(mobile/tab_switcher): no title if empty tab 2026-01-31 17:07:32 +02:00
Elian Doran
05fa1ef2fb feat(mobile/tab_switcher): clip note title 2026-01-31 17:04:19 +02:00
Elian Doran
13aebc060e feat(mobile/tab_switcher): display margins only for text 2026-01-31 17:01:22 +02:00
Elian Doran
1aae4098d6 feat(mobile/tab_switcher): 2026-01-31 16:54:39 +02:00
Elian Doran
3367bb2e5b feat(mobile/tab_switcher): basic rendering of tab content 2026-01-31 16:25:05 +02:00
Elian Doran
49fc5e1559 feat(mobile/tab_switcher): basic listing of tabs 2026-01-31 16:12:27 +02:00
Elian Doran
3a9b448a83 chore(mobile/tab_switcher): create empty modal 2026-01-31 15:59:38 +02:00
Elian Doran
a45fb975c0 chore(mobile/tab_switcher): add button in launch bar 2026-01-31 15:53:19 +02:00
Elian Doran
e070fc2f52 Translations update from Hosted Weblate (#8563) 2026-01-31 15:14:21 +02:00
Ulices
5f8aac31e1 Translated using Weblate (Spanish)
Currently translated at 100.0% (1764 of 1764 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-31 13:00:02 +00:00
green
07f2e2eafc Translated using Weblate (Japanese)
Currently translated at 100.0% (1764 of 1764 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-01-31 13:00:01 +00:00
Marcel
2b41fb7108 Translated using Weblate (German)
Currently translated at 100.0% (1764 of 1764 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-31 13:00:00 +00:00
Elian Doran
81c18f1869 Streamlining the collection properties (#8562) 2026-01-31 14:59:40 +02:00
Elian Doran
61fd2fe87d chore(collections): address requested changes 2026-01-31 14:13:41 +02:00
Elian Doran
054cb974f1 fix(collections): old layout floating toolbar interfering 2026-01-31 13:49:02 +02:00
Elian Doran
50a19ecd74 fix(board): clipped due to collection properties 2026-01-31 13:31:46 +02:00
Elian Doran
1232909a3b feat(geomap): integrate add button to collection properties 2026-01-31 13:26:54 +02:00
Elian Doran
9ba0e076a6 fix(calendar): buttons cut off on smaller mobile phones 2026-01-31 13:09:20 +02:00
Elian Doran
ede91c645d fix(presentation): cut off due to collection properties 2026-01-31 13:05:05 +02:00
Elian Doran
b6a91723e7 feat(presentation): integrate buttons into collection properties 2026-01-31 13:01:51 +02:00
Elian Doran
fddd73fdb1 feat(table): display action buttons on mobile 2026-01-31 12:59:09 +02:00
Elian Doran
ffbe8f9dc4 fix(table): wrong alignment of buttons 2026-01-31 12:54:11 +02:00
Elian Doran
f80763ffb4 fix(geomap): zoom buttons interfering with collection properties 2026-01-31 12:53:56 +02:00
Elian Doran
f65aa1b875 fix(collections): height affected due to collection properties 2026-01-31 12:41:52 +02:00
Elian Doran
0c4de9a5e0 feat(table): integrate footer buttons into collection properties 2026-01-31 12:41:31 +02:00
Elian Doran
1822970e23 feat(collections): integrate list/grid pagination into collection properties 2026-01-31 12:29:48 +02:00
Elian Doran
80615d0382 feat(search): integrate collection properties into search 2026-01-31 12:22:34 +02:00
Elian Doran
c1002ed52a chore(calendar): improve layout on mobile 2026-01-31 12:13:05 +02:00
Elian Doran
83e585ed35 fix(calendar): layout broken on desktop 2026-01-31 11:54:23 +02:00
Elian Doran
0e164b9daa chore(collections): fix alignment of collection properties in relation to content 2026-01-31 11:50:14 +02:00
Elian Doran
1c9f8a2540 chore(collections): add collection toolbar to all collections 2026-01-31 11:39:48 +02:00
Elian Doran
88ba4451eb chore(calendar): improve layout on mobile slightly 2026-01-31 11:33:00 +02:00
Elian Doran
305f195539 feat(calendar): dropdown-based view switcher for mobile 2026-01-31 11:27:11 +02:00
Elian Doran
fbc6b853ed chore(calendar): improve column fit on mobile 2026-01-31 11:17:26 +02:00
Elian Doran
f3bcab813a chore(calendar): minor improvements on mobile 2026-01-31 11:11:01 +02:00
Elian Doran
2fa9b2e4dd feat(mobile): add help button in note context menu 2026-01-31 11:08:41 +02:00
Elian Doran
fc756ba46e feat(collections): move help button to the title area 2026-01-31 11:00:55 +02:00
Elian Doran
85a82124b1 feat(calendar): basic header support on mobile 2026-01-31 10:53:37 +02:00
Elian Doran
b5cfbc92af chore(calendar): use text instead of icons 2026-01-31 10:34:37 +02:00
Elian Doran
e4f260e242 chore(calendar): relocate prev/next buttons 2026-01-31 10:23:24 +02:00
Elian Doran
7c6c0c1fbd feat(calendar): integrate calendar header into collection properites 2026-01-31 10:14:35 +02:00
Elian Doran
2f5f4dbebd chore(deps): update dependency @playwright/test to v1.58.1 (#8554) 2026-01-31 08:38:59 +02:00
Elian Doran
94adc6af95 fix(deps): update dependency @codemirror/view to v6.39.12 (#8555) 2026-01-31 08:36:49 +02:00
Elian Doran
e3ea703f3d fix(deps): update dependency @preact/signals to v2.6.2 (#8556) 2026-01-31 08:35:38 +02:00
Elian Doran
a14753e5f3 chore(deps): update dependency stylelint to v17.1.0 (#8558) 2026-01-31 08:34:17 +02:00
Elian Doran
2ffd55c2d8 chore(deps): update dependency dpdm to v4 (#8559) 2026-01-31 08:33:44 +02:00
renovate[bot]
ad0ee8da32 chore(deps): update dependency dpdm to v4 2026-01-31 01:16:04 +00:00
renovate[bot]
e5cfe37b17 chore(deps): update dependency stylelint to v17.1.0 2026-01-31 01:15:23 +00:00
renovate[bot]
9247dd8b17 fix(deps): update dependency @preact/signals to v2.6.2 2026-01-31 01:13:37 +00:00
renovate[bot]
2f69b1d6e1 fix(deps): update dependency @codemirror/view to v6.39.12 2026-01-31 01:12:39 +00:00
renovate[bot]
9c43930e7b chore(deps): update dependency @playwright/test to v1.58.1 2026-01-31 01:11:44 +00:00
Elian Doran
f18ecfa524 test(server): reduce number of workers even further 2026-01-30 21:47:01 +02:00
Elian Doran
b6f7453ed9 fix(web-clipper): cropped screenshot not working in some versions of Chrome (closes #8528) 2026-01-30 21:32:34 +02:00
Elian Doran
4a0b77dabb test(server): reduce server workers even further 2026-01-30 19:35:35 +02:00
Elian Doran
de7687b3ab chore(deps): audit 2026-01-30 19:27:23 +02:00
Elian Doran
85f2ec9d92 test(server): reduce parallel number of workers to avoid OOM in CI 2026-01-30 19:16:43 +02:00
Elian Doran
ed6b8dfc9b Translations update from Hosted Weblate (#8552) 2026-01-30 18:58:10 +02:00
Kim Nøglegaard
1539a6eabc Translated using Weblate (Norwegian Bokmål)
Currently translated at 43.9% (51 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/nb_NO/
2026-01-30 16:53:55 +00:00
Kim Nøglegaard
6424d96703 Translated using Weblate (Norwegian Bokmål)
Currently translated at 6.9% (27 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/nb_NO/
2026-01-30 16:53:55 +00:00
Elian Doran
15f6ac8e89 macOS Vibrancy Support (#8519) 2026-01-30 18:53:41 +02:00
Elian Doran
f9803a4694 Merge remote-tracking branch 'origin/main' into feature/vibrancy 2026-01-30 18:45:30 +02:00
Jon Fuller
ca893ecc21 Merge branch 'main' into feat/rice-searching-with-sqlite 2026-01-30 07:24:06 -08:00
Elian Doran
13de9975e3 chore(deps): update dependency @braintree/sanitize-url to v7.1.2 (#8548) 2026-01-30 11:35:27 +02:00
Elian Doran
a64c4ef66f chore(deps): update typescript-eslint monorepo to v8.54.0 (#8436) 2026-01-30 09:19:28 +02:00
Elian Doran
36e85eb3a7 chore(deps): update dependency @playwright/test to v1.58.0 (#8487) 2026-01-30 09:19:03 +02:00
Elian Doran
20ffe7e082 fix(deps): update dependency globals to v17.2.0 (#8489) 2026-01-30 09:18:52 +02:00
Elian Doran
ea1dc58b9a chore(deps): update dependency @preact/preset-vite to v2.10.3 (#8539) 2026-01-30 09:18:42 +02:00
Elian Doran
cd292ad605 chore(deps): update dependency @smithy/middleware-retry to v4.4.29 (#8540) 2026-01-30 09:17:59 +02:00
renovate[bot]
03d9a6c0e5 chore(deps): update dependency @braintree/sanitize-url to v7.1.2 2026-01-30 07:17:41 +00:00
Elian Doran
fa54a2e67c chore(deps): update dependency @redocly/cli to v2.15.0 (#8543) 2026-01-30 09:15:57 +02:00
Elian Doran
7f2530470d chore(deps): update dependency @anthropic-ai/sdk to v0.72.1 (#8549) 2026-01-30 09:15:47 +02:00
Elian Doran
a284934136 chore(deps): update dependency electron to v40.1.0 (#8550) 2026-01-30 09:15:36 +02:00
renovate[bot]
2ca6606508 fix(deps): update dependency globals to v17.2.0 2026-01-30 06:52:02 +00:00
renovate[bot]
bb1c691b34 chore(deps): update typescript-eslint monorepo to v8.54.0 2026-01-30 06:50:39 +00:00
renovate[bot]
19d3e1b11c chore(deps): update dependency electron to v40.1.0 2026-01-30 06:49:33 +00:00
renovate[bot]
e35e64caaa chore(deps): update dependency @redocly/cli to v2.15.0 2026-01-30 06:48:34 +00:00
renovate[bot]
a3cf72c76c chore(deps): update dependency @playwright/test to v1.58.0 2026-01-30 06:47:40 +00:00
renovate[bot]
710e95bdee chore(deps): update dependency @anthropic-ai/sdk to v0.72.1 2026-01-30 06:46:47 +00:00
renovate[bot]
d281fb7065 chore(deps): update dependency @smithy/middleware-retry to v4.4.29 2026-01-30 06:45:09 +00:00
renovate[bot]
e669d5041b chore(deps): update dependency @preact/preset-vite to v2.10.3 2026-01-30 06:44:20 +00:00
Elian Doran
d8d91451c8 e2e(server): fix some flakiness in support app 2026-01-30 08:11:32 +02:00
Elian Doran
b0910baaf0 chore(ci): fix warnings related to if 2026-01-30 07:46:30 +02:00
Elian Doran
ce3f70adc3 chore(deps): update dependency axios to v1.13.4 (#8541) 2026-01-29 18:55:00 +02:00
Elian Doran
c0d5c26f0c chore(deps): update dependency @electron/rebuild to v4.0.3 (#8538) 2026-01-29 09:51:11 +02:00
Elian Doran
1bf8a76cc3 Translations update from Hosted Weblate (#8546) 2026-01-29 09:50:48 +02:00
Ulices
7b1f74a413 Translated using Weblate (Spanish)
Currently translated at 100.0% (388 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/es/
2026-01-29 08:27:08 +01:00
Sergii Nechuiviter
3d2fde77a5 Translated using Weblate (Ukrainian)
Currently translated at 86.8% (132 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/uk/
2026-01-29 08:27:07 +01:00
Ulices
e4cc3bef73 Translated using Weblate (Spanish)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-29 08:27:07 +01:00
Hosted Weblate
f384c422c4 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-29 08:27:06 +01:00
Elian Doran
a373d2e7e0 Vite fixes (#8537) 2026-01-29 09:26:57 +02:00
Elian Doran
1aaf630979 Merge branch 'main' into feature/vite_fixes 2026-01-29 09:05:04 +02:00
Elian Doran
b7ac3aba72 fix(deps): update dependency react-i18next to v16.5.4 (#8542) 2026-01-29 08:58:39 +02:00
Elian Doran
ed89250624 chore(deps): update dependency happy-dom to v20.4.0 (#8544) 2026-01-29 08:58:28 +02:00
Elian Doran
d9a7f0c7fe chore(deps): update dependency openai to v6.17.0 (#8545) 2026-01-29 08:58:09 +02:00
Elian Doran
a07405bec3 fix(desktop): nightly failing due to missing flatpak icon 2026-01-29 08:57:29 +02:00
renovate[bot]
4c6efeb0d8 chore(deps): update dependency openai to v6.17.0 2026-01-29 01:00:59 +00:00
renovate[bot]
501b380d5e chore(deps): update dependency happy-dom to v20.4.0 2026-01-29 01:00:12 +00:00
renovate[bot]
ddc4e34dcd fix(deps): update dependency react-i18next to v16.5.4 2026-01-29 00:58:31 +00:00
renovate[bot]
d668d9f24d chore(deps): update dependency axios to v1.13.4 2026-01-29 00:57:40 +00:00
renovate[bot]
0ec4423ad4 chore(deps): update dependency @electron/rebuild to v4.0.3 2026-01-29 00:55:00 +00:00
Elian Doran
51313ff0d5 fix(client): subdir broken due to bootstrap 2026-01-28 22:27:02 +02:00
Elian Doran
37381b7c36 chore(client): use different root mechanism with fewer issues 2026-01-28 22:23:00 +02:00
Elian Doran
6de632d117 fix(client): relative import to src 2026-01-28 22:06:19 +02:00
Elian Doran
4a8fa7293b fix(calendar): unnecessary right margin for header on new layout 2026-01-28 15:37:41 +02:00
Elian Doran
b75a2e9592 fix(calendar): lower case in header (closes #8507) 2026-01-28 14:47:06 +02:00
Elian Doran
c45c1b0f93 fix(calendar): not respecting auto formatting locale (closes #8507) 2026-01-28 14:46:24 +02:00
Elian Doran
af7057f062 fix(scripts): correctly extract current version for nightly updates 2026-01-28 10:05:04 +02:00
Elian Doran
85bf1eb4ec fix(desktop): nightly icon not respected 2026-01-28 10:03:45 +02:00
Elian Doran
bd45043a36 chore(desktop): integrate icon change for dev nightlies 2026-01-28 09:44:15 +02:00
Elian Doran
fbb41168a2 chore(scripts): build dev icon for Windows installer 2026-01-28 09:05:23 +02:00
Elian Doran
674fe4fa20 chore(scripts): build dev icon for Mac 2026-01-28 08:54:36 +02:00
Elian Doran
4db86f9322 chore(scripts): fix path for building Mac icon 2026-01-28 08:48:48 +02:00
Elian Doran
5c814155d2 chore(scripts): build dev icon for Windows 2026-01-28 08:44:35 +02:00
Elian Doran
c08fb9af16 chore(scripts): update paths in create icons 2026-01-28 08:41:55 +02:00
Elian Doran
1dac4fea9c Translations update from Hosted Weblate (#8532) 2026-01-28 07:34:25 +02:00
Marcel
ac19000ad0 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-28 05:06:09 +01:00
Giovi
221182389a Translated using Weblate (Italian)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-01-28 05:06:09 +01:00
Elian Doran
5d5947f676 feat(call_to_action): enable background effects CTA on macOS 2026-01-27 23:31:05 +02:00
Elian Doran
8fc889ae08 chore(options): change description 2026-01-27 23:22:31 +02:00
Elian Doran
ff46493775 feat(options): add platform indicator 2026-01-27 23:17:38 +02:00
Elian Doran
a1cb3b8371 style(desktop): change material for horizontal layout 2026-01-27 16:42:52 +02:00
Elian Doran
51131433d3 chore(client): address requested changes 2026-01-27 15:56:57 +02:00
Elian Doran
eaccd641ed Translations update from Hosted Weblate (#8527) 2026-01-27 08:45:06 +02:00
Unknown
3e2b647f06 Translated using Weblate (Spanish)
Currently translated at 94.2% (1662 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-27 07:43:49 +01:00
Toto Yullian
0fbf9bafbc Translated using Weblate (Indonesian)
Currently translated at 3.2% (57 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-27 07:43:48 +01:00
Jason Kuanca
924a5e3110 Translated using Weblate (Indonesian)
Currently translated at 3.2% (57 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-27 07:43:47 +01:00
Marcel
be71a4b5c4 Translated using Weblate (German)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/de/
2026-01-27 07:43:47 +01:00
Marcel
1cb5a13ea4 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-27 07:43:46 +01:00
Marcel
e145cd80a9 Translated using Weblate (German)
Currently translated at 100.0% (388 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2026-01-27 07:43:45 +01:00
Toto Yullian
58ea661d4b Translated using Weblate (Indonesian)
Currently translated at 38.8% (59 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-01-27 07:43:44 +01:00
Elian Doran
ba317eff3f chore(deps): update dependency vite-plugin-static-copy to v3.2.0 (#8486) 2026-01-27 08:43:25 +02:00
Elian Doran
ead0e14118 chore(deps): update react monorepo to v19.2.4 (#8524) 2026-01-27 08:42:58 +02:00
Elian Doran
d3dd20b50f chore(deps): update dependency eslint-plugin-playwright to v2.5.1 (#8521) 2026-01-27 08:42:31 +02:00
Elian Doran
d621fb4105 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.10 (#8522) 2026-01-27 08:42:01 +02:00
Elian Doran
a239604dad chore(deps): update pnpm to v10.28.2 (#8523) 2026-01-27 08:41:41 +02:00
renovate[bot]
f7986b9049 chore(deps): update dependency vite-plugin-static-copy to v3.2.0 2026-01-27 05:54:58 +00:00
renovate[bot]
0a34ca031a chore(deps): update react monorepo to v19.2.4 2026-01-27 01:39:04 +00:00
renovate[bot]
ce63fec413 chore(deps): update pnpm to v10.28.2 2026-01-27 01:38:17 +00:00
renovate[bot]
719451bf23 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.10 2026-01-27 01:38:02 +00:00
renovate[bot]
10a27cbe86 chore(deps): update dependency eslint-plugin-playwright to v2.5.1 2026-01-27 01:37:18 +00:00
Elian Doran
3c8a066f76 feat(desktop): handle both vertical and horizontal layouts 2026-01-26 23:28:29 +02:00
Elian Doran
6856a98d50 feat(desktop): integrate vibrancy 2026-01-26 23:21:40 +02:00
Elian Doran
120b767a68 fix(desktop): background effects not applied correctly 2026-01-26 23:10:19 +02:00
Elian Doran
8c0d4cde86 feat(desktop): basic vibrancy support 2026-01-26 23:02:14 +02:00
Elian Doran
bbf090edf0 Merge branch 'main' of github.com:TriliumNext/Trilium 2026-01-26 21:11:02 +02:00
Elian Doran
fc925a5db5 chore(web-clipper): prepare for 1.1.1 2026-01-26 21:10:30 +02:00
Elian Doran
fb76ca09bd Web Clipper Fixes (#8518) 2026-01-26 21:09:29 +02:00
Elian Doran
af6c54bac7 fix(web-clipper): context menu got broken on Firefox 2026-01-26 21:00:56 +02:00
Elian Doran
a854b04300 chore(web-clipper): rename ZIP artifacts 2026-01-26 20:47:20 +02:00
Elian Doran
5d73556127 docs(dev): mention NixOS for web clipper dev 2026-01-26 20:44:19 +02:00
Elian Doran
82ea4c1a04 docs(guide): add instructions on installing from .zip 2026-01-26 20:41:22 +02:00
Elian Doran
537d92421c chore(web-clipper): fix warning related to permissions 2026-01-26 20:20:46 +02:00
Elian Doran
c97c69900b chore(web-clipper): minor typo in "screenshot" 2026-01-26 20:10:18 +02:00
Elian Doran
ab519a4caa chore(web-clipper): rephrase "already visited" & fix spacing 2026-01-26 20:06:57 +02:00
Elian Doran
bbbdab42ca fix(web-clipper): cash not working in Chrome 2026-01-26 19:38:11 +02:00
Elian Doran
810563b3f9 fix(web-clipper): duplicate context menu error 2026-01-26 19:21:19 +02:00
Elian Doran
63cf055a0d Translations update from Hosted Weblate (#8517) 2026-01-26 18:14:29 +02:00
Hosted Weblate
442aac0466 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-26 17:10:32 +01:00
Elian Doran
7c9499ad7e Translations update from Hosted Weblate (#8510) 2026-01-26 18:10:06 +02:00
Jason Kuanca
a6e8e2a127 Translated using Weblate (Indonesian)
Currently translated at 21.6% (84 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/id/
2026-01-26 08:12:29 +01:00
Jason Kuanca
671e05421a Translated using Weblate (Indonesian)
Currently translated at 34.8% (53 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-01-26 08:12:28 +01:00
Jason Kuanca
d8c7c919d1 Translated using Weblate (Indonesian)
Currently translated at 44.8% (52 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-01-26 08:12:28 +01:00
Toto Yullian
5629b9a161 Translated using Weblate (Indonesian)
Currently translated at 2.8% (51 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-26 08:12:27 +01:00
Jason Kuanca
3ba853dbad Translated using Weblate (Indonesian)
Currently translated at 2.8% (51 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-26 08:12:27 +01:00
Jason Kuanca
be8dda8523 Translated using Weblate (Indonesian)
Currently translated at 2.6% (47 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-26 07:39:12 +01:00
Jason Kuanca
2e86166400 Translated using Weblate (Indonesian)
Currently translated at 21.3% (83 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/id/
2026-01-26 07:39:11 +01:00
Jason Kuanca
784ea240ca Translated using Weblate (Indonesian)
Currently translated at 42.2% (49 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-01-26 07:39:10 +01:00
Jason Kuanca
5b10e33e72 Translated using Weblate (Indonesian)
Currently translated at 32.2% (49 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-01-26 07:39:09 +01:00
Jason Kuanca
fbcf974c73 Translated using Weblate (Indonesian)
Currently translated at 1.8% (33 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-26 07:39:08 +01:00
Jason Kuanca
d844111187 Translated using Weblate (Indonesian)
Currently translated at 22.3% (34 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-01-26 07:39:08 +01:00
Jason Kuanca
4cac419a26 Translated using Weblate (Indonesian)
Currently translated at 31.0% (36 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-01-26 07:39:07 +01:00
Francis C.
1c21519960 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-01-26 07:39:06 +01:00
Jason Kuanca
6a70c52bd1 Translated using Weblate (Indonesian)
Currently translated at 1.3% (24 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-26 07:39:05 +01:00
Jason Kuanca
a0e6023810 Translated using Weblate (Indonesian)
Currently translated at 23.2% (27 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-01-26 07:39:05 +01:00
Jason Kuanca
a24ab7ca06 Translated using Weblate (Indonesian)
Currently translated at 16.4% (25 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-01-26 07:39:04 +01:00
MarcelWie
4979a1b224 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:39:03 +01:00
Antonio Sanchez Castellón
4cdf6d8292 Translated using Weblate (Spanish)
Currently translated at 94.2% (1662 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-26 07:39:02 +01:00
MarcelWie
4c51c8e8f8 Translated using Weblate (German)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/de/
2026-01-26 07:39:01 +01:00
MarcelWie
1ab7b91f2e Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:39:01 +01:00
Antonio Sanchez Castellón
7af4fbfcce Translated using Weblate (Spanish)
Currently translated at 93.5% (1650 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-01-26 07:39:00 +01:00
Antonio Sanchez Castellón
494e23b69f Translated using Weblate (Spanish)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/es/
2026-01-26 07:38:59 +01:00
MarcelWie
a487a502f5 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:38:59 +01:00
MarcelWie
2b7a7a8767 Translated using Weblate (German)
Currently translated at 100.0% (388 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2026-01-26 07:38:58 +01:00
MarcelWie
0c72bd1539 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:38:57 +01:00
MarcelWie
9462ccc650 Translated using Weblate (German)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/de/
2026-01-26 07:38:56 +01:00
MarcelWie
81d964d3e8 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:38:55 +01:00
MarcelWie
27bf41e0ce Translated using Weblate (German)
Currently translated at 100.0% (388 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2026-01-26 07:38:55 +01:00
MarcelWie
78b0773a28 Translated using Weblate (German)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/de/
2026-01-26 07:38:54 +01:00
MarcelWie
3b76239f65 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-26 07:38:53 +01:00
Elian Doran
edd11d847a chore(deps): update dependency axios to v1.13.3 (#8511) 2026-01-26 08:38:35 +02:00
Elian Doran
0df0f8a4c9 chore(deps): update dependency happy-dom to v20.3.9 (#8512) 2026-01-26 08:37:25 +02:00
Elian Doran
0f5ee0888a fix(deps): update dependency katex to v0.16.28 (#8513) 2026-01-26 08:37:03 +02:00
renovate[bot]
30ead4080a fix(deps): update dependency katex to v0.16.28 2026-01-26 01:16:42 +00:00
renovate[bot]
9224029a16 chore(deps): update dependency happy-dom to v20.3.9 2026-01-26 01:16:05 +00:00
renovate[bot]
4ce841dc8a chore(deps): update dependency axios to v1.13.3 2026-01-26 01:15:19 +00:00
Elian Doran
d639de03c3 Translations update from Hosted Weblate (#8509) 2026-01-25 23:18:47 +02:00
MarcelWie
0cf34fb874 Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-25 22:11:45 +01:00
Elian Doran
241147c762 fix(ci): token for web-clipper release 2026-01-25 21:40:19 +02:00
Elian Doran
99eb481ed4 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-01-25 21:38:27 +02:00
Elian Doran
2d126c9cec feat(ci): create release for web-clipper 2026-01-25 21:38:01 +02:00
Elian Doran
7aec858ade chore(web-clipper): missing offscreen.html in sources 2026-01-25 21:37:49 +02:00
Elian Doran
ac7a107a9a Translations update from Hosted Weblate (#8506) 2026-01-25 21:30:28 +02:00
MarcelWie
c2c9b6819c Translated using Weblate (German)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-25 20:27:39 +01:00
Vitalii Hlas
7159ea5927 Translated using Weblate (Ukrainian)
Currently translated at 91.4% (1613 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/
2026-01-25 20:27:38 +01:00
Vitalii Hlas
44bf837310 Translated using Weblate (Ukrainian)
Currently translated at 76.3% (116 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/uk/
2026-01-25 20:27:37 +01:00
Hosted Weblate
28368a3e0d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-25 20:27:36 +01:00
Hosted Weblate
1a92eeac69 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-25 20:27:35 +01:00
Elian Doran
411c062463 chore(web-clipper): rewire TS config for sources ZIP 2026-01-25 21:20:07 +02:00
Elian Doran
18d1b8cbfe chore(web-clipper): add README for Firefox submission 2026-01-25 21:19:43 +02:00
Elian Doran
8322e4278a Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-01-25 20:53:30 +02:00
Elian Doran
4b70bb6778 chore(web-clipper): bump version to v1.1.0 2026-01-25 20:45:06 +02:00
Elian Doran
c9c9daf30c Translations update from Hosted Weblate (#8505) 2026-01-25 20:40:44 +02:00
Hosted Weblate
b26d9c0303 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-25 19:34:13 +01:00
Elian Doran
376e6dc6d7 Translations update from Hosted Weblate (#8504) 2026-01-25 20:30:44 +02:00
Hosted Weblate
34d3c318d6 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-25 19:27:25 +01:00
Elian Doran
6cbed20d5c Translations update from Hosted Weblate (#8501) 2026-01-25 20:27:08 +02:00
Vitalii Hlas
96acd7f921 Translated using Weblate (Ukrainian)
Currently translated at 38.8% (59 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/uk/
2026-01-25 19:25:45 +01:00
Vitalii Hlas
7439b9ca65 Translated using Weblate (Ukrainian)
Currently translated at 50.0% (58 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/uk/
2026-01-25 19:25:44 +01:00
Elian Doran
9eb24c6fc2 Translated using Weblate (Romanian)
Currently translated at 99.9% (1762 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ro/
2026-01-25 17:53:29 +01:00
Vitalii Hlas
595d30feb7 Translated using Weblate (Ukrainian)
Currently translated at 97.9% (380 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/uk/
2026-01-25 17:53:25 +01:00
Vitalii Hlas
836b5feaae Translated using Weblate (Ukrainian)
Currently translated at 91.3% (1611 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/
2026-01-25 17:53:24 +01:00
Vitalii Hlas
5ca0830b88 Translated using Weblate (Ukrainian)
Currently translated at 29.6% (45 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/uk/
2026-01-25 17:53:23 +01:00
Vitalii Hlas
46057fa03b Translated using Weblate (Ukrainian)
Currently translated at 40.5% (47 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/uk/
2026-01-25 17:53:21 +01:00
Toto Yullian
ba46ba77b4 Translated using Weblate (Indonesian)
Currently translated at 1.1% (20 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-25 14:37:36 +01:00
noobhjy
e348a9b907 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-01-25 14:37:35 +01:00
green
aaaf8c1e2b Translated using Weblate (Japanese)
Currently translated at 100.0% (1763 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-01-25 11:52:12 +01:00
Toto Yullian
41a85a78db Translated using Weblate (Indonesian)
Currently translated at 1.0% (19 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-01-25 11:52:12 +01:00
Elian Doran
554aa1cd64 Translated using Weblate (Romanian)
Currently translated at 94.6% (1669 of 1763 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ro/
2026-01-25 11:52:11 +01:00
Elian Doran
853822ec8a chore(deps): update dependency @redocly/cli to v2.14.9 (#8485) 2026-01-25 12:03:52 +02:00
Elian Doran
087dfa179e Web Clipper (#8494) 2026-01-25 12:02:28 +02:00
Elian Doran
65a769c5bd Merge branch 'main' into webclipper/tooling 2026-01-25 11:14:23 +02:00
Elian Doran
0bbb4ddb21 Translations update from Hosted Weblate (#8499) 2026-01-25 09:44:57 +02:00
pythaac
cb0eeee8cc Translated using Weblate (Korean)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2026-01-25 08:43:55 +01:00
Elian Doran
75d35e3ade fix(deps): update codemirror themes to v6.2.4 (#8496) 2026-01-25 09:43:48 +02:00
renovate[bot]
99e22a5636 fix(deps): update codemirror themes to v6.2.4 2026-01-25 01:58:44 +00:00
Elian Doran
b7b367b5a3 chore(webclipper): address requested changes 2026-01-24 23:54:26 +02:00
Elian Doran
4927b01d96 chore(webclipper): fix typecheck 2026-01-24 23:13:27 +02:00
Elian Doran
e2e5d485d7 docs(dev): add some information on web clipper 2026-01-24 21:35:54 +02:00
Elian Doran
ada22e4966 docs(user): update web clipper 2026-01-24 21:24:14 +02:00
Elian Doran
1cf93ff0de chore(web-clipper): add data_collection_permissions for Firefox 2026-01-24 19:25:16 +02:00
Elian Doran
199962233b chore(web-clipper): use friendly ID for Firefox 2026-01-24 19:04:38 +02:00
Elian Doran
743a6f3466 ci(web-clipper): disable compression level for artifact 2026-01-24 18:12:02 +02:00
Elian Doran
625062a268 ci(web-clipper): no files uploaded 2026-01-24 17:57:30 +02:00
Elian Doran
cb0fabf273 ci(web-clipper): fail if no files found 2026-01-24 17:43:11 +02:00
Elian Doran
4c978d8622 ci(web-clipper): fail if no files found 2026-01-24 17:41:51 +02:00
Elian Doran
d0f441ec74 ci(web-clipper): generate .zip files on change 2026-01-24 17:40:18 +02:00
Elian Doran
9d347ff3d9 chore(web-clipper): update help URL 2026-01-24 17:25:09 +02:00
Elian Doran
c2a758dd4a chore(web-clipper): address requested changes 2026-01-24 17:23:06 +02:00
Elian Doran
bba69e98ae fix(web-clipper): warning about offscreen permission in MV2 2026-01-24 17:07:53 +02:00
Elian Doran
53e3d65c52 fix(web-clipper): handling of dev port 2026-01-24 17:01:03 +02:00
Elian Doran
a2a37a0b54 chore(web-clipper): integrate old manifest 2026-01-24 16:46:04 +02:00
Elian Doran
1fb360e34f chore(web-clipper): remove polyfill 2026-01-24 16:40:00 +02:00
Elian Doran
680817d81c fix(web-clipper): duplicate context menu entry 2026-01-24 16:39:00 +02:00
Elian Doran
bf736977ab chore(web-clipper): don't render offscreen for MV2 2026-01-24 16:26:16 +02:00
Elian Doran
28ed93dcdc refactor(web-clipper): use @-imports 2026-01-24 16:22:43 +02:00
Elian Doran
785ace64ad feat(web-clipper): use new Trilium icon for now 2026-01-24 16:20:36 +02:00
Elian Doran
ac109c2ece feat(web-clipper): support manifest V2 for Firefox 2026-01-24 16:07:15 +02:00
Elian Doran
1e82043999 fix(web-clipper): saving links not working under MV3 2026-01-24 15:42:04 +02:00
Elian Doran
e37487a1cf feat(web-clipper): handle manifest V3 2026-01-24 15:28:47 +02:00
Elian Doran
a9b8ffd94c chore(web-clipper): fix package lock 2026-01-24 14:26:05 +02:00
Elian Doran
4011771b64 chore(web-clipper): port the remaining files to TypeScript 2026-01-24 14:01:58 +02:00
Elian Doran
266494ba8c chore(web-clipper): port most files to TypeScript 2026-01-24 13:20:27 +02:00
Elian Doran
2e144fac5e chore(web-clipper): set up for TypeScript 2026-01-24 12:35:31 +02:00
Elian Doran
423038100e fix(web-clipper): undefined variable in popup 2026-01-24 12:12:51 +02:00
Elian Doran
75e88c69bd fix(web-clipper): createLink not defined in popup 2026-01-24 12:12:07 +02:00
Elian Doran
f0b1319f95 refactor(web-clipper): remove unnecessary libraries 2026-01-24 11:54:00 +02:00
Elian Doran
59f2fc8d03 fix(web-clipper): clipping whole page not working 2026-01-24 11:53:26 +02:00
Elian Doran
5d07a079ef feat(web-clipper): improve error handling for content entrypoint 2026-01-24 11:46:07 +02:00
Elian Doran
b5ff71b1a0 fix(web-clipper): missing utils import 2026-01-24 11:25:32 +02:00
Elian Doran
c0a2ae99cf fix(web-clipper): toast not working 2026-01-24 11:13:17 +02:00
Elian Doran
5600a707d3 chore(web-clipper): reintegrate name and description 2026-01-24 11:00:54 +02:00
Elian Doran
17f906fb65 chore(web-clipper): reintegrate icon 2026-01-24 10:58:20 +02:00
Elian Doran
276b3f834b fix(web-clipper): triliumServerFacade is not defined 2026-01-24 10:47:41 +02:00
Elian Doran
a9218960e9 fix(web-clipper): the storage API will not work with a temporary addon ID 2026-01-24 10:40:16 +02:00
Elian Doran
957590523c fix(web-clipper): integrate trilium server facade 2026-01-24 10:29:53 +02:00
Elian Doran
22308a101e fix(web-clipper): missing permissions 2026-01-24 10:18:59 +02:00
Elian Doran
ab95f6dcc2 fix(web-clipper): script imports 2026-01-24 10:08:29 +02:00
Elian Doran
cb8b968637 chore(web-clipper): make entrypoints actually run 2026-01-24 09:59:46 +02:00
Elian Doran
e4d319c7a1 chore(web-clipper): define entrypoints 2026-01-24 09:50:25 +02:00
Elian Doran
f8e5f31970 chore(web-clipper): install WXT 2026-01-24 09:45:05 +02:00
Elian Doran
5113e2ab97 chore(web-clipper): create package JSON 2026-01-24 09:42:02 +02:00
Elian Doran
6ae1cc18e2 chore(web-clipper): remove IDEA file 2026-01-24 09:37:03 +02:00
Elian Doran
256ad05d2d Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-01-24 09:36:18 +02:00
Elian Doran
1b0a53a441 feat(notes): add default icon for doc files 2026-01-24 09:29:11 +02:00
Elian Doran
430ef62a2d feat(notes): add default icon for GIF 2026-01-24 09:24:35 +02:00
renovate[bot]
cd7daee771 chore(deps): update dependency @redocly/cli to v2.14.9 2026-01-24 06:31:00 +00:00
Elian Doran
50aeda8ee8 chore(deps): update dependency cheerio to v1.2.0 (#8488) 2026-01-24 08:28:34 +02:00
renovate[bot]
1520c696a3 chore(deps): update dependency cheerio to v1.2.0 2026-01-24 01:39:45 +00:00
Elian Doran
f63f6244a1 fix: update nixpkgs to grab elector v40 (#8483) 2026-01-23 22:20:43 +02:00
Wael Nasreddine
8611d4a67a fix: update nixpkgs to grab elector v40 2026-01-23 08:31:30 -08:00
Elian Doran
c48bd9a5c3 fix(canvas): saving on start due to library change 2026-01-23 18:12:11 +02:00
Elian Doran
dba985b308 fix(canvas): saving on start due to mismatch in version number 2026-01-23 18:07:11 +02:00
Elian Doran
a51a831fe8 fix(canvas): background color not saved (closes #8325) 2026-01-23 17:56:04 +02:00
Elian Doran
44142e980d fix(quick_edit): not working when content is centered (closes #8371) 2026-01-23 17:17:41 +02:00
Elian Doran
7f83226f84 Calendar improvements (#8478) 2026-01-23 16:25:18 +02:00
Elian Doran
e3fdae8932 chore: address requested changes 2026-01-23 15:02:33 +02:00
Elian Doran
78c62be823 test(client): fix broken tests after change in attributes 2026-01-23 14:57:33 +02:00
Elian Doran
e51cea88bf feat(calendar): don't trigger refresh on delete 2026-01-23 12:47:02 +02:00
Elian Doran
d7409bec49 feat(calendar): don't trigger refresh on rename 2026-01-23 12:35:08 +02:00
Elian Doran
17b1f599ff feat(calendar): don't trigger refresh on event change 2026-01-23 12:24:35 +02:00
Elian Doran
81c85d712e chore(calendar): create note with attributes atomically 2026-01-23 12:11:06 +02:00
Elian Doran
2eae8bbb64 Revert "chore(calendar): remove automatic fetching on note creation"
This reverts commit 2a61f51e06.
2026-01-23 12:05:12 +02:00
Elian Doran
2a61f51e06 chore(calendar): remove automatic fetching on note creation 2026-01-23 11:58:57 +02:00
Elian Doran
3e3c3e3bb4 fix(calendar): redundant refresh when adding new item 2026-01-23 11:38:18 +02:00
Elian Doran
7b41a89b8e chore(deps): update dependency happy-dom to v20.3.7 (#8470) 2026-01-23 09:39:54 +02:00
renovate[bot]
36429da6da chore(deps): update dependency happy-dom to v20.3.7 2026-01-23 07:09:50 +00:00
Elian Doran
30f6ab5976 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.2 (#8441) 2026-01-23 09:09:16 +02:00
Elian Doran
99a46f2a85 fix(deps): update dependency lodash-es to v4.17.23 [security] (#8454) 2026-01-23 09:08:39 +02:00
Elian Doran
6754b1f2e1 fix(deps): update dependency @preact/signals to v2.6.1 (#8457) 2026-01-23 09:08:25 +02:00
Elian Doran
122ad2b771 chore(deps): update dependency @redocly/cli to v2.14.7 (#8468) 2026-01-23 09:08:01 +02:00
Elian Doran
714e8ade1a chore(deps): update vitest monorepo to v4.0.18 (#8471) 2026-01-23 09:07:30 +02:00
Elian Doran
4a6ea38be0 chore(deps): update dependency express-session to v1.19.0 (#8472) 2026-01-23 09:06:31 +02:00
Elian Doran
8bc7f0b71f Translations update from Hosted Weblate (#8476) 2026-01-23 09:05:39 +02:00
Hosted Weblate
9a912c16ad Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-23 07:51:59 +01:00
Elian Doran
10a84a1356 chore(deps): update dependency @smithy/middleware-retry to v4.4.27 (#8469) 2026-01-23 08:51:48 +02:00
Wael Nasreddine
901201a7af chore: pnpm2nix to use our copy of flake-utils 2026-01-22 18:59:03 -08:00
renovate[bot]
a57a1dfc47 chore(deps): update dependency express-session to v1.19.0 2026-01-23 00:42:22 +00:00
renovate[bot]
577780cb90 chore(deps): update vitest monorepo to v4.0.18 2026-01-23 00:41:33 +00:00
renovate[bot]
b45eef9140 chore(deps): update dependency @smithy/middleware-retry to v4.4.27 2026-01-23 00:40:10 +00:00
renovate[bot]
907853bbba chore(deps): update dependency @redocly/cli to v2.14.7 2026-01-23 00:39:32 +00:00
Elian Doran
17f3ffd00c fix(mermaid): preview not rendering when read-only (closes #8419) 2026-01-22 20:56:58 +02:00
Elian Doran
8b86e17ac8 fix(client): race condition in syntax highlight (closes #8464) 2026-01-22 20:03:46 +02:00
Elian Doran
d6b6832a1d fix(promoted_attributes): checkbox not displaying initial value properly (closes #8062) 2026-01-22 08:15:40 +02:00
Elian Doran
9dfc1cdc4c fix(render): not refreshing on attribute change (closes #8321) 2026-01-22 08:10:29 +02:00
Elian Doran
673c39d798 Revert "feat(options/advanced): add description for experimental"
This reverts commit fc2ab91280.
2026-01-22 08:05:49 +02:00
renovate[bot]
8ca84d183c fix(deps): update dependency lodash-es to v4.17.23 [security] 2026-01-22 02:03:34 +00:00
renovate[bot]
9577aa2abe fix(deps): update dependency @preact/signals to v2.6.1 2026-01-22 01:45:37 +00:00
Elian Doran
227be184ac fix(shortcuts): overlap on azerty due to key matches 2026-01-21 22:34:36 +02:00
Elian Doran
d677f65eeb chore(deps): update dependency happy-dom to v20.3.4 (#8433) 2026-01-21 19:32:41 +02:00
Elian Doran
92f86bcca2 chore(deps): update dependency @smithy/middleware-retry to v4.4.26 (#8442) 2026-01-21 19:31:35 +02:00
Elian Doran
2015068d9e fix(deps): update dependency @zumer/snapdom to v2.0.2 (#8443) 2026-01-21 19:30:35 +02:00
renovate[bot]
8a280c2f9d chore(deps): update dependency happy-dom to v20.3.4 2026-01-21 17:07:19 +00:00
Elian Doran
34fd6f9502 chore(deps): update dependency lightningcss to v1.31.1 (#8444) 2026-01-21 19:05:45 +02:00
Elian Doran
02acb36e47 chore(deps): update dependency node-abi to v4.26.0 (#8445) 2026-01-21 19:03:13 +02:00
Elian Doran
280c0e0348 fix(deps): update dependency @preact/signals to v2.6.0 (#8446) 2026-01-21 18:27:23 +02:00
Elian Doran
8528f0d848 fix(deps): update dependency i18next to v25.8.0 (#8447) 2026-01-21 18:26:53 +02:00
renovate[bot]
917e881faa chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.2 2026-01-21 10:43:23 +00:00
renovate[bot]
96b1efcfdc chore(deps): update dependency lightningcss to v1.31.1 2026-01-21 06:17:33 +00:00
renovate[bot]
794e03b2cb fix(deps): update dependency i18next to v25.8.0 2026-01-21 01:37:06 +00:00
renovate[bot]
a285c46b97 fix(deps): update dependency @preact/signals to v2.6.0 2026-01-21 01:36:23 +00:00
renovate[bot]
4da6294ef2 chore(deps): update dependency node-abi to v4.26.0 2026-01-21 01:35:35 +00:00
renovate[bot]
16ed9a7e8e fix(deps): update dependency @zumer/snapdom to v2.0.2 2026-01-21 01:34:07 +00:00
renovate[bot]
798efbc22f chore(deps): update dependency @smithy/middleware-retry to v4.4.26 2026-01-21 01:33:18 +00:00
Elian Doran
60c789b6c7 fix(client): production affected by cache of index JS 2026-01-20 16:33:46 +02:00
Elian Doran
f83d95136d fix(codemirror): ctrl+enter generates newline 2026-01-20 16:11:40 +02:00
Elian Doran
f96ed0af26 Revert "fix(shortcuts): triggering in bubbling phase, not capturing"
This reverts commit 711828d6b4.
2026-01-20 15:16:51 +02:00
Elian Doran
1539664026 Revert "test(client): fix broken tests after change in shortcut behaviour"
This reverts commit e33950e000.
2026-01-20 15:16:32 +02:00
Elian Doran
8aff775d0e chore(deps): update pnpm to v10.28.1 (#8435) 2026-01-20 12:12:26 +02:00
Elian Doran
94248eafe9 chore(deps): update dependency vite-plugin-static-copy to v3.1.5 (#8434) 2026-01-20 12:12:06 +02:00
Elian Doran
02335bba3f chore(deps): update dependency turnish to v1.8.0 (#8437) 2026-01-20 12:11:38 +02:00
renovate[bot]
dad9578b83 chore(deps): update dependency turnish to v1.8.0 2026-01-20 01:16:12 +00:00
renovate[bot]
c043788b09 chore(deps): update pnpm to v10.28.1 2026-01-20 01:15:00 +00:00
renovate[bot]
e5bc416b46 chore(deps): update dependency vite-plugin-static-copy to v3.1.5 2026-01-20 01:14:50 +00:00
Elian Doran
c97f52da36 docs(user): mention breaking change update for scripts 2026-01-19 19:02:54 +02:00
Elian Doran
1661c3292a fix(deps): update dependency jquery to v4 (#8424) 2026-01-19 18:53:11 +02:00
Elian Doran
4e80c07630 Translations update from Hosted Weblate (#8427) 2026-01-19 18:42:06 +02:00
Elian Doran
d43309947e fix(client): polyfill removed jQuery methods 2026-01-19 18:39:22 +02:00
Baris Konag
c7cc702c4a Translated using Weblate (Turkish)
Currently translated at 10.5% (16 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/tr/
2026-01-19 17:22:17 +01:00
Baris Konag
c304753ffc Translated using Weblate (Turkish)
Currently translated at 4.3% (17 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/tr/
2026-01-19 17:22:17 +01:00
Toto Yullian
da59c14231 Translated using Weblate (Indonesian)
Currently translated at 18.1% (21 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-01-19 17:22:16 +01:00
Hosted Weblate
e0b3e41c9e Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-19 17:22:15 +01:00
Elian Doran
5d1a63bce0 fix(print): crash when printing presentation 2026-01-19 14:55:33 +02:00
Elian Doran
84cf4ef4a3 fix(print): only first page is shown 2026-01-19 13:51:27 +02:00
Elian Doran
ec4b6f0a90 chore(deps): update dependency happy-dom to v20.3.3 (#8426) 2026-01-19 08:48:02 +02:00
renovate[bot]
60dbdbeb71 chore(deps): update dependency happy-dom to v20.3.3 2026-01-19 02:05:32 +00:00
Elian Doran
418a546583 fix(print): clicking inside print preview hides it 2026-01-18 22:39:09 +02:00
Elian Doran
e6380b87b6 fix(print): some collections not rendered at all 2026-01-18 22:26:13 +02:00
Elian Doran
1d3d214101 fix(print): not printing at all 2026-01-18 22:05:16 +02:00
Elian Doran
a38067560b SQL console improvements (#8418) 2026-01-18 18:52:14 +02:00
Elian Doran
eac7235199 chore(sql_console): address requested changes 2026-01-18 18:38:14 +02:00
Elian Doran
4c55e857b8 fix(server): build failing due to import 2026-01-18 18:33:48 +02:00
Elian Doran
e33950e000 test(client): fix broken tests after change in shortcut behaviour 2026-01-18 18:22:21 +02:00
Elian Doran
fc0ccbfcf5 chore(sql_console): address requested changes 2026-01-18 18:19:35 +02:00
Elian Doran
4a82bbb035 docs(user): update SQL console based on new interaction 2026-01-18 18:06:30 +02:00
Elian Doran
57d894e765 feat(sql_console): enable read-only for saved notes 2026-01-18 17:47:09 +02:00
Elian Doran
97dfad419c docs(user): refresh photo for SQL console 2026-01-18 17:35:01 +02:00
Elian Doran
bfc521fdc0 fix(sql_console): enforce vertical layout 2026-01-18 17:11:03 +02:00
Elian Doran
197fa90176 style(sql_console): improve style for frozen cell 2026-01-18 13:09:46 +02:00
Elian Doran
0844914e11 style(sql_console): improve style for highlighted range 2026-01-18 13:05:18 +02:00
Elian Doran
c376b0bbe2 style(sql_console): improve filter spacing 2026-01-18 12:35:42 +02:00
Elian Doran
4491086c55 style(sql_console): improve header & footer inputs 2026-01-18 12:34:00 +02:00
Elian Doran
791697369d style(sql_console): remove background for footer 2026-01-18 12:22:53 +02:00
Elian Doran
28d0bfd229 chore(sql_console): reducing padding in footer 2026-01-18 12:12:05 +02:00
Elian Doran
8182a04eae fix(sql_console): not refreshing when switching between notes 2026-01-18 12:09:01 +02:00
Elian Doran
711828d6b4 fix(shortcuts): triggering in bubbling phase, not capturing 2026-01-18 12:07:23 +02:00
Elian Doran
69e88c1d9f chore(sql_console): set gutter color 2026-01-18 11:48:34 +02:00
Elian Doran
748b87da9a feat(sql_console): improve display for statements 2026-01-18 11:34:31 +02:00
Elian Doran
94dca4cd87 feat(sql_console): report errors inline 2026-01-18 11:13:31 +02:00
Elian Doran
7179701e0f feat(sql_console): improve no results 2026-01-18 10:58:32 +02:00
Elian Doran
af5061646c feat(sql_console): add not yet executed message 2026-01-18 10:36:44 +02:00
Elian Doran
9c4163ad3a feat(sql_console): page size selector 2026-01-18 10:24:55 +02:00
Elian Doran
46c3f5296a chore(sql_console): full-height table 2026-01-18 10:20:15 +02:00
Elian Doran
ebadcfd844 feat(sql_console): enable pagination 2026-01-18 10:14:55 +02:00
Elian Doran
b7d4947462 Merge remote-tracking branch 'origin/main' into feature/sql_console_improvements 2026-01-18 10:08:48 +02:00
Elian Doran
a599526dea chore(deps): update dependency @smithy/middleware-retry to v4.4.24 (#8420) 2026-01-18 08:34:59 +02:00
Elian Doran
c4f166fe12 chore(deps): update dependency webdriverio to v9.23.2 (#8421) 2026-01-18 08:34:19 +02:00
Elian Doran
a8ae91aa3b Translations update from Hosted Weblate (#8425) 2026-01-18 08:33:02 +02:00
Elian Doran
82b3692acb fix(deps): update dependency better-sqlite3 to v12.6.2 (#8422) 2026-01-18 08:32:12 +02:00
Hosted Weblate
432c054b68 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-18 06:25:42 +00:00
Elian Doran
dcd8bfa255 fix(deps): update dependency mind-elixir to v5.6.1 (#8423) 2026-01-18 08:25:28 +02:00
renovate[bot]
c287a2ae97 fix(deps): update dependency mind-elixir to v5.6.1 2026-01-18 05:39:59 +00:00
renovate[bot]
8fdadb3798 fix(deps): update dependency jquery to v4 2026-01-18 01:40:46 +00:00
renovate[bot]
3fce4fc66c fix(deps): update dependency better-sqlite3 to v12.6.2 2026-01-18 01:39:14 +00:00
renovate[bot]
d83d7ed106 chore(deps): update dependency webdriverio to v9.23.2 2026-01-18 01:38:26 +00:00
renovate[bot]
1b812f1886 chore(deps): update dependency @smithy/middleware-retry to v4.4.24 2026-01-18 01:37:35 +00:00
Elian Doran
56fcc7adcc chore(sql_console): fix lint warnings 2026-01-17 22:41:55 +02:00
Elian Doran
fb0c7359f1 chore(sql_console): fix typecheck issue 2026-01-17 22:39:32 +02:00
Elian Doran
4c4e5b85e9 chore(sql_console): integrate table reference 2026-01-17 22:38:18 +02:00
Elian Doran
476247beb5 feat(sql_console): increase size for results 2026-01-17 22:29:20 +02:00
Elian Doran
2c87f609f3 feat(sql_console): add filter 2026-01-17 22:13:13 +02:00
Elian Doran
bc79ff6845 feat(sql_console): row numbers 2026-01-17 22:09:24 +02:00
Elian Doran
f10373d54f feat(sql_console): add clipboard 2026-01-17 22:02:47 +02:00
Elian Doran
630d16b722 feat(sql_console): enable sorting 2026-01-17 21:47:02 +02:00
Elian Doran
769f3db21c feat(sql_console): make columns resizable 2026-01-17 21:45:10 +02:00
Elian Doran
c6896a4b33 feat(sql_console): reduce column width 2026-01-17 21:43:21 +02:00
Elian Doran
7c18025098 feat(sql_console): reduce spacing to fit more content 2026-01-17 21:41:06 +02:00
Elian Doran
6ae74b3181 feat(sql_console): make scrolls and headers always visible 2026-01-17 21:35:58 +02:00
Elian Doran
2ecfbbf284 feat(sql_console): improve fit & solve build error 2026-01-17 21:26:44 +02:00
Elian Doran
781de9a1fb feat(sql_console): basic integration of Tabulator 2026-01-17 21:22:22 +02:00
Elian Doran
6972a4b901 fix(note_detail): preview leaks between mermaid & SQL console 2026-01-17 20:58:26 +02:00
Elian Doran
52ed1750ac fix(sql_console): runtime error for inline title 2026-01-17 20:52:53 +02:00
Elian Doran
9010e0b1ce chore(sql_console): reverse preview and code sections 2026-01-17 20:44:03 +02:00
Elian Doran
5053e74447 fix(sql_console): note type switcher showing up 2026-01-17 20:41:45 +02:00
Elian Doran
f294276849 fix(sql_console): full-height not respected 2026-01-17 20:38:44 +02:00
Elian Doran
0740788cc8 chore(sql_console): link stylesheet 2026-01-17 20:32:28 +02:00
Elian Doran
9bac07ce62 chore(sql_console): integrate results into preview of split 2026-01-17 20:29:15 +02:00
Elian Doran
3d8289d394 chore(note_detail): get code editor to show 2026-01-17 20:12:16 +02:00
Elian Doran
5a60fdad8a chore(note_detail): map SQL console to own type widget 2026-01-17 20:09:25 +02:00
Elian Doran
62cca5a96b Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-01-17 18:57:03 +02:00
Elian Doran
74548d638e docs(user): sync 2026-01-17 18:13:40 +02:00
Elian Doran
e0ccf30f4f docs(user): add missing links from Evernote documentation 2026-01-17 17:59:30 +02:00
Elian Doran
d2c6081537 feat(edit-docs): remove data-list-item-id 2026-01-17 17:54:10 +02:00
Elian Doran
bd933f2c4c Translations update from Hosted Weblate (#8416) 2026-01-17 17:43:15 +02:00
Yatrik Patel
1d898d618e Translated using Weblate (Hindi)
Currently translated at 7.7% (30 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-17 15:40:43 +00:00
Yatrik Patel
11c8e5b3b2 Translated using Weblate (Hindi)
Currently translated at 35.5% (54 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-17 15:40:43 +00:00
Hosted Weblate
fe4c3ffecb Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-17 15:40:42 +00:00
Elian Doran
334a96186e chore(deps): update dependency electron to v40 (#8415) 2026-01-17 17:40:34 +02:00
Elian Doran
34b2df705b Fixes to Markdown export (#8417) 2026-01-17 14:19:36 +02:00
Elian Doran
5a7fc1c8b6 chore(markdown): address requested changes 2026-01-17 13:22:24 +02:00
Elian Doran
fabab6abb1 refactor(export/markdown): spacing issues 2026-01-17 13:16:21 +02:00
Elian Doran
0c9c20c0c5 docs(user): fix escapes 2026-01-17 13:11:53 +02:00
Elian Doran
67cc1113b1 chore(export/markdown): render emphasis with underscore 2026-01-17 13:05:29 +02:00
Elian Doran
3aacd255f4 chore(export/markdown): add test for jQuery-like text inside table 2026-01-17 12:58:24 +02:00
renovate[bot]
ccfda21413 chore(deps): update dependency electron to v40 2026-01-17 10:53:59 +00:00
Elian Doran
46c88506cc chore(deps): update dependency ejs to v4 (#8396) 2026-01-17 12:52:06 +02:00
Elian Doran
51157e1979 fix(export/markdown): error due to namespace usage 2026-01-17 12:47:34 +02:00
Elian Doran
bfb6d975ff fix(export/markdown): type error due to blankReplacement signature change 2026-01-17 12:46:55 +02:00
Elian Doran
aa01bc1457 feat(markdown): switch to turnish instead of turndown 2026-01-17 12:44:30 +02:00
Elian Doran
5600f1b7b1 chore(deps): update dependency node-abi to v4.25.0 (#8414) 2026-01-17 12:17:38 +02:00
Elian Doran
a169db807c fix(server): crashing due to EJS handling 2026-01-17 12:17:15 +02:00
Elian Doran
f40348daff chore(deps): update dependency happy-dom to v20.3.1 (#8405) 2026-01-17 12:09:57 +02:00
renovate[bot]
f63042ef87 chore(deps): update dependency ejs to v4 2026-01-17 10:01:09 +00:00
renovate[bot]
d148c9d1c6 chore(deps): update dependency node-abi to v4.25.0 2026-01-17 10:00:12 +00:00
renovate[bot]
f72929ca13 chore(deps): update dependency happy-dom to v20.3.1 2026-01-17 09:59:13 +00:00
Elian Doran
cc3e3ca4d4 fix(deps): update ckeditor monorepo to v47.4.0 (#8395) 2026-01-17 11:55:54 +02:00
Elian Doran
8fad664a6d Translations update from Hosted Weblate (#8413) 2026-01-16 23:16:08 +02:00
Kf637
f1946c1386 Translated using Weblate (Norwegian Bokmål)
Currently translated at 6.7% (26 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/nb_NO/
2026-01-16 22:08:01 +01:00
Kf637
ea8bd0136f Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-16 22:08:00 +01:00
Elian Doran
4a3f72ae50 Translations update from Hosted Weblate (#8411) 2026-01-16 19:14:11 +02:00
Hosted Weblate
c944762ef6 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-16 14:25:37 +00:00
Elian Doran
f6924d7fda feat(import/zip): remove extension from title for PDF imports 2026-01-16 16:25:15 +02:00
Elian Doran
3a0880fcd6 feat(import/single): remove extension from title for PDF imports 2026-01-16 12:01:11 +02:00
Elian Doran
df62dc87b2 feat(notes): add default icon for PDFs 2026-01-16 11:47:42 +02:00
Elian Doran
d42679315e refactor(server): use common logic for icons 2026-01-16 11:42:37 +02:00
Elian Doran
2a19be5ab6 refactor(client): extract fnote icon logic in commons 2026-01-16 09:35:51 +02:00
renovate[bot]
33bbe994d7 fix(deps): update ckeditor monorepo to v47.4.0 2026-01-16 06:55:00 +00:00
Elian Doran
04c598caea build(deps): bump diff from 4.0.2 to 8.0.3 (#8390) 2026-01-16 08:50:51 +02:00
Elian Doran
3577688bf9 fix(deps): update dependency @codemirror/view to v6.39.11 (#8392) 2026-01-16 08:50:34 +02:00
Elian Doran
e0439655df chore(deps): update dependency @smithy/middleware-retry to v4.4.23 (#8403) 2026-01-16 08:47:12 +02:00
Elian Doran
84f944f78a chore(deps): update dependency @types/node to v24.10.9 (#8404) 2026-01-16 08:46:51 +02:00
Elian Doran
022b6df959 chore(deps): update dependency stylelint to v17 (#8406) 2026-01-16 08:46:18 +02:00
renovate[bot]
9e3e92669f chore(deps): update dependency stylelint to v17 2026-01-16 00:58:48 +00:00
renovate[bot]
35b96a71fc chore(deps): update dependency @types/node to v24.10.9 2026-01-16 00:51:41 +00:00
renovate[bot]
4b78de6726 chore(deps): update dependency @smithy/middleware-retry to v4.4.23 2026-01-16 00:51:08 +00:00
Elian Doran
4771e02909 chore(deps): update dependency happy-dom to v20.3.0 (#8393) 2026-01-15 08:49:43 +02:00
Elian Doran
03dffdb65f chore(deps): update node.js to v24.13.0 (#8394) 2026-01-15 08:49:01 +02:00
Elian Doran
859a3948cd Translations update from Hosted Weblate (#8398) 2026-01-15 08:09:44 +02:00
Hasan Kara
161aa625e6 Translated using Weblate (Turkish)
Currently translated at 9.2% (14 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/tr/
2026-01-15 04:09:54 +01:00
Hasan Kara
28fd945e80 Translated using Weblate (Turkish)
Currently translated at 3.6% (14 of 388 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/tr/
2026-01-15 04:09:53 +01:00
renovate[bot]
748fb0bf05 chore(deps): update node.js to v24.13.0 2026-01-15 02:00:34 +00:00
renovate[bot]
98e1d0afd9 chore(deps): update dependency happy-dom to v20.3.0 2026-01-15 02:00:27 +00:00
renovate[bot]
9c61ce1835 fix(deps): update dependency @codemirror/view to v6.39.11 2026-01-15 01:59:39 +00:00
dependabot[bot]
c3a5705be0 build(deps): bump diff from 4.0.2 to 8.0.3
Bumps [diff](https://github.com/kpdecker/jsdiff) from 4.0.2 to 8.0.3.
- [Changelog](https://github.com/kpdecker/jsdiff/blob/master/release-notes.md)
- [Commits](https://github.com/kpdecker/jsdiff/compare/v4.0.2...v8.0.3)

---
updated-dependencies:
- dependency-name: diff
  dependency-version: 8.0.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-14 22:53:04 +00:00
Elian Doran
9d0fa9f7ca Fix sorting (#7878) 2026-01-14 17:18:41 +02:00
Elian Doran
cc4ceb975e fix(tree): not reacting to note reordering (e.g. sort) 2026-01-14 17:15:29 +02:00
Elian Doran
8a6495a0bd Translations update from Hosted Weblate (#8386) 2026-01-14 16:48:46 +02:00
Elian Doran
1d95392d22 docs(user): improve Evernote documentation 2026-01-14 16:06:58 +02:00
Hosted Weblate
93dd08d629 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-14 14:06:53 +00:00
perfectra1n
6c656c73a3 feat(dev): merge main into feature branch 2025-12-27 22:39:07 -08:00
perfectra1n
09df73e125 fix(fts): remove migration statements of old 0236 2025-11-28 21:36:12 -08:00
perfectra1n
f21aa321f6 fix(fts): remove index of components 2025-11-28 21:29:48 -08:00
perfectra1n
7be8b6c71e fix(fts): merge the two migrations into one file 2025-11-28 21:27:01 -08:00
perfectra1n
bb8e5ebd4a fix(fts): fix suggestions from elian 2025-11-28 21:25:24 -08:00
perfectra1n
6b8b71f7d1 feat(fts): implement missing unit tests 2025-11-28 21:12:39 -08:00
perfectra1n
191a18d7f6 feat(fts): add fts to in-memory sqlite for testing 2025-11-28 21:08:49 -08:00
perfectra1n
574a3441ee feat(fts): update imports from breaking up large fts_search file 2025-11-28 20:59:50 -08:00
perfectra1n
9940ee3bee feat(fts): break up the huge fts_search into smaller files 2025-11-28 20:57:18 -08:00
Romain DEP.
a1c0314334 chore(sorting): add test cases for previous commit and increase test coverage 2025-11-28 23:28:14 +01:00
Romain DEP.
3ecdcd9ea0 fix(sorting): BC! give precedence to #top notes over #sortFolderFirst 2025-11-28 23:22:20 +01:00
perfectra1n
41f6fedc61 feat(fts5): get rid of search comparison code 2025-11-24 14:24:07 -08:00
perfectra1n
0ddf48c460 feat(fts5): add more unit tests for search 2025-11-24 13:30:40 -08:00
perfectra1n
3957d789da feat(fts5): also create an fts5 index for attributes, and allow them to be searchable using fts5 indexes 2025-11-18 13:08:11 -08:00
perfectra1n
15719a1ee9 fix(fts5): correctly allow for exact word searches with fts5 2025-11-18 13:07:51 -08:00
perfectra1n
334c7dd27a Merge branch 'main' into feat/rice-searching-with-sqlite
Resolved conflicts by:
- Combining imports from both branches in search.ts (added
hoistedNoteService and beccaService alongside existing ftsSearchService
and log imports)
- Integrating FTS5 search optimization from feature branch with flatText
attribute search logic from main in note_content_fulltext.ts
- Maintained backward compatibility by keeping fallback search
implementation for cases where FTS5 is not available or not suitable
2025-11-16 14:23:01 -08:00
perfectra1n
30da95d75a feat(search): update fulltext search and add stress test improvements
- Modified note_content_fulltext.ts for enhanced search capabilities
- Updated becca_mocking.ts for better test support
- Improved stress-test-populate.ts script
2025-11-16 14:20:46 -08:00
perfectra1n
09ff9ccc65 feat(dev): add new stress test population script 2025-11-15 16:54:09 -08:00
perf3ct
5f1773609f fix(tests): rename some of the silly-ily named tests 2025-11-04 15:56:49 -08:00
perf3ct
da0302066d fix(tests): resolve issues with new search tests not passing 2025-11-04 15:55:42 -08:00
perf3ct
942647ab9c fix(search): get rid of exporting dbConnection 2025-11-04 14:47:46 -08:00
perf3ct
b8aa7402d8 feat(tests): create a ton of tests for the various search capabilities that we support 2025-11-04 14:34:50 -08:00
perf3ct
052e28ab1b feat(search): if the search is empty, return all notes 2025-11-04 11:59:41 -08:00
perf3ct
16912e606e fix(search): resolve compilation issue due to performance log in new search 2025-11-03 12:04:00 -08:00
Jon Fuller
321752ac18 Merge branch 'main' into feat/rice-searching-with-sqlite 2025-11-03 11:47:44 -08:00
perf3ct
10988095c2 feat(search): get the correct comparison and rice out the fts5 search 2025-10-27 14:37:44 -07:00
perf3ct
253da139de feat(search): try again to get fts5 searching done well 2025-10-24 21:47:06 -07:00
Jon Fuller
d992a5e4a2 Merge branch 'main' into feat/rice-searching-with-sqlite 2025-10-24 09:18:11 -07:00
perf3ct
58c225237c feat(search): try a ground-up sqlite search approach 2025-09-03 00:34:55 +00:00
perf3ct
d074841885 Revert "feat(search): try to get fts search to work in large environments"
This reverts commit 053f722cb8.
2025-09-02 19:24:50 +00:00
perf3ct
06b2d71b27 Revert "feat(search): try to decrease complexity"
This reverts commit 5b79e0d71e.
2025-09-02 19:24:47 +00:00
perf3ct
0afb8a11c8 Revert "feat(search): try to deal with huge dbs, might need to squash later"
This reverts commit 37d0136c50.
2025-09-02 19:24:46 +00:00
perf3ct
f529ddc601 Revert "feat(search): further improve fts search"
This reverts commit 7c5553bd4b.
2025-09-02 19:24:45 +00:00
perf3ct
8572f82e0a Revert "feat(search): I honestly have no idea what I'm doing"
This reverts commit b09a2c386d.
2025-09-02 19:24:44 +00:00
perf3ct
b09a2c386d feat(search): I honestly have no idea what I'm doing 2025-09-01 22:29:59 -07:00
perf3ct
7c5553bd4b feat(search): further improve fts search 2025-09-01 21:40:05 -07:00
perf3ct
37d0136c50 feat(search): try to deal with huge dbs, might need to squash later 2025-09-01 04:33:10 +00:00
perf3ct
5b79e0d71e feat(search): try to decrease complexity 2025-08-30 22:30:01 -07:00
perf3ct
053f722cb8 feat(search): try to get fts search to work in large environments 2025-08-31 03:15:29 +00:00
perf3ct
21aaec2c38 feat(search): also fix tests for new fts functionality 2025-08-30 20:48:42 +00:00
perf3ct
1db4971da6 feat(search): implement FST5 w/ sqlite for faster and better searching
feat(search): don't limit the number of blobs to put in virtual tables

fix(search): improve FTS triggers to handle all SQL operations correctly

The root cause of FTS index issues during import was that database triggers
weren't properly handling all SQL operations, particularly upsert operations
(INSERT ... ON CONFLICT ... DO UPDATE) that are commonly used during imports.

Key improvements:
- Fixed INSERT trigger to handle INSERT OR REPLACE operations
- Updated UPDATE trigger to fire on ANY change (not just specific columns)
- Improved blob triggers to use INSERT OR REPLACE for atomic updates
- Added proper handling for notes created before their blobs (import scenario)
- Added triggers for protection state changes
- All triggers now use LEFT JOIN to handle missing blobs gracefully

This ensures the FTS index stays synchronized even when:
- Entity events are disabled during import
- Notes are re-imported (upsert operations)
- Blobs are deduplicated across notes
- Notes are created before their content blobs

The solution works entirely at the database level through triggers,
removing the need for application-level workarounds.

fix(search): consolidate FTS trigger fixes into migration 234

- Merged improved trigger logic from migration 235 into 234
- Deleted unnecessary migration 235 since DB version is still 234
- Ensures triggers handle all SQL operations (INSERT OR REPLACE, upserts)
- Fixes FTS indexing for imported notes by handling missing blobs
- Schema.sql and migration 234 now have identical trigger implementations
2025-08-30 20:39:40 +00:00
316 changed files with 25402 additions and 9038 deletions

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Check if PRs have conflicts
uses: eps1lon/actions-label-merge-conflict@v3
if: github.repository == ${{ vars.REPO_MAIN }}
if: ${{ github.repository == vars.REPO_MAIN }}
with:
dirtyLabel: "merge-conflicts"
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"

View File

@@ -67,7 +67,7 @@ jobs:
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
if: github.repository == ${{ vars.REPO_MAIN }}
if: ${{ github.repository == vars.REPO_MAIN }}
with:
project_name: "trilium-docs"
comment_body: "📚 Documentation preview is ready"

View File

@@ -271,13 +271,16 @@ jobs:
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
# Create and push the manifest list with both the branch/tag name and the commit SHA
# Note: Images are only pushed to GHCR during build, so we always reference GHCR sources
# and copy to DockerHub using imagetools create
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
# Copy from GHCR to DockerHub (source digests only exist on GHCR)
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
@@ -287,9 +290,10 @@ jobs:
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
# Copy stable tag from GHCR to DockerHub
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
@@ -301,7 +305,7 @@ jobs:
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
fi

View File

@@ -26,7 +26,7 @@ permissions:
jobs:
nightly-electron:
if: github.repository == ${{ vars.REPO_MAIN }}
if: ${{ github.repository == vars.REPO_MAIN }}
name: Deploy nightly
strategy:
fail-fast: false
@@ -109,7 +109,7 @@ jobs:
path: apps/desktop/upload
nightly-server:
if: github.repository == ${{ vars.REPO_MAIN }}
if: ${{ github.repository == vars.REPO_MAIN }}
name: Deploy server nightly
strategy:
fail-fast: false

69
.github/workflows/web-clipper.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Deploy web clipper extension
on:
push:
branches:
- main
paths:
- "apps/web-clipper/**"
tags:
- "web-clipper-v*"
pull_request:
paths:
- "apps/web-clipper/**"
permissions:
contents: write
discussions: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
name: Build web clipper extension
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
node-version: 24
cache: "pnpm"
- name: Install dependencies
run: pnpm install --filter web-clipper --frozen-lockfile --ignore-scripts
- name: Build the web clipper extension
run: |
pnpm --filter web-clipper zip
pnpm --filter web-clipper zip:firefox
- name: Upload build artifacts
uses: actions/upload-artifact@v6
if: ${{ !startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
name: web-clipper-extension
path: apps/web-clipper/.output/*.zip
include-hidden-files: true
if-no-files-found: error
compression-level: 0
- name: Release web clipper extension
uses: softprops/action-gh-release@v2.5.0
if: ${{ startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
draft: false
fail_on_unmatched_files: true
files: apps/web-clipper/.output/*.zip
discussion_category_name: Releases
make_latest: false
token: ${{ secrets.RELEASE_PAT }}

View File

@@ -9,13 +9,13 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.28.0",
"packageManager": "pnpm@10.28.2",
"devDependencies": {
"@redocly/cli": "2.14.5",
"@redocly/cli": "2.15.0",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"typedoc": "0.28.16",
"typedoc-plugin-missing-exports": "4.1.2"
}

View File

@@ -13,13 +13,14 @@
<body id="trilium-app">
<noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="context-menu-cover"></div>
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
<!-- Required to match the PWA's top bar color with the theme -->
<!-- This works even when the user directly changes --root-background in CSS -->
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
<script src="./index.ts" type="module"></script>
<script src="./src/index.ts" type="module"></script>
<!-- Required for correct loading of scripts in Electron -->
<script>

View File

@@ -27,14 +27,14 @@
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.5.1",
"@preact/signals": "2.6.2",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"@zumer/snapdom": "2.0.1",
"@zumer/snapdom": "2.0.2",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
@@ -43,24 +43,24 @@
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "17.0.0",
"i18next": "25.7.4",
"globals": "17.2.0",
"i18next": "25.8.0",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.27",
"katex": "0.16.28",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "17.0.1",
"mermaid": "11.12.2",
"mind-elixir": "5.5.0",
"mind-elixir": "5.6.1",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.2",
"react-i18next": "16.5.3",
"react-i18next": "16.5.4",
"react-window": "2.2.5",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
@@ -78,9 +78,9 @@
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.1.0",
"lightningcss": "1.30.2",
"happy-dom": "20.4.0",
"lightningcss": "1.31.1",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.4"
"vite-plugin-static-copy": "3.2.0"
}
}

View File

@@ -1,6 +1,6 @@
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { SqlExecuteResults } from "@triliumnext/commons";
import { SqlExecuteResponse } from "@triliumnext/commons";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
@@ -410,7 +410,7 @@ type EventMappings = {
addNewLabel: CommandData;
addNewRelation: CommandData;
sqlQueryResults: CommandData & {
results: SqlExecuteResults;
response: SqlExecuteResponse;
};
readOnlyTemporarilyDisabled: {
noteContext: NoteContext;
@@ -542,7 +542,6 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
export class AppContext extends Component {
isMainWindow: boolean;
windowId: string;
components: Component[];
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
tabManager!: TabManager;
@@ -551,11 +550,10 @@ export class AppContext extends Component {
lastSearchString?: string;
constructor(isMainWindow: boolean, windowId: string) {
constructor(isMainWindow: boolean) {
super();
this.isMainWindow = isMainWindow;
this.windowId = windowId;
// non-widget/layout components needed for the application
this.components = [];
this.beforeUnloadListeners = [];
@@ -685,7 +683,8 @@ export class AppContext extends Component {
this.beforeUnloadListeners = this.beforeUnloadListeners.filter(l => l !== listener);
}
}
const appContext = new AppContext(window.glob.isMainWindow, window.glob.windowId);
const appContext = new AppContext(window.glob.isMainWindow);
// we should save all outstanding changes before the page/app is closed
$(window).on("beforeunload", () => {

View File

@@ -1,16 +1,17 @@
import utils from "../services/utils.js";
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
import bundleService from "../services/bundle.js";
import dateNoteService from "../services/date_notes.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import linkService from "../services/link.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import utils from "../services/utils.js";
import ws from "../services/ws.js";
import appContext, { type NoteCommandData } from "./app_context.js";
import Component from "./component.js";
import toastService from "../services/toast.js";
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";
import { CreateChildrenResponse, SqlExecuteResponse } from "@triliumnext/commons";
export default class Entrypoints extends Component {
constructor() {
@@ -142,15 +143,14 @@ export default class Entrypoints extends Component {
}
async openInWindowCommand({ notePath, hoistedNoteId, viewScope }: NoteCommandData) {
const extraWindowId = utils.randomString(4);
const extraWindowHash = linkService.calculateHash({ notePath, hoistedNoteId, viewScope });
if (utils.isElectron()) {
const { ipcRenderer } = utils.dynamicRequire("electron");
ipcRenderer.send("create-extra-window", { extraWindowId, extraWindowHash });
ipcRenderer.send("create-extra-window", { extraWindowHash });
} else {
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=${extraWindowId}${extraWindowHash}`;
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`;
window.open(url, "", "width=1000,height=800");
}
@@ -188,13 +188,8 @@ export default class Entrypoints extends Component {
} else if (note.mime.endsWith("env=backend")) {
await server.post(`script/run/${note.noteId}`);
} else if (note.mime === "text/x-sqlite;schema=trilium") {
const resp = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
if (!resp.success) {
toastService.showError(t("entrypoints.sql-error", { message: resp.error }));
}
await appContext.triggerEvent("sqlQueryResults", { ntxId: ntxId, results: resp.results });
const response = await server.post<SqlExecuteResponse>(`sql/execute/${note.noteId}`);
await appContext.triggerEvent("sqlQueryResults", { ntxId, response });
}
toastService.showMessage(t("entrypoints.note-executed"));

View File

@@ -11,8 +11,6 @@ import linkService from "../services/link.js";
import type { EventData } from "./app_context.js";
import type FNote from "../entities/fnote.js";
const MAX_SAVED_WINDOWS = 10;
interface TabState {
contexts: NoteContext[];
position: number;
@@ -27,13 +25,6 @@ interface NoteContextState {
viewScope: Record<string, any>;
}
interface WindowState {
windowId: string;
createdAt: number;
closedAt: number;
contexts: NoteContextState[];
}
export default class TabManager extends Component {
public children: NoteContext[];
public mutex: Mutex;
@@ -50,6 +41,9 @@ export default class TabManager extends Component {
this.recentlyClosedTabs = [];
this.tabsUpdate = new SpacedUpdate(async () => {
if (!appContext.isMainWindow) {
return;
}
if (options.is("databaseReadonly")) {
return;
}
@@ -58,21 +52,9 @@ export default class TabManager extends Component {
.map((nc) => nc.getPojoState())
.filter((t) => !!t);
// Update the current windows openNoteContexts in options
const savedWindows = options.getJson("openNoteContexts") || [];
const win = savedWindows.find((w: WindowState) => w.windowId === appContext.windowId);
if (win) {
win.contexts = openNoteContexts;
} else {
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: 0,
contexts: openNoteContexts
} as WindowState);
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
await server.put("options", {
openNoteContexts: JSON.stringify(openNoteContexts)
});
});
appContext.addBeforeUnloadListener(this);
@@ -87,13 +69,8 @@ export default class TabManager extends Component {
}
async loadTabs() {
// Get the current windows openNoteContexts
const savedWindows = options.getJson("openNoteContexts") || [];
const currentWin = savedWindows.find(w => w.windowId === appContext.windowId);
const openNoteContexts = currentWin ? currentWin.contexts : undefined;
try {
const noteContextsToOpen = openNoteContexts || [];
const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || [];
// preload all notes at once
await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) =>
@@ -142,51 +119,6 @@ export default class TabManager extends Component {
}
});
// Save window contents
if (currentWin as WindowState) {
currentWin.createdAt = Date.now();
currentWin.closedAt = 0;
currentWin.contexts = filteredNoteContexts;
} else {
if (savedWindows?.length >= MAX_SAVED_WINDOWS) {
// Filter out the oldest entry
// 1) Never remove the "main" window
// 2) Prefer removing the oldest closed window (closedAt !== 0)
// 3) If no closed window exists, remove the window with the oldest created window
let oldestClosedIndex = -1;
let oldestClosedTime = Infinity;
let oldestCreatedIndex = -1;
let oldestCreatedTime = Infinity;
savedWindows.forEach((w: WindowState, i: number) => {
if (w.windowId === "main") return;
if (w.closedAt !== 0) {
if (w.closedAt < oldestClosedTime) {
oldestClosedTime = w.closedAt;
oldestClosedIndex = i;
}
} else {
if (w.createdAt < oldestCreatedTime) {
oldestCreatedTime = w.createdAt;
oldestCreatedIndex = i;
}
}
});
const indexToRemove = oldestClosedIndex !== -1 ? oldestClosedIndex : oldestCreatedIndex;
if (indexToRemove !== -1) {
savedWindows.splice(indexToRemove, 1);
}
}
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: 0,
contexts: filteredNoteContexts
} as WindowState);
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
// if there's a notePath in the URL, make sure it's open and active
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
if (parsedFromUrl.notePath) {

View File

@@ -99,15 +99,22 @@ function initFullScreenDetection(currentWindow: Electron.BrowserWindow) {
}
function initTransparencyEffects(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {
const material = style.getPropertyValue("--background-material").trim();
if (window.glob.platform === "win32") {
const material = style.getPropertyValue("--background-material");
// TriliumNextTODO: find a nicer way to make TypeScript happy unfortunately TS did not like Array.includes here
const bgMaterialOptions = ["auto", "none", "mica", "acrylic", "tabbed"] as const;
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
if (foundBgMaterialOption) {
currentWindow.setBackgroundMaterial(foundBgMaterialOption);
}
}
if (window.glob.platform === "darwin") {
const bgMaterialOptions = [ "popover", "tooltip", "titlebar", "selection", "menu", "sidebar", "header", "sheet", "window", "hud", "fullscreen-ui", "content", "under-window", "under-page" ] as const;
const foundBgMaterialOption = bgMaterialOptions.find((bgMaterialOption) => material === bgMaterialOption);
if (foundBgMaterialOption) {
currentWindow.setVibrancy(foundBgMaterialOption);
}
}
}
/**

View File

@@ -1,4 +1,4 @@
import { MIME_TYPES_DICT } from "@triliumnext/commons";
import { getNoteIcon } from "@triliumnext/commons";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
@@ -13,25 +13,6 @@ import type { AttributeType, default as FAttribute } from "./fattribute.js";
const LABEL = "label";
const RELATION = "relation";
export const NOTE_TYPE_ICONS = {
file: "bx bx-file",
image: "bx bx-image",
code: "bx bx-code",
render: "bx bx-extension",
search: "bx bx-file-find",
relationMap: "bx bxs-network-chart",
book: "bx bx-book",
noteMap: "bx bxs-network-chart",
mermaid: "bx bx-selection",
canvas: "bx bx-pen",
webView: "bx bx-globe-alt",
launcher: "bx bx-link",
doc: "bx bxs-file-doc",
contentWidget: "bx bxs-widget",
mindMap: "bx bx-sitemap",
aiChat: "bx bx-bot"
};
/**
* There are many different Note types, some of which are entirely opaque to the
* end user. Those types should be used only for checking against, they are
@@ -582,32 +563,18 @@ export default class FNote {
}
getIcon() {
return `tn-icon ${this.#getIconInternal()}`;
}
#getIconInternal() {
const iconClassLabels = this.getLabels("iconClass");
const workspaceIconClass = this.getWorkspaceIconClass();
if (iconClassLabels && iconClassLabels.length > 0) {
return iconClassLabels[0].value;
} else if (workspaceIconClass) {
return workspaceIconClass;
} else if (this.noteId === "root") {
return "bx bx-home-alt-2";
}
if (this.noteId === "_share") {
return "bx bx-share-alt";
} else if (this.type === "text") {
if (this.isFolder()) {
return "bx bx-folder";
}
return "bx bx-note";
} else if (this.type === "code") {
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
}
return NOTE_TYPE_ICONS[this.type];
const icon = getNoteIcon({
noteId: this.noteId,
type: this.type,
mime: this.mime,
iconClass: iconClassLabels.length > 0 ? iconClassLabels[0].value : undefined,
workspaceIconClass,
isFolder: this.isFolder.bind(this)
});
return `tn-icon ${icon}`;
}
getColorClass() {

View File

@@ -16,10 +16,21 @@ async function initJQuery() {
const $ = (await import("jquery")).default;
window.$ = $;
window.jQuery = $;
// Polyfill removed jQuery methods for autocomplete.js compatibility
($ as any).isArray = Array.isArray;
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
($ as any).isPlainObject = function(obj: any) {
if (obj == null || typeof obj !== 'object') { return false; }
const proto = Object.getPrototypeOf(obj);
if (proto === null) { return true; }
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor === Object;
};
}
async function setupGlob() {
const response = await fetch(`/bootstrap${window.location.search}`);
const response = await fetch(`./bootstrap${window.location.search}`);
const json = await response.json();
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
@@ -39,22 +50,25 @@ async function loadBootstrapCss() {
}
function loadStylesheets() {
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const { device, assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const cssToLoad: string[] = [];
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
cssToLoad.push(`api/fonts`);
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
if (device !== "print") {
cssToLoad.push(`${assetPath}/stylesheets/ckeditor-theme.css`);
cssToLoad.push(`api/fonts`);
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`);
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`);
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`);
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
for (const href of cssToLoad) {
const linkEl = document.createElement("link");
@@ -71,7 +85,7 @@ function loadIcons() {
}
function setBodyAttributes() {
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale, isMainWindow } = window.glob;
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
const classesToSet = [
device,
`heading-style-${headingStyle}`,
@@ -79,8 +93,7 @@ function setBodyAttributes() {
`platform-${platform}`,
isElectron && "electron",
hasNativeTitleBar && "native-titlebar",
hasBackgroundEffects && "background-effects",
!isMainWindow && 'extra-window'
hasBackgroundEffects && "background-effects"
].filter(Boolean) as string[];
for (const classToSet of classesToSet) {
@@ -92,10 +105,17 @@ function setBodyAttributes() {
}
async function loadScripts() {
if (glob.device === "mobile") {
await import("./mobile.js");
} else {
await import("./desktop.js");
switch (glob.device) {
case "mobile":
await import("./mobile.js");
break;
case "print":
await import("./print.js");
break;
case "desktop":
default:
await import("./desktop.js");
break;
}
}

View File

@@ -46,8 +46,6 @@ import ScrollPadding from "../widgets/scroll_padding.js";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx";
import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TabRowWidget from "../widgets/tab_row.js";
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
@@ -163,11 +161,9 @@ export default class DesktopLayout {
.child(<SharedInfo />)
)
.optChild(!isNewLayout, <PromotedAttributes />)
.child(<SqlTableSchemas />)
.child(<NoteDetail />)
.child(<NoteList media="screen" />)
.child(<SearchResult />)
.child(<SqlResults />)
.child(<ScrollPadding />)
)
.child(<ApiLog />)

View File

@@ -179,7 +179,6 @@ export default class MobileLayout {
new FlexContainer("column")
.contentSized()
.id("mobile-bottom-bar")
.child(new TabRowWidget().css("height", "40px"))
.child(new FlexContainer("row")
.class("horizontal")
.css("height", "53px")

View File

@@ -1,8 +1,9 @@
import { KeyboardActionNames } from "@triliumnext/commons";
import { h, JSX, render } from "preact";
import keyboardActionService, { getActionSync } from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js";
import { h, JSX, render } from "preact";
export interface ContextMenuOptions<T> {
x: number;
@@ -62,17 +63,17 @@ export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEve
class ContextMenu {
private $widget: JQuery<HTMLElement>;
private $cover: JQuery<HTMLElement>;
private $cover?: JQuery<HTMLElement>;
private options?: ContextMenuOptions<any>;
private isMobile: boolean;
constructor() {
this.$widget = $("#context-menu-container");
this.$cover = $("#context-menu-cover");
this.$widget.addClass("dropend");
this.isMobile = utils.isMobile();
if (this.isMobile) {
this.$cover = $("#context-menu-cover");
this.$cover.on("click", () => this.hide());
} else {
$(document).on("click", (e) => this.hide());
@@ -91,7 +92,7 @@ class ContextMenu {
}
this.$widget.toggleClass("mobile-bottom-menu", !this.options.forcePositionOnMobile);
this.$cover.addClass("show");
this.$cover?.addClass("show");
$("body").addClass("context-menu-shown");
this.$widget.empty();
@@ -140,16 +141,14 @@ class ContextMenu {
} else {
left = this.options.x - contextMenuWidth + CONTEXT_MENU_OFFSET;
}
} else if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
// Overflow: right
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
// Overflow: left
left = CONTEXT_MENU_PADDING;
} else {
if (contextMenuWidth && this.options.x + contextMenuWidth - CONTEXT_MENU_OFFSET > clientWidth - CONTEXT_MENU_PADDING) {
// Overflow: right
left = clientWidth - contextMenuWidth - CONTEXT_MENU_PADDING;
} else if (this.options.x - CONTEXT_MENU_OFFSET < CONTEXT_MENU_PADDING) {
// Overflow: left
left = CONTEXT_MENU_PADDING;
} else {
left = this.options.x - CONTEXT_MENU_OFFSET;
}
left = this.options.x - CONTEXT_MENU_OFFSET;
}
this.$widget
@@ -261,7 +260,7 @@ class ContextMenu {
.append(item.title);
if ("badges" in item && item.badges) {
for (let badge of item.badges) {
for (const badge of item.badges) {
const badgeElement = $(`<span class="badge">`).text(badge.title);
if (badge.className) {
@@ -352,7 +351,7 @@ class ContextMenu {
async hide() {
this.options?.onHide?.();
this.$widget.removeClass("show");
this.$cover.removeClass("show");
this.$cover?.removeClass("show");
$("body").removeClass("context-menu-shown");
this.$widget.hide();
}

View File

@@ -29,7 +29,9 @@ async function main() {
const froca = (await import("./services/froca")).default;
const note = await froca.getNote(noteId);
render(<App note={note} noteId={noteId} />, document.body);
const bodyWrapper = document.createElement("div");
render(<App note={note} noteId={noteId} />, bodyWrapper);
document.body.appendChild(bodyWrapper);
}
function App({ note, noteId }: { note: FNote | null | undefined, noteId: string }) {

View File

@@ -8,6 +8,17 @@ async function loadBootstrap() {
}
}
// Polyfill removed jQuery methods for autocomplete.js compatibility
($ as any).isArray = Array.isArray;
($ as any).isFunction = function(obj: any) { return typeof obj === 'function'; };
($ as any).isPlainObject = function(obj: any) {
if (obj == null || typeof obj !== 'object') { return false; }
const proto = Object.getPrototypeOf(obj);
if (proto === null) { return true; }
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor === Object;
};
(window as any).$ = $;
(window as any).jQuery = $;
await loadBootstrap();

View File

@@ -42,7 +42,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
it("removes boolean normally without inheritance", async () => {
@@ -91,7 +91,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "false",
isInheritable: false
});
}, undefined);
});
it("overrides boolean with inherited false", async () => {
@@ -112,7 +112,7 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
it("deletes override boolean with inherited false with already existing value", async () => {
@@ -134,6 +134,6 @@ describe("Set boolean with inheritance", () => {
name: "foo",
value: "",
isInheritable: false
});
}, undefined);
});
});

View File

@@ -14,13 +14,13 @@ async function addLabel(noteId: string, name: string, value: string = "", isInhe
});
}
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false, componentId?: string) {
await server.put(`notes/${noteId}/set-attribute`, {
type: "label",
name,
value,
isInheritable
});
isInheritable,
}, componentId);
}
export async function setRelation(noteId: string, name: string, value: string = "", isInheritable = false) {
@@ -117,15 +117,15 @@ function removeOwnedRelationByName(note: FNote, relationName: string) {
* @param name the name of the attribute to set.
* @param value the value of the attribute to set.
*/
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined, componentId?: string) {
if (value !== null && value !== undefined) {
// Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }, componentId);
} else {
// Remove the attribute if it exists on the server but we don't define a value for it.
const attributeId = note.getAttribute(type, name)?.attributeId;
if (attributeId) {
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`);
await server.remove(`notes/${note.noteId}/attributes/${attributeId}`, componentId);
}
}
}

View File

@@ -103,7 +103,7 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
*/
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true, componentId?: string) {
branchIdsToDelete = filterRootNote(branchIdsToDelete);
if (branchIdsToDelete.length === 0) {
@@ -139,9 +139,9 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
const branch = froca.getBranch(branchIdToDelete);
if (deleteAllClones && branch) {
await server.remove(`notes/${branch.noteId}${query}`);
await server.remove(`notes/${branch.noteId}${query}`, componentId);
} else {
await server.remove(`branches/${branchIdToDelete}${query}`);
await server.remove(`branches/${branchIdToDelete}${query}`, componentId);
}
}

View File

@@ -49,7 +49,7 @@ function createClassForColor(colorString: string | null) {
return clsx("use-note-color", className, colorsWithHue.has(className) && "with-hue");
}
function parseColor(color: string) {
export function parseColor(color: string) {
try {
return Color(color.toLowerCase());
} catch (ex) {
@@ -77,7 +77,7 @@ function adjustColorLightness(color: ColorInstance, lightThemeMaxLightness: numb
}
/** Returns the hue of the specified color, or undefined if the color is grayscale. */
function getHue(color: ColorInstance) {
export function getHue(color: ColorInstance) {
const hslColor = color.hsl();
if (hslColor.saturationl() > 0) {
return hslColor.hue();

View File

@@ -27,6 +27,10 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
loadResults.addRevision(ec.entityId, ec.noteId, ec.componentId);
} else if (ec.entityName === "options") {
const attributeEntity = ec.entity as FAttributeRow;
if (attributeEntity.name === "openNoteContexts") {
continue; // only noise
}
options.set(attributeEntity.name as OptionNames, attributeEntity.value);
loadResults.addOption(attributeEntity.name as OptionNames);
} else if (ec.entityName === "attachments") {

View File

@@ -1,5 +1,6 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import shortcuts, { keyMatches, matchesShortcut, isIMEComposing } from "./shortcuts.js";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import shortcuts, { isIMEComposing, keyMatches, matchesShortcut } from "./shortcuts.js";
// Mock utils module
vi.mock("./utils.js", () => ({
@@ -61,9 +62,10 @@ describe("shortcuts", () => {
});
describe("keyMatches", () => {
const createKeyboardEvent = (key: string, code?: string) => ({
const createKeyboardEvent = (key: string, code?: string, extraProps: Partial<KeyboardEvent> = {}) => ({
key,
code: code || `Key${key.toUpperCase()}`
code: code || `Key${key.toUpperCase()}`,
...extraProps
} as KeyboardEvent);
it("should match regular letter keys using key code", () => {
@@ -101,17 +103,23 @@ describe("shortcuts", () => {
consoleSpy.mockRestore();
});
it("should match azerty keys", () => {
const event = createKeyboardEvent("A", "KeyQ");
expect(keyMatches(event, "a")).toBe(true);
expect(keyMatches(event, "q")).toBe(false);
});
it("should match letter keys using code when key is a special character (macOS Alt behavior)", () => {
// On macOS, pressing Option/Alt + A produces 'å' as the key, but code is still 'KeyA'
const macOSAltAEvent = createKeyboardEvent("å", "KeyA");
const macOSAltAEvent = createKeyboardEvent("å", "KeyA", { altKey: true });
expect(keyMatches(macOSAltAEvent, "a")).toBe(true);
// Option + H produces '˙'
const macOSAltHEvent = createKeyboardEvent("˙", "KeyH");
const macOSAltHEvent = createKeyboardEvent("˙", "KeyH", { altKey: true });
expect(keyMatches(macOSAltHEvent, "h")).toBe(true);
// Option + S produces 'ß'
const macOSAltSEvent = createKeyboardEvent("ß", "KeyS");
const macOSAltSEvent = createKeyboardEvent("ß", "KeyS", { altKey: true });
expect(keyMatches(macOSAltSEvent, "s")).toBe(true);
});
});
@@ -215,6 +223,15 @@ describe("shortcuts", () => {
consoleSpy.mockRestore();
});
it("matches azerty", () => {
const event = createKeyboardEvent({
key: "a",
code: "KeyQ",
ctrlKey: true
});
expect(matchesShortcut(event, "Ctrl+A")).toBe(true);
});
it("should match Alt+letter shortcuts on macOS where key is a special character", () => {
// On macOS, pressing Option/Alt + A produces 'å' but code remains 'KeyA'
const macOSAltAEvent = createKeyboardEvent({

View File

@@ -215,9 +215,12 @@ export function keyMatches(e: KeyboardEvent, key: string): boolean {
// For letter keys, use the physical key code for consistency
// On macOS, Option/Alt key produces special characters, so we must use e.code
if (key.length === 1 && key >= 'a' && key <= 'z') {
// e.code is like "KeyA", "KeyB", etc.
const expectedCode = `Key${key.toUpperCase()}`;
return e.code === expectedCode || e.key.toLowerCase() === key.toLowerCase();
if (e.altKey) {
// e.code is like "KeyA", "KeyB", etc.
const expectedCode = `Key${key.toUpperCase()}`;
return e.code === expectedCode || e.key.toLowerCase() === key.toLowerCase();
}
return e.key.toLowerCase() === key.toLowerCase();
}
// For regular keys, check both key and code as fallback

View File

@@ -1,10 +1,11 @@
import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
import { MimeType } from "@triliumnext/commons";
import { type AutoHighlightResult, ensureMimeTypes, highlight, highlightAuto, type HighlightResult, loadTheme, type Theme,Themes } from "@triliumnext/highlightjs";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { t } from "./i18n.js";
import mime_types from "./mime_types.js";
import options from "./options.js";
import { t } from "./i18n.js";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { isShare } from "./utils.js";
import { MimeType } from "@triliumnext/commons";
let highlightingLoaded = false;
@@ -76,13 +77,15 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
}
export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
if (highlightingLoaded) {
if (!mimeTypeHint && highlightingLoaded) {
return;
}
// Load theme.
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
if (!highlightingLoaded) {
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
}
// Load mime types.
let mimeTypes: MimeType[];
@@ -94,7 +97,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
enabled: true,
mime: mimeTypeHint.replace("-", "/")
}
]
];
} else {
mimeTypes = mime_types.getMimeTypes();
}
@@ -124,9 +127,9 @@ export function isSyntaxHighlightEnabled() {
if (!isShare) {
const theme = options.get("codeBlockTheme");
return !!theme && theme !== "none";
} else {
return true;
}
return true;
}
/**

View File

@@ -224,10 +224,6 @@ body.mobile .modal .modal-dialog {
width: 100%;
}
body.mobile .modal .modal-content {
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
}
.component {
contain: size;
}
@@ -1255,7 +1251,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
z-index: 1000;
z-index: 2500;
background: rgba(0, 0, 0, 0.1);
}
@@ -1614,6 +1610,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
body.mobile .modal-content {
overflow-y: auto;
border-radius: var(--bs-modal-border-radius) var(--bs-modal-border-radius) 0 0;
}
body.mobile .modal-footer {
@@ -1669,6 +1666,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
#detail-container {
background: var(--main-background-color);
}
.modal-dialog {
margin: var(--bs-modal-margin);
max-width: 80%;
}
.modal-content {
height: 100%;
}
}
@media (max-width: 991px) {

View File

@@ -14,13 +14,13 @@
--row-moving-background-color: var(--accented-background-color);
--row-text-color: var(--main-text-color);
--row-delimiter-color: var(--more-accented-background-color);
--cell-horiz-padding-size: 8px;
--cell-vert-padding-size: 8px;
--cell-editable-hover-outline-color: var(--main-border-color);
--cell-read-only-text-color: var(--muted-text-color);
--cell-editing-border-color: var(--main-border-color);
--cell-editing-border-width: 2px;
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
@@ -40,10 +40,42 @@
border-bottom: var(--col-header-bottom-border);
background: var(--col-header-background-color);
color: var(--col-header-text-color);
}
font-weight: normal;
.tabulator .tabulator-col-content {
padding: 8px 4px !important;
.tabulator-col.tabulator-range-highlight {
background: inherit;
color: inherit;
font-weight: bold;
}
.tabulator-col-content {
padding: 0 !important;
.tabulator-col-title-holder {
padding: 8px 4px;
}
&:has(.tabulator-header-filter) {
.tabulator-col-title-holder {
padding: 4px;
padding-bottom: 0;
}
}
.tabulator-header-filter {
background: var(--main-background-color);
padding: 2px 1px;
input {
background: var(--main-background-color);
color: var(--main-text-color);
border: 1px solid var(--button-border-color);
border-radius: 3px;
outline: none;
padding: 2px;
}
}
}
}
@media (hover: hover) and (pointer: fine) {
@@ -80,7 +112,6 @@
.tabulator-tableholder {
padding-top: 10px;
height: unset !important; /* Don't extend on the full height */
}
/* Rows */
@@ -99,6 +130,14 @@
border-top: none;
border-bottom: 1px solid var(--row-delimiter-color);
color: var(--row-text-color);
&:last-of-type {
border-bottom: none;
}
&.tabulator-range-highlight > .tabulator-cell.tabulator-frozen {
font-weight: bold;
}
}
.tabulator-row.tabulator-row-odd {
@@ -120,11 +159,14 @@
margin-inline-end: var(--cell-editing-border-width);
}
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
.tabulator-row .tabulator-cell {
border-inline-end-color: transparent;
}
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
border-inline-end-color: var(--main-border-color);
}
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
color: var(--cell-read-only-text-color);
}
@@ -174,10 +216,6 @@
margin: 0;
}
.tabulator .tabulator-footer {
color: var(--main-text-color);
}
/* Context menus */
.tabulator-popup-container {
@@ -192,8 +230,27 @@
}
/* Footer */
:root .tabulator .tabulator-footer {
border-top: unset;
background: transparent;
color: var(--main-text-color);
border-top: 1px solid var(--main-border-color);
padding: 10px 0;
}
.tabulator-page {
background: var(--button-background-color);
color: var(--button-text-color);
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
&:hover {
border-color: var(--hover-item-border-color);
color: var(--button-text-color);
}
}
select {
background: var(--button-background-color);
color: var(--input-text-color);
border: 1px solid var(--button-border-color);
}
}

View File

@@ -40,13 +40,30 @@ body.mobile {
/* #region Mica */
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
body.background-effects.platform-win32 {
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
--background-material: tabbed;
&.layout-vertical {
--background-material: mica;
}
&.layout-horizontal {
--background-material: tabbed;
}
}
body.background-effects.theme-supports-background-effects.platform-win32 {
body.background-effects.platform-darwin {
/** Reference: https://developer.apple.com/documentation/appkit/nsvisualeffectview?preferredLanguage=objc **/
&.layout-vertical {
--background-material: under-window;
}
&.layout-horizontal {
--background-material: hud;
}
}
body.background-effects.theme-supports-background-effects {
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
@@ -56,33 +73,29 @@ body.background-effects.theme-supports-background-effects.platform-win32 {
--root-background: transparent;
}
body.background-effects.platform-win32.layout-vertical {
--background-material: mica;
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
body.background-effects.theme-supports-background-effects.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
--right-pane-background-color: var(--right-pane-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
body.background-effects.theme-supports-background-effects.layout-horizontal {
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
--gutter-color: var(--left-pane-background-color);
}
body.background-effects.theme-supports-background-effects.platform-win32,
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
body.background-effects.theme-supports-background-effects,
body.background-effects.theme-supports-background-effects #root-widget {
background: var(--window-background-color-bgfx) !important;
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
body.background-effects.theme-supports-background-effects.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.layout-vertical #vertical-main-container {
background-color: var(--root-background);
}
/* Note split with background effects */
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
body.background-effects.theme-supports-background-effects #center-pane .note-split.bgfx {
--note-split-background-color: var(--center-pane-background-color-bgfx);
}
@@ -1054,7 +1067,7 @@ body.layout-horizontal .tab-row-widget-container {
overflow: hidden;
}
body.desktop:not(.background-effects.platform-win32) #root-widget.horizontal-layout {
body.desktop:not(.background-effects) #root-widget.horizontal-layout {
background-color: var(--root-background) !important;
}

View File

@@ -13,8 +13,7 @@ function injectGlobals() {
uncheckedWindow.$ = $;
uncheckedWindow.WebSocket = () => {};
uncheckedWindow.glob = {
isMainWindow: true,
windowId: "main"
isMainWindow: true
};
}

View File

@@ -1639,7 +1639,11 @@
"configure_launchbar": "配置启动栏"
},
"sql_result": {
"no_rows": "此查询没有返回任何数据"
"no_rows": "此查询没有返回任何数据",
"not_executed": "查询尚未执行。",
"failed": "SQL 查询执行失败",
"execute_now": "立即执行",
"statement_result": "执行结果"
},
"sql_table_schemas": {
"tables": "表"

View File

@@ -28,9 +28,9 @@
},
"open-script-note": "Script-Notiz öffnen",
"widget-render-error": {
"title": "Eine externe React Integration konnte nicht dargestellt werden"
"title": "Benutzerdefiniertes React-Widget konnte nicht dargestellt werden"
},
"widget-missing-parent": "Der externen Integration fehlt die erforderliche Eigenschaft '{{property}}'\n\nFalls dieses Skript ohne UI-Element ausgeführt werden soll, benutze stattdessen '#run=frontendStartup'.",
"widget-missing-parent": "Benutzerdefiniertes Widget hat die erforderliche '{{property}}'-Eigenschaft nicht korrekt definiert.\n\nFalls dieses Skript ohne UI-Element ausgeführt werden soll, benutze stattdessen '#run=frontendStartup'.",
"scripting-error": "Benutzerdefinierter Skriptfehler: {{title}}"
},
"add_link": {
@@ -129,7 +129,7 @@
"scrollToActiveNote": "Scrolle zur aktiven Notiz",
"jumpToParentNote": "Zur übergeordneten Notiz springen",
"collapseWholeTree": "Reduziere den gesamten Notizbaum",
"collapseSubTree": "Teilbaum einklappen",
"collapseSubTree": "Zweig einklappen",
"tabShortcuts": "Tab-Tastenkürzel",
"newTabNoteLink": "auf den Notizlink öffnet die Notiz in einem neuen Tab",
"onlyInDesktop": "Nur im Desktop (Electron Build)",
@@ -230,7 +230,7 @@
"move_to": {
"dialog_title": "Notizen verschieben nach ...",
"notes_to_move": "Notizen zum Verschieben",
"target_parent_note": "Ziel-Elternnotiz",
"target_parent_note": "Übergeordnete Notiz bestimmen",
"search_placeholder": "Suche nach einer Notiz anhand ihres Namens",
"move_button": "Zur ausgewählten Notiz wechseln",
"error_no_path": "Kein Weg, auf den man sich bewegen kann.",
@@ -333,8 +333,8 @@
"target_note_title": "Eine Beziehung ist eine benannte Verbindung zwischen Quellnotiz und Zielnotiz.",
"target_note": "Zielnotiz",
"promoted_title": "Das heraufgestufte Attribut wird deutlich in der Notiz angezeigt.",
"promoted": "Gefördert",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für heraufgestufte Attribute angezeigt werden soll.",
"promoted": "Hervorgehoben",
"promoted_alias_title": "Der Name, der in der Benutzeroberfläche für hervorgehobene Attribute angezeigt werden soll.",
"promoted_alias": "Alias",
"multiplicity_title": "Multiplizität definiert, wie viele Attribute mit demselben Namen erstellt werden können maximal 1 oder mehr als 1.",
"multiplicity": "Vielzahl",
@@ -367,7 +367,7 @@
"disable_versioning": "deaktiviert die automatische Versionierung. Nützlich z.B. große, aber unwichtige Notizen z.B. große JS-Bibliotheken, die für die Skripterstellung verwendet werden",
"calendar_root": "Markiert eine Notiz, die als Basis für Tagesnotizen verwendet werden soll. Nur einer sollte als solcher gekennzeichnet sein.",
"archived": "Notizen mit dieser Bezeichnung werden standardmäßig nicht in den Suchergebnissen angezeigt (auch nicht in den Dialogen „Springen zu“, „Link hinzufügen“ usw.).",
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht in den Notizexport einbezogen",
"exclude_from_export": "Notizen (mit ihrem Unterbaum) werden nicht im Notizexport inkludiert",
"run": "Definiert, bei welchen Ereignissen das Skript ausgeführt werden soll. Mögliche Werte sind:\n<ul>\n<li>frontendStartup - wenn das Trilium-Frontend startet (oder aktualisiert wird), außer auf mobilen Geräten.</li>\n<li>mobileStartup - wenn das Trilium-Frontend auf einem mobilen Gerät startet (oder aktualisiert wird).</li>\n<li>backendStartup - wenn das Trilium-Backend startet</li>\n<li>hourly - einmal pro Stunde ausführen. Du kannst das zusätzliche Label <code>runAtHour</code> verwenden, um die genaue Stunde festzulegen.</li>\n<li>daily - einmal pro Tag ausführen</li>\n</ul>",
"run_on_instance": "Definiere, auf welcher Trilium-Instanz dies ausgeführt werden soll. Standardmäßig alle Instanzen.",
"run_at_hour": "Zu welcher Stunde soll das laufen? Sollte zusammen mit <code>#runu003dhourly</code> verwendet werden. Kann für mehr Läufe im Laufe des Tages mehrfach definiert werden.",
@@ -376,7 +376,7 @@
"sort_direction": "ASC (Standard) oder DESC",
"sort_folders_first": "Ordner (Notizen mit Unternotizen) sollten oben sortiert werden",
"top": "Behalte die angegebene Notiz oben in der übergeordneten Notiz (gilt nur für sortierte übergeordnete Notizen)",
"hide_promoted_attributes": "Heraufgestufte Attribute für diese Notiz ausblenden",
"hide_promoted_attributes": "Hervorgehobene Attribute für diese Notiz ausblenden",
"read_only": "Der Editor befindet sich im schreibgeschützten Modus. Funktioniert nur für Text- und Codenotizen.",
"auto_read_only_disabled": "Text-/Codenotizen können automatisch in den Lesemodus versetzt werden, wenn sie zu groß sind. Du kannst dieses Verhalten für jede einzelne Notiz deaktivieren, indem du diese Beschriftung zur Notiz hinzufügst",
"app_css": "markiert CSS-Notizen, die in die Trilium-Anwendung geladen werden und somit zur Änderung des Aussehens von Trilium verwendet werden können.",
@@ -416,13 +416,13 @@
"toc": "<code>#toc</code> oder <code>#tocu003dshow</code> erzwingen die Anzeige des Inhaltsverzeichnisses, <code>#tocu003dhide</code> erzwingt das Ausblenden. Wenn die Bezeichnung nicht vorhanden ist, wird die globale Einstellung beachtet",
"color": "Definiert die Farbe der Notiz im Notizbaum, in Links usw. Verwende einen beliebigen gültigen CSS-Farbwert wie „rot“ oder #a13d5f",
"keyboard_shortcut": "Definiert eine Tastenkombination, die sofort zu dieser Notiz springt. Beispiel: „Strg+Alt+E“. Erfordert ein Neuladen des Frontends, damit die Änderung wirksam wird.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Unterbaum nicht angezeigt werden kann.",
"keep_current_hoisting": "Das Öffnen dieses Links ändert das Hochziehen nicht, selbst wenn die Notiz im aktuell hochgezogenen Zweig nicht angezeigt werden kann.",
"execute_button": "Titel der Schaltfläche, welche die aktuelle Codenotiz ausführt",
"execute_description": "Längere Beschreibung der aktuellen Codenotiz, die zusammen mit der Schaltfläche „Ausführen“ angezeigt wird",
"exclude_from_note_map": "Notizen mit dieser Bezeichnung werden in der Notizenkarte ausgeblendet",
"new_notes_on_top": "Neue Notizen werden oben in der übergeordneten Notiz erstellt, nicht unten.",
"hide_highlight_widget": "Widget „Hervorhebungsliste“ ausblenden",
"run_on_note_creation": "Wird ausgeführt, wenn eine Notiz im Backend erstellt wird. Verwende diese Beziehung, wenn du das Skript für alle Notizen ausführen möchtest, die unter einer bestimmten Unternotiz erstellt wurden. Erstelle es in diesem Fall auf der Unternotiz-Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb der Unternotiz (beliebige Tiefe) erstellt wird, löst das Skript aus.",
"hide_highlight_widget": "Widget „Markierungsliste“ ausblenden",
"run_on_note_creation": "Wird ausgeführt, wenn eine Notiz im Backend erstellt wird. Verwende diese Beziehung, wenn du das Skript für alle Notizen ausführen möchtest, die unter einem bestimmten Zweig erstellt wurden. Erstelle es in diesem Fall auf der Stammnotiz und mache es vererbbar. Eine neue Notiz, die innerhalb des Zweigs (beliebige Tiefe) erstellt wird, löst das Skript aus.",
"run_on_child_note_creation": "Wird ausgeführt, wenn eine neue Notiz unter der Notiz erstellt wird, in der diese Beziehung definiert ist",
"run_on_note_title_change": "Wird ausgeführt, wenn der Notiztitel geändert wird (einschließlich der Notizerstellung)",
"run_on_note_content_change": "Wird ausgeführt, wenn der Inhalt einer Notiz geändert wird (einschließlich der Erstellung von Notizen).",
@@ -433,8 +433,8 @@
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Der Inhalt und der Unterbaum der Notiz werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
"inherit": "Die Attribute einer Notiz werden auch ohne eine Eltern-Kind-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributvererbung in der Dokumentation.",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Der Inhalt und der Zweig werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
"inherit": "Die Attribute einer Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Ein ähnliches Konzept findest du unter Vorlagenbeziehung. Siehe Attributsvererbung in der Dokumentation.",
"render_note": "Notizen vom Typ \"HTML-Notiz rendern\" werden mit einer Code-Notiz (HTML oder Skript) gerendert, und es ist notwendig, über diese Beziehung anzugeben, welche Notiz gerendert werden soll",
"widget_relation": "Das Ziel dieser Beziehung wird ausgeführt und als Widget in der Seitenleiste gerendert",
"share_css": "CSS-Hinweis, der in die Freigabeseite eingefügt wird. Die CSS-Notiz muss sich ebenfalls im gemeinsamen Unterbaum befinden. Erwäge auch die Verwendung von „share_hidden_from_tree“ und „share_omit_default_css“.",
@@ -632,7 +632,7 @@
"show_toc": "Inhaltsverzeichnis anzeigen"
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Hervorhebungen anzeigen"
"show_highlights_list": "Markierungsliste anzeigen"
},
"global_menu": {
"menu": "Menü",
@@ -645,8 +645,8 @@
"zoom_out": "Herauszoomen",
"reset_zoom_level": "Zoomstufe zurücksetzen",
"zoom_in": "Hineinzoomen",
"configure_launchbar": "Konfiguriere die Launchbar",
"show_shared_notes_subtree": "Unterbaum „Freigegebene Notizen“ anzeigen",
"configure_launchbar": "Konfiguriere die Starterleiste",
"show_shared_notes_subtree": "Zweig „Freigegebene Notizen“ anzeigen",
"advanced": "Erweitert",
"open_dev_tools": "Öffne die Entwicklungstools",
"open_sql_console": "Öffne die SQL-Konsole",
@@ -655,7 +655,7 @@
"show_backend_log": "Backend-Protokoll anzeigen",
"reload_hint": "Ein Neuladen kann bei einigen visuellen Störungen Abhilfe schaffen, ohne die gesamte App neu starten zu müssen.",
"reload_frontend": "Frontend neu laden",
"show_hidden_subtree": "Versteckten Teilbaum anzeigen",
"show_hidden_subtree": "Versteckten Zweig anzeigen",
"show_help": "Hilfe anzeigen",
"about": "Über Trilium Notes",
"logout": "Abmelden",
@@ -703,8 +703,8 @@
"export_as_image_png": "PNG (Raster)",
"export_as_image_svg": "SVG (Vektor)",
"note_map": "Notizen Karte",
"view_revisions": "Änderungshistorie...",
"advanced": "Fortgeschritten"
"view_revisions": "Notizrevisionen...",
"advanced": "Erweitert"
},
"onclick_button": {
"no_click_handler": "Das Schaltflächen-Widget „{{componentId}}“ hat keinen definierten Klick-Handler"
@@ -720,7 +720,7 @@
"update_available": "Update verfügbar"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Dieser Launcher definiert keine Zielnotiz."
"this_launcher_doesnt_define_target_note": "Dieser Starter definiert keine Zielnotiz."
},
"code_buttons": {
"execute_button_title": "Skript ausführen",
@@ -763,8 +763,8 @@
"change_note_icon": "Notiz-Icon ändern",
"search": "Suche:",
"reset-default": "Standard wiederherstellen",
"search_placeholder_one": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Icons über {{count}} Pakete",
"search_placeholder_one": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_other": "Suche {{number}} Symbole über {{count}} Pakete",
"search_placeholder_filtered": "Suche {{number}} Icons in {{name}}",
"filter": "Filter",
"filter-none": "Alle Icons",
@@ -798,7 +798,7 @@
"expand_tooltip": "Erweitert die direkten Unterelemente dieser Sammlung (eine Ebene tiefer). Für weitere Optionen auf den Pfeil rechts klicken.",
"expand_first_level": "Direkte Unterelemente erweitern",
"expand_nth_level": "{{depth}} Ebenen erweitern",
"hide_child_notes": "Unterknoten im Baum ausblenden"
"hide_child_notes": "Unternotizen im Baum ausblenden"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -842,7 +842,7 @@
"note_size": "Notengröße",
"note_size_info": "Die Notizgröße bietet eine grobe Schätzung des Speicherbedarfs für diese Notiz. Es berücksichtigt den Inhalt der Notiz und den Inhalt ihrer Notizrevisionen.",
"calculate": "berechnen",
"subtree_size": "(Teilbaumgröße: {{size}} in {{count}} Notizen)",
"subtree_size": "(Zweiggröße: {{size}} in {{count}} Notizen)",
"title": "Notizinfo",
"mime": "MIME Typ",
"show_similar_notes": "Zeige ähnliche Notizen"
@@ -871,7 +871,7 @@
"owned_attributes": "Eigene Attribute"
},
"promoted_attributes": {
"promoted_attributes": "Übergebene Attribute",
"promoted_attributes": "Hervorgehobene Attribute",
"url_placeholder": "http://website...",
"open_external_link": "Externen Link öffnen",
"unknown_label_type": "Unbekannter Labeltyp „{{type}}“",
@@ -1115,7 +1115,7 @@
"vacuum_database": {
"title": "Datenbank aufräumen",
"description": "Dadurch wird die Datenbank neu erstellt, was normalerweise zu einer kleineren Datenbankdatei führt. Es werden keine Daten tatsächlich geändert.",
"button_text": "Vakuumdatenbank",
"button_text": "Datenbank aufräumen",
"vacuuming_database": "Datenbank wird geleert...",
"database_vacuumed": "Die Datenbank wurde geleert"
},
@@ -1156,7 +1156,7 @@
},
"ribbon": {
"widgets": "Multifunktionsleisten-Widgets",
"promoted_attributes_message": "Die Multifunktionsleisten-Registerkarte „Heraufgestufte Attribute“ wird automatisch geöffnet, wenn in der Notiz heraufgestufte Attribute vorhanden sind",
"promoted_attributes_message": "Die „Hervorgehobene Attribute“-Leiste wird automatisch geöffnet, wenn in der Notiz hervorgehobene Attribute vorhanden sind",
"edited_notes_message": "Die Multifunktionsleisten-Registerkarte „Bearbeitete Notizen“ wird bei Tagesnotizen automatisch geöffnet"
},
"theme": {
@@ -1169,7 +1169,7 @@
"layout": "Layout",
"layout-vertical-title": "Vertikal",
"layout-horizontal-title": "Horizontal",
"layout-vertical-description": "Startleiste ist auf der linken Seite (standard)",
"layout-vertical-description": "Startleiste ist auf der linken Seite (Standard)",
"layout-horizontal-description": "Startleiste ist unter der Tableiste. Die Tableiste wird dadurch auf die ganze Breite erweitert.",
"auto_theme": "Alt (Folge dem Farbschema des Systems)",
"light_theme": "Alt (Hell)",
@@ -1177,7 +1177,7 @@
},
"zoom_factor": {
"title": "Zoomfaktor (nur Desktop-Build)",
"description": "Das Zoomen kann auch mit den Tastenkombinationen STRG+- und STRG+u003d gesteuert werden."
"description": "Das Zoomen kann auch mit den Tastenkombinationen Strg+- und Strg+= gesteuert werden."
},
"code_auto_read_only_size": {
"title": "Automatische schreibgeschützte Größe",
@@ -1266,16 +1266,16 @@
"markdown": "Markdown-Stil"
},
"highlights_list": {
"title": "Highlights-Liste",
"description": "Du kannst die im rechten Bereich angezeigte Highlights-Liste anpassen:",
"title": "Markierungsliste",
"description": "Du kannst die im rechten Bereich angezeigte Markierungsliste anpassen:",
"bold": "Fettgedruckter Text",
"italic": "Kursiver Text",
"underline": "Unterstrichener Text",
"color": "Farbiger Text",
"bg_color": "Text mit Hintergrundfarbe",
"visibility_title": "Sichtbarkeit der Highlights-Liste",
"visibility_description": "Du kannst das Hervorhebungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Hervorhebungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
"visibility_title": "Sichtbarkeit der Markierungsliste",
"visibility_description": "Du kannst das Markierungs-Widget pro Notiz ausblenden, indem du die Beschriftung #hideHighlightWidget hinzufügst.",
"shortcut_info": "Du kannst eine Tastenkombination zum schnellen Umschalten des rechten Bereichs (einschließlich Markierungen) in den Optionen -> Tastenkombinationen konfigurieren (Name „toggleRightPane“)."
},
"table_of_contents": {
"title": "Inhaltsverzeichnis",
@@ -1445,19 +1445,19 @@
"insert-note-after": "Notiz dahinter einfügen",
"insert-child-note": "Unternotiz einfügen",
"delete": "Löschen",
"search-in-subtree": "Im Notizbaum suchen",
"search-in-subtree": "Im Zweig suchen",
"hoist-note": "Notiz-Fokus setzen",
"unhoist-note": "Notiz-Fokus aufheben",
"edit-branch-prefix": "Zweig-Präfix bearbeiten",
"advanced": "Erweitert",
"expand-subtree": "Unterzweig aufklappen",
"collapse-subtree": "Notizbaum einklappen",
"expand-subtree": "Zweig aufklappen",
"collapse-subtree": "Zweig einklappen",
"sort-by": "Sortieren nach...",
"recent-changes-in-subtree": "Kürzliche Änderungen im Notizbaum",
"recent-changes-in-subtree": "Kürzliche Änderungen im Zweig",
"convert-to-attachment": "Als Anhang konvertieren",
"copy-note-path-to-clipboard": "Notiz-Pfad in die Zwischenablage kopieren",
"protect-subtree": "Notizbaum schützen",
"unprotect-subtree": "Notizenbaum-Schutz aufheben",
"protect-subtree": "Zweig schützen",
"unprotect-subtree": "Zweig-Schutz aufheben",
"copy-clone": "Kopieren / Klonen",
"clone-to": "Klonen nach...",
"cut": "Ausschneiden",
@@ -1474,12 +1474,12 @@
"archive": "Archiviere",
"unarchive": "Entarchivieren",
"open-in-a-new-window": "In neuem Fenster öffnen",
"hide-subtree": "Teilbaum ausblenden",
"show-subtree": "Teilbaum anzeigen"
"hide-subtree": "Zweig ausblenden",
"show-subtree": "Zweig anzeigen"
},
"shared_info": {
"shared_publicly": "Diese Notiz ist öffentlich geteilt auf {{- link}}.",
"shared_locally": "Diese Notiz ist lokal geteilt auf {{- link}}.",
"shared_publicly": "Diese Notiz ist öffentlich freigegeben über {{- link}}.",
"shared_locally": "Diese Notiz ist lokal freigegeben über {{- link}}.",
"help_link": "Für Hilfe besuche <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"note_types": {
@@ -1490,12 +1490,12 @@
"note-map": "Notizkarte",
"render-note": "Render Notiz",
"mermaid-diagram": "Mermaid Diagramm",
"canvas": "Canvas",
"canvas": "Leinwand",
"web-view": "Webansicht",
"mind-map": "Mind Map",
"file": "Datei",
"image": "Bild",
"launcher": "Launcher",
"launcher": "Starter",
"doc": "Dokument",
"widget": "Widget",
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
@@ -1514,10 +1514,10 @@
"toggle-off-hint": "Notiz ist geschützt, klicken, um den Schutz aufzuheben"
},
"shared_switch": {
"shared": "Teilen",
"toggle-on-title": "Notiz teilen",
"shared": "Freigegeben",
"toggle-on-title": "Notiz freigeben",
"toggle-off-title": "Notiz-Freigabe aufheben",
"shared-branch": "Diese Notiz existiert nur als geteilte Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
"shared-branch": "Diese Notiz existiert nur als freigegebene Notiz, das Aufheben der Freigabe würde sie löschen. Möchtest du fortfahren und die Notiz damit löschen?",
"inherited": "Die Notiz kann hier nicht von der Freigabe entfernt werden, da sie über Vererbung von einer übergeordneten Notiz geteilt wird."
},
"template_switch": {
@@ -1535,13 +1535,13 @@
"replace_all": "Alle Ersetzen"
},
"highlights_list_2": {
"title": "Hervorhebungs-Liste",
"title": "Markierungsliste",
"options": "Optionen",
"title_with_count_one": "{{count}} Highlight",
"title_with_count_other": "{{count}} Highlights",
"modal_title": "Highlight Liste konfigurieren",
"menu_configure": "Highlight Liste konfigurieren…",
"no_highlights": "Keine Highlights gefunden."
"title_with_count_one": "{{count}} Markierung",
"title_with_count_other": "{{count}} Markierungen",
"modal_title": "Markierungsliste konfigurieren",
"menu_configure": "Markierungsliste konfigurieren…",
"no_highlights": "Keine Markierungen gefunden."
},
"quick-search": {
"placeholder": "Schnellsuche",
@@ -1566,15 +1566,15 @@
"unhoist": "Fokus verlassen",
"toggle-sidebar": "Seitenleiste ein-/ausblenden",
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig.",
"clone-indicator-tooltip": "Diese Notiz hat {{- count}} Elterknoten: {{- parents}}",
"clone-indicator-tooltip-single": "Diese Notiz ist geklont (1 weiterer Elternknoten: {{- parent}})",
"shared-indicator-tooltip": "Diese Notiz ist öffentlich einsehbar",
"shared-indicator-tooltip-with-url": "Diese Notiz ist unter {{- url}} öffentlich einsehbar",
"subtree-hidden-tooltip_one": "{{count}} Unterknoten, der im Baum ausgeblendet ist",
"subtree-hidden-tooltip_other": "{{count}} Unterknoten, die im Baum ausgeblendet sind",
"clone-indicator-tooltip": "Diese Notiz hat {{- count}} übergeordnete Knoten: {{- parents}}",
"clone-indicator-tooltip-single": "Diese Notiz ist geklont (1 weitere Quelle: {{- parent}})",
"shared-indicator-tooltip": "Diese Notiz ist öffentlich freigegeben",
"shared-indicator-tooltip-with-url": "Diese Notiz ist öffentlich freigegeben unter: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} untergeordnete Notiz, die im Baum ausgeblendet ist",
"subtree-hidden-tooltip_other": "{{count}} untergeordnete Notizen, die im Baum ausgeblendet sind",
"subtree-hidden-moved-title": "Zu {{title}} hinzugefügt",
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizem im Baum aus.",
"subtree-hidden-moved-description-other": "Diese Sammlung blendet ihre Unterknoten im Baum aus."
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizen im Baum aus.",
"subtree-hidden-moved-description-other": "Untergeordnete Notizen sind im Baum für diese Notiz ausgeblendet."
},
"title_bar_buttons": {
"window-on-top": "Dieses Fenster immer oben halten"
@@ -1586,8 +1586,8 @@
"print_report_title": "Druckreport",
"print_report_collection_details_button": "Details anzeigen",
"print_report_collection_details_ignored_notes": "Ignorierte Notizen",
"print_report_collection_content_one": "{{count}} Notiz in der Sammlung konnte nicht gedruckt werden, weil sie nicht unterstützt ist oder geschützt ist.",
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt sind oder geschützt sind."
"print_report_collection_content_one": "{{count}} Notiz in der Sammlung konnte nicht gedruckt werden, weil sie nicht unterstützt oder geschützt ist.",
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt oder geschützt sind."
},
"note_title": {
"placeholder": "Titel der Notiz hier eingeben…",
@@ -1595,7 +1595,7 @@
"last_modified": "Bearbeitet am <Value />",
"note_type_switcher_label": "Ändere von {{type}} zu:",
"note_type_switcher_others": "Andere Notizart",
"note_type_switcher_templates": "Template",
"note_type_switcher_templates": "Vorlage",
"note_type_switcher_collection": "Sammlung",
"edited_notes": "Notizen, bearbeitet an diesem Tag",
"promoted_attributes": "Hervorgehobene Attribute"
@@ -1605,10 +1605,14 @@
"search_not_executed": "Die Suche wurde noch nicht ausgeführt. Klicke oben auf „Suchen“, um die Ergebnisse anzuzeigen."
},
"spacer": {
"configure_launchbar": "Startleiste konfigurieren"
"configure_launchbar": "Starterleiste konfigurieren"
},
"sql_result": {
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben"
"no_rows": "Es wurden keine Zeilen für diese Abfrage zurückgegeben",
"not_executed": "Die Abfrage wurde noch nicht ausgeführt.",
"failed": "SQL-Abfrage ist fehlgeschlagen",
"execute_now": "Jetzt ausführen",
"statement_result": "Abfrageergebnis"
},
"sql_table_schemas": {
"tables": "Tabellen"
@@ -1679,16 +1683,16 @@
"confirm_unhoisting": "Die angeforderte Notiz {{requestedNote}} befindet sich außerhalb des hoisted Bereichs der Notiz {{hoistedNote}}. Du musst sie unhoisten, um auf die Notiz zuzugreifen. Möchtest du mit dem Unhoisting fortfahren?"
},
"launcher_context_menu": {
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Launcher wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Launcher für Notiz hinzufügen",
"add-script-launcher": "Launcher für Skript hinzufügen",
"reset_launcher_confirm": "Möchtest du „{{title}}“ wirklich zurücksetzen? Alle Daten / Einstellungen in dieser Notiz (und ihren Unternotizen) gehen verloren und der Starter wird an seinen ursprünglichen Standort zurückgesetzt.",
"add-note-launcher": "Notiz-Starter hinzufügen",
"add-script-launcher": "Skript-Starter hinzufügen",
"add-custom-widget": "Benutzerdefiniertes Widget hinzufügen",
"add-spacer": "Spacer hinzufügen",
"add-spacer": "Abstandhalter hinzufügen",
"delete": "Löschen <kbd data-command=\"deleteNotes\"></kbd>",
"reset": "Zurücksetzen",
"move-to-visible-launchers": "Zu sichtbaren Launchern verschieben",
"move-to-available-launchers": "Zu verfügbaren Launchern verschieben",
"duplicate-launcher": "Launcher duplizieren <kbd data-command=\"duplicateSubtree\">"
"move-to-visible-launchers": "Zu sichtbaren Startern verschieben",
"move-to-available-launchers": "Zu verfügbaren Startern verschieben",
"duplicate-launcher": "Starter duplizieren <kbd data-command=\"duplicateSubtree\">"
},
"highlighting": {
"description": "Steuert die Syntaxhervorhebung für Codeblöcke in Textnotizen, Code-Notizen sind nicht betroffen.",
@@ -1697,7 +1701,7 @@
},
"code_block": {
"word_wrapping": "Wortumbruch",
"theme_none": "Keine Syntax-Hervorhebung",
"theme_none": "Keine Syntaxhervorhebung",
"theme_group_light": "Helle Themen",
"theme_group_dark": "Dunkle Themen",
"copy_title": "Kopiere in Zwischenablage"
@@ -1747,8 +1751,8 @@
"desktop-application": "Desktop Anwendung",
"native-title-bar": "Native Anwendungsleiste",
"native-title-bar-description": "In Windows und macOS, sorgt das Deaktivieren der nativen Anwendungsleiste für ein kompakteres Aussehen. Unter Linux, sorgt das Aktivieren der nativen Anwendungsleiste für eine bessere Integration mit anderen Teilen des Systems.",
"background-effects": "Hintergrundeffekte aktivieren (nur Windows 11)",
"background-effects-description": "Der Mica Effekt fügt einen unscharfen, stylischen Hintergrund in Anwendungsfenstern ein. Dieser erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
"background-effects": "Hintergrundeffekte aktivieren",
"background-effects-description": "Fügt einen unscharfen, stylischen Hintergrund in das Anwendungsfenstern ein. Dies erzeugt Tiefe und ein modernes Auftreten. \"Native Titelleiste\" muss deaktiviert sein.",
"restart-app-button": "Anwendung neustarten um Änderungen anzuwenden",
"zoom-factor": "Zoomfaktor"
},
@@ -1996,7 +2000,7 @@
"check_share_root": "Status des Freigabe-Roots prüfen",
"share_root_found": "Freigabe-Root-Notiz '{{noteTitle}}' ist bereit",
"share_root_not_found": "Keine Notiz mit #shareRoot Label gefunden",
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht geteilt"
"share_root_not_shared": "Notiz '{{noteTitle}}' hat das #shareRoot Label, wurde jedoch noch nicht freigegeben"
},
"tasks": {
"due": {
@@ -2114,8 +2118,8 @@
"show_attachments_description": "Notizanhänge anzeigen",
"search_notes_title": "Suche Notiz",
"search_notes_description": "Öffne erweiterte Suche",
"search_subtree_title": "Im Unterzweig suchen",
"search_subtree_description": "Im aktuellen Unterzweig suchen",
"search_subtree_title": "Im Zweig suchen",
"search_subtree_description": "Im aktuellen Zweig suchen",
"search_history_title": "Zeige Suchhistorie",
"search_history_description": "Zeige vorherige Suchen",
"configure_launch_bar_title": "Startleiste anpassen",
@@ -2129,7 +2133,7 @@
"next_theme_message": "Es wird aktuell das alte Design verwendet. Möchten Sie das neue Design ausprobieren?",
"next_theme_button": "Teste das neue Design",
"background_effects_title": "Hintergrundeffekte sind jetzt zuverlässig nutzbar",
"background_effects_message": "Auf Windows-Geräten sind die Hintergrundeffekte nun vollständig stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird. Diese Technik wird auch in anderen Anwendungen wie dem Windows-Explorer eingesetzt.",
"background_effects_message": "Auf Windows- und macOS-Geräten sind die Hintergrundeffekte nun stabil. Die Hintergrundeffekte verleihen der Benutzeroberfläche einen Farbakzent, indem der Hintergrund dahinter weichgezeichnet wird.",
"background_effects_button": "Aktiviere Hintergrundeffekte",
"dismiss": "Ablehnen",
"new_layout_title": "Neues Layout",
@@ -2184,9 +2188,9 @@
"new_layout_description": "Probiere das neue Layout für eine modernere Darstellung und verbesserte Benutzbarkeit aus. Kann sich in Zukunft stark ändern."
},
"server": {
"unknown_http_error_title": "Bei der Kommunikation mit dem Server ist ein Fehler aufgetreten",
"unknown_http_error_title": "Kommunikationsfehler mit dem Server",
"unknown_http_error_content": "Statuscode: {{statusCode}}\nURL: {{method}} {{url}}\nNachricht: {{message}}",
"traefik_blocks_requests": "Der Traefik Reverse-Proxy hat ein fatales Update bekommen, welche die Kommunikation mit dem Server stört."
"traefik_blocks_requests": "Der Traefik Reverse-Proxy hat eine Änderung erfahren, welches die Kommunikation mit dem Server beeinflusst."
},
"tab_history_navigation_buttons": {
"go-back": "Zur vorherigen Notiz zurück kehren",
@@ -2201,30 +2205,30 @@
"empty_hide_archived_notes": "Archivierte Notizen ausblenden"
},
"breadcrumb_badges": {
"read_only_explicit": "Nicht Änderbar",
"read_only_explicit_description": "Diese Notiz wurde händisch als nicht änderbar markiert.\nKlicke hier um sie temporär zu bearbeiten.",
"read_only_auto": "Automatisch nicht änderbar",
"read_only_auto_description": "Diese Notiz wurde automatisch aus Leistungsgründen als nicht änderbar markiert. Dieses automatische Limit kann in den Einstellungen angepasst werden.\n\nKlicke hier, um sie temporär zu bearbeiten.",
"read_only_explicit": "Schreibgeschützt",
"read_only_explicit_description": "Diese Notiz wurde händisch schreibgeschützt.\nKlicke hier um sie temporär zu bearbeiten.",
"read_only_auto": "Automatisch schreibgeschützt",
"read_only_auto_description": "Diese Notiz wurde automatisch aus Leistungsgründen als schreibgeschützt markiert. Dieses automatische Limit kann in den Einstellungen angepasst werden.\n\nKlicke hier, um sie temporär zu bearbeiten.",
"read_only_temporarily_disabled": "Temporär bearbeitbar",
"read_only_temporarily_disabled_description": "Diese Notiz ist aktuell bearbeitbar, ist aber normalerweise nicht änderbar. Sobald du zu einer anderen Notiz navigierst, kehrt diese Notiz in ihren Normalzustand zurück.\n\nKlicke hier, um die Notiz wieder nicht änderbar zu machen.",
"shared_publicly": "Öffentlich geteilt",
"shared_locally": "Lokal geteilt",
"read_only_temporarily_disabled_description": "Diese Notiz ist aktuell bearbeitbar, ist aber normalerweise schreibgeschützt. Sobald du zu einer anderen Notiz navigierst wird diese wieder schreibgeschützt.\n\nKlicke hier, um die Notiz wieder schreibgeschützt zu machen.",
"shared_publicly": "Öffentlich freigegeben",
"shared_locally": "Lokal freigegeben",
"shared_copy_to_clipboard": "Link in die Zwischenablage kopieren",
"shared_open_in_browser": "Link öffnen",
"shared_unshare": "Teilen aufheben",
"shared_open_in_browser": "Link im Browser öffnen",
"shared_unshare": "Freigabe aufheben",
"clipped_note": "Internetschnellverweis",
"clipped_note_description": "Diese Notiz wurde von {{url}} übernommen.\n\nKlicke hier, um zum Ursprung zu gehen.",
"clipped_note_description": "Diese Notiz wurde von {{url}} übernommen.\n\nKlicke hier, um zur Quelle zu gehen.",
"execute_script": "Skript ausführen",
"execute_script_description": "Diese Notiz ist eine Skriptnotiz. Klicke hier, um das Skript auszuführen.",
"execute_sql": "SQL ausführen",
"execute_sql_description": "Diese Notiz ist eine SQL-Notiz. Klicke hier, um die SQL-Abfrage auszuführen.",
"save_status_saved": "Gespeichert",
"save_status_saving": "Speichern...",
"save_status_saving": "Speichere...",
"save_status_unsaved": "Nicht gespeichert",
"save_status_error": "Speichern fehlgeschlagen",
"save_status_saving_tooltip": "Änderungen werden gespeichert.",
"save_status_unsaved_tooltip": "Es gibt ungespeicherte Änderungen, welche gleich automatisch gespeichert werden.",
"save_status_error_tooltip": "Beim speichern der Notiz ist ein Fehler aufgetreten. Wenn möglich, versuche die Notiz woandershin zu kopieren und die Applikation neu zu laden."
"save_status_error_tooltip": "Beim speichern der Notiz ist ein Fehler aufgetreten. Wenn möglich, versuche die Notiz woandershin zu kopieren und die Anwendung neu zu laden."
},
"status_bar": {
"language_title": "Inhaltssprache ändern",
@@ -2237,22 +2241,22 @@
"attachments_other": "{{count}} Anhänge",
"attachments_title_one": "Anhang in einem neuen Tab öffnen",
"attachments_title_other": "Anhänge in einem neuen Tab öffnen",
"attributes_one": "{{count}} Eigenschaft",
"attributes_other": "{{count}} Eigenschaften",
"attributes_title": "Eigene und gererbte Eigenschaften",
"attributes_one": "{{count}} Attribut",
"attributes_other": "{{count}} Attribute",
"attributes_title": "Eigene und geerbte Attribute",
"note_paths_one": "{{count}} Pfad",
"note_paths_other": "{{count}} Pfade",
"note_paths_title": "Notizpfade",
"code_note_switcher": "Sprachmodus ändern"
},
"attributes_panel": {
"title": "Notizeigenschaften"
"title": "Notizattribute"
},
"right_pane": {
"empty_message": "Für diese Notiz gibt es nichts anzuzeigen",
"empty_button": "Anzeige ausblenden",
"toggle": "Rechte Anzeige umschalten",
"custom_widget_go_to_source": "Zum Ursprungscode"
"empty_button": "Leiste ausblenden",
"toggle": "Rechte Leiste umschalten",
"custom_widget_go_to_source": "Zum Quellcode"
},
"pdf": {
"attachments_one": "{{count}} Anhang",
@@ -2262,6 +2266,9 @@
"pages_one": "{{count}} Seite",
"pages_other": "{{count}} Seiten",
"pages_alt": "Seite {{pageNumber}}",
"pages_loading": "Laden..."
"pages_loading": "Lädt..."
},
"platform_indicator": {
"available_on": "Verfügbar auf {{platform}}"
}
}

View File

@@ -1815,7 +1815,11 @@
"configure_launchbar": "Configure Launchbar"
},
"sql_result": {
"no_rows": "No rows have been returned for this query"
"not_executed": "The query has not been executed yet.",
"no_rows": "No rows have been returned for this query",
"failed": "SQL query execution has failed",
"statement_result": "Statement result",
"execute_now": "Execute now"
},
"sql_table_schemas": {
"tables": "Tables"
@@ -1954,8 +1958,8 @@
"desktop-application": "Desktop Application",
"native-title-bar": "Native title bar",
"native-title-bar-description": "For Windows and macOS, keeping the native title bar off makes the application look more compact. On Linux, keeping the native title bar on integrates better with the rest of the system.",
"background-effects": "Enable background effects (Windows 11 only)",
"background-effects-description": "The Mica effect adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
"background-effects": "Enable background effects",
"background-effects-description": "Adds a blurred, stylish background to app windows, creating depth and a modern look. \"Native title bar\" must be disabled.",
"restart-app-button": "Restart the application to view the changes",
"zoom-factor": "Zoom factor"
},
@@ -1973,6 +1977,7 @@
},
"geo-map": {
"create-child-note-title": "Create a new child note and add it to the map",
"create-child-note-text": "Add marker",
"create-child-note-instruction": "Click on the map to create a new note at that location or press Escape to dismiss.",
"unable-to-load-map": "Unable to load map."
},
@@ -2148,7 +2153,7 @@
"next_theme_message": "You are currently using the legacy theme, would you like to try the new theme?",
"next_theme_button": "Try the new theme",
"background_effects_title": "Background effects are now stable",
"background_effects_message": "On Windows devices, background effects are now fully stable. The background effects adds a touch of color to the user interface by blurring the background behind it. This technique is also used in other applications such as Windows Explorer.",
"background_effects_message": "On Windows and macOS devices, background effects are now stable. The background effects adds a touch of color to the user interface by blurring the background behind it.",
"background_effects_button": "Enable background effects",
"new_layout_title": "New layout",
"new_layout_message": "Weve introduced a modernized layout for Trilium. The ribbon has been removed and seamlessly integrated into the main interface, with a new status bar and expandable sections (such as promoted attributes) taking over key functions.\n\nThe new layout is enabled by default, and can be temporarily disabled via Options → Appearance.",
@@ -2263,5 +2268,13 @@
"pages_other": "{{count}} pages",
"pages_alt": "Page {{pageNumber}}",
"pages_loading": "Loading..."
},
"platform_indicator": {
"available_on": "Available on {{platform}}"
},
"mobile_tab_switcher": {
"title_one": "{{count}} tab",
"title_other": "{{count}} tabs",
"more_options": "More options"
}
}

View File

@@ -28,7 +28,10 @@
},
"widget-render-error": {
"title": "Hubo un fallo al renderizar un widget personalizado de React"
}
},
"widget-missing-parent": "El widget personalizado no tiene definida la propiedad obligatoria '{{property}}'.\n\nSi este script está pensado para ejecutarse sin un elemento de interfaz de usuario, utilice '#run=frontendStartup' en su lugar.",
"open-script-note": "Abrir script",
"scripting-error": "Error en script personalizado: {{title}}"
},
"add_link": {
"add_link": "Agregar enlace",
@@ -211,7 +214,8 @@
"info": {
"modalTitle": "Mensaje informativo",
"closeButton": "Cerrar",
"okButton": "Aceptar"
"okButton": "Aceptar",
"copy_to_clipboard": "Copiar al portapapeles"
},
"jump_to_note": {
"search_button": "Buscar en texto completo",
@@ -697,7 +701,13 @@
"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?",
"print_pdf": "Exportar como PDF...",
"open_note_on_server": "Abrir nota en servidor"
"open_note_on_server": "Abrir nota en servidor",
"view_revisions": "Revisiones de la nota...",
"advanced": "Avanzado",
"export_as_image": "Exportar como imagen",
"export_as_image_png": "PNG (ráster)",
"export_as_image_svg": "SVG (vectorial)",
"note_map": "Mapa de la nota"
},
"onclick_button": {
"no_click_handler": "El widget de botón '{{componentId}}' no tiene un controlador de clics definido"
@@ -759,7 +769,13 @@
"reset-default": "Restablecer a icono por defecto",
"search_placeholder_one": "Buscar {{number}} icono a través de {{count}} paquetes",
"search_placeholder_many": "Buscar {{number}} iconos a través de {{count}} paquetes",
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes"
"search_placeholder_other": "Buscar {{number}} iconos a través de {{count}} paquetes",
"search_placeholder_filtered": "Buscar {{number}} iconos en {{name}}",
"filter": "Filtro",
"filter-none": "Todos los iconos",
"filter-default": "Iconos predeterminados",
"icon_tooltip": "{{name}}\nPaquete de iconos: {{iconPack}}",
"no_results": "No se encontraron iconos."
},
"basic_properties": {
"note_type": "Tipo de nota",
@@ -783,10 +799,11 @@
"board": "Tablero",
"include_archived_notes": "Mostrar notas archivadas",
"presentation": "Presentación",
"expand_tooltip": "Expande las notas hijas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
"expand_tooltip": "Expande las subnotas inmediatas de esta colección (un nivel). Para más opciones, pulsa la flecha a la derecha.",
"expand_first_level": "Expandir hijos inmediatos",
"expand_nth_level": "Expandir {{depth}} niveles",
"expand_all_levels": "Expandir todos los niveles"
"expand_all_levels": "Expandir todos los niveles",
"hide_child_notes": "Ocultar subnotas en el árbol"
},
"edited_notes": {
"no_edited_notes_found": "Aún no hay notas editadas en este día...",
@@ -819,7 +836,8 @@
},
"inherited_attribute_list": {
"title": "Atributos heredados",
"no_inherited_attributes": "Sin atributos heredados."
"no_inherited_attributes": "Sin atributos heredados.",
"none": "ninguno"
},
"note_info_widget": {
"note_id": "ID de nota",
@@ -830,7 +848,9 @@
"note_size_info": "El tamaño de la nota proporciona una estimación aproximada de los requisitos de almacenamiento para esta nota. Toma en cuenta el contenido de la nota y el contenido de sus revisiones de nota.",
"calculate": "calcular",
"subtree_size": "(tamaño del subárbol: {{size}} en {{count}} notas)",
"title": "Información de nota"
"title": "Información de nota",
"mime": "Tipo MIME",
"show_similar_notes": "Mostrar notas similares"
},
"note_map": {
"open_full": "Ampliar al máximo",
@@ -893,7 +913,8 @@
"search_parameters": "Parámetros de búsqueda",
"unknown_search_option": "Opción de búsqueda desconocida {{searchOptionName}}",
"search_note_saved": "La nota de búsqueda se ha guardado en {{- notePathTitle}}",
"actions_executed": "Las acciones han sido ejecutadas."
"actions_executed": "Las acciones han sido ejecutadas.",
"view_options": "Ver opciones:"
},
"similar_notes": {
"title": "Notas similares",
@@ -996,7 +1017,13 @@
},
"editable_text": {
"placeholder": "Escribe aquí el contenido de tu nota...",
"auto-detect-language": "Detectado automáticamente"
"auto-detect-language": "Detectado automáticamente",
"editor_crashed_title": "El editor de texto ha dejado de responder",
"editor_crashed_content": "Su contenido ha sido recuperado con éxito, pero puede que algunos de sus cambios más recientes no se hayan guardado.",
"editor_crashed_details_button": "Ver más detalles...",
"editor_crashed_details_intro": "Si experimenta este error varias veces, considere informarlo en GitHub adjuntando la siguiente información.",
"editor_crashed_details_title": "Información técnica",
"keeps-crashing": "El componente de edición sigue fallando. Por favor, intente reiniciar Trilium. Si el problema persiste, considere crear un informe de fallos."
},
"empty": {
"open_note_instruction": "Abra una nota escribiendo el título de la nota en la entrada a continuación o elija una nota en el árbol.",
@@ -1312,8 +1339,8 @@
"code_mime_types": {
"title": "Tipos MIME disponibles en el menú desplegable",
"tooltip_syntax_highlighting": "Resaltado de sintaxis",
"tooltip_code_block_syntax": "Bloques de Código en notas de Texto",
"tooltip_code_note_syntax": "Notas de Código"
"tooltip_code_block_syntax": "Bloques de código en Notas de texto",
"tooltip_code_note_syntax": "Notas de código"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Combinaciones de teclas Vim",
@@ -1390,16 +1417,16 @@
"markdown": "Estilo Markdown"
},
"highlights_list": {
"title": "Lista de aspectos destacados",
"description": "Puede personalizar la lista de aspectos destacados que se muestra en el panel derecho:",
"title": "Lista de puntos destacados",
"description": "Puede personalizar la lista de puntos destacados que se muestra en el panel derecho:",
"bold": "Texto en negrita",
"italic": "Texto en cursiva",
"underline": "Texto subrayado",
"color": "Texto con color",
"bg_color": "Texto con color de fondo",
"visibility_title": "Visibilidad de la lista de aspectos destacados",
"visibility_description": "Puede ocultar el widget de aspectos destacados por nota agregando una etiqueta #hideHighlightWidget.",
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los aspectos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
"visibility_title": "Visibilidad de la lista de puntos destacados",
"visibility_description": "Puede ocultar el widget de puntos destacados por nota agregando la etiqueta #hideHighlightWidget.",
"shortcut_info": "Puede configurar un método abreviado de teclado para alternar rápidamente el panel derecho (incluidos los puntos destacados) en Opciones -> Atajos (nombre 'toggleRightPane')."
},
"table_of_contents": {
"title": "Tabla de contenido",
@@ -1587,7 +1614,7 @@
},
"bookmark_switch": {
"bookmark": "Marcador",
"bookmark_this_note": "Añadir esta nota a marcadores en el panel lateral izquierdo",
"bookmark_this_note": "Agregar esta nota a marcadores en el panel lateral izquierdo",
"remove_bookmark": "Eliminar marcador"
},
"editability_select": {
@@ -1635,7 +1662,10 @@
"convert-to-attachment-confirm": "¿Está seguro que desea convertir las notas seleccionadas en archivos adjuntos de sus notas padres? Esta operación solo aplica a notas de Imagen, otras notas serán omitidas.",
"open-in-popup": "Edición rápida",
"archive": "Archivar",
"unarchive": "Desarchivar"
"unarchive": "Desarchivar",
"open-in-a-new-window": "Abrir en una nueva ventana",
"hide-subtree": "Ocultar subárbol",
"show-subtree": "Mostrar subárbol"
},
"shared_info": {
"shared_publicly": "Esta nota está compartida públicamente en {{- link}}.",
@@ -1696,7 +1726,13 @@
},
"highlights_list_2": {
"title": "Lista de destacados",
"options": "Opciones"
"options": "Opciones",
"title_with_count_one": "{{count}} punto destacado",
"title_with_count_many": "{{count}} puntos destacados",
"title_with_count_other": "{{count}} puntos destacados",
"modal_title": "Configurar la lista de puntos destacados",
"menu_configure": "Configurar la lista de puntos destacados...",
"no_highlights": "Ningún punto destacado encontrado."
},
"quick-search": {
"placeholder": "Búsqueda rápida",
@@ -1719,7 +1755,18 @@
"refresh-saved-search-results": "Refrescar resultados de búsqueda guardados",
"create-child-note": "Crear subnota",
"unhoist": "Desanclar",
"toggle-sidebar": "Alternar barra lateral"
"toggle-sidebar": "Alternar barra lateral",
"dropping-not-allowed": "No está permitido soltar notas en esta ubicación.",
"clone-indicator-tooltip": "Esta nota tiene {{- count}} padres: {{- parents}}",
"clone-indicator-tooltip-single": "Esta nota está clonada (1 padre adicional: {{- parent}})",
"shared-indicator-tooltip": "Esta nota está compartida públicamente",
"shared-indicator-tooltip-with-url": "Esta nota está compartida públicamente en: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} subnota que está oculta del árbol",
"subtree-hidden-tooltip_many": "{{count}} subnotas que están ocultas del árbol",
"subtree-hidden-tooltip_other": "{{count}} subnotas que están ocultas del árbol",
"subtree-hidden-moved-title": "Agregado a {{title}}",
"subtree-hidden-moved-description-collection": "Esta colección oculta sus subnotas en el árbol.",
"subtree-hidden-moved-description-other": "Las subnotas están ocultas en el árbol para esta nota."
},
"title_bar_buttons": {
"window-on-top": "Mantener esta ventana en la parte superior"
@@ -1730,10 +1777,21 @@
"printing_pdf": "Exportando a PDF en curso..",
"print_report_collection_content_one": "{{count}} nota en la colección no se puede imprimir porque no son compatibles o está protegida.",
"print_report_collection_content_many": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas."
"print_report_collection_content_other": "{{count}} notas en la colección no se pueden imprimir porque no son compatibles o están protegidas.",
"print_report_title": "Imprimir informe",
"print_report_collection_details_button": "Ver detalles",
"print_report_collection_details_ignored_notes": "Notas ignoradas"
},
"note_title": {
"placeholder": "escriba el título de la nota aquí..."
"placeholder": "escriba el título de la nota aquí...",
"created_on": "Creado en <Value />",
"last_modified": "Modificado en <Value />",
"note_type_switcher_label": "Cambiar de {{type}} a:",
"note_type_switcher_others": "Otro tipo de nota",
"note_type_switcher_templates": "Plantilla",
"note_type_switcher_collection": "Colección",
"edited_notes": "Notas editadas en este día",
"promoted_attributes": "Atributos promovidos"
},
"search_result": {
"no_notes_found": "No se han encontrado notas para los parámetros de búsqueda dados.",
@@ -1743,7 +1801,11 @@
"configure_launchbar": "Configurar barra de lanzamiento"
},
"sql_result": {
"no_rows": "No se han devuelto filas para esta consulta"
"no_rows": "No se han devuelto filas para esta consulta",
"not_executed": "La consulta aún no ha sido ejecutada.",
"failed": "La ejecución de la consulta SQL ha fallado",
"statement_result": "Resultado de declaración",
"execute_now": "Ejecutar ahora"
},
"sql_table_schemas": {
"tables": "Tablas"
@@ -1762,7 +1824,8 @@
},
"toc": {
"table_of_contents": "Tabla de contenido",
"options": "Opciones"
"options": "Opciones",
"no_headings": "Sin encabezados."
},
"watched_file_update_status": {
"file_last_modified": "Archivo <code class=\"file-path\"></code> ha sido modificado por última vez en<span class=\"file-last-modified\"></span>.",
@@ -1874,14 +1937,15 @@
"open_note_in_new_tab": "Abrir nota en una pestaña nueva",
"open_note_in_new_split": "Abrir nota en una nueva división",
"open_note_in_new_window": "Abrir nota en una nueva ventana",
"open_note_in_popup": "Edición rápida"
"open_note_in_popup": "Edición rápida",
"open_note_in_other_split": "Abrir nota en la otra división"
},
"electron_integration": {
"desktop-application": "Aplicación de escritorio",
"native-title-bar": "Barra de título nativa",
"native-title-bar-description": "Para Windows y macOS, quitar la barra de título nativa hace que la aplicación se vea más compacta. En Linux, mantener la barra de título nativa hace que se integre mejor con el resto del sistema.",
"background-effects": "Habilitar efectos de fondo (sólo en Windows 11)",
"background-effects-description": "El efecto Mica agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
"background-effects": "Habilitar efectos de fondo",
"background-effects-description": "Agrega un fondo borroso y elegante a las ventanas de la aplicación, creando profundidad y un aspecto moderno. \"Título nativo de la barra\" debe deshabilitarse.",
"restart-app-button": "Reiniciar la aplicación para ver los cambios",
"zoom-factor": "Factor de zoom"
},
@@ -1900,7 +1964,8 @@
"geo-map": {
"create-child-note-title": "Crear una nueva subnota y agregarla al mapa",
"create-child-note-instruction": "Dé clic en el mapa para crear una nueva nota en esa ubicación o presione Escape para cancelar.",
"unable-to-load-map": "No se puede cargar el mapa."
"unable-to-load-map": "No se puede cargar el mapa.",
"create-child-note-text": "Agregar marcador"
},
"geo-map-context": {
"open-location": "Abrir ubicación",
@@ -1943,10 +2008,11 @@
},
"note_language": {
"not_set": "Idioma no establecido",
"configure-languages": "Configurar idiomas..."
"configure-languages": "Configurar idiomas...",
"help-on-languages": "Ayuda en idiomas de contenido..."
},
"content_language": {
"title": "Contenido de idiomas",
"title": "Idiomas de contenido",
"description": "Seleccione uno o más idiomas que deben aparecer en la selección del idioma en la sección Propiedades Básicas de una nota de texto de solo lectura o editable. Esto permitirá características tales como corrección de ortografía o soporte de derecha a izquierda."
},
"switch_layout_button": {
@@ -1961,7 +2027,8 @@
"button_title": "Exportar diagrama como PNG"
},
"svg": {
"export_to_png": "El diagrama no pudo ser exportado a PNG."
"export_to_png": "El diagrama no pudo ser exportado a PNG.",
"export_to_svg": "El diagrama no pudo ser exportado a SVG."
},
"code_theme": {
"title": "Apariencia",
@@ -2064,9 +2131,12 @@
"next_theme_message": "Estás usando actualmente el tema heredado. ¿Te gustaría probar el nuevo tema?",
"next_theme_button": "Prueba el nuevo tema",
"background_effects_title": "Los efectos de fondo son ahora estables",
"background_effects_message": "En los dispositivos Windows, los efectos de fondo ya son totalmente estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás. Esta técnica también se utiliza en otras aplicaciones como el Explorador de Windows.",
"background_effects_message": "En los dispositivos Windows y macOS, los efectos de fondo ya son estables. Los efectos de fondo añaden un toque de color a la interfaz de usuario difuminando el fondo que hay detrás.",
"background_effects_button": "Activar efectos de fondo",
"dismiss": "Desestimar"
"dismiss": "Desestimar",
"new_layout_title": "Nuevo diseño",
"new_layout_message": "Hemos introducido un diseño modernizado para Trilium. La cinta se ha eliminado y se ha integrado perfectamente en la interfaz principal, con una nueva barra de estado y secciones ampliables (como los atributos promovidos) que tienen funciones clave.\n\nEl nuevo diseño está habilitado por defecto, y puede ser deshabilitado temporalmente a través de Opciones → Apariencia.",
"new_layout_button": "Más información"
},
"ui-performance": {
"title": "Rendimiento",
@@ -2081,7 +2151,10 @@
},
"settings_appearance": {
"related_code_blocks": "Esquema de colores para bloques de código en notas de texto",
"related_code_notes": "Esquema de colores para notas de código"
"related_code_notes": "Esquema de colores para notas de código",
"ui": "Interfaz de usuario",
"ui_old_layout": "Antiguo diseño",
"ui_new_layout": "Nuevo diseño"
},
"units": {
"percentage": "%"
@@ -2129,7 +2202,12 @@
"attributes_other": "{{count}} atributos",
"note_paths_one": "{{count}} ruta",
"note_paths_many": "{{count}} rutas",
"note_paths_other": "{{count}} rutas"
"note_paths_other": "{{count}} rutas",
"language_title": "Cambiar el idioma del contenido",
"note_info_title": "Ver información de la nota (p. e., fechas, tamaño de la nota)",
"attributes_title": "Atributos propios y atributos heredados",
"note_paths_title": "Rutas de nota",
"code_note_switcher": "Cambiar modo de idioma"
},
"pdf": {
"attachments_one": "{{count}} adjunto",
@@ -2140,6 +2218,72 @@
"layers_other": "{{count}} capas",
"pages_one": "{{count}} página",
"pages_many": "{{count}} páginas",
"pages_other": "{{count}} páginas"
"pages_other": "{{count}} páginas",
"pages_alt": "Página {{pageNumber}}",
"pages_loading": "Cargando..."
},
"experimental_features": {
"title": "Opciones experimentales",
"disclaimer": "Estas opciones son experimentales y pueden causar inestabilidad. Úselas con precaución.",
"new_layout_name": "Nuevo diseño",
"new_layout_description": "Pruebe el nuevo diseño para tener un aspecto más moderno y usabilidad mejorada. Sujeto a grandes cambios en las próximas versiones."
},
"popup-editor": {
"maximize": "Cambiar a editor completo"
},
"server": {
"unknown_http_error_title": "Error de comunicación con el servidor",
"unknown_http_error_content": "Código de estado: {{statusCode}}\nURL: {{method}} {{url}}\nMensaje: {{message}}",
"traefik_blocks_requests": "Si está usando el proxy inverso Traefik, este introdujo un cambio que afecta la comunicación con el servidor."
},
"tab_history_navigation_buttons": {
"go-back": "Volver a la nota anterior",
"go-forward": "Avanzar a la siguiente nota"
},
"breadcrumb": {
"hoisted_badge": "Anclada",
"hoisted_badge_title": "Desanclar",
"workspace_badge": "Espacio de trabajo",
"scroll_to_top_title": "Saltar al inicio de la nota",
"create_new_note": "Crear nueva subnota",
"empty_hide_archived_notes": "Ocultar notas archivadas"
},
"breadcrumb_badges": {
"read_only_explicit": "Sólo lectura",
"read_only_explicit_description": "Esta nota se ha fijado manualmente como sólo lectura.\nHaga clic para editarla temporalmente.",
"read_only_auto": "Sólo lectura automática",
"read_only_auto_description": "Esta nota se fijó automáticamente con el modo de sólo lectura por razones de rendimiento. Este límite automático es ajustable desde los ajustes.\n\nHaga clic para editarla temporalmente.",
"read_only_temporarily_disabled": "Temporalmente editable",
"read_only_temporarily_disabled_description": "Esta nota actualmente es editable, pero normalmente es de sólo lectura. La nota volverá a ser de sólo lectura tan pronto como navegue a otra nota.\n\nHaga clic para volver a habilitar el modo de sólo lectura.",
"shared_publicly": "Compartida públicamente",
"shared_locally": "Compartida localmente",
"shared_copy_to_clipboard": "Copiar enlace al portapapeles",
"shared_open_in_browser": "Abrir enlace en el navegador",
"shared_unshare": "Eliminar compartido",
"clipped_note_description": "Esta nota fue tomada originalmente de {{url}}.\n\nHaga clic para navegar a la página web de origen.",
"execute_script": "Ejecutar script",
"execute_script_description": "Esta nota es una nota de script. Haga clic para ejecutar el script.",
"execute_sql": "Ejecutar SQL",
"execute_sql_description": "Esta nota es una nota SQL. Haga clic para ejecutar la consulta SQL.",
"save_status_saved": "Guardado",
"save_status_saving": "Guardando...",
"save_status_unsaved": "Sin guardar",
"save_status_error": "Fallo al guardar",
"save_status_saving_tooltip": "Los cambios están siendo guardados.",
"save_status_unsaved_tooltip": "Hay cambios sin guardar. Se guardarán automáticamente en un momento.",
"save_status_error_tooltip": "Se produjo un error al guardar la nota. Si es posible, trate de copiar el contenido de la nota en otro lugar y recargar la aplicación.",
"clipped_note": "Clip web"
},
"attributes_panel": {
"title": "Atributos de nota"
},
"right_pane": {
"empty_message": "Nada que mostrar para esta nota",
"empty_button": "Ocultar el panel",
"toggle": "Alternar panel derecho",
"custom_widget_go_to_source": "Ir al código fuente"
},
"platform_indicator": {
"available_on": "Disponible en {{platform}}"
}
}

View File

@@ -11,8 +11,8 @@
},
"toast": {
"critical-error": {
"title": "Kesalahan kritis",
"message": "Telah terjadi kesalahan kritis yang mencegah aplikasi klien untuk memulai:\n\n{{message}}\n\nHal ini kemungkinan besar disebabkan oleh skrip yang gagal secara tidak terduga. Coba jalankan aplikasi dalam mode aman dan atasi masalahnya."
"title": "Eror kritikal",
"message": "Telah terjadi eror kritikal yang mencegah aplikasi klien untuk memulai:\n\n{{message}}\n\nHal ini kemungkinan besar disebabkan oleh skrip yang gagal secara tidak terduga. Coba jalankan aplikasi dalam mode aman dan atasi masalahnya."
},
"widget-error": {
"title": "Gagal menginisialisasi widget",
@@ -21,12 +21,65 @@
},
"bundle-error": {
"title": "Gagal memuat skrip kustom",
"message": "Skrip dari catatan dengan ID \"{{id}}\", berjudul \"{{title}}\" tidak dapat dijalankan karena:\n\n{{message}}"
}
"message": "Skrip tidak dapat dijalankan:\n\n{{message}}"
},
"widget-list-error": {
"title": "Gagal mendapatkan daftar widget dari server"
},
"open-script-note": "Buka skrip catatan",
"widget-render-error": {
"title": "Gagal render widget React custom"
},
"widget-missing-parent": "Widget custom '{{property}}' tidak terdefinisi.\n\nJika skrip ini bermaksud untuk bisa dijalankan tanpa elemen UI, gunakanlah '#run=frontendStartup'.",
"scripting-error": "Skrip custom eror : {{title}}"
},
"add_link": {
"add_link": "Tambah tautan",
"help_on_links": "Bantuan pada tautan",
"note": "Catatan"
"note": "Catatan",
"search_note": "cari catatan berdasarkan nama",
"link_title_mirrors": "judul tautan mencerminkan judul catatan saat ini",
"link_title_arbitrary": "judul tautan dapat diubah secara bebas",
"link_title": "Judul tautan",
"button_add_link": "Tambah tautan"
},
"branch_prefix": {
"edit_branch_prefix_multiple": "Edit prefiks cabang untuk {{count}} cabang",
"help_on_tree_prefix": "Bantuan pada prefiks pohon catatan",
"prefix": "Prefiks: ",
"save": "Simpan",
"branch_prefix_saved": "Prefiks cabang telah disimpan.",
"branch_prefix_saved_multiple": "Prefix cabang telah disimpan pada {{count}} cabang.",
"affected_branches": "Cabang terdampak ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Aksi borongan",
"affected_notes": "Catatan terdampak",
"include_descendants": "Sertakan anakan dari catatan yang dipilih",
"available_actions": "Pilihan aksi",
"chosen_actions": "Aksi terpilih",
"execute_bulk_actions": "Eksekusi aksi borongan",
"bulk_actions_executed": "Aksi borongan telah di eksekusi dengan sukses.",
"none_yet": "Belum ada... tambahkan aksi dengan memilih salah satu dari aksi di atas.",
"labels": "Label-label"
},
"confirm": {
"cancel": "Batal",
"ok": "Oke",
"are_you_sure_remove_note": "Apakah anda yakin mau membuang catatan \"{{title}}\" dari peta relasi? ",
"if_you_dont_check": "Jika Anda tidak mencentang ini, catatan hanya akan dihapus dari peta relasi.",
"also_delete_note": "Hapus juga catatannya"
},
"delete_notes": {
"delete_notes_preview": "Hapus pratinjau catatan",
"close": "Tutup",
"delete_all_clones_description": "Hapus seluruh duplikat (bisa dikembalikan di menu revisi)",
"erase_notes_description": "Penghapusan normal hanya menandai catatan sebagai dihapus dan dapat dipulihkan (melalui dialog versi revisi) dalam jangka waktu tertentu. Mencentang opsi ini akan menghapus catatan secara permanen seketika dan catatan tidak akan bisa dipulihkan kembali.",
"erase_notes_warning": "Hapus catatan secara permanen (tidak bisa dikembalikan), termasuk semua duplikat. Aksi akan memaksa aplikasi untuk mengulang kembali.",
"notes_to_be_deleted": "Catatan-catatan berikut akan dihapuskan ({{notesCount}})",
"no_note_to_delete": "Tidak ada Catatan yang akan dihapus (hanya duplikat)."
},
"clone_to": {
"clone_notes_to": "Duplikat catatan ke…"
}
}

View File

@@ -325,7 +325,10 @@
"apply-bulk-actions": "Applica azioni in blocco",
"converted-to-attachments": "{{count}} note sono state convertite in allegati.",
"convert-to-attachment-confirm": "Sei sicuro di voler convertire le note selezionate in allegati delle note principali? Questa operazione si applica solo alle note immagine, le altre note verranno ignorate.",
"open-in-popup": "Modifica rapida"
"open-in-popup": "Modifica rapida",
"open-in-a-new-window": "Apri in una nuova finestra",
"hide-subtree": "Nascondi sottostruttura",
"show-subtree": "Mostra sottoalbero"
},
"electron_context_menu": {
"cut": "Taglia",
@@ -1378,7 +1381,8 @@
"expand_tooltip": "Espande i figli diretti di questa raccolta (a un livello di profondità). Per ulteriori opzioni, premere la freccia a destra.",
"expand_first_level": "Espandi figli diretti",
"expand_nth_level": "Espandi {{depth}} livelli",
"expand_all_levels": "Espandi tutti i livelli"
"expand_all_levels": "Espandi tutti i livelli",
"hide_child_notes": "Nascondi note secondarie nell'albero"
},
"edited_notes": {
"no_edited_notes_found": "Nessuna nota modificata per questo giorno...",
@@ -1899,7 +1903,13 @@
"clone-indicator-tooltip": "Questa nota ha {{- count}} genitori: {{- parents}}",
"clone-indicator-tooltip-single": "Questa nota è stata clonata (1 genitore aggiuntivo: {{- parent}})",
"shared-indicator-tooltip": "Questa nota è condivisa pubblicamente",
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}"
"shared-indicator-tooltip-with-url": "Questa nota è condivisa pubblicamente all'indirizzo: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} nota secondaria nascosta dall'albero",
"subtree-hidden-tooltip_many": "{{count}} note secondarie nascoste dall'albero",
"subtree-hidden-tooltip_other": "{{count}} note secondarie nascoste dall'albero",
"subtree-hidden-moved-title": "Aggiunto a {{title}}",
"subtree-hidden-moved-description-collection": "Questa raccolta nasconde le sue note secondarie nell'albero.",
"subtree-hidden-moved-description-other": "Le note secondarie sono nascoste nell'albero di questa nota."
},
"title_bar_buttons": {
"window-on-top": "Mantieni la finestra in primo piano"
@@ -1934,7 +1944,11 @@
"configure_launchbar": "Configura Launchbar"
},
"sql_result": {
"no_rows": "Nessuna riga è stata restituita per questa query"
"no_rows": "Nessuna riga è stata restituita per questa query",
"not_executed": "La query non è stata ancora eseguita.",
"failed": "Esecuzione query SQL non riuscita",
"statement_result": "Risultato della dichiarazione",
"execute_now": "Esegui ora"
},
"watched_file_update_status": {
"file_last_modified": "Il file <code class=\"file-path\"></code> è stato modificato l'ultima volta il <span class=\"file-last-modified\"></span>.",

View File

@@ -1288,7 +1288,11 @@
"search_not_executed": "検索はまだ実行されていません。上の「検索」ボタンをクリックすると、検索結果が表示されます。"
},
"sql_result": {
"no_rows": "このクエリでは行が返されませんでした"
"no_rows": "このクエリでは行が返されませんでした",
"not_executed": "クエリはまだ実行されていません。",
"failed": "SQLクエリの実行に失敗しました",
"statement_result": "ステートメント結果",
"execute_now": "今すぐ実行"
},
"sql_table_schemas": {
"tables": "テーブル"
@@ -1753,8 +1757,8 @@
"desktop-application": "デスクトップアプリケーション",
"native-title-bar": "ネイティブタイトルバー",
"native-title-bar-description": "WindowsとmacOSでは、ネイティブタイトルバーをオフにしておくと、アプリケーションがよりコンパクトに見えます。Linuxでは、ネイティブタイトルバーを表示したままの方が、他のシステムとの統一性が高まります。",
"background-effects": "背景効果を有効化Windows 11のみ",
"background-effects-description": "Mica効果は、アプリウィンドウにぼかされたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
"background-effects": "背景効果を有効化",
"background-effects-description": "アプリウィンドウにぼかしの効いたスタイリッシュな背景を追加し、奥行きとモダンな外観を演出します。「ネイティブタイトルバー」を無効にする必要があります。",
"restart-app-button": "アプリケーションを再起動して変更を反映",
"zoom-factor": "ズーム倍率"
},
@@ -2040,7 +2044,7 @@
"next_theme_message": "現在、レガシーテーマを使用しています。新しいテーマを試してみませんか?",
"next_theme_button": "新しいテーマを試す",
"background_effects_title": "背景効果が安定しました",
"background_effects_message": "Windowsデバイスで、背景効果が完全に安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。この技術は、Windowsエクスプローラーなどの他のアプリケーションでも使用されています。",
"background_effects_message": "WindowsおよびmacOSデバイスで、背景効果が安定しました。背景効果は、背景をぼかすことでユーザーインターフェースに彩りを添えます。",
"background_effects_button": "背景効果を有効にする",
"dismiss": "却下",
"new_layout_title": "新しいレイアウト",
@@ -2249,5 +2253,8 @@
"pages_other": "{{count}} ページ",
"pages_alt": "ページ {{pageNumber}}",
"pages_loading": "読み込み中..."
},
"platform_indicator": {
"available_on": "{{platform}} で利用可能"
}
}

View File

@@ -68,7 +68,7 @@
"attachment_detail_2": {
"deletion_reason": ", deoarece nu există o legătură către atașament în conținutul notiței. Pentru a preveni ștergerea, trebuie adăugată înapoi o legătură către atașament în conținut sau atașamentul trebuie convertit în notiță.",
"link_copied": "O legătură către atașament a fost copiată în clipboard.",
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}",
"role_and_size": "Rol: {{role}}, dimensiune: {{size}}, MIME: {{- mimeType}}",
"unrecognized_role": "Rol atașament necunoscut: „{{role}}”.",
"will_be_deleted_in": "Acest atașament va fi șters automat în {{time}}",
"will_be_deleted_soon": "Acest atașament va fi șters automat în curând"
@@ -293,7 +293,8 @@
"expand_tooltip": "Expandează subnotițele directe ale acestei colecții (un singur nivel de adâncime). Pentru mai multe opțiuni, apăsați săgeata din dreapta.",
"expand_first_level": "Expandează subnotițele directe",
"expand_nth_level": "Expandează pe {{depth}} nivele",
"expand_all_levels": "Expandează pe toate nivelele"
"expand_all_levels": "Expandează pe toate nivelele",
"hide_child_notes": "Ascunde subnotițele din arbore"
},
"bookmark_switch": {
"bookmark": "Semn de carte",
@@ -569,7 +570,7 @@
"file_size": "Dimensiunea fișierului",
"file_type": "Tipul fișierului",
"note_id": "ID-ul notiței",
"open": "Deschide",
"open": "Deschide în exterior",
"original_file_name": "Denumirea originală a fișierului",
"title": "Fișier",
"upload_failed": "Încărcarea a unei noi revizii ale fișierului a eșuat.",
@@ -795,7 +796,8 @@
},
"inherited_attribute_list": {
"no_inherited_attributes": "Niciun atribut moștenit.",
"title": "Atribute moștenite"
"title": "Atribute moștenite",
"none": "niciunul"
},
"jump_to_note": {
"search_button": "Caută în întregul conținut",
@@ -880,7 +882,11 @@
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
"print_pdf": "Exportare ca PDF...",
"open_note_on_server": "Deschide notița pe server",
"view_revisions": "Revizii ale notițelor..."
"view_revisions": "Revizii ale notițelor...",
"export_as_image": "Exportează ca imagine",
"export_as_image_png": "PNG (bitmap)",
"export_as_image_svg": "SVG (vectorial)",
"note_map": "Harta notițelor"
},
"note_erasure_timeout": {
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
@@ -899,7 +905,9 @@
"note_size_info": "Dimensiunea notiței reprezintă o aproximare a cerințelor de stocare ale acestei notițe. Ia în considerare conținutul notiței dar și ale reviziilor sale.",
"subtree_size": "(dimensiunea sub-arborelui: {{size}} în {{count}} notițe)",
"title": "Informații despre notiță",
"type": "Tip"
"type": "Tip",
"mime": "Tip MIME",
"show_similar_notes": "Afișează notițe similare"
},
"note_launcher": {
"this_launcher_doesnt_define_target_note": "Acesată scurtătură nu definește o notiță-destinație."
@@ -1157,7 +1165,8 @@
"search_parameters": "Parametrii de căutare",
"search_script": "script de căutare",
"search_string": "șir de căutat",
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”"
"unknown_search_option": "Opțiune de căutare necunoscută „{{searchOptionName}}”",
"view_options": "Opțiuni de afișare:"
},
"search_engine": {
"baidu": "Baidu",
@@ -1309,8 +1318,17 @@
},
"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}}"
}
"message": "Scriptul nu a putut fi executat din cauza:\n\n{{message}}"
},
"widget-list-error": {
"title": "Nu s-a putut obține lista de widget-uri de la server"
},
"widget-render-error": {
"title": "Nu s-a putut randa un widget React"
},
"widget-missing-parent": "Widget-ul personalizat nu are definită proprietatea necesară „{{property}}“.\n\nDacă acest script este menit să ruleze fără interfață grafică, folosiți '#run=frontendStartup'.",
"open-script-note": "Deschide notița scriptului",
"scripting-error": "Eroare script personalizat: {{title}}"
},
"tray": {
"enable_tray": "Activează system tray-ul (este necesară repornirea aplicației pentru a avea efect)",
@@ -1417,7 +1435,10 @@
"convert-to-attachment-confirm": "Doriți convertirea notițelor selectate în atașamente ale notiței părinte? Această operațiune se aplică doar notițelor de tip imagine, celelalte vor fi ignorate.",
"open-in-popup": "Editare rapidă",
"archive": "Arhivează",
"unarchive": "Dezarhivează"
"unarchive": "Dezarhivează",
"open-in-a-new-window": "Deschide în fereastră nouă",
"hide-subtree": "Ascunde subnotițele",
"show-subtree": "Afișează subnotițele"
},
"shared_info": {
"help_link": "Pentru informații vizitați <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki-ul</a>.",
@@ -1478,12 +1499,27 @@
},
"highlights_list_2": {
"options": "Setări",
"title": "Listă de evidențieri"
"title": "Listă de evidențieri",
"title_with_count_one": "{{count}} evidențiere",
"title_with_count_few": "{{count}} evidențieri",
"title_with_count_other": "{{count}} de evidențieri",
"modal_title": "Configurează lista de evidențieri",
"menu_configure": "Configurează lista de evidențieri...",
"no_highlights": "Nu există nicio evidențiere."
},
"note_icon": {
"change_note_icon": "Schimbă iconița notiței",
"reset-default": "Resetează la iconița implicită",
"search": "Căutare:"
"search": "Căutare:",
"search_placeholder_one": "Caută printre {{number}} iconițe dintr-un pachet",
"search_placeholder_few": "Caută printre {{number}} iconițe din {{count}} pachete",
"search_placeholder_other": "Caută printre {{number}} iconițe din {{count}} de pachete",
"search_placeholder_filtered": "Căutați printre {{number}} iconițe în {{name}}",
"filter": "Filtrează",
"filter-none": "Toate iconițele",
"filter-default": "Iconițele implicite",
"icon_tooltip": "{{name}}\nPachet iconițe: {{iconPack}}",
"no_results": "Nu s-a găsit nicio iconiță."
},
"show_highlights_list_widget_button": {
"show_highlights_list": "Afișează lista de evidențieri"
@@ -1521,7 +1557,17 @@
"refresh-saved-search-results": "Reîmprospătează căutarea salvată",
"unhoist": "Defocalizează notița",
"toggle-sidebar": "Comută bara laterală",
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor."
"dropping-not-allowed": "Aici nu este permisă plasarea notițelor.",
"clone-indicator-tooltip": "Această notiță are {{- count}} părinți: {{- parents}}",
"clone-indicator-tooltip-single": "Această notiță este clonată (un singur părinte: {{- parent}})",
"shared-indicator-tooltip": "Această notiță este partajată public",
"shared-indicator-tooltip-with-url": "Această notiță este partajată public la: {{- url}}",
"subtree-hidden-tooltip_one": "{{count}} subnotiță ascunsă din arbore",
"subtree-hidden-tooltip_few": "{{count}} subnotițe ascunse din arbore",
"subtree-hidden-tooltip_other": "{{count}} de subnotițe ascunse din arbore",
"subtree-hidden-moved-title": "Adăugat în {{title}}",
"subtree-hidden-moved-description-collection": "Subnotițele din această colecție sunt ascunse din arbore.",
"subtree-hidden-moved-description-other": "Subnotițele din această notiță sunt ascunse."
},
"title_bar_buttons": {
"window-on-top": "Menține fereastra mereu vizibilă"
@@ -1529,12 +1575,24 @@
"note_detail": {
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”",
"printing": "Imprimare în curs...",
"printing_pdf": "Exportare ca PDF în curs..."
"printing_pdf": "Exportare ca PDF în curs...",
"print_report_title": "Raport de imprimare",
"print_report_collection_content_one": "{{count}} notiță din colecție nu a putut fi imprimată deoarece nu este suportată sau este protejată.",
"print_report_collection_content_few": "{{count}} notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
"print_report_collection_content_other": "{{count}} de notițe din colecție nu au putut fi imprimate deoarece nu sunt suportate sau sunt protejate.",
"print_report_collection_details_button": "Afișează detalii",
"print_report_collection_details_ignored_notes": "Notițe ignorate"
},
"note_title": {
"placeholder": "introduceți titlul notiței aici...",
"created_on": "Creată la <Value />",
"last_modified": "Modificată la <Value />"
"last_modified": "Modificată la <Value />",
"note_type_switcher_label": "Schimbă din {{type}} la:",
"note_type_switcher_others": "Mai multe tipuri de notițe",
"note_type_switcher_templates": "Șablon",
"note_type_switcher_collection": "Colecție",
"edited_notes": "Notițe editate în această zi",
"promoted_attributes": "Atribute promovate"
},
"revisions_snapshot_limit": {
"erase_excess_revision_snapshots": "Șterge acum reviziile excesive",
@@ -1555,7 +1613,11 @@
"configure_launchbar": "Configurează bara de lansare"
},
"sql_result": {
"no_rows": "Nu s-a găsit niciun rând pentru această interogare"
"no_rows": "Nu s-a găsit niciun rând pentru această interogare",
"not_executed": "Această interogare nu a fost executată încă.",
"failed": "Interogarea SQL a eșuat",
"statement_result": "Rezultatul comenzii SQL",
"execute_now": "Execută acum"
},
"sql_table_schemas": {
"tables": "Tabele"
@@ -1577,7 +1639,8 @@
},
"toc": {
"options": "Setări",
"table_of_contents": "Cuprins"
"table_of_contents": "Cuprins",
"no_headings": "Niciun titlu."
},
"watched_file_update_status": {
"file_last_modified": "Fișierul <code class=\"file-path\"></code> a fost ultima oară modificat la data de <span class=\"file-last-modified\"></span>.",
@@ -2012,7 +2075,7 @@
"book_properties_config": {
"hide-weekends": "Ascunde weekend-urile",
"display-week-numbers": "Afișează numărul săptămânii",
"map-style": "Stil hartă:",
"map-style": "Stil hartă",
"max-nesting-depth": "Nivel maxim de imbricare:",
"raster": "Raster",
"vector_light": "Vectorial (culoare deschisă)",
@@ -2069,7 +2132,10 @@
"next_theme_title": "Încercați noua temă Trilium",
"next_theme_message": "Utilizați tema clasică, doriți să încercați noua temă?",
"next_theme_button": "Testează noua temă",
"dismiss": "Treci peste"
"dismiss": "Treci peste",
"new_layout_title": "Aspect nou",
"new_layout_message": "Am introdus un aspect modernizat pentru Trilium. Panglică a fost integrată în restul interfeței, cu o bară de stare nouă și secțiuni expandabile (precum atributele promovate) ce preiau funcționalitatea de bază.\n\nNoul aspect este activat în mod implicit, și se poate dezactiva momentan din Opțiuni → Aspect.",
"new_layout_button": "Mai multe informații"
},
"ui-performance": {
"title": "Setări de performanță",
@@ -2084,7 +2150,10 @@
},
"settings_appearance": {
"related_code_blocks": "Tema de culori pentru blocuri de cod în notițe de tip text",
"related_code_notes": "Tema de culori pentru notițele de tip cod"
"related_code_notes": "Tema de culori pentru notițele de tip cod",
"ui": "Interfață grafică",
"ui_old_layout": "Aspect vechi",
"ui_new_layout": "Aspect nou"
},
"units": {
"percentage": "%"
@@ -2140,6 +2209,77 @@
"read_only_temporarily_disabled": "Editabilă temporar",
"read_only_temporarily_disabled_description": "Această notiță se poate modifica, deși în mod normal ea este doar în citire. Notița va reveni la modul doar în citire imediat ce navigați către altă notiță.\n\nClick pentru a re-activa modul doar în citire.",
"shared_publicly": "Partajată public",
"shared_locally": "Partajată local"
"shared_locally": "Partajată local",
"shared_copy_to_clipboard": "Copiază legătură în clipboard",
"shared_open_in_browser": "Deschide legătura în browser",
"shared_unshare": "Înlătură partajarea",
"clipped_note": "Decupare web",
"clipped_note_description": "Această notiță a fost preluată de la {{url}}.\n\nClic pentru a naviga la pagina web sursă.",
"execute_script": "Rulează script",
"execute_script_description": "Această notiță este un script. Clic pentru a executa scriptul.",
"execute_sql": "Rulează SQL",
"execute_sql_description": "Această notiță este de tip SQL. Clic pentru a executa interogarea SQL.",
"save_status_saved": "Salvat",
"save_status_saving": "Se salvează...",
"save_status_unsaved": "Nesalvat",
"save_status_error": "Salvarea a eșuat",
"save_status_saving_tooltip": "Modificările sunt în curs de salvare.",
"save_status_unsaved_tooltip": "Există schimbări ce nu au fost încă salvate. Acestea vor fi salvate automat într-un moment.",
"save_status_error_tooltip": "A intervenit o eroare la salvarea notiței. Dacă este posibil, încercați să copiați conținutul notiței într-un alt loc și să reîmprospătați aplicația."
},
"breadcrumb": {
"hoisted_badge": "Focalizat",
"hoisted_badge_title": "Defocalizează",
"workspace_badge": "Spațiu de lucru",
"scroll_to_top_title": "Sari la începutul notiței",
"create_new_note": "Crează subnotiță",
"empty_hide_archived_notes": "Ascunde notițele arhivate"
},
"status_bar": {
"language_title": "Schimbă limba conținutului",
"note_info_title": "Afișează informații despre notiță precum data modificării și dimensiunea",
"backlinks_one": "{{count}} legătură de retur",
"backlinks_few": "{{count}} legături de retur",
"backlinks_other": "{{count}} de legături de retur",
"backlinks_title_one": "Afișează legătura de retur",
"backlinks_title_few": "Afișează legăturile de retur",
"backlinks_title_other": "Afișează legăturile de retur",
"attachments_one": "{{count}} atașament",
"attachments_few": "{{count}} atașamente",
"attachments_other": "{{count}} de atașamente",
"attachments_title_one": "Deschide atașamentul într-un tab nou",
"attachments_title_few": "Deschide atașamentele într-un tab nou",
"attachments_title_other": "Deschide atașamentele într-un tab nou",
"attributes_one": "{{count}} atribut",
"attributes_few": "{{count}} atribute",
"attributes_other": "{{count}} de atribute",
"attributes_title": "Atribute proprii și moștenite",
"note_paths_one": "O cale",
"note_paths_few": "{{count}} căi",
"note_paths_other": "{{count}} de căi",
"note_paths_title": "Căi ale notiței",
"code_note_switcher": "Schimbă limbajul"
},
"attributes_panel": {
"title": "Atributele notiței"
},
"right_pane": {
"empty_message": "Nimic de afișat pentru această notiță",
"empty_button": "Ascunde panoul",
"toggle": "Comută panoul din dreapta",
"custom_widget_go_to_source": "Mergi la codul sursă"
},
"pdf": {
"attachments_one": "{{count}} atașament",
"attachments_few": "{{count}} atașamente",
"attachments_other": "{{count}} de atașamente",
"layers_one": "{{count}} strat",
"layers_few": "{{count}} straturi",
"layers_other": "{{count}} de straturi",
"pages_one": "{{count}} pagină",
"pages_few": "{{count}} pagini",
"pages_other": "{{count}} de pagini",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Încărcare..."
}
}

View File

@@ -1566,6 +1566,7 @@
"shared-indicator-tooltip": "此筆記已公開分享",
"shared-indicator-tooltip-with-url": "此筆記已公開分享至:{{- url}}",
"subtree-hidden-tooltip_one": "從樹中隱藏的 {{count}} 篇子筆記",
"subtree-hidden-tooltip_other": "",
"subtree-hidden-moved-title": "已新增至 {{title}}",
"subtree-hidden-moved-description-collection": "此集合隱藏其樹中的子筆記。",
"subtree-hidden-moved-description-other": "子筆記隱藏於此筆記的樹中。"
@@ -1602,7 +1603,11 @@
"configure_launchbar": "設定啟動欄"
},
"sql_result": {
"no_rows": "此次查詢沒有返回任何數據"
"no_rows": "此次查詢沒有返回任何數據",
"not_executed": "查詢尚未執行。",
"failed": "SQL 查詢執行失敗",
"statement_result": "查詢結果",
"execute_now": "立即執行"
},
"sql_table_schemas": {
"tables": "表"

View File

@@ -14,7 +14,10 @@
"edit_branch_prefix": "Редагувати префікс гілки",
"help_on_tree_prefix": "Довідка щодо префіксу дерева",
"prefix": "Префікс: ",
"branch_prefix_saved": "Префікс гілки збережено."
"branch_prefix_saved": "Префікс гілки збережено.",
"edit_branch_prefix_multiple": "Редагувати префікс гілки для {{count}} гілок",
"branch_prefix_saved_multiple": "Префікс гілки збережено для {{count}} гілок.",
"affected_branches": "Уражені гілки ({{count}}):"
},
"about": {
"app_version": "Версія програми:",
@@ -70,8 +73,17 @@
},
"bundle-error": {
"title": "Не вдалося завантажити користувацький скрипт",
"message": "Скрипт з нотатки ID \"{{id}}\" з заголовком \"{{title}}\" не вдалося виконати через:\n\n{{message}}"
}
"message": "Скрипт не вдалося виконати через:\n\n{{message}}"
},
"widget-list-error": {
"title": "Не вдалося отримати список віджетів з сервера"
},
"widget-render-error": {
"title": "Не вдалося відобразити користувацький віджет"
},
"widget-missing-parent": "Для власного віджета не визначено {{property}} обов'язкову властивість\n\nЯкщо цей скрипт призначений для запуску без елемента інтерфейсу користувача, використовуйте замість нього '#run=frontendStartup'.",
"open-script-note": "Відкрити нотатку сценарію",
"scripting-error": "Помилка користувацького скрипта: {{title}}"
},
"bulk_actions": {
"bulk_actions": "Масові дії",
@@ -199,7 +211,8 @@
"export_status": "Статус експорту",
"export_in_progress": "Триває експорт: {{progressCount}}",
"export_finished_successfully": "Експорт успішно завершено.",
"format_pdf": "PDF для друку або спільного використання."
"format_pdf": "PDF для друку або спільного використання.",
"share-format": "HTML для веб-публікацій використовує ту саму тему, що й для спільних нотаток, але може бути опублікований як статичний веб-сайт."
},
"help": {
"title": "Шпаргалка",
@@ -253,7 +266,8 @@
"showSQLConsole": "показати консоль SQL",
"other": "Інше",
"quickSearch": "фокус на швидкому введенні пошуку",
"inPageSearch": "пошук на сторінці"
"inPageSearch": "пошук на сторінці",
"editShortcuts": "Редагувати комбінації клавіш"
},
"import": {
"importIntoNote": "Імпортувати в нотатку",
@@ -849,7 +863,10 @@
"note_icon": {
"change_note_icon": "Змінити значок нотатки",
"search": "Пошук:",
"reset-default": "Скинути значок до стандартного значення"
"reset-default": "Скинути значок до стандартного значення",
"search_placeholder_one": "Пошук {{number}} значка у {{count}} пакеті",
"search_placeholder_few": "Пошук {{number}} значків у {{count}} пакетах",
"search_placeholder_many": "Пошук {{number}} значків у {{count}} пакетах"
},
"basic_properties": {
"note_type": "Тип нотатки",
@@ -884,7 +901,7 @@
"file_type": "Тип файлу",
"file_size": "Розмір файлу",
"download": "Завантажити",
"open": "Відкрити",
"open": "Відкрити зовні",
"upload_new_revision": "Завантажити нову версію",
"upload_success": "Завантажено нову версію файлу.",
"upload_failed": "Не вдалося завантажити нову версію файлу.",
@@ -1589,13 +1606,19 @@
"refresh-saved-search-results": "Оновити збережені результати пошуку",
"create-child-note": "Створити дочірню нотатку",
"unhoist": "Відкріпити",
"toggle-sidebar": "Перемикання бічної панелі"
"toggle-sidebar": "Перемикання бічної панелі",
"subtree-hidden-tooltip_one": "{{count}} дочірня нотатка, прихована від дерев",
"subtree-hidden-tooltip_few": "{{count}} дочірніх нотатки, прихованих від дерев",
"subtree-hidden-tooltip_many": "{{count}} дочірніх нотаток, прихованих від дерев"
},
"title_bar_buttons": {
"window-on-top": "Тримати вікно зверху"
},
"note_detail": {
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'"
"could_not_find_typewidget": "Не вдалося знайти typeWidget для типу '{{type}}'",
"print_report_collection_content_one": "{{count}} нотатку з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
"print_report_collection_content_few": "{{count}} нотатки з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені.",
"print_report_collection_content_many": "{{count}} нотаток з колекції не вдалося роздрукувати, тому що вони не підтримуються або захищені."
},
"note_title": {
"placeholder": "введіть тут заголовок нотатки..."
@@ -1743,7 +1766,7 @@
"unknown_widget": "Невідомий віджет для \"{{id}}\"."
},
"note_language": {
"not_set": "Не встановлено",
"not_set": "Мову не встановлено",
"configure-languages": "Налаштувати мови..."
},
"content_language": {
@@ -1810,7 +1833,7 @@
"book_properties_config": {
"hide-weekends": "Приховати вихідні",
"display-week-numbers": "Відображення номерів тижнів",
"map-style": "Стиль карти:",
"map-style": "Стиль карти",
"max-nesting-depth": "Максимальна глибина вкладення:",
"raster": "Растр",
"vector_light": "Вектор (Світла)",
@@ -1863,7 +1886,7 @@
"will_be_deleted_in": "Це вкладення буде автоматично видалено через {{time}}",
"will_be_deleted_soon": "Це вкладення незабаром буде автоматично видалено",
"deletion_reason": ", оскільки вкладення не має посилання у вмісті нотатки. Щоб запобігти видаленню, додайте посилання на вкладення назад у вміст або перетворіть вкладення на нотатку.",
"role_and_size": "Роль: {{role}}, Розмір: {{size}}",
"role_and_size": "Роль: {{role}}, розмір: {{size}}, формат даних: {{- mimeType}}",
"link_copied": "Посилання на вкладення скопійовано в буфер обміну.",
"unrecognized_role": "Нерозпізнана роль вкладення '{{role}}'."
},
@@ -1914,7 +1937,7 @@
"import-into-note": "Імпортувати в нотатку",
"apply-bulk-actions": "Застосувати масові дії",
"converted-to-attachments": "({{count}}) нотаток перетворено на вкладення.",
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток?",
"convert-to-attachment-confirm": "Ви впевнені, що хочете конвертувати вибрані нотатки у вкладення до їхніх батьківських нотаток? Ця операція застосовується лише до нотаток із зображеннями, інші нотатки будуть пропущені.",
"open-in-popup": "Швидке редагування",
"archive": "Архівувати",
"unarchive": "Розархівувати"
@@ -1978,7 +2001,10 @@
},
"highlights_list_2": {
"title": "Список основних моментів",
"options": "Параметри"
"options": "Параметри",
"title_with_count_one": "{{count}} виділення",
"title_with_count_few": "{{count}} виділення",
"title_with_count_many": "{{count}} виділень"
},
"table_context_menu": {
"delete_row": "Видалити рядок"
@@ -2051,5 +2077,36 @@
},
"collections": {
"rendering_error": "Не вдалося показати вміст через помилку."
},
"status_bar": {
"backlinks_one": "{{count}} зворотне посилання",
"backlinks_few": "{{count}} зворотні посилання",
"backlinks_many": "{{count}} зворотних посилань",
"backlinks_title_one": "Переглянути зворотне посилання",
"backlinks_title_few": "Переглянути зворотні посилання",
"backlinks_title_many": "Переглянути зворотніх посилань",
"attachments_one": "{{count}} вкладення",
"attachments_few": "{{count}} вкладення",
"attachments_many": "{{count}} вкладень",
"attachments_title_one": "Переглянути вкладення в новій вкладці",
"attachments_title_few": "Переглянути вкладення в новій вкладці",
"attachments_title_many": "Переглянути вкладень в новій вкладці",
"attributes_one": "{{count}} атрибут",
"attributes_few": "{{count}} атрибути",
"attributes_many": "{{count}} атрибутів",
"note_paths_one": "{{count}} шлях",
"note_paths_few": "{{count}} шляхи",
"note_paths_many": "{{count}} шляхів"
},
"pdf": {
"attachments_one": "{{count}} вкладення",
"attachments_few": "{{count}} вкладення",
"attachments_many": "{{count}} вкладень",
"layers_one": "{{count}} шар",
"layers_few": "{{count}} шари",
"layers_many": "{{count}} шарів",
"pages_one": "{{count}} сторінка",
"pages_few": "{{count}} сторінки",
"pages_many": "{{count}} сторінок"
}
}

View File

@@ -36,7 +36,6 @@ interface CustomGlobals {
isProtectedSessionAvailable: boolean;
isDev: boolean;
isMainWindow: boolean;
windowId: string;
maxEntityChangeIdAtLoad: number;
maxEntityChangeSyncIdAtLoad: number;
assetPath: string;

View File

@@ -7,7 +7,6 @@ import Component from "../components/component";
import NoteContext from "../components/note_context";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import { isExperimentalFeatureEnabled } from "../services/experimental_features";
import froca from "../services/froca";
import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image";
@@ -55,7 +54,6 @@ export const DESKTOP_FLOATING_BUTTONS: FloatingButtonsList = [
OpenTriliumApiDocsButton,
SaveToNoteButton,
RelationMapButtons,
GeoMapButtons,
CopyImageReferenceButton,
ExportImageButtons,
InAppHelpButton,
@@ -99,9 +97,10 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: F
/>;
}
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingButtonContext) {
function ToggleReadOnlyButton({ note, isDefaultViewMode }: FloatingButtonContext) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || isSavedSqlite)
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <FloatingButton
@@ -243,17 +242,6 @@ function RelationMapButtons({ note, isDefaultViewMode, triggerEvent }: FloatingB
);
}
function GeoMapButtons({ triggerEvent, viewType, isReadOnly }: FloatingButtonContext) {
const isEnabled = viewType === "geoMap" && !isReadOnly;
return isEnabled && (
<FloatingButton
icon="bx bx-plus-circle"
text={t("geo-map.create-child-note-title")}
onClick={() => triggerEvent("geoMapCreateChildNote")}
/>
);
}
function CopyImageReferenceButton({ note, isDefaultViewMode }: FloatingButtonContext) {
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
const isEnabled = (
@@ -305,7 +293,7 @@ function ExportImageButtons({ note, triggerEvent, isDefaultViewMode }: FloatingB
function InAppHelpButton({ note }: FloatingButtonContext) {
const helpUrl = getHelpUrlForNote(note);
const isEnabled = !!helpUrl;
const isEnabled = note.type !== "book" && !!helpUrl;
return isEnabled && (
<FloatingButton

View File

@@ -265,9 +265,13 @@ function useNoteInfo() {
const [ note, setNote ] = useState<FNote | null | undefined>();
const [ type, setType ] = useState<ExtendedNoteType>();
const [ mime, setMime ] = useState<string>();
const refreshIdRef = useRef(0);
function refresh() {
const refreshId = ++refreshIdRef.current;
getExtendedWidgetType(actualNote, noteContext).then(type => {
if (refreshId !== refreshIdRef.current) return;
setNote(actualNote);
setType(type);
setMime(actualNote?.mime);
@@ -318,6 +322,8 @@ export async function getExtendedWidgetType(note: FNote | null | undefined, note
resultingType = "noteMap";
} else if (type === "text" && (await noteContext?.isReadOnly())) {
resultingType = "readOnlyText";
} else if (note.isTriliumSqlite()) {
resultingType = "sqlConsole";
} else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) {
resultingType = "readOnlyCode";
} else if (type === "text") {
@@ -342,9 +348,8 @@ export function checkFullHeight(noteContext: NoteContext | undefined, type: Exte
// https://github.com/zadam/trilium/issues/2522
const isBackendNote = noteContext?.noteId === "_backendLog";
const isSqlNote = noteContext.note?.mime === "text/x-sqlite;schema=trilium";
const isFullHeightNoteType = type && TYPE_MAPPINGS[type].isFullHeight;
return (!noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
return (!noteContext?.hasNoteList() && isFullHeightNoteType)
|| noteContext?.viewScope?.viewMode === "attachments"
|| isBackendNote;
}
@@ -358,8 +363,8 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
});
}
function handlePrintReport(printReport: PrintReport) {
if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
function handlePrintReport(printReport?: PrintReport) {
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
toast.showPersistent({
id: "print-report",
icon: "bx bx-collection",

View File

@@ -217,6 +217,7 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
id={inputId}
type={LABEL_MAPPINGS[definition.labelType ?? "text"]}
value={valueAttr.value}
checked={definition.labelType === "boolean" ? valueAttr.value === "true" : undefined}
placeholder={t("promoted_attributes.unset-field-placeholder")}
data-attribute-id={valueAttr.attributeId}
data-attribute-type={valueAttr.type}

View File

@@ -1,7 +1,7 @@
.note-list-widget {
min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: auto;
contain: none !important;
}
@@ -11,10 +11,6 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
margin-inline: auto;
}
.note-list-widget .note-list {
padding-block: 10px;
}
.note-list-widget.full-height,
.note-list-widget.full-height .note-list-widget-content {
height: 100%;
@@ -25,8 +21,12 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
}
/* #region Pagination */
.note-list-pager span.current-page {
text-decoration: underline;
font-weight: bold;
.note-list-pager {
font-size: 1rem;
span.current-page {
text-decoration: underline;
font-weight: bold;
}
}
/* #endregion */
/* #endregion */

View File

@@ -1,7 +1,7 @@
import "./NoteList.css";
import { WebSocketMessage } from "@triliumnext/commons";
import { VNode } from "preact";
import { Component, VNode } from "preact";
import { lazy, Suspense } from "preact/compat";
import { useEffect, useRef, useState } from "preact/hooks";
@@ -120,7 +120,9 @@ export function CustomNoteList({ note, viewType, isEnabled: shouldEnable, notePa
}
const ComponentToRender = viewType && props && isEnabled && (
props.media === "print" ? ViewComponents[viewType].print : ViewComponents[viewType].normal
props.media === "print"
? ViewComponents[viewType].print ?? ViewComponents[viewType].normal
: ViewComponents[viewType].normal
);
return (

View File

@@ -2,7 +2,8 @@
position: relative;
height: 100%;
user-select: none;
overflow-x: auto;
display: flex;
flex-direction: column;
--card-font-size: 0.9em;
--card-line-height: 1.2;
@@ -19,8 +20,10 @@ body.mobile .board-view {
height: 100%;
display: flex;
gap: 1em;
padding: 1em;
padding-inline: 12px;
padding-block: 4px;
align-items: flex-start;
overflow-x: auto;
}
.board-view-container .board-column {
@@ -352,4 +355,4 @@ body.mobile .board-view-container .board-column {
font-size: 0.9em;
max-width: 200px;
word-wrap: break-word;
}
}

View File

@@ -1,20 +1,23 @@
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { ViewModeProps } from "../interface";
import "./index.css";
import { ColumnMap, getBoardData } from "./data";
import { createContext, TargetedKeyboardEvent } from "preact";
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import toast from "../../../services/toast";
import CollectionProperties from "../../note_bars/CollectionProperties";
import FormTextArea from "../../react/FormTextArea";
import FormTextBox from "../../react/FormTextBox";
import { useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import Icon from "../../react/Icon";
import { t } from "../../../services/i18n";
import Api from "./api";
import FormTextBox from "../../react/FormTextBox";
import { createContext, TargetedKeyboardEvent } from "preact";
import { onWheelHorizontalScroll } from "../../widget_utils";
import Column from "./column";
import BoardApi from "./api";
import FormTextArea from "../../react/FormTextArea";
import FNote from "../../../entities/fnote";
import NoteAutocomplete from "../../react/NoteAutocomplete";
import toast from "../../../services/toast";
import { onWheelHorizontalScroll } from "../../widget_utils";
import { ViewModeProps } from "../interface";
import Api from "./api";
import BoardApi from "./api";
import Column from "./column";
import { ColumnMap, getBoardData } from "./data";
export interface BoardViewData {
columns?: BoardColumnData[];
@@ -145,7 +148,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
const insertBefore = mouseX < columnMiddle;
// Calculate the target position
let targetIndex = insertBefore ? index : index + 1;
const targetIndex = insertBefore ? index : index + 1;
setColumnDropPosition(targetIndex);
}, [draggedColumn]);
@@ -159,15 +162,14 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
}, [draggedColumn, columnDropPosition, handleColumnDrop]);
return (
<div
className="board-view"
onWheel={onWheelHorizontalScroll}
>
<div className="board-view">
<CollectionProperties note={parentNote} />
<BoardViewContext.Provider value={boardViewContext}>
{byColumn && columns && <div
className="board-view-container"
onDragOver={handleColumnDragOver}
onDrop={handleContainerDrop}
onWheel={onWheelHorizontalScroll}
>
{columns.map((column, index) => (
<>
@@ -194,7 +196,7 @@ export default function BoardView({ note: parentNote, noteIds, viewConfig, saveC
</div>}
</BoardViewContext.Provider>
</div>
)
);
}
function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMode: boolean }) {
@@ -218,26 +220,26 @@ function AddNewColumn({ api, isInRelationMode }: { api: BoardApi, isInRelationMo
tabIndex={300}
>
{!isCreatingNewColumn
? <>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.add-column")}
</>
: (
<TitleEditor
placeholder={t("board_view.add-column-placeholder")}
save={async (columnName) => {
const created = await api.addNewColumn(columnName);
if (!created) {
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
}
}}
dismiss={() => setIsCreatingNewColumn(false)}
isNewItem
mode={isInRelationMode ? "relation" : "normal"}
/>
)}
? <>
<Icon icon="bx bx-plus" />{" "}
{t("board_view.add-column")}
</>
: (
<TitleEditor
placeholder={t("board_view.add-column-placeholder")}
save={async (columnName) => {
const created = await api.addNewColumn(columnName);
if (!created) {
toast.showMessage(t("board_view.column-already-exists"), undefined, "bx bx-duplicate");
}
}}
dismiss={() => setIsCreatingNewColumn(false)}
isNewItem
mode={isInRelationMode ? "relation" : "normal"}
/>
)}
</div>
)
);
}
export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, isNewItem }: {
@@ -302,26 +304,26 @@ export function TitleEditor({ currentValue, placeholder, save, dismiss, mode, is
onBlur={onBlur}
/>
);
} else {
return (
<NoteAutocomplete
inputRef={inputRef}
noteId={currentValue ?? ""}
opts={{
hideAllButtons: true,
allowCreatingNotes: true
}}
onKeyDown={(e) => {
if (e.key === "Escape") {
dismiss();
}
}}
onBlur={() => dismiss()}
noteIdChanged={(newValue) => {
save(newValue);
dismiss();
}}
/>
);
}
return (
<NoteAutocomplete
inputRef={inputRef}
noteId={currentValue ?? ""}
opts={{
hideAllButtons: true,
allowCreatingNotes: true
}}
onKeyDown={(e) => {
if (e.key === "Escape") {
dismiss();
}
}}
onBlur={() => dismiss()}
noteIdChanged={(newValue) => {
save(newValue);
dismiss();
}}
/>
);
}

View File

@@ -1,8 +1,8 @@
import { CreateChildrenResponse } from "@triliumnext/commons";
import server from "../../../services/server";
import { AttributeRow, CreateChildrenResponse } from "@triliumnext/commons";
import FNote from "../../../entities/fnote";
import { setAttribute, setLabel } from "../../../services/attributes";
import froca from "../../../services/froca";
import server from "../../../services/server";
interface NewEventOpts {
title: string;
@@ -10,6 +10,7 @@ interface NewEventOpts {
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
componentId?: string;
}
interface ChangeEventOpts {
@@ -17,30 +18,48 @@ interface ChangeEventOpts {
endDate?: string | null;
startTime?: string | null;
endTime?: string | null;
componentId?: string;
}
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime }: NewEventOpts) {
// Create the note.
const { note } = await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text"
export async function newEvent(parentNote: FNote, { title, startDate, endDate, startTime, endTime, componentId }: NewEventOpts) {
const attributes: Omit<AttributeRow, "noteId" | "attributeId">[] = [];
attributes.push({
type: "label",
name: "startDate",
value: startDate
});
// Set the attributes.
setLabel(note.noteId, "startDate", startDate);
if (endDate) {
setLabel(note.noteId, "endDate", endDate);
attributes.push({
type: "label",
name: "endDate",
value: endDate
});
}
if (startTime) {
setLabel(note.noteId, "startTime", startTime);
attributes.push({
type: "label",
name: "startTime",
value: startTime
});
}
if (endTime) {
setLabel(note.noteId, "endTime", endTime);
attributes.push({
type: "label",
name: "endTime",
value: endTime
});
}
// Create the note.
await server.post<CreateChildrenResponse>(`notes/${parentNote.noteId}/children?target=into`, {
title,
content: "",
type: "text",
attributes
}, componentId);
}
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime }: ChangeEventOpts) {
export async function changeEvent(note: FNote, { startDate, endDate, startTime, endTime, componentId }: ChangeEventOpts) {
// Don't store the end date if it's empty.
if (endDate === startDate) {
endDate = undefined;
@@ -52,12 +71,12 @@ export async function changeEvent(note: FNote, { startDate, endDate, startTime,
let endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endDate").shift()?.value||"endDate";
const noteId = note.noteId;
setLabel(noteId, startAttribute, startDate);
setAttribute(note, "label", endAttribute, endDate);
setLabel(noteId, startAttribute, startDate, false, componentId);
setAttribute(note, "label", endAttribute, endDate, componentId);
startAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:startTime").shift()?.value||"startTime";
endAttribute = note.getAttributes("label").filter(attr => attr.name == "calendar:endTime").shift()?.value||"endTime";
setAttribute(note, "label", startAttribute, startTime);
setAttribute(note, "label", endAttribute, endTime);
setAttribute(note, "label", startAttribute, startTime, componentId);
setAttribute(note, "label", endAttribute, endTime, componentId);
}

View File

@@ -1,12 +1,12 @@
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
import FNote from "../../../entities/fnote";
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
import NoteColorPicker from "../../../menus/custom-items/NoteColorPicker";
import link_context_menu from "../../../menus/link_context_menu";
import branches from "../../../services/branches";
import { getArchiveMenuItem } from "../../../menus/context_menu_utils";
import { t } from "../../../services/i18n";
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote) {
export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parentNote: FNote, componentId?: string) {
e.preventDefault();
e.stopPropagation();
@@ -30,16 +30,16 @@ export function openCalendarContextMenu(e: ContextMenuEvent, note: FNote, parent
}
if (branchIdToDelete) {
await branches.deleteNotes([ branchIdToDelete ], false, false);
await branches.deleteNotes([ branchIdToDelete ], false, false, componentId);
}
}
},
{ kind: "separator" },
{
kind: "custom",
componentFn: () => NoteColorPicker({note: note})
componentFn: () => NoteColorPicker({note})
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, e, note.noteId),
})
});
}

View File

@@ -1,10 +1,11 @@
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
import froca from "../../../services/froca";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import clsx from "clsx";
import FNote from "../../../entities/fnote";
import froca from "../../../services/froca";
import server from "../../../services/server";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
interface Event {
startDate: string,
endDate?: string | null,
@@ -105,7 +106,8 @@ export async function buildEvent(note: FNote, { startDate, endDate, startTime, e
endDate = (endTime ? `${endDate}T${endTime}:00` : endDate);
const eventData: EventInput = {
title: title,
id: note.noteId,
title,
start: startDate,
url: `#${note.noteId}?popup`,
noteId: note.noteId,
@@ -148,12 +150,12 @@ async function parseCustomTitle(customTitlettributeName: string | null, note: FN
}
async function buildDisplayedAttributes(note: FNote, calendarDisplayedAttributes: string[]) {
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name))
const filteredDisplayedAttributes = note.getAttributes().filter((attr): boolean => calendarDisplayedAttributes.includes(attr.name));
const result: Array<[string, string]> = [];
for (const attribute of filteredDisplayedAttributes) {
if (attribute.type === "label") result.push([attribute.name, attribute.value]);
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""])
else result.push([attribute.name, (await attribute.getTargetNote())?.title || ""]);
}
return result;

View File

@@ -21,7 +21,16 @@
outline: 0;
height: 100%;
user-select: none;
padding: 10px;
padding: 0;
@media (max-width: 991px) {
padding: 0;
th {
font-weight: normal;
font-size: 0.9em;
}
}
}
.calendar-view a,
@@ -43,6 +52,7 @@
--fc-border-color: var(--main-border-color);
--fc-neutral-bg-color: var(--launcher-pane-background-color);
--fc-list-event-hover-bg-color: var(--left-pane-item-hover-background);
padding: 0 12px;
}
.calendar-container .fc-list-sticky .fc-list-day > * {
@@ -59,33 +69,35 @@
overflow: hidden;
}
/* #region Header */
.calendar-view .calendar-header {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.calendar-view .collection-properties {
.center-container {
justify-content: center;
.calendar-view .calendar-header .btn {
min-width: unset !important;
}
.title {
min-width: 150px;
font-size: 1.2em;
text-align: center;
}
}
.calendar-view .calendar-header > .title {
flex-grow: 1;
font-size: 1.3rem;
font-weight: normal;
}
@media (max-width: 991px) {
>div {
justify-content: flex-start;
}
body.desktop:not(.zen) .calendar-view .calendar-header {
padding-block-start: 4px;
padding-inline-end: 5em;
}
.right-container {
flex-grow: 0;
}
.search-result-widget-content .calendar-view .calendar-header {
padding-inline-end: unset !important;
.center-container {
.title {
flex-grow: 1;
min-width: 110px;
font-size: 0.95em;
}
}
}
}
/* #endregion */
/* #region Events */
@@ -93,7 +105,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
* week, month, year views
*/
.calendar-container a.fc-event {
.calendar-container a.fc-event {
text-decoration: none;
}
@@ -126,7 +138,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
.calendar-view a.fc-timegrid-event,
.calendar-view a.fc-daygrid-event {
--border-color: transparent;
border: 2px solid;
border-left-width: 4px;
border-color: var(--border-color) var(--border-color) var(--border-color)
@@ -175,7 +187,7 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
opacity: .75;
}
/*
/*
* List view
*/
@@ -188,4 +200,4 @@ body.desktop:not(.zen) .calendar-view .calendar-header {
--fc-event-border-color: var(--custom-color);
}
/* #endregion */
/* #endregion */

View File

@@ -5,7 +5,7 @@ import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, Local
import { DateClickArg } from "@fullcalendar/interaction";
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
import { RefObject } from "preact";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
@@ -14,9 +14,13 @@ import dialog from "../../../services/dialog";
import froca from "../../../services/froca";
import { t } from "../../../services/i18n";
import { isMobile } from "../../../services/utils";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import Button, { ButtonGroup } from "../../react/Button";
import Dropdown from "../../react/Dropdown";
import { FormListItem } from "../../react/FormList";
import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { changeEvent, newEvent } from "./api";
@@ -40,24 +44,28 @@ const CALENDAR_VIEWS = [
{
type: "timeGridWeek",
name: t("calendar.week"),
icon: "bx bx-calendar-week",
previousText: t("calendar.week_previous"),
nextText: t("calendar.week_next")
},
{
type: "dayGridMonth",
name: t("calendar.month"),
icon: "bx bx-calendar",
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
},
{
type: "multiMonthYear",
name: t("calendar.year"),
icon: "bx bx-layer",
previousText: t("calendar.year_previous"),
nextText: t("calendar.year_next")
},
{
type: "listMonth",
name: t("calendar.list"),
icon: "bx bx-list-ol",
previousText: t("calendar.month_previous"),
nextText: t("calendar.month_next")
}
@@ -87,6 +95,7 @@ export const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, (() => Promise<{ de
};
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
const parentComponent = useContext(ParentComponent);
const containerRef = useRef<HTMLDivElement>(null);
const calendarRef = useRef<FullCalendar>(null);
@@ -105,32 +114,40 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
const eventBuilder = useMemo(() => {
if (!isCalendarRoot) {
return async () => await buildEvents(noteIds);
}
}
return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e);
}, [isCalendarRoot, noteIds]);
const plugins = usePlugins(isEditable, isCalendarRoot);
const locale = useLocale();
const { eventDidMount } = useEventDisplayCustomization(note);
const editingProps = useEditing(note, isEditable, isCalendarRoot);
const { eventDidMount } = useEventDisplayCustomization(note, parentComponent?.componentId);
const editingProps = useEditing(note, isEditable, isCalendarRoot, parentComponent?.componentId);
// React to changes.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getNoteIds().some(noteId => noteIds.includes(noteId)) // note title change.
|| loadResults.getAttributeRows().some((a) => noteIds.includes(a.noteId ?? ""))) // subnote change.
{
const api = calendarRef.current;
if (!api) return;
// Subnote attribute change.
if (loadResults.getAttributeRows(parentComponent?.componentId).some((a) => noteIds.includes(a.noteId ?? ""))) {
// Defer execution after the load results are processed so that the event builder has the updated data to work with.
setTimeout(() => {
calendarRef.current?.refetchEvents();
}, 0);
setTimeout(() => api.refetchEvents(), 0);
return; // early return since we'll refresh the events anyway
}
// Title change.
for (const noteId of loadResults.getNoteIds().filter(noteId => noteIds.includes(noteId))) {
const event = api.getEventById(noteId);
const note = froca.getNoteFromCache(noteId);
if (!event || !note) continue;
event.setProp("title", note.title);
}
});
return (plugins &&
<div className="calendar-view" ref={containerRef} tabIndex={100}>
<CalendarHeader calendarRef={calendarRef} />
<CalendarCollectionProperties note={note} calendarRef={calendarRef} />
<Calendar
events={eventBuilder}
calendarRef={calendarRef}
@@ -159,28 +176,67 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
);
}
function CalendarHeader({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
function CalendarCollectionProperties({ note, calendarRef }: {
note: FNote;
calendarRef: RefObject<FullCalendar>;
}) {
const { title, viewType: currentViewType } = useOnDatesSet(calendarRef);
const currentViewData = CALENDAR_VIEWS.find(v => calendarRef.current && v.type === currentViewType);
const isMobileLocal = isMobile();
return (
<div className="calendar-header">
<span className="title">{title}</span>
<CollectionProperties
note={note}
centerChildren={<>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} onClick={() => calendarRef.current?.prev()} />
<span className="title">{title}</span>
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} onClick={() => calendarRef.current?.next()} />
<Button text={t("calendar.today")} onClick={() => calendarRef.current?.today()} />
{isMobileLocal && <MobileCalendarViewSwitcher calendarRef={calendarRef} />}
</>}
rightChildren={<>
{!isMobileLocal && <DesktopCalendarViewSwitcher calendarRef={calendarRef} />}
</>}
/>
);
}
function DesktopCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
return (
<>
<ButtonGroup>
{CALENDAR_VIEWS.map(viewData => (
<Button
text={viewData.name.toLocaleLowerCase()}
key={viewData.type}
text={viewData.name}
className={currentViewType === viewData.type ? "active" : ""}
onClick={() => calendarRef.current?.changeView(viewData.type)}
/>
))}
</ButtonGroup>
<Button text={t("calendar.today").toLocaleLowerCase()} onClick={() => calendarRef.current?.today()} />
<ButtonGroup>
<ActionButton icon="bx bx-chevron-left" text={currentViewData?.previousText ?? ""} frame onClick={() => calendarRef.current?.prev()} />
<ActionButton icon="bx bx-chevron-right" text={currentViewData?.nextText ?? ""} frame onClick={() => calendarRef.current?.next()} />
</ButtonGroup>
</div>
</>
);
}
function MobileCalendarViewSwitcher({ calendarRef }: { calendarRef: RefObject<FullCalendar> }) {
const { viewType: currentViewType } = useOnDatesSet(calendarRef);
const currentViewTypeData = CALENDAR_VIEWS.find(view => view.type === currentViewType);
return (
<Dropdown
text={currentViewTypeData?.name}
>
{CALENDAR_VIEWS.map(viewData => (
<FormListItem
key={viewData.type}
selected={currentViewType === viewData.type}
icon={viewData.icon}
onClick={() => calendarRef.current?.changeView(viewData.type)}
>{viewData.name}</FormListItem>
))}
</Dropdown>
);
}
@@ -207,22 +263,23 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
}
function useLocale() {
const [ locale ] = useTriliumOption("locale");
const [ formattingLocale ] = useTriliumOption("formattingLocale");
const [ calendarLocale, setCalendarLocale ] = useState<LocaleInput>();
useEffect(() => {
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale];
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale] ?? LOCALE_MAPPINGS[locale];
if (correspondingLocale) {
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
} else {
setCalendarLocale(undefined);
}
});
}, [formattingLocale, locale]);
return calendarLocale;
}
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean, componentId: string | undefined) {
const onCalendarSelection = useCallback(async (e: DateSelectArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e);
if (!startDate) return;
@@ -234,8 +291,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
return;
}
newEvent(note, { title, startDate, endDate, startTime, endTime });
}, [ note ]);
newEvent(note, { title, startDate, endDate, startTime, endTime, componentId });
}, [ note, componentId ]);
const onEventChange = useCallback(async (e: EventChangeArg) => {
const { startDate, endDate } = parseStartEndDateFromEvent(e.event);
@@ -244,8 +301,8 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
const { startTime, endTime } = parseStartEndTimeFromEvent(e.event);
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
changeEvent(note, { startDate, endDate, startTime, endTime });
}, []);
changeEvent(note, { startDate, endDate, startTime, endTime, componentId });
}, [ componentId ]);
// Called upon when clicking the day number in the calendar, opens or creates the day note but only if in a calendar root.
const onDateClick = useCallback(async (e: DateClickArg) => {
@@ -264,7 +321,7 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
};
}
function useEventDisplayCustomization(parentNote: FNote) {
function useEventDisplayCustomization(parentNote: FNote, componentId: string | undefined) {
const eventDidMount = useCallback((e: EventMountArg) => {
const { iconClass, promotedAttributes } = e.event.extendedProps;
@@ -321,7 +378,7 @@ function useEventDisplayCustomization(parentNote: FNote) {
const note = await froca.getNote(e.event.extendedProps.noteId);
if (!note) return;
openCalendarContextMenu(contextMenuEvent, note, parentNote);
openCalendarContextMenu(contextMenuEvent, note, parentNote, componentId);
}
if (isMobile()) {

View File

@@ -32,9 +32,11 @@ export function formatDateToLocalISO(date: Date | null | undefined) {
return undefined;
}
const offset = date.getTimezoneOffset();
const localDate = new Date(date.getTime() - offset * 60 * 1000);
return localDate.toISOString().split("T")[0];
// Use local date methods directly - no timezone conversion needed
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
export function offsetDate(date: Date | string | null | undefined, offset: number) {
@@ -42,6 +44,15 @@ export function offsetDate(date: Date | string | null | undefined, offset: numbe
return undefined;
}
// When given a date string (YYYY-MM-DD), parse as local time rather than UTC
// to avoid timezone-related off-by-one errors
if (typeof date === 'string') {
const [year, month, day] = date.split('-').map(Number);
const newDate = new Date(year, month - 1, day);
newDate.setDate(newDate.getDate() + offset);
return newDate;
}
const newDate = new Date(date);
newDate.setDate(newDate.getDate() + offset);
return newDate;

View File

@@ -2,6 +2,13 @@
overflow: hidden;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
> .collection-properties {
position: relative;
z-index: 2000;
}
}
.geo-map-container {

View File

@@ -1,24 +1,29 @@
import Map from "./map";
import "./index.css";
import { ViewModeProps } from "../interface";
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import { divIcon, GPXOptions, LatLng, LeafletMouseEvent } from "leaflet";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import Marker, { GpxTrack } from "./marker";
import froca from "../../../services/froca";
import FNote from "../../../entities/fnote";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import appContext from "../../../components/app_context";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import toast from "../../../services/toast";
import FNote from "../../../entities/fnote";
import branches from "../../../services/branches";
import froca from "../../../services/froca";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import branches from "../../../services/branches";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSlider } from "../../react/TouchBar";
import toast from "../../../services/toast";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import { ButtonOrActionButton } from "../../react/Button";
import { useNoteBlob, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useNoteTreeDrag, useSpacedUpdate, useTriliumEvent } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import TouchBar, { TouchBarButton, TouchBarSlider } from "../../react/TouchBar";
import { ViewModeProps } from "../interface";
import { createNewNote, moveMarker } from "./api";
import openContextMenu, { openMapContextMenu } from "./context_menu";
import Map from "./map";
import { DEFAULT_MAP_LAYER_NAME } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
@@ -50,7 +55,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
}
}, 5000);
useEffect(() => { froca.getNotes(noteIds).then(setNotes) }, [ noteIds ]);
useEffect(() => { froca.getNotes(noteIds).then(setNotes); }, [ noteIds ]);
useEffect(() => {
if (!note) return;
@@ -60,7 +65,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
// Note creation.
useTriliumEvent("geoMapCreateChildNote", () => {
toast.showPersistent({
toast.showPersistent({
icon: "plus",
id: "geo-new-note",
title: "New note",
@@ -130,6 +135,19 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
return (
<div className={`geo-view ${state === State.NewNote ? "placing-note" : ""}`}>
<CollectionProperties
note={note}
rightChildren={<>
<ToggleReadOnlyButton note={note} />
<ButtonOrActionButton
icon="bx bx-plus"
text={t("geo-map.create-child-note-text")}
title={t("geo-map.create-child-note-title")}
triggerCommand="geoMapCreateChildNote"
disabled={isReadOnly}
/>
</>}
/>
{ coordinates !== undefined && zoom !== undefined && <Map
apiRef={apiRef} containerRef={containerRef}
coordinates={coordinates}
@@ -151,6 +169,16 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
);
}
function ToggleReadOnlyButton({ note }: { note: FNote }) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
return <ActionButton
text={isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")}
icon={isReadOnly ? "bx bx-lock-open-alt" : "bx bx-lock-alt"}
onClick={() => setReadOnly(!isReadOnly)}
/>;
}
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
const mime = useNoteProperty(note, "mime");
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
@@ -204,7 +232,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
onDragged={editable ? onDragged : undefined}
onClick={!editable ? onClick : undefined}
onContextMenu={onContextMenu}
/>
/>;
}
function NoteGpxTrack({ note }: { note: FNote }) {
@@ -238,7 +266,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
}
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string, archived?: boolean) {
@@ -292,5 +320,5 @@ function GeoMapTouchBar({ state, map }: { state: State, map: L.Map | null | unde
enabled={state === State.Normal}
/>
</TouchBar>
)
);
}

View File

@@ -7,7 +7,8 @@ import attribute_renderer from "../../../services/attribute_renderer";
import content_renderer from "../../../services/content_renderer";
import { t } from "../../../services/i18n";
import link from "../../../services/link";
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean } from "../../react/hooks";
import CollectionProperties from "../../note_bars/CollectionProperties";
import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean, useNoteProperty } from "../../react/hooks";
import Icon from "../../react/Icon";
import NoteLink from "../../react/NoteLink";
import { ViewModeProps } from "../interface";
@@ -19,11 +20,18 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const noteType = useNoteProperty(note, "type");
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
return (
<div class="note-list list-view">
<CollectionProperties
note={note}
centerChildren={<Pager {...pagination} />}
/>
{ noteIds.length > 0 && <div class="note-list-wrapper">
<Pager {...pagination} />
{!hasCollectionProperties && <Pager {...pagination} />}
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
@@ -45,11 +53,18 @@ export function GridView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
const noteIds = useFilteredNoteIds(note, unfilteredNoteIds);
const { pageNotes, ...pagination } = usePagination(note, noteIds);
const [ includeArchived ] = useNoteLabelBoolean(note, "includeArchived");
const noteType = useNoteProperty(note, "type");
const hasCollectionProperties = [ "book", "search" ].includes(noteType ?? "");
return (
<div class="note-list grid-view">
<CollectionProperties
note={note}
centerChildren={<Pager {...pagination} />}
/>
<div class="note-list-wrapper">
<Pager {...pagination} />
{!hasCollectionProperties && <Pager {...pagination} />}
<div class="note-list-container use-tn-links">
{pageNotes?.map(childNote => (
@@ -140,7 +155,7 @@ function NoteAttributes({ note }: { note: FNote }) {
return <span className="note-list-attributes" ref={ref} />;
}
function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
export function NoteContent({ note, trim, noChildrenList, highlightedTokens, includeArchivedNotes }: {
note: FNote;
trim?: boolean;
noChildrenList?: boolean;

View File

@@ -1,11 +1,7 @@
.presentation-button-bar {
position: absolute;
top: 1em;
right: 1em;
.floating-buttons-children {
top: 0;
}
.presentation-view {
display: flex;
flex-direction: column;
height: 100%;
}
.presentation-container {

View File

@@ -1,18 +1,21 @@
import { ViewModeMedia, ViewModeProps } from "../interface";
import "./index.css";
import { RefObject } from "preact";
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
import Reveal from "reveal.js";
import slideBaseStylesheet from "reveal.js/dist/reveal.css?raw";
import slideCustomStylesheet from "./slidejs.css?raw";
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
import ShadowDom from "../../react/ShadowDom";
import ActionButton from "../../react/ActionButton";
import "./index.css";
import { RefObject } from "preact";
import { openInCurrentNoteContext } from "../../../components/note_context";
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import { t } from "../../../services/i18n";
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import CollectionProperties from "../../note_bars/CollectionProperties";
import ActionButton from "../../react/ActionButton";
import { useNoteLabelWithDefault, useTriliumEvent } from "../../react/hooks";
import ShadowDom from "../../react/ShadowDom";
import { ViewModeMedia, ViewModeProps } from "../interface";
import { buildPresentationModel, PresentationModel, PresentationSlideBaseModel } from "./model";
import slideCustomStylesheet from "./slidejs.css?raw";
import { DEFAULT_THEME, loadPresentationTheme } from "./themes";
export default function PresentationView({ note, noteIds, media, onReady, onProgressChanged }: ViewModeProps<{}>) {
const [ presentation, setPresentation ] = useState<PresentationModel>();
@@ -51,14 +54,17 @@ export default function PresentationView({ note, noteIds, media, onReady, onProg
if (media === "screen") {
return (
<>
<div class="presentation-view">
<CollectionProperties
note={note}
rightChildren={<ButtonOverlay containerRef={containerRef} api={api} />}
/>
<ShadowDom
className="presentation-container"
containerRef={containerRef}
>{content}</ShadowDom>
<ButtonOverlay containerRef={containerRef} api={api} />
</>
)
</div>
);
} else if (media === "print") {
// Printing needs a query parameter that is read by Reveal.js.
const url = new URL(window.location.href);
@@ -108,42 +114,34 @@ function ButtonOverlay({ containerRef, api }: { containerRef: RefObject<HTMLDivE
}, [ api ]);
return (
<div className="presentation-button-bar">
<div className="floating-buttons-children">
<ActionButton
className="floating-button"
icon="bx bx-edit"
text={t("presentation_view.edit-slide")}
noIconActionClass
onClick={e => {
const currentSlide = api?.getCurrentSlide();
const noteId = getNoteIdFromSlide(currentSlide);
<>
<ActionButton
icon="bx bx-edit"
text={t("presentation_view.edit-slide")}
onClick={e => {
const currentSlide = api?.getCurrentSlide();
const noteId = getNoteIdFromSlide(currentSlide);
if (noteId) {
openInCurrentNoteContext(e, noteId);
}
}}
/>
if (noteId) {
openInCurrentNoteContext(e, noteId);
}
}}
/>
<ActionButton
className="floating-button"
icon="bx bx-grid-horizontal"
text={t("presentation_view.slide-overview")}
active={isOverviewActive}
noIconActionClass
onClick={() => api?.toggleOverview()}
/>
<ActionButton
icon="bx bx-grid-horizontal"
text={t("presentation_view.slide-overview")}
active={isOverviewActive}
onClick={() => api?.toggleOverview()}
/>
<ActionButton
className="floating-button"
icon="bx bx-fullscreen"
text={t("presentation_view.start-presentation")}
noIconActionClass
onClick={() => containerRef.current?.requestFullscreen()}
/>
</div>
</div>
)
<ActionButton
icon="bx bx-fullscreen"
text={t("presentation_view.start-presentation")}
onClick={() => containerRef.current?.requestFullscreen()}
/>
</>
);
}
function Presentation({ presentation, setApi } : { presentation: PresentationModel, setApi: (api: Reveal.Api | undefined) => void }) {
@@ -179,7 +177,7 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
api.destroy();
setRevealApi(undefined);
setApi(undefined);
}
};
}, []);
useEffect(() => {
@@ -191,19 +189,19 @@ function Presentation({ presentation, setApi } : { presentation: PresentationMod
<div className="slides">
{presentation.slides?.map(slide => {
if (!slide.verticalSlides) {
return <Slide key={slide.noteId} slide={slide} />
} else {
return (
<section>
<Slide key={slide.noteId} slide={slide} />
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
</section>
);
return <Slide key={slide.noteId} slide={slide} />;
}
return (
<section>
<Slide key={slide.noteId} slide={slide} />
{slide.verticalSlides.map(slide => <Slide key={slide.noteId} slide={slide} /> )}
</section>
);
})}
</div>
</div>
)
);
}

View File

@@ -3,7 +3,12 @@
position: relative;
height: 100%;
user-select: none;
padding: 0 5px 0 10px;
display: flex;
flex-direction: column;
.tabulator-tableholder {
height: unset !important;
}
}
.table-view-container {
@@ -68,4 +73,4 @@
inset-inline-start: 0;
font-size: 1.5em;
transform: translateY(-50%);
}
}

View File

@@ -1,20 +1,22 @@
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { ViewModeProps } from "../interface";
import { TableData } from "./rows";
import { useLegacyWidget } from "../../react/hooks";
import Tabulator from "./tabulator";
import { Tabulator as VanillaTabulator, SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule, Options, RowComponent} from 'tabulator-tables';
import { useContextMenu } from "./context_menu";
import { ParentComponent } from "../../react/react_utils";
import FNote from "../../../entities/fnote";
import { t } from "../../../services/i18n";
import Button from "../../react/Button";
import "./index.css";
import useRowTableEditing from "./row_editing";
import useColTableEditing from "./col_editing";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { DataTreeModule, EditModule, FormatModule, FrozenColumnsModule, InteractionModule, MoveColumnsModule, MoveRowsModule, Options, PersistenceModule, ResizeColumnsModule, RowComponent,SortModule, Tabulator as VanillaTabulator} from 'tabulator-tables';
import { t } from "../../../services/i18n";
import SpacedUpdate from "../../../services/spaced_update";
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
import CollectionProperties from "../../note_bars/CollectionProperties";
import { ButtonOrActionButton } from "../../react/Button";
import { useLegacyWidget } from "../../react/hooks";
import { ParentComponent } from "../../react/react_utils";
import { ViewModeProps } from "../interface";
import useColTableEditing from "./col_editing";
import { useContextMenu } from "./context_menu";
import useData, { TableConfig } from "./data";
import useRowTableEditing from "./row_editing";
import { TableData } from "./rows";
import Tabulator from "./tabulator";
export default function TableView({ note, noteIds, notePath, viewConfig, saveConfig }: ViewModeProps<TableConfig>) {
const tabulatorRef = useRef<VanillaTabulator>(null);
@@ -36,7 +38,7 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
dataTreeChildIndent: 20,
dataTreeExpandElement: `<button class="tree-expand"><span class="bx bx-chevron-right"></span></button>`,
dataTreeCollapseElement: `<button class="tree-collapse"><span class="bx bx-chevron-down"></span></button>`
}
};
}, [ hasChildren ]);
const rowFormatter = useCallback((row: RowComponent) => {
@@ -46,6 +48,16 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
return (
<div className="table-view">
<CollectionProperties
note={note}
rightChildren={note.type !== "search" &&
<>
<ButtonOrActionButton triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
<ButtonOrActionButton triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
</>
}
/>
{rowData !== undefined && persistenceProps && (
<>
<Tabulator
@@ -54,7 +66,6 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
columns={columnDefs ?? []}
data={rowData}
modules={[ SortModule, FormatModule, InteractionModule, EditModule, ResizeColumnsModule, FrozenColumnsModule, PersistenceModule, MoveColumnsModule, MoveRowsModule, DataTreeModule ]}
footerElement={<TableFooter note={note} />}
events={{
...contextMenuEvents,
...rowEditingEvents
@@ -67,24 +78,11 @@ export default function TableView({ note, noteIds, notePath, viewConfig, saveCon
rowFormatter={rowFormatter}
{...dataTreeProps}
/>
<TableFooter note={note} />
</>
)}
{attributeDetailWidgetEl}
</div>
)
}
function TableFooter({ note }: { note: FNote }) {
return (note.type !== "search" &&
<div className="tabulator-footer">
<div className="tabulator-footer-contents">
<Button triggerCommand="addNewRow" icon="bx bx-plus" text={t("table_view.new-row")} />
{" "}
<Button triggerCommand="addNewTableColumn" icon="bx bx-carousel" text={t("table_view.new-column")} />
</div>
</div>
)
);
}
function usePersistence(viewConfig: TableConfig | null | undefined, saveConfig: (newConfig: TableConfig) => void) {

View File

@@ -1,18 +1,20 @@
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
import "tabulator-tables/dist/css/tabulator.css";
import "../../../../src/stylesheets/table.css";
import { ParentComponent, renderReactWidget } from "../../react/react_utils";
import { JSX } from "preact/jsx-runtime";
import { isValidElement, RefObject } from "preact";
import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
import { JSX } from "preact/jsx-runtime";
import { EventCallBackMethods, Module, Options, Tabulator as VanillaTabulator } from "tabulator-tables";
import { ParentComponent, renderReactWidget } from "../../react/react_utils";
interface TableProps<T> extends Omit<Options, "data" | "footerElement" | "index"> {
tabulatorRef: RefObject<VanillaTabulator>;
tabulatorRef?: RefObject<VanillaTabulator>;
className?: string;
data?: T[];
modules?: (new (table: VanillaTabulator) => Module)[];
events?: Partial<EventCallBackMethods>;
index: keyof T;
index?: keyof T;
footerElement?: string | HTMLElement | JSX.Element;
onReady?: () => void;
}
@@ -43,7 +45,9 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
tabulator.on("tableBuilt", () => {
tabulatorRef.current = tabulator;
externalTabulatorRef.current = tabulator;
if (externalTabulatorRef) {
externalTabulatorRef.current = tabulator;
}
onReady?.();
});
@@ -62,12 +66,15 @@ export default function Tabulator<T>({ className, columns, data, modules, tabula
for (const [ eventName, handler ] of Object.entries(events)) {
tabulator.off(eventName as keyof EventCallBackMethods, handler);
}
}
};
}, Object.values(events ?? {}));
// Change in data.
useEffect(() => { tabulatorRef.current?.setData(data) }, [ data ]);
useEffect(() => { columns && tabulatorRef.current?.setColumns(columns)}, [ data]);
useEffect(() => { tabulatorRef.current?.setData(data); }, [ data ]);
useEffect(() => {
if (!columns) return;
tabulatorRef.current?.setColumns(columns);
}, [ columns ]);
return (
<div ref={containerRef} className={className} />

View File

@@ -5,7 +5,7 @@
> .inline-title,
> .note-detail > .note-detail-editable-text,
> .note-list-widget:not(.full-height) {
> .note-list-widget:not(.full-height) .note-list-wrapper {
padding-inline: 24px;
}

View File

@@ -82,6 +82,10 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
align-items: flex-start;
}
.modal.popup-editor-dialog .note-detail {
width: 100%;
}
.modal.popup-editor-dialog .note-detail.full-height {
flex-grow: 0;
height: 100%;
@@ -106,4 +110,4 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
margin: 0;
border-radius: 0;
}
}
}

View File

@@ -1,7 +1,7 @@
import appContext from "../../components/app_context";
import { t } from "../../services/i18n";
import options from "../../services/options";
import utils from "../../services/utils";
import utils, { isMac } from "../../services/utils";
/**
* A "call-to-action" is an interactive message for the user, generally to present new features.
@@ -41,10 +41,6 @@ export interface CallToAction {
}[];
}
function isNextTheme() {
return [ "next", "next-light", "next-dark" ].includes(options.get("theme"));
}
const CALL_TO_ACTIONS: CallToAction[] = [
{
id: "new_layout",
@@ -63,7 +59,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
id: "background_effects",
title: t("call_to_action.background_effects_title"),
message: t("call_to_action.background_effects_message"),
enabled: () => false,
enabled: () => (isMac() && !options.is("backgroundEffects")),
buttons: [
{
text: t("call_to_action.background_effects_button"),
@@ -78,7 +74,7 @@ const CALL_TO_ACTIONS: CallToAction[] = [
id: "next_theme",
title: t("call_to_action.next_theme_title"),
message: t("call_to_action.next_theme_message"),
enabled: () => !isNextTheme(),
enabled: () => ![ "next", "next-light", "next-dark" ].includes(options.get("theme")),
buttons: [
{
text: t("call_to_action.next_theme_button"),

View File

@@ -3,6 +3,7 @@ import { useCallback, useLayoutEffect, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { isDesktop, isMobile } from "../../services/utils";
import TabSwitcher from "../mobile_widgets/TabSwitcher";
import { useTriliumEvent } from "../react/hooks";
import { onWheelHorizontalScroll } from "../widget_utils";
import BookmarkButtons from "./BookmarkButtons";
@@ -97,6 +98,8 @@ function initBuiltinWidget(note: FNote, isHorizontalLayout: boolean) {
return <QuickSearchLauncherWidget />;
case "aiChatLauncher":
return <AiChatButton launcherNote={note} />;
case "mobileTabSwitcher":
return <TabSwitcher />;
default:
throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`);
}

View File

@@ -1,3 +1,4 @@
import clsx from "clsx";
import { createContext } from "preact";
import { useContext } from "preact/hooks";
@@ -18,12 +19,12 @@ export interface LauncherNoteProps {
launcherNote: FNote;
}
export function LaunchBarActionButton(props: Omit<ActionButtonProps, "className" | "noIconActionClass" | "titlePosition">) {
export function LaunchBarActionButton({ className, ...props }: Omit<ActionButtonProps, "noIconActionClass" | "titlePosition">) {
const { isHorizontalLayout } = useContext(LaunchBarContext);
return (
<ActionButton
className="button-widget launcher-button"
className={clsx("button-widget launcher-button", className)}
noIconActionClass
titlePosition={isHorizontalLayout ? "bottom" : "right"}
{...props}

View File

@@ -5,8 +5,10 @@ import { Tooltip } from "bootstrap";
import clsx from "clsx";
import { ComponentChild } from "preact";
import { useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import type React from "react";
import { Trans } from "react-i18next";
import FNote from "../../entities/fnote";
import { ViewScope } from "../../services/link";
import { formatDateTime } from "../../utils/formatters";
import NoteIcon from "../note_icon";
@@ -22,12 +24,12 @@ const supportedNoteTypes = new Set<NoteType>([
export default function InlineTitle() {
const { note, parentComponent, viewScope } = useNoteContext();
const type = useNoteProperty(note, "type");
const [ shown, setShown ] = useState(shouldShow(note?.noteId, type, viewScope));
const [ shown, setShown ] = useState(shouldShow(note, type, viewScope));
const containerRef = useRef<HTMLDivElement>(null);
const [ titleHidden, setTitleHidden ] = useState(false);
useLayoutEffect(() => {
setShown(shouldShow(note?.noteId, type, viewScope));
setShown(shouldShow(note, type, viewScope));
}, [ note, type, viewScope ]);
useLayoutEffect(() => {
@@ -69,9 +71,10 @@ export default function InlineTitle() {
);
}
function shouldShow(noteId: string | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
function shouldShow(note: FNote | null | undefined, type: NoteType | undefined, viewScope: ViewScope | undefined) {
if (viewScope?.viewMode !== "default") return false;
if (noteId?.startsWith("_options")) return true;
if (note?.noteId?.startsWith("_options")) return true;
if (note?.isTriliumSqlite()) return false;
return type && supportedNoteTypes.has(type);
}

View File

@@ -26,7 +26,6 @@ export default function NoteTitleActions() {
<div className="title-actions">
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
{!isHiddenNote && note && noteType === "book" && <CollectionProperties note={note} />}
<EditedNotes />
<NoteTypeSwitcher />
</div>

View File

@@ -39,7 +39,7 @@ export default function NoteTypeSwitcher() {
const currentNoteTypeData = useMemo(() => NOTE_TYPES.find(t => t.type === currentNoteType), [ currentNoteType ]);
const { builtinTemplates, collectionTemplates } = useBuiltinTemplates();
return (currentNoteType && supportedNoteTypes.has(currentNoteType) &&
return (currentNoteType && supportedNoteTypes.has(currentNoteType) && !note?.isTriliumSqlite() &&
<div
className="note-type-switcher"
onWheel={onWheelHorizontalScroll}

View File

@@ -1,6 +1,6 @@
import "./StatusBar.css";
import { Locale, NoteType } from "@triliumnext/commons";
import { Locale, NOTE_TYPE_ICONS, NoteType } from "@triliumnext/commons";
import { Dropdown as BootstrapDropdown } from "bootstrap";
import clsx from "clsx";
import { type ComponentChildren, RefObject } from "preact";
@@ -9,7 +9,7 @@ import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "p
import { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context";
import FNote, { NOTE_TYPE_ICONS } from "../../entities/fnote";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import { ViewScope } from "../../services/link";

View File

@@ -0,0 +1,133 @@
#launcher-container .mobile-tab-switcher {
position: relative;
&::after {
content: attr(data-tab-count);
font-family: var(--main-font-family);
font-size: 10px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.modal.tab-bar-modal {
.modal-dialog {
min-height: 85vh;
}
.tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1em;
@media (min-width: 850px) {
grid-template-columns: 1fr 1fr 1fr;
}
.tab-card {
background: var(--card-background-color);
border-radius: 1em;
min-width: 0;
overflow: hidden;
height: 200px;
display: flex;
flex-direction: column;
&.with-hue {
background-color: hsl(var(--bg-hue), 8.8%, 11.2%);
border-color: hsl(var(--bg-hue), 9.4%, 25.1%);
}
&.active {
outline: 4px solid var(--more-accented-background-color);
background: var(--card-background-hover-color);
.title {
font-weight: bold;
}
}
header {
padding: 0.4em 0.5em;
border-bottom: 1px solid rgba(150, 150, 150, 0.1);
display: flex;
overflow: hidden;
align-items: center;
color: var(--custom-color, inherit);
flex-shrink: 0;
&:not(:first-of-type) {
border-top: 1px solid rgba(150, 150, 150, 0.1);
}
>.tn-icon {
margin-inline-end: 0.4em;
}
.title {
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
font-size: 0.9em;
flex-grow: 1;
}
.icon-action {
flex-shrink: 0;
}
}
.tab-preview {
flex-grow: 1;
height: 100%;
overflow: hidden;
font-size: 0.5em;
user-select: none;
pointer-events: none;
&.type-text {
padding: 10px;
}
&.type-book,
&.type-contentWidget,
&.type-search,
&.type-empty {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25em;
color: var(--muted-text-color);
}
.preview-placeholder {
font-size: 500%;
}
p { margin-bottom: 0.2em;}
h2 { font-size: 1.20em; }
h3 { font-size: 1.15em; }
h4 { font-size: 1.10em; }
h5 { font-size: 1.05em}
h6 { font-size: 1em; }
}
&.with-split {
.preview-placeholder {
font-size: 250%;
}
}
}
}
.modal-footer {
.tn-link {
color: var(--main-text-color);
width: 40%;
text-align: center;
text-decoration: none;
}
}
}

View File

@@ -0,0 +1,240 @@
import "./TabSwitcher.css";
import clsx from "clsx";
import { createPortal, Fragment } from "preact/compat";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext, { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context";
import FNote from "../../entities/fnote";
import contextMenu from "../../menus/context_menu";
import { getHue, parseColor } from "../../services/css_class_manager";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import { NoteContent } from "../collections/legacy/ListOrGridView";
import { LaunchBarActionButton } from "../launch_bar/launch_bar_widgets";
import { ICON_MAPPINGS } from "../note_bars/CollectionProperties";
import ActionButton from "../react/ActionButton";
import { useActiveNoteContext, useNoteIcon, useTriliumEvents } from "../react/hooks";
import Icon from "../react/Icon";
import LinkButton from "../react/LinkButton";
import Modal from "../react/Modal";
export default function TabSwitcher() {
const [ shown, setShown ] = useState(false);
const mainNoteContexts = useMainNoteContexts();
return (
<>
<LaunchBarActionButton
className="mobile-tab-switcher"
icon="bx bx-rectangle"
text="Tabs"
onClick={() => setShown(true)}
data-tab-count={mainNoteContexts.length > 99 ? "∞" : mainNoteContexts.length}
/>
{createPortal(<TabBarModal mainNoteContexts={mainNoteContexts} shown={shown} setShown={setShown} />, document.body)}
</>
);
}
function TabBarModal({ mainNoteContexts, shown, setShown }: {
mainNoteContexts: NoteContext[];
shown: boolean;
setShown: (newValue: boolean) => void;
}) {
const [ fullyShown, setFullyShown ] = useState(false);
const selectTab = useCallback((noteContextToActivate: NoteContext) => {
appContext.tabManager.activateNoteContext(noteContextToActivate.ntxId);
setShown(false);
}, [ setShown ]);
return (
<Modal
className="tab-bar-modal"
size="xl"
title={t("mobile_tab_switcher.title", { count: mainNoteContexts.length})}
show={shown}
onShown={() => setFullyShown(true)}
customTitleBarButtons={[
{
iconClassName: "bx bx-dots-vertical-rounded",
title: t("mobile_tab_switcher.more_options"),
onClick(e) {
contextMenu.show<CommandNames>({
x: e.pageX,
y: e.pageY,
items: [
{ title: t("tab_row.new_tab"), command: "openNewTab", uiIcon: "bx bx-plus" },
{ title: t("tab_row.reopen_last_tab"), command: "reopenLastTab", uiIcon: "bx bx-undo", enabled: appContext.tabManager.recentlyClosedTabs.length !== 0 },
{ kind: "separator" },
{ title: t("tab_row.close_all_tabs"), command: "closeAllTabs", uiIcon: "bx bx-trash destructive-action-icon" },
],
selectMenuItemHandler: ({ command }) => {
if (command) {
appContext.triggerCommand(command);
}
}
});
},
}
]}
footer={<>
<LinkButton
text={t("tab_row.new_tab")}
onClick={() => {
appContext.triggerCommand("openNewTab");
setShown(false);
}}
/>
</>}
scrollable
onHidden={() => {
setShown(false);
setFullyShown(false);
}}
>
<TabBarModelContent mainNoteContexts={mainNoteContexts} selectTab={selectTab} shown={fullyShown} />
</Modal>
);
}
function TabBarModelContent({ mainNoteContexts, selectTab, shown }: {
mainNoteContexts: NoteContext[];
shown: boolean;
selectTab: (noteContextToActivate: NoteContext) => void;
}) {
const activeNoteContext = useActiveNoteContext();
const tabRefs = useRef<Record<string, HTMLDivElement | null>>({});
// Scroll to active tab.
useEffect(() => {
if (!shown || !activeNoteContext?.ntxId) return;
const correspondingEl = tabRefs.current[activeNoteContext.ntxId];
requestAnimationFrame(() => {
correspondingEl?.scrollIntoView();
});
}, [ activeNoteContext, shown ]);
return (
<div className="tabs">
{mainNoteContexts.map((noteContext) => (
<Tab
key={noteContext.ntxId}
noteContext={noteContext}
activeNtxId={activeNoteContext.ntxId}
selectTab={selectTab}
containerRef={el => (tabRefs.current[noteContext.ntxId ?? ""] = el)}
/>
))}
</div>
);
}
function Tab({ noteContext, containerRef, selectTab, activeNtxId }: {
containerRef: (el: HTMLDivElement | null) => void;
noteContext: NoteContext;
selectTab: (noteContextToActivate: NoteContext) => void;
activeNtxId: string | null | undefined;
}) {
const { note } = noteContext;
const iconClass = useNoteIcon(note);
const colorClass = note?.getColorClass() || '';
const workspaceTabBackgroundColorHue = getWorkspaceTabBackgroundColorHue(noteContext);
const subContexts = noteContext.getSubContexts();
return (
<div
ref={containerRef}
class={clsx("tab-card", {
active: noteContext.ntxId === activeNtxId,
"with-hue": workspaceTabBackgroundColorHue !== undefined,
"with-split": subContexts.length > 1
})}
onClick={() => selectTab(noteContext)}
style={{
"--bg-hue": workspaceTabBackgroundColorHue
}}
>
{subContexts.map(subContext => (
<Fragment key={subContext.ntxId}>
<header className={colorClass}>
{subContext.note && <Icon icon={iconClass} />}
<span className="title">{subContext.note?.title ?? t("tab_row.new_tab")}</span>
{subContext.isMainContext() && <ActionButton
icon="bx bx-x"
text={t("tab_row.close_tab")}
onClick={(e) => {
// We are closing a tab, so we need to prevent propagation for click (activate tab).
e.stopPropagation();
appContext.tabManager.removeNoteContext(subContext.ntxId);
}}
/>}
</header>
<div className={clsx("tab-preview", `type-${subContext.note?.type ?? "empty"}`)}>
<TabPreviewContent note={subContext.note} />
</div>
</Fragment>
))}
</div>
);
}
function TabPreviewContent({ note }: {
note: FNote | null
}) {
if (!note) {
return <PreviewPlaceholder icon="bx bx-plus" />;
}
if (note.type === "book") {
return <PreviewPlaceholder icon={ICON_MAPPINGS[note.getLabelValue("viewType") ?? ""] ?? "bx bx-book"} />;
}
return (
<NoteContent
note={note}
highlightedTokens={undefined}
trim
includeArchivedNotes={false}
/>
);
}
function PreviewPlaceholder({ icon}: {
icon: string;
}) {
return (
<div className="preview-placeholder">
<Icon icon={icon} />
</div>
);
}
function getWorkspaceTabBackgroundColorHue(noteContext: NoteContext) {
if (!noteContext.hoistedNoteId) return;
const hoistedNote = froca.getNoteFromCache(noteContext.hoistedNoteId);
if (!hoistedNote) return;
const workspaceTabBackgroundColor = hoistedNote.getWorkspaceTabBackgroundColor();
if (!workspaceTabBackgroundColor) return;
try {
const parsedColor = parseColor(workspaceTabBackgroundColor);
if (!parsedColor) return;
return getHue(parsedColor);
} catch (e) {
// Colors are non-critical, simply ignore.
console.warn(e);
}
}
function useMainNoteContexts() {
const [ noteContexts, setNoteContexts ] = useState(appContext.tabManager.getMainNoteContexts());
useTriliumEvents([ "newNoteContextCreated", "noteContextRemoved" ] , () => {
setNoteContexts(appContext.tabManager.getMainNoteContexts());
});
return noteContexts;
}

View File

@@ -1,13 +1,16 @@
import { useContext } from "preact/hooks";
import appContext, { CommandMappings } from "../../components/app_context";
import contextMenu, { MenuItem } from "../../menus/context_menu";
import branches from "../../services/branches";
import { t } from "../../services/i18n";
import { getHelpUrlForNote } from "../../services/in_app_help";
import note_create from "../../services/note_create";
import tree from "../../services/tree";
import { openInAppHelpFromUrl } from "../../services/utils";
import BasicWidget from "../basic_widget";
import ActionButton from "../react/ActionButton";
import { ParentComponent } from "../react/react_utils";
import BasicWidget from "../basic_widget";
export default function MobileDetailMenu() {
const parentComponent = useContext(ParentComponent);
@@ -24,6 +27,7 @@ export default function MobileDetailMenu() {
const subContexts = noteContext.getMainContext().getSubContexts();
const isMainContext = noteContext?.isMainContext();
const note = noteContext.note;
const helpUrl = getHelpUrlForNote(note);
const items: (MenuItem<keyof CommandMappings>)[] = [
{ title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" },
@@ -31,6 +35,12 @@ export default function MobileDetailMenu() {
{ kind: "separator" },
{ title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" },
{ kind: "separator" },
helpUrl && {
title: t("help-button.title"),
uiIcon: "bx bx-help-circle",
handler: () => openInAppHelpFromUrl(helpUrl)
},
{ kind: "separator" },
subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" },
!isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" }
].filter(i => !!i) as MenuItem<keyof CommandMappings>[];
@@ -70,5 +80,5 @@ export default function MobileDetailMenu() {
});
}}
/>
)
);
}

View File

@@ -1,5 +1,5 @@
.collection-properties {
padding: 0;
padding: 0.55em 12px;
display: flex;
gap: 0.25em;
align-items: center;
@@ -14,7 +14,31 @@
}
}
.spacer {
>div {
display: inline-flex;
align-items: center;
gap: 0.5em;
}
.center-container {
flex-grow: 1;
justify-content: center;
}
.right-container {
justify-content: flex-end;
}
button.btn {
min-width: 0;
}
@media (max-width: 991px) {
flex-wrap: wrap;
padding: 0.55em 1em;
>div {
flex-grow: 1;
}
}
}

View File

@@ -1,24 +1,22 @@
import "./CollectionProperties.css";
import { t } from "i18next";
import { ComponentChildren } from "preact";
import { useContext, useRef } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
import FNote from "../../entities/fnote";
import { getHelpUrlForNote } from "../../services/in_app_help";
import { openInAppHelpFromUrl } from "../../services/utils";
import { ViewTypeOptions } from "../collections/interface";
import ActionButton from "../react/ActionButton";
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
import FormTextBox from "../react/FormTextBox";
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useTriliumEvent } from "../react/hooks";
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useNoteProperty, useTriliumEvent } from "../react/hooks";
import Icon from "../react/Icon";
import { ParentComponent } from "../react/react_utils";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../ribbon/collection-properties-config";
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: "bx bxs-grid",
list: "bx bx-list-ul",
calendar: "bx bx-calendar",
@@ -28,15 +26,26 @@ const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
presentation: "bx bx-rectangle"
};
export default function CollectionProperties({ note }: { note: FNote }) {
export default function CollectionProperties({ note, centerChildren, rightChildren }: {
note: FNote;
centerChildren?: ComponentChildren;
rightChildren?: ComponentChildren;
}) {
const [ viewType, setViewType ] = useViewType(note);
const noteType = useNoteProperty(note, "type");
return (
return ([ "book", "search" ].includes(noteType ?? "") &&
<div className="collection-properties">
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} />
<div className="spacer" />
<HelpButton note={note} />
<div className="left-container">
<ViewTypeSwitcher viewType={viewType} setViewType={setViewType} />
<ViewOptions note={note} viewType={viewType} />
</div>
<div className="center-container">
{centerChildren}
</div>
<div className="right-container">
{rightChildren}
</div>
</div>
);
}
@@ -222,15 +231,3 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check
/>
);
}
function HelpButton({ note }: { note: FNote }) {
const helpUrl = getHelpUrlForNote(note);
return (helpUrl && (
<ActionButton
icon="bx bx-help-circle"
onClick={(() => openInAppHelpFromUrl(helpUrl))}
text={t("help-button.title")}
/>
));
}

View File

@@ -6,6 +6,7 @@ import clsx from "clsx";
import { t } from "i18next";
import { CSSProperties, RefObject } from "preact";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import type React from "react";
import { CellComponentProps, Grid } from "react-window";
import FNote from "../entities/fnote";
@@ -153,10 +154,10 @@ function NoteIconList({ note, dropdownRef }: {
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{
filteredIcons: IconWithName[];
}>): React.JSX.Element {
}>) {
const iconIndex = rowIndex * 12 + columnIndex;
const iconData = filteredIcons[iconIndex] as IconWithName | undefined;
if (!iconData) return <></>;
if (!iconData) return <></> as React.ReactElement;
const { id, terms, iconPack } = iconData;
return (
@@ -166,7 +167,7 @@ function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellCompo
title={t("note_icon.icon_tooltip", { name: terms?.[0] ?? id, iconPack })}
style={style as CSSProperties}
/>
);
) as React.ReactElement;
}
function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {

View File

@@ -1232,7 +1232,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
refreshCtx.noteIdsToUpdate.add(noteId);
}
if (refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0) {
const hasNotesToUpdateOrReload = refreshCtx.noteIdsToUpdate.size + refreshCtx.noteIdsToReload.size > 0;
const hasNoteReorderingChange = loadResults.getNoteReorderings().length > 0;
if (hasNotesToUpdateOrReload || hasNoteReorderingChange) {
await this.#executeTreeUpdates(refreshCtx, loadResults);
}
@@ -1393,6 +1395,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
for (const parentNoteId of loadResults.getNoteReorderings()) {
for (const node of this.getNodesByNoteId(parentNoteId)) {
console.log("Reordering ", node);
if (node.isLoaded()) {
this.sortChildren(node);
}

View File

@@ -12,7 +12,7 @@ import { TypeWidgetProps } from "./type_widgets/type_widget";
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
* for protected session or attachment information.
*/
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat" | "sqlConsole";
export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element | undefined);
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
@@ -140,5 +140,10 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
view: () => import("./type_widgets/AiChat"),
className: "ai-chat-widget-container",
isFullHeight: true
},
sqlConsole: {
view: () => import("./type_widgets/SqlConsole"),
className: "sql-console-widget-container",
isFullHeight: true
}
};

View File

@@ -1,10 +1,11 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { CommandNames } from "../../components/app_context";
import { useStaticTooltip } from "./hooks";
import keyboard_actions from "../../services/keyboard_actions";
import { HTMLAttributes } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu"> {
import { CommandNames } from "../../components/app_context";
import keyboard_actions from "../../services/keyboard_actions";
import { useStaticTooltip } from "./hooks";
export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu" | "style"> {
text: string;
titlePosition?: "top" | "right" | "bottom" | "left";
icon: string;

View File

@@ -1,8 +1,11 @@
import type { ComponentChildren, RefObject } from "preact";
import type { CSSProperties } from "preact/compat";
import { useMemo } from "preact/hooks";
import { memo } from "preact/compat";
import { useMemo } from "preact/hooks";
import { CommandNames } from "../../components/app_context";
import { isDesktop } from "../../services/utils";
import ActionButton from "./ActionButton";
import Icon from "./Icon";
export interface ButtonProps {
@@ -78,7 +81,7 @@ export function ButtonGroup({ children }: { children: ComponentChildren }) {
<div className="btn-group" role="group">
{children}
</div>
)
);
}
export function SplitButton({ text, icon, children, ...restProps }: {
@@ -103,7 +106,17 @@ export function SplitButton({ text, icon, children, ...restProps }: {
{children}
</ul>
</ButtonGroup>
)
);
}
export function ButtonOrActionButton(props: {
text: string;
icon: string;
} & Pick<ButtonProps, "onClick" | "triggerCommand" | "disabled" | "title">) {
if (isDesktop()) {
return <Button {...props} />;
}
return <ActionButton {...props} />;
}
export default Button;

View File

@@ -1,7 +1,7 @@
import clsx from "clsx";
import { HTMLAttributes } from "preact";
interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" | "onClick"> {
interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" | "onClick" | "title"> {
icon?: string;
className?: string;
}

View File

@@ -1,17 +1,17 @@
import clsx from "clsx";
import { useEffect, useRef, useMemo } from "preact/hooks";
import { t } from "../../services/i18n";
import { ComponentChildren } from "preact";
import type { CSSProperties, RefObject } from "preact/compat";
import { openDialog } from "../../services/dialog";
import { Modal as BootstrapModal } from "bootstrap";
import clsx from "clsx";
import { ComponentChildren, CSSProperties, RefObject } from "preact";
import { memo } from "preact/compat";
import { useEffect, useMemo, useRef } from "preact/hooks";
import { openDialog } from "../../services/dialog";
import { t } from "../../services/i18n";
import { useSyncedRef } from "./hooks";
interface CustomTitleBarButton {
title: string;
iconClassName: string;
onClick: () => void;
onClick: (e: MouseEvent) => void;
}
export interface ModalProps {
@@ -80,7 +80,7 @@ export interface ModalProps {
noFocus?: boolean;
}
export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden: onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) {
export default function Modal({ children, className, size, title, customTitleBarButtons: titleBarButtons, header, footer, footerStyle, footerAlignment, onShown, onSubmit, helpPageId, minWidth, maxWidth, zIndex, scrollable, onHidden, modalRef: externalModalRef, formRef, bodyStyle, show, stackable, keepInDom, noFocus }: ModalProps) {
const modalRef = useSyncedRef<HTMLDivElement>(externalModalRef);
const modalInstanceRef = useRef<BootstrapModal>();
const elementToFocus = useRef<Element | null>();
@@ -116,7 +116,7 @@ export default function Modal({ children, className, size, title, customTitleBar
focus: !noFocus
}).then(($widget) => {
modalInstanceRef.current = BootstrapModal.getOrCreateInstance($widget[0]);
})
});
} else {
modalInstanceRef.current?.hide();
}
@@ -159,13 +159,12 @@ export default function Modal({ children, className, size, title, customTitleBar
{titleBarButtons?.filter((b) => b !== null).map((titleBarButton) => (
<button type="button"
className={clsx("custom-title-bar-button bx", titleBarButton.iconClassName)}
title={titleBarButton.title}
onClick={titleBarButton.onClick}>
</button>
className={clsx("custom-title-bar-button bx", titleBarButton.iconClassName)}
title={titleBarButton.title}
onClick={titleBarButton.onClick} />
))}
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")}></button>
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")} />
</div>

View File

@@ -0,0 +1,18 @@
.no-items {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
flex-direction: column;
padding: 0.75em;
color: var(--muted-text-color);
height: 100%;
.tn-icon {
font-size: 3em;
}
button {
margin-top: 1em;
}
}

View File

@@ -0,0 +1,21 @@
import "./NoItems.css";
import { ComponentChildren } from "preact";
import Icon from "./Icon";
interface NoItemsProps {
icon: string;
text: string;
children?: ComponentChildren;
}
export default function NoItems({ icon, text, children }: NoItemsProps) {
return (
<div className="no-items">
<Icon icon={icon} />
{text}
{children}
</div>
);
}

View File

@@ -182,9 +182,10 @@ function SwitchSplitOrientationButton({ note, isReadOnly, isDefaultViewMode }: N
/>;
}
function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: NoteActionsCustomInnerProps) {
export function ToggleReadOnlyButton({ note, isDefaultViewMode }: NoteActionsCustomInnerProps) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || viewType === "geoMap")
const isSavedSqlite = note.isTriliumSqlite() && !note.isHiddenCompletely();
const isEnabled = ([ "mermaid", "mindMap", "canvas" ].includes(note.type) || isSavedSqlite)
&& note.isContentAvailable() && isDefaultViewMode;
return isEnabled && <ActionButton
@@ -233,9 +234,9 @@ function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) {
/>;
}
function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) {
function InAppHelpButton({ note }: NoteActionsCustomInnerProps) {
const helpUrl = getHelpUrlForNote(note);
const isEnabled = !!helpUrl && (noteType !== "book");
const isEnabled = !!helpUrl;
return isEnabled && (
<ActionButton
@@ -246,15 +247,8 @@ function InAppHelpButton({ note, noteType }: NoteActionsCustomInnerProps) {
);
}
function AddChildButton({ parentComponent, noteType, viewType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
if (noteType === "book" && viewType === "geoMap") {
return <ActionButton
icon="bx bx-plus-circle"
text={t("geo-map.create-child-note-title")}
onClick={() => parentComponent.triggerEvent("geoMapCreateChildNote", { ntxId })}
disabled={isReadOnly}
/>;
} else if (noteType === "relationMap") {
function AddChildButton({ parentComponent, noteType, ntxId, isReadOnly }: NoteActionsCustomInnerProps) {
if (noteType === "relationMap") {
return <ActionButton
icon="bx bx-folder-plus"
text={t("relation_map_buttons.create_child_note_title")}

View File

@@ -7,7 +7,6 @@ import appContext from "../../components/app_context";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import bulk_action, { ACTION_GROUPS } from "../../services/bulk_action";
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
import froca from "../../services/froca";
import { t } from "../../services/i18n";
import server from "../../services/server";
@@ -16,7 +15,6 @@ import tree from "../../services/tree";
import { getErrorMessage } from "../../services/utils";
import ws from "../../services/ws";
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import CollectionProperties from "../note_bars/CollectionProperties";
import Button from "../react/Button";
import Dropdown from "../react/Dropdown";
import { FormListHeader, FormListItem } from "../react/FormList";
@@ -26,8 +24,6 @@ import { ParentComponent } from "../react/react_utils";
import { TabContext } from "./ribbon-interface";
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabContext, "note" | "ntxId" | "hidden">) {
const parentComponent = useContext(ParentComponent);
const [ searchOptions, setSearchOptions ] = useState<{ availableOptions: SearchOption[], activeOptions: SearchOption[] }>();
@@ -115,11 +111,6 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
defaultValue={defaultValue}
/>;
})}
{isNewLayout && <tr className="view-options">
<td className="title-column">{t("search_definition.view_options")}</td>
<td><CollectionProperties note={note} /></td>
</tr>}
</tbody>
<BulkActionsList note={note} />
<tbody className="search-actions">

View File

@@ -459,7 +459,7 @@ body.experimental-feature-new-layout {
gap: var(--button-gap);
&> button:last-of-type {
margin-right: 1em;
margin-right: 0.5em;
}
}
}

View File

@@ -1,11 +1,14 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { useNoteContext } from "./react/hooks";
export default function ScrollPadding() {
const { note, parentComponent, ntxId, viewScope } = useNoteContext();
const ref = useRef<HTMLDivElement>(null);
const [height, setHeight] = useState<number>(10);
const isEnabled = ["text", "code"].includes(note?.type ?? "") && viewScope?.viewMode === "default";
const isEnabled = ["text", "code"].includes(note?.type ?? "")
&& viewScope?.viewMode === "default"
&& !note?.isTriliumSqlite();
const refreshHeight = () => {
if (!ref.current) return;
@@ -37,6 +40,6 @@ export default function ScrollPadding() {
style={{ height }}
onClick={() => parentComponent.triggerCommand("scrollToEnd", { ntxId })}
/>
: <div></div>
)
: <div />
);
}

View File

@@ -40,22 +40,4 @@ body.experimental-feature-new-layout #right-pane {
.gutter-vertical + .card .card-header {
padding-top: 0;
}
.no-items {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
flex-direction: column;
padding: 0.75em;
color: var(--muted-text-color);
.tn-icon {
font-size: 3em;
}
button {
margin-top: 1em;
}
}
}

View File

@@ -3,7 +3,7 @@ import "./RightPanelContainer.css";
import Split from "@triliumnext/split.js";
import { VNode } from "preact";
import { useState, useEffect, useRef, useCallback } from "preact/hooks";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import appContext from "../../components/app_context";
import { WidgetsByParent } from "../../services/bundle";
@@ -12,7 +12,7 @@ import options from "../../services/options";
import { DEFAULT_GUTTER_SIZE } from "../../services/resizer";
import Button from "../react/Button";
import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent, useTriliumOptionJson } from "../react/hooks";
import Icon from "../react/Icon";
import NoItems from "../react/NoItems";
import LegacyRightPanelWidget from "../right_panel_widget";
import HighlightsList from "./HighlightsList";
import PdfAttachments from "./pdf/PdfAttachments";
@@ -47,14 +47,15 @@ export default function RightPanelContainer({ widgetsByParent }: { widgetsByPare
items.length > 0 ? (
items
) : (
<div className="no-items">
<Icon icon="bx bx-sidebar" />
{t("right_pane.empty_message")}
<NoItems
icon="bx bx-sidebar"
text={t("right_pane.empty_message")}
>
<Button
text={t("right_pane.empty_button")}
triggerCommand="toggleRightPane"
/>
</div>
</NoItems>
)
)}
</div>

View File

@@ -1,7 +0,0 @@
.sql-result-widget {
padding: 15px;
}
.sql-console-result-container td {
white-space: preserve;
}

View File

@@ -1,63 +0,0 @@
import { SqlExecuteResults } from "@triliumnext/commons";
import { useNoteContext, useTriliumEvent } from "./react/hooks";
import "./sql_result.css";
import { useState } from "preact/hooks";
import Alert from "./react/Alert";
import { t } from "../services/i18n";
export default function SqlResults() {
const { note, ntxId } = useNoteContext();
const [ results, setResults ] = useState<SqlExecuteResults>();
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, results }) => {
if (eventNtxId !== ntxId) return;
setResults(results);
})
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium";
return (
<div className={`sql-result-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
results?.length === 1 && Array.isArray(results[0]) && results[0].length === 0 ? (
<Alert type="info">
{t("sql_result.no_rows")}
</Alert>
) : (
<div className="sql-console-result-container selectable-text">
{results?.map(rows => {
// inserts, updates
if (typeof rows === "object" && !Array.isArray(rows)) {
return <pre>{JSON.stringify(rows, null, "\t")}</pre>
}
// selects
return <SqlResultTable rows={rows} />
})}
</div>
)
)}
</div>
)
}
function SqlResultTable({ rows }: { rows: object[] }) {
if (!rows.length) return;
return (
<table className="table table-striped">
<thead>
<tr>
{Object.keys(rows[0]).map(key => <th>{key}</th>)}
</tr>
</thead>
<tbody>
{rows.map(row => (
<tr>
{Object.values(row).map(cell => <td>{cell}</td>)}
</tr>
))}
</tbody>
</table>
)
}

View File

@@ -1,43 +0,0 @@
.sql-table-schemas-widget {
padding: 12px;
padding-inline-end: 10%;
contain: none !important;
}
.sql-table-schemas > .dropdown {
display: inline-block !important;
}
.sql-table-schemas button.btn {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
cursor: pointer;
}
.sql-console-result-container {
width: 100%;
font-size: smaller;
margin-top: 10px;
flex-grow: 1;
overflow: auto;
min-height: 0;
}
.table-schema td {
padding: 5px;
}
.dropdown .table-schema {
font-family: var(--monospace-font-family);
font-size: .85em;
}
/* Data type */
.dropdown .table-schema td:nth-child(2) {
color: var(--muted-text-color);
}

View File

@@ -1,46 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import { t } from "../services/i18n";
import { useNoteContext } from "./react/hooks";
import "./sql_table_schemas.css";
import { SchemaResponse } from "@triliumnext/commons";
import server from "../services/server";
import Dropdown from "./react/Dropdown";
export default function SqlTableSchemas() {
const { note } = useNoteContext();
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
useEffect(() => {
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
}, []);
const isEnabled = note?.mime === "text/x-sqlite;schema=trilium" && schemas;
return (
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
<>
{t("sql_table_schemas.tables")}{": "}
<span class="sql-table-schemas">
{schemas.map(({ name, columns }) => (
<>
<Dropdown text={name} noSelectButtonStyle hideToggleArrow
>
<table className="table-schema">
{columns.map(column => (
<tr>
<td>{column.name}</td>
<td>{column.type}</td>
</tr>
))}
</table>
</Dropdown>
{" "}
</>
))}
</span>
</>
)}
</div>
)
}

View File

@@ -1,12 +1,15 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
import render from "../../services/render";
import { refToJQuerySelector } from "../react/react_utils";
import Alert from "../react/Alert";
import "./Render.css";
import { useEffect, useRef, useState } from "preact/hooks";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import RawHtml from "../react/RawHtml";
import render from "../../services/render";
import Alert from "../react/Alert";
import { useTriliumEvent } from "../react/hooks";
import RawHtml from "../react/RawHtml";
import { refToJQuerySelector } from "../react/react_utils";
import { TypeWidgetProps } from "./type_widget";
export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
const contentRef = useRef<HTMLDivElement>(null);
@@ -31,6 +34,13 @@ export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
refresh();
});
// Refresh on attribute change.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(a => a.type === "relation" && a.name === "renderNote" && attributes.isAffecting(a, note))) {
refresh();
}
});
// Integration with search.
useTriliumEvent("executeWithContentElement", ({ resolve, ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;

View File

@@ -0,0 +1,81 @@
.sql-console-widget-container {
.note-detail-split.split-vertical {
flex-direction: column-reverse;
}
.note-detail-split-preview {
overflow: auto;
}
.gutter {
background-color: var(--accented-background-color) !important;
}
.sql-result-widget {
height: 100%;
> .sql-console-result-container {
width: 100%;
height: 100%;
font-size: smaller;
flex-grow: 1;
overflow: auto;
min-height: 0;
> .tabulator {
--cell-vert-padding-size: 4px;
> .tabulator-tableholder {
padding: 0;
}
> .tabulator-footer,
> .tabulator-footer .tabulator-footer-contents {
padding: 2px 4px;
}
}
}
}
.sql-table-schemas-widget {
padding: 12px;
padding-inline-end: 10%;
contain: none !important;
.sql-table-schemas {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
}
> .dropdown {
display: inline-block !important;
}
button.btn {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
border: 1px solid var(--button-border-color);
border-radius: var(--button-border-radius);
background: var(--button-background-color);
color: var(--button-text-color);
cursor: pointer;
}
.table-schema td {
padding: 5px;
}
.dropdown .table-schema {
font-family: var(--monospace-font-family);
font-size: .85em;
}
/* Data type */
.dropdown .table-schema td:nth-child(2) {
color: var(--muted-text-color);
}
}
}

View File

@@ -0,0 +1,176 @@
import "./SqlConsole.css";
import { SchemaResponse, SqlExecuteResponse } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
import { ClipboardModule, EditModule, ExportModule, FilterModule, FormatModule, FrozenColumnsModule, KeybindingsModule, PageModule, ResizeColumnsModule, SelectRangeModule, SelectRowModule, SortModule } from "tabulator-tables";
import { t } from "../../services/i18n";
import server from "../../services/server";
import Tabulator from "../collections/table/tabulator";
import Button from "../react/Button";
import Dropdown from "../react/Dropdown";
import { useTriliumEvent } from "../react/hooks";
import NoItems from "../react/NoItems";
import SplitEditor from "./helpers/SplitEditor";
import { TypeWidgetProps } from "./type_widget";
export default function SqlConsole(props: TypeWidgetProps) {
return (
<SplitEditor
noteType="code"
{...props}
editorBefore={<SqlTableSchemas {...props} />}
previewContent={<SqlResults key={props.note.noteId} {...props} />}
forceOrientation="vertical"
splitOptions={{
sizes: [ 70, 30 ]
}}
/>
);
}
function SqlResults({ ntxId }: TypeWidgetProps) {
const [ response, setResponse ] = useState<SqlExecuteResponse>();
useTriliumEvent("sqlQueryResults", ({ ntxId: eventNtxId, response }) => {
if (eventNtxId !== ntxId) return;
setResponse(response);
});
// Not yet executed.
if (response === undefined) {
return (
<NoItems
icon="bx bx-data"
text={t("sql_result.not_executed")}
>
<Button
text={t("sql_result.execute_now")}
triggerCommand="runActiveNote"
/>
</NoItems>
);
}
// Executed but failed.
if (response && !response.success) {
return (
<NoItems
icon="bx bx-error"
text={t("sql_result.failed")}
>
<pre className="sql-error-message selectable-text">{response.error}</pre>
</NoItems>
);
}
// Zero results.
if (response?.results.length === 1 && Array.isArray(response.results[0]) && response.results[0].length === 0) {
return (
<NoItems
icon="bx bx-rectangle"
text={t("sql_result.no_rows")}
/>
);
}
return (
<div className="sql-result-widget">
<div className="sql-console-result-container selectable-text">
{response?.results.map((rows, index) => {
// inserts, updates
if (typeof rows === "object" && !Array.isArray(rows)) {
return (
<NoItems
key={index}
icon="bx bx-play"
text={t("sql_result.statement_result")}
>
<pre key={index}>{JSON.stringify(rows, null, "\t")}</pre>
</NoItems>
);
}
// selects
return <SqlResultTable key={index} rows={rows} />;
})}
</div>
</div>
);
}
function SqlResultTable({ rows }: { rows: object[] }) {
if (!rows.length) return;
return (
<Tabulator
layout="fitDataFill"
modules={[ ResizeColumnsModule, SortModule, SelectRangeModule, ClipboardModule, KeybindingsModule, EditModule, ExportModule, SelectRowModule, FormatModule, FrozenColumnsModule, FilterModule, PageModule ]}
selectableRange
clipboard="copy"
clipboardCopyRowRange="range"
clipboardCopyConfig={{
rowHeaders: false,
columnHeaders: false
}}
pagination
paginationSize={15}
paginationSizeSelector
paginationCounter="rows"
height="100%"
columns={[
{
title: "#",
formatter: "rownum",
width: 60,
hozAlign: "right",
frozen: true
},
...Object.keys(rows[0]).map(key => ({
title: key,
field: key,
width: 250,
minWidth: 100,
widthGrow: 1,
resizable: true,
headerFilter: true as const
}))
]}
data={rows}
/>
);
}
export function SqlTableSchemas({ note }: TypeWidgetProps) {
const [ schemas, setSchemas ] = useState<SchemaResponse[]>();
useEffect(() => {
server.get<SchemaResponse[]>("sql/schema").then(setSchemas);
}, []);
const isEnabled = note.isTriliumSqlite() && schemas;
return (
<div className={`sql-table-schemas-widget ${!isEnabled ? "hidden-ext" : ""}`}>
{isEnabled && (
<>
{t("sql_table_schemas.tables")}{": "}
<span class="sql-table-schemas">
{schemas.map(({ name, columns }) => (
<Dropdown key={name} text={name} noSelectButtonStyle hideToggleArrow>
<table className="table-schema">
{columns.map(column => (
<tr key={column.name}>
<td>{column.name}</td>
<td>{column.type}</td>
</tr>
))}
</table>
</Dropdown>
))}
</span>
</>
)}
</div>
);
}

View File

@@ -20,6 +20,9 @@ export interface CanvasContent {
appState: Partial<AppState>;
}
/** Subset of the app state that should be persisted whenever they change. This explicitly excludes transient state like the current selection or zoom level. */
type ImportantAppState = Pick<AppState, "gridModeEnabled" | "viewBackgroundColor">;
export default function useCanvasPersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
const libraryChanged = useRef(false);
@@ -37,6 +40,8 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
const libraryCache = useRef<LibraryItem[]>([]);
const attachmentMetadata = useRef<AttachmentMetadata[]>([]);
const appStateToCompare = useRef<Partial<ImportantAppState>>({});
const spacedUpdate = useEditorSpacedUpdate({
note,
noteContext,
@@ -47,7 +52,6 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
libraryCache.current = [];
attachmentMetadata.current = [];
currentSceneVersion.current = -1;
// load saved content into excalidraw canvas
let content: CanvasContent = {
@@ -65,6 +69,9 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
loadData(api, content, theme);
// Initialize tracking state after loading to prevent redundant updates from initial onChange events
currentSceneVersion.current = getSceneVersion(api.getSceneElements());
// load the library state
loadLibrary(note).then(({ libraryItems, metadata }) => {
// Update the library and save to independent variables
@@ -78,7 +85,7 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
async getData() {
const api = apiRef.current;
if (!api) return;
const { content, svg } = await getData(api);
const { content, svg } = await getData(api, appStateToCompare);
const attachments: SavedData["attachments"] = [{ role: "image", title: "canvas-export.svg", mime: "image/svg+xml", content: svg, position: 0 }];
// libraryChanged is unset in dataSaved()
@@ -149,21 +156,47 @@ export default function useCanvasPersistence(note: FNote, noteContext: NoteConte
const oldSceneVersion = currentSceneVersion.current;
const newSceneVersion = getSceneVersion(apiRef.current.getSceneElements());
if (newSceneVersion !== oldSceneVersion) {
let hasChanges = (newSceneVersion !== oldSceneVersion);
// There are cases where the scene version does not change, but appState did.
if (!hasChanges) {
const importantAppState = appStateToCompare.current;
const currentAppState = apiRef.current.getAppState();
for (const key in importantAppState) {
if (importantAppState[key as keyof ImportantAppState] !== currentAppState[key as keyof ImportantAppState]) {
hasChanges = true;
break;
}
}
}
if (hasChanges) {
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
currentSceneVersion.current = newSceneVersion;
}
},
onLibraryChange: () => {
libraryChanged.current = true;
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
onLibraryChange: (libraryItems) => {
if (!apiRef.current || isReadOnly) return;
// Check if library actually changed by comparing with cached state
const hasChanges =
libraryItems.length !== libraryCache.current.length ||
libraryItems.some(item => {
const cachedItem = libraryCache.current.find(cached => cached.id === item.id);
return !cachedItem || cachedItem.name !== item.name;
});
if (hasChanges) {
libraryChanged.current = true;
spacedUpdate.resetUpdateTimer();
spacedUpdate.scheduleUpdate();
}
}
};
}
async function getData(api: ExcalidrawImperativeAPI) {
async function getData(api: ExcalidrawImperativeAPI, appStateToCompare: RefObject<Partial<ImportantAppState>>) {
const elements = api.getSceneElements();
const appState = api.getAppState();
@@ -188,6 +221,12 @@ async function getData(api: ExcalidrawImperativeAPI) {
}
});
const importantAppState: ImportantAppState = {
gridModeEnabled: appState.gridModeEnabled,
viewBackgroundColor: appState.viewBackgroundColor
};
appStateToCompare.current = importantAppState;
const content = {
type: "excalidraw",
version: 2,
@@ -197,7 +236,7 @@ async function getData(api: ExcalidrawImperativeAPI) {
scrollX: appState.scrollX,
scrollY: appState.scrollY,
zoom: appState.zoom,
gridModeEnabled: appState.gridModeEnabled
...importantAppState
}
};

Some files were not shown because too many files have changed in this diff Show More