Compare commits

...

258 Commits

Author SHA1 Message Date
Adorian Doran
ea25477e3d ui/pager: fix some issues 2026-02-16 02:06:39 +02:00
Adorian Doran
82576c9703 ui/pager: rewrite the pagination button distribution algorithm to better control the length 2026-02-16 01:51:32 +02:00
Adorian Doran
0b8cba78d5 ui/pager: remove unused styles 2026-02-15 21:36:53 +02:00
Adorian Doran
d8806eaa04 ui/pager: replace the page number links with buttons 2026-02-15 21:31:58 +02:00
Adorian Doran
b8bc85856b ui/pager: add prev/next page navigation buttons 2026-02-15 21:12:42 +02:00
Adorian Doran
7551d0e044 ui/cards: fix attribute name 2026-02-15 20:32:01 +02:00
Adorian Doran
489a88b8da ui/cards: refactor 2026-02-15 20:23:19 +02:00
Adorian Doran
48013dc264 ui/cards: use a better HTML structure 2026-02-15 20:14:14 +02:00
Adorian Doran
4ee9d45dfc ui/cards: implement card titles (heading) 2026-02-15 20:06:01 +02:00
Adorian Doran
e00150a876 ui/cards: do not include nesting level CSS variables if not necessary 2026-02-15 19:46:08 +02:00
Adorian Doran
71668f8f8d List collections & search results: overhaul the list UI (#8705) 2026-02-15 19:32:21 +02:00
Adorian Doran
b71424d239 Merge branch 'main' into feat/list-view-overhaul 2026-02-15 19:20:41 +02:00
Adorian Doran
d1a3bceaa6 client/list view: refactor 2026-02-15 19:15:58 +02:00
Adorian Doran
483c57029a client/list view: extract event handler 2026-02-15 19:10:31 +02:00
Adorian Doran
7e6daf5b36 style/list view: refactor 2026-02-15 18:55:23 +02:00
Adorian Doran
b658253687 style/card: refactor 2026-02-15 18:55:04 +02:00
Adorian Doran
80b488deec style/list view: tweak indentation size 2026-02-15 18:34:54 +02:00
Adorian Doran
10cf1a371e style/card: improve 2026-02-15 18:34:00 +02:00
Elian Doran
81445901fa Support for custom geomap tiles (#8703) 2026-02-15 18:21:49 +02:00
Adorian Doran
f645d9d721 style/list view: tweak 2026-02-15 18:20:45 +02:00
Adorian Doran
900bfdff9d style/list view: refactor 2026-02-15 18:18:50 +02:00
Adorian Doran
108ca5afb5 client/list view: add support for archived items 2026-02-15 18:16:46 +02:00
Adorian Doran
3df03a551c client/list view: add support for archived items 2026-02-15 18:15:07 +02:00
Adorian Doran
6ffbe19667 client/list view: add support for tinted notes 2026-02-15 18:07:35 +02:00
Elian Doran
e3172ebf1c fix(client): unreadable disabled item 2026-02-15 17:51:06 +02:00
Elian Doran
9b2876a8ff docs(user): mention custom tiles & hiding labels for geomap 2026-02-15 17:47:28 +02:00
Elian Doran
3db2c910e0 fix(client/active_badges): wrong enable + missing toggle for icon pack & custom CSS 2026-02-15 17:32:34 +02:00
Elian Doran
2799e4392f Merge remote-tracking branch 'origin/main' into feature/custom_map_improvements
; Conflicts:
;	apps/client/src/widgets/note_bars/CollectionProperties.tsx
;	apps/client/src/widgets/ribbon/collection-properties-config.tsx
2026-02-15 17:30:39 +02:00
Elian Doran
61963fcfda UX improvements for web view & render note (#8725) 2026-02-15 17:23:28 +02:00
Elian Doran
b0a3d54276 chore(client): address requested changes 2026-02-15 17:15:51 +02:00
Elian Doran
2135412c84 Translations update from Hosted Weblate (#8723) 2026-02-15 17:08:54 +02:00
Elian Doran
7a534c3ea7 chore(client/active_badges): disable toggle for web view & render note 2026-02-15 17:06:23 +02:00
Elian Doran
a6e596a5e3 feat(client/render_note): display rendering errors 2026-02-15 17:04:31 +02:00
Elian Doran
8b3e3c2c3a chore(react): not reacting properly to deleted relations 2026-02-15 16:56:37 +02:00
Elian Doran
ec8b0a3801 feat(client/render_note): warning for disabled render note 2026-02-15 16:54:34 +02:00
Elian Doran
197b769838 feat(client/render_note): create sample note with HTML 2026-02-15 16:46:19 +02:00
Elian Doran
1ba498c0e3 feat(client/render_note): create sample note with preact 2026-02-15 16:38:09 +02:00
Elian Doran
5550cb7b95 feat(client/render_note): basic setup for with note picker 2026-02-15 16:30:10 +02:00
Elian Doran
06a005acec refactor(client/render_note): separate content from setup 2026-02-15 16:17:41 +02:00
Elian Doran
2103be9d28 refactor(client): extract learn more into component 2026-02-15 16:11:52 +02:00
Elian Doran
a02f3c4440 refactor(client): extract setup form to dedicated component 2026-02-15 16:08:58 +02:00
Elian Doran
4b8d341e00 feat(web_view): add a screen if web view is disabled 2026-02-15 16:02:51 +02:00
Elian Doran
964633f426 refactor(client/web_view): fix lint & formatting 2026-02-15 15:36:58 +02:00
Marcel
d11fb38280 Translated using Weblate (German)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2026-02-15 13:34:30 +00:00
Elian Doran
04f4530990 Mobile improvements (#8724) 2026-02-15 15:34:20 +02:00
Elian Doran
5a77318a9e chore(mobile): address requested changes 2026-02-15 15:33:03 +02:00
Elian Doran
9ed2894a0c fix(mobile): badges not collapsing in quick edit 2026-02-15 15:29:27 +02:00
Elian Doran
e0766ad439 feat(mobile): display grid view on two columns 2026-02-15 15:25:28 +02:00
Elian Doran
67acfaab62 fix(mobile): missing scroll padding 2026-02-15 15:17:08 +02:00
Elian Doran
10b5d29107 fix(mobile): promoted attributes having a max-height 2026-02-15 15:16:23 +02:00
Elian Doran
297dd41170 fix(mobile): incorrect tab hue on light theme 2026-02-15 15:12:12 +02:00
Elian Doran
1364223599 fix(mobile): horizontall scroll in empty tab due to long attributes 2026-02-15 15:00:34 +02:00
Elian Doran
a6b7761dfa fix(mobile): double-scroll bar for empty tab 2026-02-15 14:58:28 +02:00
Elian Doran
2e6290c514 feat(mobile): improve empty tab workspace switcher 2026-02-15 14:55:01 +02:00
Elian Doran
78f4928611 feat(mobile): reduce margins for empty tab 2026-02-15 14:53:01 +02:00
Elian Doran
d044fce9c1 fix(mobile): remove redundant file properties footer 2026-02-15 14:44:21 +02:00
Elian Doran
11fa815e13 Active content badges (#8716) 2026-02-15 14:34:04 +02:00
Elian Doran
8d917eb970 Merge branch 'main' into feature/active_content_badges 2026-02-15 14:20:21 +02:00
Elian Doran
e411e5f2cf chore(client): fix typecheck 2026-02-15 14:20:09 +02:00
Adorian Doran
fea8de89c6 style/list view: handle the title and attributes when overflowing the container 2026-02-15 14:08:50 +02:00
Elian Doran
48773636ca feat(collections): improve properties dropdown on mobile 2026-02-15 13:54:01 +02:00
Elian Doran
5a3c7355c1 chore(badges/content): improve dropdown on mobile 2026-02-15 13:51:48 +02:00
Adorian Doran
4afbabb977 style/list view: tweak 2026-02-15 13:51:13 +02:00
Elian Doran
afa9fe3063 refactor(badges/content): integrate title into definition 2026-02-15 13:46:47 +02:00
Elian Doran
178ac088b4 refactor(badges/content): integrate widget switcher using additional options 2026-02-15 13:44:16 +02:00
Adorian Doran
b2ebaf111f style/card: add legacy theme fallback 2026-02-15 13:43:06 +02:00
Elian Doran
ffd5ebbe79 refactor(badges/content): integrate execute using additional options 2026-02-15 13:38:02 +02:00
Elian Doran
6b78bfecb4 refactor(badges/content): integrate run options using additional options 2026-02-15 13:34:48 +02:00
Adorian Doran
9c2b01e3c9 style/list view: tweak 2026-02-15 13:34:45 +02:00
Adorian Doran
dca201ce42 style/list view: tweak 2026-02-15 13:31:45 +02:00
Elian Doran
d3c0a44c00 chore(badges/content): remove the base attribute for legacy 2026-02-15 13:26:23 +02:00
Elian Doran
33ea2de231 chore(badges/content): use translations 2026-02-15 13:23:36 +02:00
Elian Doran
43ebbfc321 chore(badges/content): add separator 2026-02-15 13:21:29 +02:00
Elian Doran
1e5b294eb3 feat(badges/content): adjustable base for app theme 2026-02-15 13:19:12 +02:00
Elian Doran
29855112c8 refactor(client): move note properties mapping outside collection properties 2026-02-15 13:13:18 +02:00
Elian Doran
8af7b3c81a feat(badges/content): make app theme toggleable 2026-02-15 12:42:46 +02:00
Elian Doran
b72b82ff1a feat(badges/content): add badge for app themes 2026-02-15 12:37:37 +02:00
Elian Doran
1588c8103c docs(user): improve documentation on scripts & active content 2026-02-15 12:32:41 +02:00
Elian Doran
15e569dcea chore(client): address requested changes 2026-02-15 12:32:30 +02:00
Adorian Doran
3774ea3768 style/list view: tweak 2026-02-15 02:28:52 +02:00
Adorian Doran
0a9c6a3119 client/list view: add a menu button for list items 2026-02-15 02:26:23 +02:00
Adorian Doran
a6cbde88bb client/list view: add an alternate style for search results 2026-02-15 01:57:10 +02:00
Elian Doran
4bdb407404 Translations update from Hosted Weblate (#8718) 2026-02-14 22:16:09 +02:00
Elian Doran
4568cedcd3 chore(deps): update dependency @redocly/cli to v2.18.1 (#8706) 2026-02-14 22:05:15 +02:00
AggelosPnS
f290317acc Translated using Weblate (Greek)
Currently translated at 12.3% (48 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/el/
2026-02-14 20:03:29 +00:00
AggelosPnS
645279c8fa Translated using Weblate (Greek)
Currently translated at 2.7% (49 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/el/
2026-02-14 20:03:28 +00:00
Hosted Weblate
09436f8d65 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-02-14 20:03:27 +00:00
Elian Doran
b616e0e5f9 chore(deps): update dependency electron to v40.4.1 (#8707) 2026-02-14 22:03:19 +02:00
Elian Doran
f06a0852a1 chore(deps): update dependency typedoc to v0.28.17 (#8708) 2026-02-14 22:02:02 +02:00
Elian Doran
9e688138be fix(deps): update dependency i18next to v25.8.7 (#8709) 2026-02-14 21:56:00 +02:00
Elian Doran
5d46970a38 fix(deps): update dependency react-window to v2.2.7 (#8710) 2026-02-14 21:54:40 +02:00
Elian Doran
ecc441c074 chore(deps): update dependency openai to v6.22.0 (#8711) 2026-02-14 21:53:59 +02:00
Elian Doran
f2ce3678c4 chore(deps): update dependency stylelint to v17.3.0 (#8712) 2026-02-14 21:53:41 +02:00
Elian Doran
c9ad390647 fix(deps): update dependency @preact/signals to v2.8.0 (#8713) 2026-02-14 21:52:55 +02:00
Elian Doran
92acc7accd Translations update from Hosted Weblate (#8715) 2026-02-14 21:52:11 +02:00
Adorian Doran
9c13f36ca0 client/list view: show the contents of a note only after its rendering completes 2026-02-14 16:39:20 +02:00
Adorian Doran
fe4a11c5ad client/list view: improve appearance 2026-02-14 16:22:13 +02:00
Elian Doran
2ef4eb7eae chore(client): fix type errors 2026-02-14 12:43:40 +02:00
Elian Doran
7b230706cb feat(badges/content): improve color support 2026-02-14 12:41:41 +02:00
Elian Doran
5a2b04adba feat(badges/content): add support for web view 2026-02-14 12:35:06 +02:00
Elian Doran
50dcd3ba44 feat(badges/content): add support for render note 2026-02-14 12:30:43 +02:00
Elian Doran
740b02952f feat(badges/content): disable toggle when not necessary 2026-02-14 12:22:27 +02:00
Aindriú Mac Giolla Eoin
5d3d42ffdd Translated using Weblate (Irish)
Currently translated at 100.0% (1777 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-14 10:09:46 +00:00
Elian Doran
866d3110da feat(badges/content): add badge for custom CSS 2026-02-14 12:05:24 +02:00
Elian Doran
7a3e7fccec feat(badges/content): handle widgets as separate content type 2026-02-14 11:56:40 +02:00
Elian Doran
3107bc8840 feat(badges/content): add toggle for widget 2026-02-14 11:47:42 +02:00
Elian Doran
2d34cdef5e feat(badges/content): integrate options for frontend script 2026-02-14 11:37:20 +02:00
Elian Doran
bd1f0909a2 feat(badges/content): configurable backend run options 2026-02-14 11:30:05 +02:00
Elian Doran
ef75de63fe feat(badges/content): option to execute now 2026-02-14 11:15:08 +02:00
Elian Doran
a739d28563 feat(badges/content): option to open API docs 2026-02-14 11:12:55 +02:00
Elian Doran
66ff009b72 feat(badges/content): option to open documentation 2026-02-14 11:08:30 +02:00
Elian Doran
a68e82c1c8 feat(badges/content): basic support for backend scripts 2026-02-14 10:57:32 +02:00
Elian Doran
46556c1c14 chore(badges/content): make toggle more compact 2026-02-14 10:40:19 +02:00
Elian Doran
7be637798f feat(badges/content): functional enable/disable toggle 2026-02-14 10:30:34 +02:00
Elian Doran
9b3396349e refactor(commons): add builtin_attributes to commons 2026-02-14 10:30:24 +02:00
Elian Doran
ccff210b4c feat(badges/content): indicate enabled/disabled state 2026-02-14 09:57:40 +02:00
Elian Doran
a2264847b6 refactor(badges/content): use shared mechanism for extracting info 2026-02-14 09:53:37 +02:00
Elian Doran
7d103f8c50 refactor(badges/content): extract to separate file 2026-02-14 09:37:48 +02:00
Elian Doran
f3dccc0aec feat(badges/content): detect icon pack 2026-02-14 09:19:46 +02:00
renovate[bot]
311b1d8a64 fix(deps): update dependency @preact/signals to v2.8.0 2026-02-14 02:08:43 +00:00
renovate[bot]
f3094e3079 chore(deps): update dependency stylelint to v17.3.0 2026-02-14 02:08:04 +00:00
renovate[bot]
f3b37b16d5 chore(deps): update dependency openai to v6.22.0 2026-02-14 02:07:20 +00:00
renovate[bot]
5f16ecf02d fix(deps): update dependency react-window to v2.2.7 2026-02-14 02:06:40 +00:00
renovate[bot]
8b75287827 fix(deps): update dependency i18next to v25.8.7 2026-02-14 02:05:55 +00:00
renovate[bot]
bb38e806cd chore(deps): update dependency typedoc to v0.28.17 2026-02-14 02:05:04 +00:00
renovate[bot]
8cbc15f1b0 chore(deps): update dependency electron to v40.4.1 2026-02-14 02:04:20 +00:00
renovate[bot]
870524f9cf chore(deps): update dependency @redocly/cli to v2.18.1 2026-02-14 02:03:29 +00:00
Adorian Doran
218343ca14 client/list view: use a sectioned card to display the list items 2026-02-14 01:14:54 +02:00
Adorian Doran
61953fd713 client/ui/cards: make a property optional 2026-02-14 00:10:54 +02:00
Adorian Doran
62ddf3a11b client/ui/cards: automatically determine the opacity of the background color of nested card sections 2026-02-14 00:08:59 +02:00
Adorian Doran
be12658864 client/ui/cards: add support for custom class names and action callbacks 2026-02-14 00:08:00 +02:00
Adorian Doran
b618e5a00f client: define the foundation of sectioned cards with multi-level nesting 2026-02-13 22:50:03 +02:00
Elian Doran
5da9963f31 fix(website): web clipper URL incorrect 2026-02-13 19:43:07 +02:00
Elian Doran
34e885812f fix(global_menu): advanced menu not accessible in other languages (closes #8694) 2026-02-13 19:40:51 +02:00
Elian Doran
a9ac11452d fix(relation_map): crash when valid JSON, but missing data (closes #8702) 2026-02-13 19:32:29 +02:00
Elian Doran
04b91308b1 chore(search): remove redundant border on mobile 2026-02-13 19:26:08 +02:00
Elian Doran
b09ef222f5 fix(search): errors not displayed (closes #8704) 2026-02-13 19:22:21 +02:00
Elian Doran
2d0ed06d50 Update Node.js to v24.13.1 (#8629) 2026-02-13 13:23:22 +02:00
renovate[bot]
8dd7cf6085 Update Node.js to v24.13.1 2026-02-13 05:50:03 +00:00
Elian Doran
4999bd4f1e Translations update from Hosted Weblate (#8701) 2026-02-13 07:48:43 +02:00
green
22f408addb Translated using Weblate (Japanese)
Currently translated at 100.0% (1777 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-13 06:48:06 +01:00
AggelosPnS
9ca1dbe638 Translated using Weblate (Greek)
Currently translated at 8.9% (35 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/el/
2026-02-13 06:48:06 +01:00
AggelosPnS
26662952e3 Translated using Weblate (Greek)
Currently translated at 2.0% (36 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/el/
2026-02-13 06:48:06 +01:00
Hosted Weblate
f0c9fa4ca3 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-02-13 06:48:06 +01:00
Elian Doran
b51aa1dd71 Update pnpm to v10.29.3 (#8683) 2026-02-13 07:48:00 +02:00
Elian Doran
846253c9e3 Update dependency @codemirror/view to v6.39.14 (#8695) 2026-02-13 07:47:46 +02:00
Elian Doran
6ab6ea97ac Update dependency i18next to v25.8.6 (#8696) 2026-02-13 07:47:31 +02:00
Elian Doran
bf41f70b98 Update dependency wxt to v0.20.17 (#8697) 2026-02-13 07:46:48 +02:00
Elian Doran
67ddbedd08 Update dependency dotenv to v17.3.1 (#8698) 2026-02-13 07:45:17 +02:00
renovate[bot]
2573e219dc Update dependency dotenv to v17.3.1 2026-02-13 01:02:52 +00:00
renovate[bot]
7e368678ab Update dependency wxt to v0.20.17 2026-02-13 01:02:01 +00:00
renovate[bot]
4a9fcf7ab6 Update dependency i18next to v25.8.6 2026-02-13 01:01:10 +00:00
renovate[bot]
65856c61c5 Update dependency @codemirror/view to v6.39.14 2026-02-13 01:00:19 +00:00
renovate[bot]
b5a97bffab Update pnpm to v10.29.3 2026-02-12 17:58:49 +00:00
Elian Doran
e6d728715f feat(geomap): support hiding labels 2026-02-12 19:47:24 +02:00
Elian Doran
54a52f0589 feat(geomap): support for custom tile URLs 2026-02-12 19:32:22 +02:00
Elian Doran
badfa23f86 refactor(geomap): delegate layer data handling to index 2026-02-12 19:12:56 +02:00
Elian Doran
30ccd3487a Translations update from Hosted Weblate (#8666) 2026-02-12 07:50:46 +02:00
Toto Yullian
75e012f2c9 Translated using Weblate (Indonesian)
Currently translated at 3.5% (63 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-02-12 06:48:36 +01:00
Ulices
5ecb1d1e2d Translated using Weblate (Spanish)
Currently translated at 100.0% (1777 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-12 06:48:36 +01:00
Toto Yullian
f8c24c838a Translated using Weblate (Indonesian)
Currently translated at 41.1% (65 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-02-12 06:48:35 +01:00
noobhjy
4ad9cfcdf4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1777 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-02-12 06:48:34 +01:00
Toto Yullian
a57253dd35 Translated using Weblate (Indonesian)
Currently translated at 65.5% (76 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-02-12 06:48:33 +01:00
Marcel
222e65bd45 Translated using Weblate (German)
Currently translated at 100.0% (1777 of 1777 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-12 06:48:32 +01:00
Jason Kuanca
3192ea3383 Translated using Weblate (Indonesian)
Currently translated at 60.3% (70 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-02-12 06:48:32 +01:00
green
3a27f873cd Translated using Weblate (Japanese)
Currently translated at 100.0% (1776 of 1776 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-12 06:48:31 +01:00
noobhjy
e8fb279036 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1776 of 1776 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-02-12 06:48:30 +01:00
Ulices
21ec7078d2 Translated using Weblate (Spanish)
Currently translated at 100.0% (1776 of 1776 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-12 06:48:29 +01:00
Aindriú Mac Giolla Eoin
94937e9fa4 Translated using Weblate (Irish)
Currently translated at 100.0% (1774 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-12 06:48:28 +01:00
Giovi
36401a20b8 Translated using Weblate (Italian)
Currently translated at 99.7% (1769 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-02-12 06:48:27 +01:00
noobhjy
8d1c4e4661 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1774 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-02-12 06:48:27 +01:00
Kuzma Simonov
14362060c8 Translated using Weblate (Russian)
Currently translated at 99.3% (1763 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2026-02-12 06:48:26 +01:00
Kuzma Simonov
de47e94f62 Translated using Weblate (Russian)
Currently translated at 99.3% (157 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2026-02-12 06:48:25 +01:00
Giovi
c78ed78bf6 Translated using Weblate (Italian)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/it/
2026-02-12 06:48:24 +01:00
Ulices
7674c95124 Translated using Weblate (Spanish)
Currently translated at 100.0% (1774 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-12 06:48:24 +01:00
Giovi
ec522c20b2 Translated using Weblate (Italian)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/it/
2026-02-12 06:48:23 +01:00
green
81f9578526 Translated using Weblate (Japanese)
Currently translated at 100.0% (1774 of 1774 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-12 06:48:22 +01:00
Hosted Weblate
20bca751d4 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-02-12 06:48:22 +01:00
Francis C.
49b5c49776 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/zh_Hant/
2026-02-12 06:48:20 +01:00
Francis C.
5d514fae61 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1772 of 1772 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-02-12 06:48:20 +01:00
Hosted Weblate
9e17e93dd7 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-02-12 06:48:19 +01:00
Elian Doran
7f70c641dc chore(deps): update dependency dotenv to v17.2.4 (#8630) 2026-02-12 07:47:59 +02:00
Elian Doran
7e3af4b7bc chore(deps): update dependency happy-dom to v20.6.1 (#8662) 2026-02-12 07:47:18 +02:00
Elian Doran
59ff4c0aef chore(deps): update dependency wxt to v0.20.15 (#8682) 2026-02-12 07:46:46 +02:00
Elian Doran
875e6b8e53 fix(deps): update dependency i18next to v25.8.5 (#8685) 2026-02-12 07:45:10 +02:00
Elian Doran
fafc44de7c fix(deps): update dependency marked to v17.0.2 (#8686) 2026-02-12 07:44:53 +02:00
Elian Doran
e0d7eb10d5 fix(deps): update dependency mathlive to v0.108.3 (#8687) 2026-02-12 07:44:40 +02:00
Elian Doran
ae01eb28a3 chore(deps): update dependency @redocly/cli to v2.18.0 (#8688) 2026-02-12 07:44:20 +02:00
Elian Doran
637c66c04f chore(deps): update dependency electron to v40.4.0 (#8689) 2026-02-12 07:44:01 +02:00
renovate[bot]
1c061e4428 chore(deps): update dependency electron to v40.4.0 2026-02-12 02:18:16 +00:00
renovate[bot]
cbe0626572 chore(deps): update dependency @redocly/cli to v2.18.0 2026-02-12 02:17:42 +00:00
renovate[bot]
86dc98174a fix(deps): update dependency mathlive to v0.108.3 2026-02-12 02:17:07 +00:00
renovate[bot]
f0ac8ea977 fix(deps): update dependency marked to v17.0.2 2026-02-12 02:16:29 +00:00
renovate[bot]
35d4c2cdfc fix(deps): update dependency i18next to v25.8.5 2026-02-12 02:15:53 +00:00
renovate[bot]
394f7c0d09 chore(deps): update dependency wxt to v0.20.15 2026-02-12 02:13:10 +00:00
Elian Doran
b83eee9bdc Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-11 20:21:55 +02:00
Elian Doran
e41b2e8d31 fix(options/shortcuts): filter text box forces lower case 2026-02-11 20:21:52 +02:00
Elian Doran
d93cec2bfd feat(options/shortcuts): add no results 2026-02-11 20:19:53 +02:00
Elian Doran
9eb87a39cd chore(options/shortcuts): refactor filtering 2026-02-11 20:15:39 +02:00
Elian Doran
07818ec1df fix(options): unnecessary full-height 2026-02-11 20:11:46 +02:00
Elian Doran
d4052dbe37 feat(options/shortcuts): equal height 2026-02-11 20:04:09 +02:00
Elian Doran
656f5e0a7f feat(options/shortcuts): make header and footer sticky (closes #8675) 2026-02-11 20:02:37 +02:00
Elian Doran
0cc5e4dac3 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.3 (#8609) 2026-02-11 07:51:46 +02:00
Elian Doran
8bbfff3cb2 fix(deps): update dependency i18next to v25.8.4 (#8611) 2026-02-11 07:51:31 +02:00
renovate[bot]
93059798b0 chore(deps): update dependency dotenv to v17.2.4 2026-02-11 05:51:15 +00:00
Elian Doran
33fae88cad chore(deps): update dependency stylelint to v17.2.0 (#8612) 2026-02-11 07:51:15 +02:00
Elian Doran
4feb23e9ca fix(deps): update dependency @preact/signals to v2.7.1 (#8619) 2026-02-11 07:50:04 +02:00
Elian Doran
c2b6b7ba72 chore(deps): update dependency esbuild to v0.27.3 (#8631) 2026-02-11 07:48:22 +02:00
Elian Doran
fb76aee258 chore(deps): update dependency @anthropic-ai/sdk to v0.74.0 (#8633) 2026-02-11 07:47:46 +02:00
Elian Doran
320b1829cc chore(deps): update dependency openai to v6.21.0 (#8634) 2026-02-11 07:47:16 +02:00
Elian Doran
601f0255a4 chore(deps): update dependency @playwright/test to v1.58.2 (#8642) 2026-02-11 07:46:46 +02:00
Elian Doran
b6cc2b227a chore(deps): update dependency wxt to v0.20.14 (#8643) 2026-02-11 07:46:36 +02:00
renovate[bot]
cc7da4b948 chore(deps): update dependency happy-dom to v20.6.1 2026-02-11 05:46:19 +00:00
Elian Doran
1ae3be2fda fix(deps): update codemirror (#8644) 2026-02-11 07:44:55 +02:00
Elian Doran
5b4d35ea86 chore(deps): update dependency @redocly/cli to v2.17.0 (#8645) 2026-02-11 07:44:38 +02:00
Elian Doran
00735e6c8e chore(deps): update dependency axios to v1.13.5 (#8661) 2026-02-11 07:44:27 +02:00
Elian Doran
66a42a38c9 chore(deps): update dependency @smithy/middleware-retry to v4.4.31 (#8669) 2026-02-11 07:43:54 +02:00
Elian Doran
05af1fba80 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.11 (#8670) 2026-02-11 07:43:45 +02:00
Elian Doran
3b2dd0f5e9 chore(deps): update pnpm to v10.29.2 (#8671) 2026-02-11 07:43:33 +02:00
Elian Doran
1493a66a36 chore(deps): update dependency webdriverio to v9.24.0 (#8672) 2026-02-11 07:43:15 +02:00
Elian Doran
b4bf103fd8 chore(deps): update typescript-eslint monorepo to v8.55.0 (#8673) 2026-02-11 07:43:00 +02:00
renovate[bot]
94286becfd chore(deps): update typescript-eslint monorepo to v8.55.0 2026-02-11 01:04:05 +00:00
renovate[bot]
a68aade58c chore(deps): update dependency webdriverio to v9.24.0 2026-02-11 01:03:25 +00:00
renovate[bot]
014201edf4 chore(deps): update pnpm to v10.29.2 2026-02-11 01:02:09 +00:00
renovate[bot]
8ae6297148 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.11 2026-02-11 01:01:56 +00:00
renovate[bot]
5e0300aa8e chore(deps): update dependency @smithy/middleware-retry to v4.4.31 2026-02-11 01:01:18 +00:00
renovate[bot]
80a7e18413 chore(deps): update dependency openai to v6.21.0 2026-02-10 21:08:56 +00:00
Elian Doran
43be0a1a3f chore(desktop): disable dev tools for the print window 2026-02-10 17:29:00 +02:00
Elian Doran
5eb32744c3 feat(desktop): display print errors 2026-02-10 17:26:58 +02:00
Elian Doran
89d39f5f2b feat(desktop): add logs when printing 2026-02-10 17:12:28 +02:00
Elian Doran
1f4900dd1e fix(desktop): print failing on upgrade due to cache issues 2026-02-10 16:30:16 +02:00
renovate[bot]
1c561c1483 chore(deps): update dependency stylelint to v17.2.0 2026-02-10 11:44:18 +00:00
Adorian Doran
6baaf60b67 style/web view setup: update icon 2026-02-10 02:53:46 +02:00
Adorian Doran
dde73f6c2b style/attachments: tweak 2026-02-10 02:46:10 +02:00
Adorian Doran
f445a49b34 client/notes/web view: create a web view setup form 2026-02-10 02:45:43 +02:00
Adorian Doran
29016d1cf5 style/refactor: promote centered form as global style 2026-02-10 01:26:07 +02:00
Adorian Doran
c06435046b style/alert bars: use a global style, tweak spacing 2026-02-09 23:39:22 +02:00
Adorian Doran
134422802f style/protected session password request: improve layout and appearance 2026-02-09 23:14:32 +02:00
Adorian Doran
5e00d6a305 style/collapsible: use low profile style for the expand/collapse button 2026-02-09 20:34:58 +02:00
Adorian Doran
b5f0137d8e Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-09 20:33:32 +02:00
Adorian Doran
081d041cbc style/buttons: define a style for low profile buttons 2026-02-09 20:33:24 +02:00
Elian Doran
95e733a67c Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-09 20:22:43 +02:00
Elian Doran
a664057312 fix(website): missing RPM GPG key (closes #8618) 2026-02-09 20:22:41 +02:00
Elian Doran
5b1a2d93bf fix(tree): child note badge overlapping text (closes #8567) 2026-02-09 20:20:20 +02:00
Adorian Doran
0323f95828 style/status bar: do not wrap the action buttons text 2026-02-09 19:57:13 +02:00
Elian Doran
375838449f fix(geomap): z-index issues with toolbar and buttons (closes #8649) 2026-02-09 19:55:57 +02:00
Adorian Doran
4562de8c2c close #2137 2026-02-09 19:19:41 +02:00
Adorian Doran
68d21669e7 style/similar notes: fix font size variation according to similarity 2026-02-09 19:12:47 +02:00
renovate[bot]
625e0cf159 chore(deps): update dependency @redocly/cli to v2.17.0 2026-02-09 13:58:05 +00:00
renovate[bot]
551ef00c61 fix(deps): update dependency @preact/signals to v2.7.1 2026-02-09 10:46:41 +00:00
renovate[bot]
10518f6364 chore(deps): update dependency axios to v1.13.5 2026-02-09 00:39:09 +00:00
renovate[bot]
3c33b5b169 fix(deps): update codemirror 2026-02-08 13:39:10 +00:00
renovate[bot]
000c31b66c chore(deps): update dependency @anthropic-ai/sdk to v0.74.0 2026-02-07 20:46:58 +00:00
renovate[bot]
fd7780abb0 chore(deps): update dependency esbuild to v0.27.3 2026-02-07 11:53:22 +00:00
renovate[bot]
c8a981e8d6 chore(deps): update dependency wxt to v0.20.14 2026-02-07 00:33:00 +00:00
renovate[bot]
faac45784c chore(deps): update dependency @playwright/test to v1.58.2 2026-02-07 00:32:04 +00:00
renovate[bot]
f6b454cb9a fix(deps): update dependency i18next to v25.8.4 2026-02-05 13:44:21 +00:00
renovate[bot]
563463782c Update dependency @ckeditor/ckeditor5-dev-build-tools to v54.3.3 2026-02-04 01:51:41 +00:00
141 changed files with 4824 additions and 2483 deletions

2
.nvmrc
View File

@@ -1 +1 @@
24.13.0
24.13.1

View File

@@ -9,14 +9,14 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.29.1",
"packageManager": "pnpm@10.29.3",
"devDependencies": {
"@redocly/cli": "2.15.1",
"@redocly/cli": "2.18.1",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"typedoc": "0.28.16",
"typedoc": "0.28.17",
"typedoc-plugin-missing-exports": "4.1.2"
}
}

View File

@@ -27,7 +27,7 @@
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.6.2",
"@preact/signals": "2.8.0",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
@@ -44,7 +44,7 @@
"draggabilly": "3.0.0",
"force-graph": "1.51.1",
"globals": "17.3.0",
"i18next": "25.8.0",
"i18next": "25.8.7",
"i18next-http-backend": "3.0.2",
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
@@ -54,14 +54,14 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "17.0.1",
"marked": "17.0.2",
"mermaid": "11.12.2",
"mind-elixir": "5.8.0",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.3",
"react-i18next": "16.5.4",
"react-window": "2.2.6",
"react-window": "2.2.7",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
@@ -78,7 +78,7 @@
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.5.0",
"happy-dom": "20.6.1",
"lightningcss": "1.31.1",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.2.0"

View File

@@ -700,6 +700,15 @@ export default class FNote {
return this.hasAttribute(LABEL, name);
}
/**
* Returns `true` if the note has a label with the given name (same as {@link hasOwnedLabel}), or it has a label with the `disabled:` prefix (for example due to a safe import).
* @param name the name of the label to look for.
* @returns `true` if the label exists, or its version with the `disabled:` prefix.
*/
hasLabelOrDisabled(name: string) {
return this.hasLabel(name) || this.hasLabel(`disabled:${name}`);
}
/**
* @param name - label name
* @returns true if label exists (including inherited) and does not have "false" value.

View File

@@ -24,9 +24,8 @@ import NoteWrapperWidget from "../widgets/note_wrapper.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import QuickSearchWidget from "../widgets/quick_search.js";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import ScrollPadding from "../widgets/scroll_padding";
import SearchResult from "../widgets/search_result.jsx";
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
import { applyModals } from "./layout_commons.js";
@@ -78,7 +77,7 @@ export default class MobileLayout {
.child(<NoteDetail />)
.child(<NoteList media="screen" />)
.child(<SearchResult />)
.child(<FilePropertiesWrapper />)
.child(<ScrollPadding />)
)
.child(<MobileEditorToolbar />)
.child(new FindWidget())
@@ -102,13 +101,3 @@ export default class MobileLayout {
return rootContainer;
}
}
function FilePropertiesWrapper() {
const { note, ntxId } = useNoteContext();
return (
<div>
{note?.type === "file" && <FilePropertiesTab note={note} ntxId={ntxId} />}
</div>
);
}

View File

@@ -18,6 +18,10 @@ export type PrintReport = {
} | {
type: "collection";
ignoredNoteIds: string[];
} | {
type: "error";
message: string;
stack?: string;
};
async function main() {

View File

@@ -168,6 +168,49 @@ function isAffecting(attrRow: AttributeRow, affectedNote: FNote | null | undefin
return false;
}
/**
* Toggles whether a dangerous attribute is enabled or not. When an attribute is disabled, its name is prefixed with `disabled:`.
*
* Note that this work for non-dangerous attributes as well.
*
* If there are multiple attributes with the same name, all of them will be toggled at the same time.
*
* @param note the note whose attribute to change.
* @param type the type of dangerous attribute (label or relation).
* @param name the name of the dangerous attribute.
* @param willEnable whether to enable or disable the attribute.
* @returns a promise that will resolve when the request to the server completes.
*/
async function toggleDangerousAttribute(note: FNote, type: "label" | "relation", name: string, willEnable: boolean) {
const attrs = [
...note.getOwnedAttributes(type, name),
...note.getOwnedAttributes(type, `disabled:${name}`)
];
for (const attr of attrs) {
const baseName = getNameWithoutDangerousPrefix(attr.name);
const newName = willEnable ? baseName : `disabled:${baseName}`;
if (newName === attr.name) continue;
// We are adding and removing afterwards to avoid a flicker (because for a moment there would be no active content attribute anymore) because the operations are done in sequence and not atomically.
if (attr.type === "label") {
await setLabel(note.noteId, newName, attr.value);
} else {
await setRelation(note.noteId, newName, attr.value);
}
await removeAttributeById(note.noteId, attr.attributeId);
}
}
/**
* Returns the name of an attribute without the `disabled:` prefix, or the same name if it's not disabled.
* @param name the name of an attribute.
* @returns the name without the `disabled:` prefix.
*/
function getNameWithoutDangerousPrefix(name: string) {
return name.startsWith("disabled:") ? name.substring(9) : name;
}
export default {
addLabel,
setLabel,
@@ -177,5 +220,7 @@ export default {
removeAttributeById,
removeOwnedLabelByName,
removeOwnedRelationByName,
isAffecting
isAffecting,
toggleDangerousAttribute,
getNameWithoutDangerousPrefix
};

View File

@@ -6,7 +6,7 @@ import bundleService, { type Bundle } from "./bundle.js";
import froca from "./froca.js";
import server from "./server.js";
async function render(note: FNote, $el: JQuery<HTMLElement>) {
async function render(note: FNote, $el: JQuery<HTMLElement>, onError?: (e: unknown) => void) {
const relations = note.getRelations("renderNote");
const renderNoteIds = relations.map((rel) => rel.value).filter((noteId) => noteId);
@@ -21,12 +21,14 @@ async function render(note: FNote, $el: JQuery<HTMLElement>) {
$scriptContainer.append(bundle.html);
// async so that scripts cannot block trilium execution
bundleService.executeBundle(bundle, note, $scriptContainer).then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el);
}
});
bundleService.executeBundle(bundle, note, $scriptContainer)
.catch(onError)
.then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el).catch(onError);
}
});
}
return renderNoteIds.length > 0;

View File

@@ -153,6 +153,11 @@ textarea,
background: var(--input-background-color);
}
.form-control:disabled {
background-color: var(--input-background-color);
opacity: 0.6;
}
.form-control:focus {
color: var(--input-text-color);
background: var(--input-background-color);
@@ -942,6 +947,7 @@ table.promoted-attributes-in-tooltip th {
color: var(--muted-text-color);
opacity: 0.6;
line-height: 1;
word-wrap: break-word;
}
.aa-dropdown-menu .aa-suggestion p {
@@ -1336,15 +1342,12 @@ body.desktop .dropdown-submenu > .dropdown-menu {
max-width: 300px;
}
.dropdown-submenu.dropstart > .dropdown-menu {
.dropdown-submenu.dropstart > .dropdown-menu,
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: auto;
inset-inline-end: calc(100% - 2px);
}
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: calc(-100% + 10px);
}
.right-dropdown-widget {
flex-shrink: 0;
}

View File

@@ -210,6 +210,7 @@
--badge-share-background-color: #4d4d4d;
--badge-clipped-note-background-color: #295773;
--badge-execute-background-color: #604180;
--badge-active-content-background-color: rgb(12, 68, 70);
--note-icon-background-color: #444444;
--note-icon-color: #d4d4d4;
@@ -238,9 +239,9 @@
--bottom-panel-background-color: #11111180;
--bottom-panel-title-bar-background-color: #3F3F3F80;
--status-bar-border-color: var(--main-border-color);
--scrollbar-thumb-color: #fdfdfd5c;
--scrollbar-thumb-hover-color: #ffffff7d;
--scrollbar-background-color: transparent;
@@ -290,6 +291,15 @@
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: rgba(0, 0, 0, .2);
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: black;
--calendar-coll-event-background-saturation: 25%;
--calendar-coll-event-background-lightness: 20%;
--calendar-coll-event-background-color: #3c3c3c;
@@ -303,7 +313,8 @@
* Dark color scheme tweaks
*/
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--dark-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -337,12 +348,24 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%);
}
.modal.tab-bar-modal .tabs .tab-card.with-hue {
background-color: hsl(var(--bg-hue), 8.8%, 11.2%);
border-color: hsl(var(--bg-hue), 9.4%, 25.1%);
}
.modal.tab-bar-modal .tabs .tab-card.active.with-hue {
background-color: hsl(var(--bg-hue), 8.8%, 16.2%);
border-color: hsl(var(--bg-hue), 9.4%, 25.1%);
}
.use-note-color {
--custom-color: var(--dark-theme-custom-color);
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);
@@ -351,4 +374,4 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.note-split.with-hue *::selection,
.quick-edit-dialog-wrapper.with-hue *::selection {
--selection-background-color: hsl(var(--custom-color-hue), 49.2%, 35%);
}
}

View File

@@ -202,6 +202,7 @@
--badge-share-background-color: #6b6b6b;
--badge-clipped-note-background-color: #2284c0;
--badge-execute-background-color: #7b47af;
--badge-active-content-background-color: rgb(27, 164, 168);
--note-icon-background-color: #4f4f4f;
--note-icon-color: white;
@@ -288,6 +289,15 @@
--ck-editor-toolbar-button-on-shadow: none;
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: #b1b1b133;
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: white;
--calendar-coll-event-background-lightness: 95%;
--calendar-coll-event-background-saturation: 80%;
--calendar-coll-event-background-color: #eaeaea;
@@ -297,7 +307,8 @@
--calendar-coll-today-background-color: #00000006;
}
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color {
--custom-color: var(--light-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -312,8 +323,19 @@
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
}
.modal.tab-bar-modal .tabs .tab-card.with-hue {
background-color: hsl(var(--bg-hue), 56%, 96%);
border-color: hsl(var(--bg-hue), 33%, 41%);
}
.modal.tab-bar-modal .tabs .tab-card.active.with-hue {
background-color: hsl(var(--bg-hue), 86%, 96%);
border-color: hsl(var(--bg-hue), 33%, 41%);
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);
@@ -322,4 +344,4 @@
.note-split.with-hue *::selection,
.quick-edit-dialog-wrapper.with-hue *::selection {
--selection-background-color: hsl(var(--custom-color-hue), 60%, 90%);
}
}

View File

@@ -800,3 +800,18 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
background: var(--hover-item-background-color);
color: var(--hover-item-text-color);
}
/*
* Alert bars
*/
div.alert {
margin-bottom: 8px;
background: var(--alert-bar-background) !important;
border-radius: 8px;
font-size: .85em;
}
div.alert p + p {
margin-block: 1em 0;
}

View File

@@ -84,6 +84,22 @@ button.btn.btn-success kbd {
letter-spacing: 0.5pt;
}
/*
* Low profile buttons
*/
button.tn-low-profile {
appearance: none;
background: transparent;
border: 0;
border-radius: 8px;
color: inherit;
}
button.tn-low-profile:hover {
background-color: var(--icon-button-hover-background);
}
/*
* Icon buttons
*/
@@ -794,3 +810,35 @@ input[type="range"] {
scrollbar-width: unset;
}
}
/*
* Centered forms
*/
.tn-centered-form {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 20vh;
}
.tn-centered-form .form-group {
text-align: center;
color: var(--muted-text-color);
}
.tn-centered-form .form-icon {
font-size: 140px;
color: var(--main-border-color);
}
.tn-centered-form .protected-session-password {
margin-inline: auto;
max-width: 350px;
text-align: center;
}
.tn-centered-form .input-group,
.tn-centered-form button {
margin-top: 12px;
}

View File

@@ -265,13 +265,6 @@ body.desktop .options-section:not(.tn-no-card) {
margin-bottom: 6px;
}
.options-section .alert {
margin-bottom: 8px;
background: var(--alert-bar-background) !important;
border-radius: 8px;
font-size: .85em;
}
nav.options-section-tabs {
min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width);

View File

@@ -751,12 +751,14 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
}
}
#left-pane .fancytree-expander {
#left-pane .fancytree-expander,
.nested-note-list-item .note-expander {
opacity: 0.65;
transition: opacity 150ms ease-in;
}
#left-pane .fancytree-expander:hover {
#left-pane .fancytree-expander:hover,
.nested-note-list-item .note-expander:hover {
opacity: 1;
transition: opacity 300ms ease-out;
}

View File

@@ -1180,9 +1180,6 @@
"note_not_found": "الملاحظة {{noteId}} غير موجودة!",
"cannot_match_transform": "تعذر مطابقة التحويل: {{transform}}"
},
"web_view": {
"web_view": "عرض الويب"
},
"consistency_checks": {
"title": "فحوصات التناسق"
},

View File

@@ -1068,11 +1068,6 @@
"note_detail_render_help_1": "之所以显示此帮助说明,是因为这个类型为渲染 HTML 的笔记没有正常工作所需的关系。",
"note_detail_render_help_2": "渲染 HTML 笔记类型用于<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">编写脚本</a>。简而言之,您有一份 HTML 代码笔记(可包含一些 JavaScript然后这个笔记会把页面渲染出来。要使其正常工作您需要定义一个名为 \"renderNote\" 的<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">关系</a>指向要渲染的 HTML 笔记。"
},
"web_view": {
"web_view": "网页视图",
"embed_websites": "网页视图类型的笔记允许您将网站嵌入到 Trilium 中。",
"create_label": "首先,请创建一个带有您要嵌入的 URL 地址的标签,例如 #webViewSrc=\"https://www.bing.com\""
},
"backend_log": {
"refresh": "刷新"
},
@@ -1421,7 +1416,8 @@
"description": "描述",
"reload_app": "重载应用以应用更改",
"set_all_to_default": "将所有快捷键重置为默认值",
"confirm_reset": "您确定要将所有键盘快捷键重置为默认值吗?"
"confirm_reset": "您确定要将所有键盘快捷键重置为默认值吗?",
"no_results": "未找到与“{{filter}}”匹配的快捷方式"
},
"spellcheck": {
"title": "拼写检查",
@@ -1622,7 +1618,9 @@
"print_report_title": "打印报告",
"print_report_collection_content_other": "集合中的 {{count}} 篇笔记无法打印,因为它们不受支持或受到保护。",
"print_report_collection_details_button": "查看详情",
"print_report_collection_details_ignored_notes": "忽略的笔记"
"print_report_collection_details_ignored_notes": "忽略的笔记",
"print_report_error_title": "打印失败",
"print_report_stack_trace": "堆栈跟踪"
},
"note_title": {
"placeholder": "请输入笔记标题...",
@@ -2268,5 +2266,12 @@
},
"bookmark_buttons": {
"bookmarks": "书签"
},
"web_view_setup": {
"title": "直接在 Trilium 中创建网页的实时视图",
"url_placeholder": "输入或粘贴网站地址,例如 https://triliumnotes.org",
"create_button": "创建网页视图",
"invalid_url_title": "无效的地址",
"invalid_url_message": "请输入有效的网址,例如 https://triliumnotes.org。"
}
}

View File

@@ -1067,11 +1067,6 @@
"note_detail_render_help_1": "Diese Hilfesnotiz wird angezeigt, da diese Notiz vom Typ „HTML rendern“ nicht über die erforderliche Beziehung verfügt, um ordnungsgemäß zu funktionieren.",
"note_detail_render_help_2": "Render-HTML-Notiztyp wird benutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. Kurzgesagt, du hast ein HTML-Code-Notiz (optional mit JavaScript) und diese Notiz rendert es. Damit es funktioniert, musst du eine a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">Beziehung</a> namens \"renderNote\" zeigend auf die HTML-Notiz zum rendern definieren."
},
"web_view": {
"web_view": "Webansicht",
"embed_websites": "Notiz vom Typ Web View ermöglicht das Einbetten von Websites in Trilium.",
"create_label": "Um zu beginnen, erstelle bitte ein Label mit einer URL-Adresse, die eingebettet werden soll, z. B. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Aktualisieren"
},
@@ -1387,7 +1382,8 @@
"description": "Beschreibung",
"reload_app": "Lade die App neu, um die Änderungen zu übernehmen",
"set_all_to_default": "Setze alle Verknüpfungen auf die Standardeinstellungen",
"confirm_reset": "Möchtest du wirklich alle Tastaturkürzel auf die Standardeinstellungen zurücksetzen?"
"confirm_reset": "Möchtest du wirklich alle Tastaturkürzel auf die Standardeinstellungen zurücksetzen?",
"no_results": "Keine Tastenkürzel für '{{filter}}' gefunden"
},
"spellcheck": {
"title": "Rechtschreibprüfung",
@@ -1591,7 +1587,9 @@
"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 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."
"print_report_collection_content_other": "{{count}} Notizen in der Sammlung konnten nicht gedruckt werden, weil sie nicht unterstützt oder geschützt sind.",
"print_report_error_title": "Druck fehlgeschlagen",
"print_report_stack_trace": "Stapelzurückverfolgung"
},
"note_title": {
"placeholder": "Titel der Notiz hier eingeben…",
@@ -2283,5 +2281,12 @@
},
"bookmark_buttons": {
"bookmarks": "Lesezeichen"
},
"web_view_setup": {
"title": "Erstelle eine Live-Ansicht einer Webseite direkt in Trilium",
"url_placeholder": "Gib oder füge die Adresse der Webseite ein, zum Beispiel https://triliumnotes.org",
"create_button": "Erstelle Web Ansicht",
"invalid_url_title": "Ungültige Adresse",
"invalid_url_message": "Füge eine valide Webadresse ein, zum Beispiel https://triliumnotes.org."
}
}

View File

@@ -13,6 +13,61 @@
"critical-error": {
"title": "Κρίσιμο σφάλμα",
"message": "Συνέβη κάποιο κρίσιμο σφάλμα, το οποίο δεν επιτρέπει στην εφαρμογή χρήστη να ξεκινήσει:\n\n{{message}}\n\nΤο πιθανότερο είναι να προκλήθηκε από κάποιο script που απέτυχε απρόοπτα. Δοκιμάστε να ξεκινήσετε την εφαρμογή σε ασφαλή λειτουργία για να λύσετε το πρόβλημα."
}
},
"widget-error": {
"title": "Δεν ήταν δυνατή η αρχικοποίηση του widget",
"message-custom": "Προσαρμοσμένο widget της σημείωσης με ID \"{{id}}\", με τίτλο \"{{title}}\", δεν ήταν δυνατό να αρχικοποιηθεί λόγω:\n\n{{message}}",
"message-unknown": "Άγνωστο widget δεν ήταν δυνατό να αρχικοποιηθεί λόγω:\n\n{{message}}"
},
"bundle-error": {
"title": "Δεν ήταν δυνατή η φόρτωση προσαρμοσμένου script",
"message": "Το script δεν ήταν δυνατό να εκτελεστεί λόγω:\n\n{{message}}"
},
"widget-list-error": {
"title": "Δεν ήταν δυνατή η λήψη της λίστας των widgets από τον server"
},
"widget-render-error": {
"title": "Δεν ήταν δυνατή η απόδοση προσαρμοσμένου React widget"
},
"widget-missing-parent": "Το προσαρμοσμένο widget δεν έχει ορισμένη την υποχρεωτική ιδιότητα '{{property}}'.\n\nΕάν το script προορίζεται για εκτέλεση χωρίς UI element, χρησιμοποιήστε '#run=frontendStartup' αντί για αυτό.",
"open-script-note": "Άνοιγμα σημείωσης script",
"scripting-error": "Σφάλμα προσαρμοσμένου script: {{title}}"
},
"bookmark_buttons": {
"bookmarks": "Σελιδοδείκτες"
},
"add_link": {
"add_link": "Προσθήκη συνδέσμου",
"help_on_links": "Βοήθεια για συνδέσμους",
"note": "Σημείωση",
"search_note": "Αναζήτηση σημείωσης με βάση το όνομά της",
"link_title_mirrors": "Ο τίτλος του συνδέσμου αντικατοπτρίζει τον τρέχοντα τίτλο της σημείωσης",
"link_title_arbitrary": "Ο τίτλος του συνδέσμου μπορεί να τροποποιηθεί ελεύθερα",
"link_title": "Τίτλος συνδέσμου",
"button_add_link": "Προσθήκη συνδέσμου"
},
"branch_prefix": {
"edit_branch_prefix": "Επεξεργασία προθέματος κλάδου",
"edit_branch_prefix_multiple": "Επεξεργασία προθέματος κλάδου για {{count}} κλάδους",
"help_on_tree_prefix": "Βοήθεια για πρόθεμα δέντρου",
"prefix": "Πρόθεμα: ",
"save": "Αποθήκευση",
"branch_prefix_saved": "Το πρόθεμα κλάδου αποθηκεύτηκε.",
"branch_prefix_saved_multiple": "Το πρόθεμα κλάδου αποθηκεύτηκε για {{count}} κλάδους.",
"affected_branches": "Επηρεαζόμενοι κλάδοι ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Μαζικές ενέργειες",
"affected_notes": "Επηρεαζόμενες σημειώσεις",
"include_descendants": "Συμπερίληψη απογόνων των επιλεγμένων σημειώσεων",
"available_actions": "Διαθέσιμες ενέργειες",
"chosen_actions": "Επιλεγμένες ενέργειες",
"execute_bulk_actions": "Εκτέλεση μαζικών ενεργειών",
"bulk_actions_executed": "Οι μαζικές ενέργειες εκτελέστηκαν επιτυχώς.",
"none_yet": "Καμία ακόμη… προσθέστε μια ενέργεια επιλέγοντας μία από τις διαθέσιμες παραπάνω.",
"labels": "Ετικέτες",
"relations": "Συσχετίσεις",
"notes": "Σημειώσεις",
"other": "Λοιπά"
}
}

View File

@@ -1067,13 +1067,21 @@
"click_on_canvas_to_place_new_note": "Click on canvas to place new note"
},
"render": {
"note_detail_render_help_1": "This help note is shown because this note of type Render HTML doesn't have required relation to function properly.",
"note_detail_render_help_2": "Render HTML note type is used for <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. In short, you have a HTML code note (optionally with some JavaScript) and this note will render it. To make it work, you need to define a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relation</a> called \"renderNote\" pointing to the HTML note to render."
"setup_title": "Display custom HTML or Preact JSX inside this note",
"setup_create_sample_preact": "Create sample note with Preact",
"setup_create_sample_html": "Create sample note with HTML",
"setup_sample_created": "A sample note was created as a child note.",
"disabled_description": "This render notes comes from an external source. To protect you from malicious content, it is not enabled by default. Make sure you trust the source before enabling it.",
"disabled_button_enable": "Enable render note"
},
"web_view": {
"web_view": "Web View",
"embed_websites": "Note of type Web View allows you to embed websites into Trilium.",
"create_label": "To start, please create a label with a URL address you want to embed, e.g. #webViewSrc=\"https://www.google.com\""
"web_view_setup": {
"title": "Create a live view of a webpage directly into Trilium",
"url_placeholder": "Enter or paste the website address, for example https://triliumnotes.org",
"create_button": "Create Web View",
"invalid_url_title": "Invalid address",
"invalid_url_message": "Insert a valid web address, for example https://triliumnotes.org.",
"disabled_description": "This web view was imported from an external source. To help protect you from phishing or malicious content, it isnt loading automatically. You can enable it if you trust the source.",
"disabled_button_enable": "Enable web view"
},
"backend_log": {
"refresh": "Refresh"
@@ -1589,7 +1597,8 @@
"description": "Description",
"reload_app": "Reload app to apply changes",
"set_all_to_default": "Set all shortcuts to the default",
"confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?"
"confirm_reset": "Do you really want to reset all keyboard shortcuts to the default?",
"no_results": "No shortcuts found matching '{{filter}}'"
},
"spellcheck": {
"title": "Spell Check",
@@ -1795,6 +1804,8 @@
"printing": "Printing in progress...",
"printing_pdf": "Exporting to PDF in progress...",
"print_report_title": "Print report",
"print_report_error_title": "Failed to print",
"print_report_stack_trace": "Stack trace",
"print_report_collection_content_one": "{{count}} note in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_content_other": "{{count}} notes in the collection could not be printed because they are not supported or they are protected.",
"print_report_collection_details_button": "See details",
@@ -2099,7 +2110,8 @@
"raster": "Raster",
"vector_light": "Vector (Light)",
"vector_dark": "Vector (Dark)",
"show-scale": "Show scale"
"show-scale": "Show scale",
"show-labels": "Show marker names"
},
"table_context_menu": {
"delete_row": "Delete row"
@@ -2178,8 +2190,9 @@
"percentage": "%"
},
"pagination": {
"page_title": "Page of {{startIndex}} - {{endIndex}}",
"total_notes": "{{count}} notes"
"total_notes": "{{count}} notes",
"prev_page": "Previous page",
"next_page": "Next page"
},
"collections": {
"rendering_error": "Unable to show content due to an error."
@@ -2283,5 +2296,32 @@
},
"bookmark_buttons": {
"bookmarks": "Bookmarks"
},
"active_content_badges": {
"type_icon_pack": "Icon pack",
"type_backend_script": "Backend script",
"type_frontend_script": "Frontend script",
"type_widget": "Widget",
"type_app_css": "Custom CSS",
"type_render_note": "Render note",
"type_web_view": "Web view",
"type_app_theme": "Custom theme",
"toggle_tooltip_enable_tooltip": "Click to enable this {{type}}.",
"toggle_tooltip_disable_tooltip": "Click to disable this {{type}}.",
"menu_docs": "Open documentation",
"menu_execute_now": "Execute script now",
"menu_run": "Run automatically",
"menu_run_disabled": "Manually",
"menu_run_backend_startup": "When the backend starts up",
"menu_run_hourly": "Hourly",
"menu_run_daily": "Daily",
"menu_run_frontend_startup": "When the desktop frontend starts up",
"menu_run_mobile_startup": "When the mobile frontend starts up",
"menu_change_to_widget": "Change to widget",
"menu_change_to_frontend_script": "Change to frontend script",
"menu_theme_base": "Theme base"
},
"setup_form": {
"more_info": "Learn more"
}
}

View File

@@ -1072,11 +1072,6 @@
"note_detail_render_help_1": "Esta nota de ayuda se muestra porque esta nota de tipo Renderizar HTML no tiene la relación requerida para funcionar correctamente.",
"note_detail_render_help_2": "El tipo de nota Render HTML es usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. De forma resumida, tiene una nota con código HTML (opcionalmente con algo de JavaScript) y esta nota la renderizará. Para que funcione, es necesario definir una <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relación</a> llamada \"renderNote\" apuntando a la nota HTML nota a renderizar."
},
"web_view": {
"web_view": "Vista web",
"embed_websites": "La nota de tipo Web View le permite insertar sitios web en Trilium.",
"create_label": "Para comenzar, por favor cree una etiqueta con una dirección URL que desee empotrar, e.g. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Refrescar"
},
@@ -1577,7 +1572,8 @@
"description": "Descripción",
"reload_app": "Vuelva a cargar la aplicación para aplicar los cambios",
"set_all_to_default": "Establecer todos los accesos directos al valor predeterminado",
"confirm_reset": "¿Realmente desea restablecer todos los atajos de teclado a sus valores predeterminados?"
"confirm_reset": "¿Realmente desea restablecer todos los atajos de teclado a sus valores predeterminados?",
"no_results": "No se encontraron atajos que coincidan con '{{filter}} '"
},
"spellcheck": {
"title": "Revisión ortográfica",
@@ -1784,7 +1780,9 @@
"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"
"print_report_collection_details_ignored_notes": "Notas ignoradas",
"print_report_stack_trace": "Rastreo de pila",
"print_report_error_title": "Fallo al imprimir"
},
"note_title": {
"placeholder": "escriba el título de la nota aquí...",
@@ -2298,5 +2296,12 @@
},
"bookmark_buttons": {
"bookmarks": "Marcadores"
},
"web_view_setup": {
"title": "Crear una vista en vivo de una página web directamente en Trilium",
"url_placeholder": "Ingresar o pegar la dirección del sitio web, por ejemplo https://triliumnotes.org",
"create_button": "Crear Vista Web",
"invalid_url_title": "Dirección inválida",
"invalid_url_message": "Ingrese una dirección web válida, por ejemplo https://triliumnotes.org."
}
}

View File

@@ -1057,11 +1057,6 @@
"note_detail_render_help_1": "Cette note d'aide s'affiche car cette note de type Rendu HTML n'a pas la relation requise pour fonctionner correctement.",
"note_detail_render_help_2": "Le type de note Rendu HTML est utilisé pour les <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripts</a>. En résumé, vous disposez d'une note de code HTML (éventuellement contenant JavaScript) et cette note affichera le rendu. Pour que cela fonctionne, vous devez définir une <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relation</a> appelée \"renderNote\" pointant vers la note HTML à rendre."
},
"web_view": {
"web_view": "Affichage Web",
"embed_websites": "Les notes de type Affichage Web vous permet d'intégrer des sites Web dans Trilium.",
"create_label": "Pour commencer, veuillez créer un label avec l'adresse URL que vous souhaitez intégrer, par ex. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Rafraîchir"
},

View File

@@ -1076,11 +1076,6 @@
"note_detail_render_help_1": "Taispeántar an nóta cabhrach seo mar nach bhfuil aon ghaol riachtanach ag an nóta seo den chineál Render HTML le go bhfeidhmeoidh sé i gceart.",
"note_detail_render_help_2": "Úsáidtear cineál nóta HTML rindreála le haghaidh <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scriptithe</a>. Go hachomair, tá nóta cóid HTML agat (le roinnt JavaScript más féidir) agus déanfaidh an nóta seo é a rindreáil. Chun go n-oibreoidh sé, ní mór duit <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">gaol</a> ar a dtugtar \"renderNote\" a shainiú ag pointeáil chuig an nóta HTML atá le rindreáil."
},
"web_view": {
"web_view": "Radharc Gréasáin",
"embed_websites": "Nóta den chineál Gréasáin a ligeann duit suíomhanna gréasáin a leabú i Trilium.",
"create_label": "Chun tús a chur leis, cruthaigh lipéad le seoladh URL ar mhaith leat a leabú, m.sh. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Athnuachan"
},
@@ -1595,7 +1590,8 @@
"description": "Cur síos",
"reload_app": "Athlódáil an aip chun na hathruithe a chur i bhfeidhm",
"set_all_to_default": "Socraigh gach aicearra go dtí an réamhshocrú",
"confirm_reset": "An bhfuil tú cinnte gur mhaith leat na haicearraí méarchláir go léir a athshocrú go dtí an rogha réamhshocraithe?"
"confirm_reset": "An bhfuil tú cinnte gur mhaith leat na haicearraí méarchláir go léir a athshocrú go dtí an rogha réamhshocraithe?",
"no_results": "Níor aimsíodh aon aicearraí a mheaitseálann '{{filter}}'"
},
"spellcheck": {
"title": "Seiceáil Litrithe",
@@ -1813,7 +1809,9 @@
"print_report_collection_content_many": "Níorbh fhéidir {{count}} nótaí sa bhailiúchán a phriontáil mar nach dtacaítear leo nó mar go bhfuil siad faoi chosaint.",
"print_report_collection_content_other": "Níorbh fhéidir {{count}} nótaí sa bhailiúchán a phriontáil mar nach dtacaítear leo nó mar go bhfuil siad faoi chosaint.",
"print_report_collection_details_button": "Féach sonraí",
"print_report_collection_details_ignored_notes": "Nótaí neamhairdithe"
"print_report_collection_details_ignored_notes": "Nótaí neamhairdithe",
"print_report_error_title": "Theip ar phriontáil",
"print_report_stack_trace": "Rian cruachta"
},
"note_title": {
"placeholder": "clóscríobh teideal an nóta anseo...",
@@ -2328,5 +2326,12 @@
},
"bookmark_buttons": {
"bookmarks": "Leabharmharcanna"
},
"web_view_setup": {
"title": "Cruthaigh radharc beo de leathanach gréasáin go díreach isteach i Trilium",
"url_placeholder": "Cuir isteach nó greamaigh seoladh an tsuímh ghréasáin, mar shampla https://triliumnotes.org",
"create_button": "Cruthaigh Radharc Gréasáin",
"invalid_url_title": "Seoladh neamhbhailí",
"invalid_url_message": "Cuir isteach seoladh gréasáin bailí, mar shampla https://triliumnotes.org."
}
}

View File

@@ -50,7 +50,8 @@
"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}}):"
"affected_branches": "Cabang terdampak ({{count}}):",
"edit_branch_prefix": "Sunting awalan cabang"
},
"bulk_actions": {
"bulk_actions": "Aksi borongan",
@@ -61,7 +62,10 @@
"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"
"labels": "Label-label",
"relations": "Hubungan",
"notes": "Catatan",
"other": "Lainnya"
},
"confirm": {
"cancel": "Batal",
@@ -80,6 +84,8 @@
"no_note_to_delete": "Tidak ada Catatan yang akan dihapus (hanya duplikat)."
},
"clone_to": {
"clone_notes_to": "Duplikat catatan ke…"
"clone_notes_to": "Duplikat catatan ke…",
"help_on_links": "Bantuan pada tautan",
"notes_to_clone": "Catatan untuk kloning"
}
}

View File

@@ -186,7 +186,8 @@
"geo-map": {
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
"unable-to-load-map": "Impossibile caricare la mappa."
"unable-to-load-map": "Impossibile caricare la mappa.",
"create-child-note-text": "Aggiungi indicatore"
},
"geo-map-context": {
"open-location": "Apri la posizione",
@@ -422,7 +423,8 @@
"unknown_search_option": "Opzione di ricerca sconosciuta {{searchOptionName}}",
"search_note_saved": "La nota di ricerca è stata salvata in {{- notePathTitle}}",
"actions_executed": "Le azioni sono state eseguite.",
"view_options": "Opzioni di visualizzazione:"
"view_options": "Opzioni di visualizzazione:",
"option": "opzione"
},
"modal": {
"close": "Chiudi",
@@ -1241,7 +1243,8 @@
"show-cheatsheet": "Mostra il foglietto illustrativo",
"toggle-zen-mode": "Modalità Zen",
"new-version-available": "Nuovo aggiornamento disponibile",
"download-update": "Ottieni la versione {{latestVersion}}"
"download-update": "Ottieni la versione {{latestVersion}}",
"search_notes": "Cerca note"
},
"zen_mode": {
"button_exit": "Esci dalla modalità Zen"
@@ -1340,7 +1343,9 @@
"delete_this_note": "Elimina questa nota",
"note_revisions": "Revisioni delle note",
"error_cannot_get_branch_id": "Impossibile ottenere branchId per notePath '{{notePath}}'",
"error_unrecognized_command": "Comando non riconosciuto {{command}}"
"error_unrecognized_command": "Comando non riconosciuto {{command}}",
"backlinks": "Backlinks",
"content_language_switcher": "Lingua dei contenuti: {{language}}"
},
"note_icon": {
"change_note_icon": "Cambia icona nota",
@@ -1581,11 +1586,6 @@
"note_detail_render_help_1": "Questa nota di aiuto viene visualizzata perché questa nota di tipo Render HTML non ha la relazione richiesta per funzionare correttamente.",
"note_detail_render_help_2": "Il tipo di nota HTML Render viene utilizzato per lo <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. In breve, si ottiene una nota in codice HTML (opzionalmente con un po' di JavaScript) che verrà visualizzata. Per farla funzionare, è necessario definire una <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relazione</a> denominata \"renderNote\" che punti alla nota HTML da visualizzare."
},
"web_view": {
"web_view": "Visualizzazione Web",
"embed_websites": "La nota di tipo Web View consente di incorporare siti web in Trilium.",
"create_label": "Per iniziare, crea un'etichetta con l'indirizzo URL che desideri incorporare, ad esempio #webViewSrc=\"https://www.google.com\""
},
"vacuum_database": {
"title": "Pulizia del database",
"description": "Questa operazione ricostruirà il database, generando in genere un file di dimensioni inferiori. In realtà, nessun dato verrà modificato.",
@@ -2145,7 +2145,7 @@
"background_effects_title": "Gli effetti di sfondo sono ora stabili",
"background_effects_message": "Sui dispositivi Windows, gli effetti di sfondo sono ora completamente stabili. Gli effetti di sfondo aggiungono un tocco di colore all'interfaccia utente sfocando lo sfondo retrostante. Questa tecnica è utilizzata anche in altre applicazioni come Esplora risorse di Windows.",
"background_effects_button": "Abilita gli effetti di sfondo",
"dismiss": "Congedare",
"dismiss": "Chiudi",
"new_layout_title": "Nuovo layout",
"new_layout_message": "Abbiamo introdotto un layout modernizzato per Trilium. La barra multifunzione è stata rimossa e integrata perfettamente nell'interfaccia principale, con una nuova barra di stato e sezioni espandibili (come gli attributi promossi) che assumono le funzioni chiave.\n\nIl nuovo layout è abilitato di default e può essere temporaneamente disabilitato tramite Opzioni → Aspetto.",
"new_layout_button": "Maggiori informazioni"
@@ -2281,5 +2281,24 @@
"pages_other": "{{count}} pagine",
"pages_alt": "Pagina {{pageNumber}}",
"pages_loading": "Caricamento in corso..."
},
"web_view_setup": {
"title": "Crea una visualizzazione live di una pagina web direttamente in Trilium",
"url_placeholder": "Inserisci o incolla l'indirizzo del sito web, ad esempio https://triliumnotes.org",
"create_button": "Crea vista Web",
"invalid_url_title": "Indirizzo non valido",
"invalid_url_message": "Inserisci un indirizzo web valido, ad esempio https://triliumnotes.org."
},
"platform_indicator": {
"available_on": "Disponibile su {{platform}}"
},
"mobile_tab_switcher": {
"title_one": "Scheda {{count}}",
"title_many": "Schede {{count}}",
"title_other": "Schede {{count}}",
"more_options": "Altre opzioni"
},
"bookmark_buttons": {
"bookmarks": "Segnalibri"
}
}

View File

@@ -249,7 +249,8 @@
"reload_app": "リロードして変更を適用する",
"set_all_to_default": "すべてのショートカットをデフォルトに戻す",
"confirm_reset": "キーボードショートカットをすべてデフォルトにリセットしますか?",
"keyboard_shortcuts": "キーボードショートカット"
"keyboard_shortcuts": "キーボードショートカット",
"no_results": "'{{filter}}' に一致するショートカットが見つかりません"
},
"confirm": {
"confirmation": "確認",
@@ -826,11 +827,6 @@
"error_no_path": "移動するパスがありません。",
"move_success_message": "選択したノートは以下に移動されました "
},
"web_view": {
"web_view": "Web ビュー",
"embed_websites": "Web ビュータイプでは、web サイトを Trilium に埋め込むことができます。",
"create_label": "まず始めに、埋め込みたいURLアドレスのラベルを作成してください。例: #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "リフレッシュ"
},
@@ -1959,7 +1955,9 @@
"print_report_title": "レポートを印刷",
"print_report_collection_content_other": "コレクション内の {{count}} 件のノートは、サポートされていないか保護されているため、印刷できませんでした。",
"print_report_collection_details_button": "詳細を見る",
"print_report_collection_details_ignored_notes": "無視されたノート"
"print_report_collection_details_ignored_notes": "無視されたノート",
"print_report_error_title": "印刷に失敗しました",
"print_report_stack_trace": "スタックトレース"
},
"watched_file_update_status": {
"ignore_this_change": "この変更を無視する",
@@ -2268,5 +2266,12 @@
},
"bookmark_buttons": {
"bookmarks": "ブックマーク"
},
"web_view_setup": {
"title": "Trilium に直接 Web ページのライブビューを作成",
"url_placeholder": "Web サイトのアドレスを入力または貼り付けて下さい。 例: https://triliumnotes.org",
"create_button": "Web ビューを作成",
"invalid_url_title": "無効なアドレス",
"invalid_url_message": "有効な Web アドレスを入力してください。 例: https://triliumnotes.org"
}
}

View File

@@ -1436,11 +1436,6 @@
"note_detail_render_help_1": "Ta notatka pomocy jest wyświetlana, ponieważ ta notatka typu Render HTML nie ma wymaganej relacji do poprawnego działania.",
"note_detail_render_help_2": "Typ notatki Render HTML jest używany do <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">skryptowania</a>. W skrócie, masz notatkę kodu HTML (opcjonalnie z JavaScript) i ta notatka ją wyrenderuje. Aby to zadziałało, musisz zdefiniować <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relację</a> o nazwie \"renderNote\" wskazującą na notatkę HTML do wyrenderowania."
},
"web_view": {
"web_view": "Widok WWW",
"embed_websites": "Notatka typu Widok WWW pozwala na osadzanie stron internetowych w Trilium.",
"create_label": "Aby rozpocząć, utwórz etykietę z adresem URL, który chcesz osadzić, np. #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Odśwież"
},

View File

@@ -1068,11 +1068,6 @@
"note_detail_render_help_1": "Esta nota de ajuda é mostrada porque esta nota do tipo Renderizar HTML não possui a relação necessária para funcionar corretamente.",
"note_detail_render_help_2": "O tipo de nota Renderizar HTML é usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">automação</a>. Em suma, tem uma nota de código HTML (opcionalmente com algum JavaScript) e esta nota irá renderizá-la. Para fazê-lo funcionar, deve definir uma <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">relação</a> chamada \"renderNote\" que aponta para a nota HTML a ser renderizada."
},
"web_view": {
"web_view": "Web View",
"embed_websites": "Nota do tipo Visualização Web permite que incorpore sites no Trilium.",
"create_label": "Para começar, crie uma etiqueta com um endereço URL que deseja incorporar, por exemplo, #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Recarregar"
},

View File

@@ -1271,11 +1271,6 @@
"start_dragging_relations": "Comece arrastando as relações daqui e solte-as em outra nota.",
"cannot_match_transform": "Não foi possível combinar a transformação: {{transform}}"
},
"web_view": {
"web_view": "Web View",
"embed_websites": "Nota do tipo Visualização Web permite que você incorpore sites dentro do Trilium.",
"create_label": "Para começar, crie uma etiqueta com um endereço URL que deseja incorporar, por exemplo, #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Recarregar"
},

View File

@@ -1376,11 +1376,6 @@
"enable_vim_keybindings": "Permite utilizarea combinațiilor de taste în stil Vim pentru notițele de tip cod (fără modul ex)",
"use_vim_keybindings_in_code_notes": "Combinații de taste Vim"
},
"web_view": {
"create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"",
"embed_websites": "Notițele de tip „Vizualizare web” permit încorporarea site-urilor web în Trilium.",
"web_view": "Vizualizare web"
},
"wrap_lines": {
"enable_line_wrap": "Activează trecerea automată pe rândul următor (poate necesita o reîncărcare a interfeței pentru a avea efect)",
"wrap_lines_in_code_notes": "Trecerea automată pe rândul următor în notițe de cod"

View File

@@ -668,7 +668,8 @@
"geo-map": {
"unable-to-load-map": "Не удалось загрузить карту.",
"create-child-note-instruction": "Щелкните по карте, чтобы создать новую заметку в этом месте, или нажмите Escape, чтобы закрыть ее.",
"create-child-note-title": "Создать новую дочернюю заметку и добавить ее на карту"
"create-child-note-title": "Создать новую дочернюю заметку и добавить ее на карту",
"create-child-note-text": "Добавить маркер"
},
"note_tooltip": {
"quick-edit": "Быстрое редактирование",
@@ -685,8 +686,8 @@
"electron_integration": {
"zoom-factor": "Коэффициент масштабирования",
"restart-app-button": "Применить изменения и перезапустить приложение",
"background-effects-description": "Эффект Mica добавляет размытый, стильный фон окнам приложений, создавая глубину и современный вид. Опция \"Системная строка заголовка\" должна быть отключена.",
"background-effects": "Включить фоновые эффекты (только Windows 11)",
"background-effects-description": "Добавляет размытый, стильный фон окнам приложений, создавая глубину и современный вид. Опция \"Системная строка заголовка\" должна быть отключена.",
"background-effects": "Включить фоновые эффекты",
"native-title-bar-description": "В Windows и macOS отключение системной строки заголовка делает приложение более компактным. В Linux включение системной строки заголовка улучшает интеграцию с остальной частью системы.",
"native-title-bar": "Системная панель заголовка",
"desktop-application": "Десктопное приложение"
@@ -776,7 +777,11 @@
"refresh-saved-search-results": "Обновить сохраненные результаты поиска",
"automatically-collapse-notes-title": "Заметки будут свернуты после определенного периода бездействия, чтобы навести порядок в дереве.",
"toggle-sidebar": "Переключить боковую панель",
"dropping-not-allowed": "Перетаскивание заметок в эту область не разрешено."
"dropping-not-allowed": "Перетаскивание заметок в эту область не разрешено.",
"shared-indicator-tooltip": "Эта заметка опубликована",
"shared-indicator-tooltip-with-url": "Эта заметка доступно публично по адресу: {{- url}}",
"subtree-hidden-moved-description-other": "В дереве, к которому относится эта заметка, скрыты дочерние заметки.",
"subtree-hidden-moved-description-collection": "Эта коллекция скрывает свои дочерние заметки в дереве."
},
"quick-search": {
"no-results": "Результаты не найдены",
@@ -856,7 +861,10 @@
"convert-to-attachment-confirm": "Вы уверены, что хотите преобразовать выбранные заметки во вложения их родительских заметок? Эта операция применяется только к заметкам в виде изображений; другие заметки будут пропущены.",
"converted-to-attachments": "{{count}} заметок были преобразованы во вложения.",
"archive": "Архивировать",
"unarchive": "Разархивировать"
"unarchive": "Разархивировать",
"open-in-a-new-window": "Открыть в новом окне",
"hide-subtree": "Скрыть поддерево",
"show-subtree": "Показать поддерево"
},
"info": {
"closeButton": "Закрыть",
@@ -1000,7 +1008,8 @@
"switch_to_mobile_version": "Перейти на мобильную версию",
"switch_to_desktop_version": "Переключиться на версию для ПК",
"new-version-available": "Доступно обновление",
"download-update": "Обновить до {{latestVersion}}"
"download-update": "Обновить до {{latestVersion}}",
"search_notes": "Поиск заметок"
},
"zpetne_odkazy": {
"relation": "отношение",
@@ -1047,7 +1056,8 @@
"expand_all_levels": "Развернуть все вложенные уровни",
"expand_nth_level": "Развернуть уровни: {{depth}} шт.",
"expand_first_level": "Развернуть прямые дочерние уровни",
"expand_tooltip": "Разщвернуть дочерние элементы этой коллекции (на один уровень вложенности). Для получения дополнительных параметров нажмите стрелку справа."
"expand_tooltip": "Разщвернуть дочерние элементы этой коллекции (на один уровень вложенности). Для получения дополнительных параметров нажмите стрелку справа.",
"hide_child_notes": "Скрыть дочерние заметки в дереве"
},
"edited_notes": {
"deleted": "(удалено)",
@@ -1692,7 +1702,7 @@
"zoom_in_title": "Увеличить масштаб",
"zoom_out_title": "Уменьшить масштаб",
"reset_pan_zoom_title": "Сбросить панорамирование и масштабирование",
"create_child_note_title": "Создать новую дочернюю заметку и добавить ее в эту карту связей"
"create_child_note_title": "Создать дочернюю заметку и добавить ее в карту"
},
"code_auto_read_only_size": {
"unit": "символов",
@@ -1845,7 +1855,8 @@
"error_cannot_get_branch_id": "Невозможно получить branchId для notePath '{{notePath}}'",
"delete_this_note": "Удалить эту заметку",
"insert_child_note": "Вставить дочернюю заметку",
"note_revisions": "История изменений"
"note_revisions": "История изменений",
"content_language_switcher": "Язык содержимого: {{language}}"
},
"svg_export_button": {
"button_title": "Экспортировать диаграмму как SVG"
@@ -1900,7 +1911,7 @@
"dismiss": "Отклонить",
"background_effects_button": "Включить эффекты фона",
"next_theme_button": "Попробовать новую тему",
"background_effects_message": "На устройствах Windows фоновые эффекты теперь полностью стабильны. Они добавляют цвет в пользовательский интерфейс, размывая фон за ним. Этот приём также используется в других приложениях, например, в проводнике Windows.",
"background_effects_message": "На устройствах с ОС Windows или macOS, фоновые эффекты теперь полностью стабильны. Они добавляют цвета в пользовательский интерфейс, размывая фон за ним.",
"background_effects_title": "Фоновые эффекты теперь стабильны",
"next_theme_title": "Попробуйте новую тему Trilium",
"new_layout_button": "Подробнее",
@@ -1988,11 +1999,6 @@
"attachment_deleted": "Это вложение было удалено.",
"you_can_also_open": ", вы также можете открыть "
},
"web_view": {
"web_view": "Веб-страница",
"create_label": "Для начала создайте метку с URL-адресом, который вы хотите встроить, например, #webViewSrc=\"https://www.google.com\"",
"embed_websites": "Заметки типа \"Веб-страница\" позволяет встраивать веб-сайты в Trilium."
},
"ribbon": {
"widgets": "Виджеты ленты",
"promoted_attributes_message": "Вкладка \"Продвигаемые атрибуты\" будет автоматически открыта, если таковые атрибуты установлены у заметки",
@@ -2094,7 +2100,11 @@
"ui": "Пользовательский интерфейс"
},
"sql_result": {
"no_rows": "По этому запросу не возвращено ни одной строки"
"no_rows": "По этому запросу не возвращено ни одной строки",
"not_executed": "Запрос еще не выполнен.",
"failed": "Выполнение SQL-запроса завершилось с ошибкой",
"statement_result": "Результат заявления",
"execute_now": "Выполнить сейчас"
},
"editable_code": {
"placeholder": "Введите содержимое для заметки с кодом..."
@@ -2189,7 +2199,14 @@
"read_only_auto_description": "Эта заметка была автоматически переведена в режим только для чтения по соображениям производительности. Это автоматическое ограничение можно изменить в настройках.\n\nНажмите, чтобы временно отредактировать её.",
"read_only_auto": "Автоматический режим \"только для чтения\"",
"read_only_explicit_description": "Эта заметка была вручную установлена в режим «только для чтения».\nНажмите, чтобы временно отредактировать её.",
"read_only_explicit": "Только для чтения"
"read_only_explicit": "Только для чтения",
"save_status_saving": "Сохранение...",
"save_status_saved": "Сохранение",
"save_status_unsaved": "Не сохранено",
"save_status_error": "Ошибка сохранения",
"save_status_saving_tooltip": "Изменения сохраняются.",
"save_status_unsaved_tooltip": "Есть несохраненные изменения. Они будут сохранены автоматически через некоторое время.",
"save_status_error_tooltip": "Произошла ошибка при сохранении заметки. Если возможно, попробуйте скопировать содержимое заметки в другое место и перезагрузить приложение."
},
"breadcrumb": {
"hoisted_badge_title": "Снять фокус",
@@ -2243,5 +2260,30 @@
},
"attributes_panel": {
"title": "Атрибуты заметки"
},
"bookmark_buttons": {
"bookmarks": "Закладки"
},
"mobile_tab_switcher": {
"more_options": "Показать больше",
"title_one": "{{count}} вкладка",
"title_few": "{{count}} вкладки",
"title_many": "{{count}} вкладок"
},
"pdf": {
"pages_loading": "Загрузка...",
"pages_alt": "Страница {{pageNumber}}",
"pages_one": "{{count}} страница",
"pages_few": "{{count}} страницы",
"pages_many": "{{count}} страниц",
"layers_one": "{{count}} слой",
"layers_few": "{{count}} слоя",
"layers_many": "{{count}} слоев",
"attachments_one": "{{count}} вложение",
"attachments_few": "{{count}} вложения",
"attachments_many": "{{count}} вложений"
},
"platform_indicator": {
"available_on": "Доступно для {{platform}}"
}
}

View File

@@ -662,7 +662,8 @@
"show-cheatsheet": "顯示快捷鍵說明",
"toggle-zen-mode": "禪模式",
"new-version-available": "發現新更新",
"download-update": "取得版本 {{latestVersion}}"
"download-update": "取得版本 {{latestVersion}}",
"search_notes": "搜尋筆記"
},
"sync_status": {
"unknown": "<p>同步狀態將在下一次同步嘗試開始後顯示。</p><p>點擊以立即觸發同步。</p>",
@@ -758,7 +759,8 @@
"error_cannot_get_branch_id": "無法獲取 notePath '{{notePath}}' 的 branchId",
"error_unrecognized_command": "無法識別的命令 {{command}}",
"note_revisions": "筆記歷史版本",
"backlinks": "反向連結"
"backlinks": "反向連結",
"content_language_switcher": "內文語言:{{language}}"
},
"note_icon": {
"change_note_icon": "更改筆記圖標",
@@ -910,7 +912,8 @@
"unknown_search_option": "未知的搜尋選項 {{searchOptionName}}",
"search_note_saved": "搜尋筆記已儲存至 {{- notePathTitle}}",
"actions_executed": "已執行操作。",
"view_options": "查看選項:"
"view_options": "查看選項:",
"option": "選項"
},
"similar_notes": {
"title": "相似筆記",
@@ -1064,11 +1067,6 @@
"note_detail_render_help_1": "之所以顯示此說明筆記,是因為該類型的渲染 HTML 沒有設定好必須的關聯。",
"note_detail_render_help_2": "渲染筆記類型用於編寫 <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">腳本</a>。簡單說就是您可以寫HTML程式碼或者加上一些JavaScript程式碼 然後這個筆記會把頁面渲染出來。要使其正常工作,您需要定義一個名為 \"renderNote\" 的 <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">關聯</a> 指向要呈現的 HTML 筆記。"
},
"web_view": {
"web_view": "網頁顯示",
"embed_websites": "網頁顯示類型的筆記允許您將網站嵌入至 Trilium 中。",
"create_label": "首先,請新增一個帶有您要嵌入的 URL 地址的標籤,例如 #webViewSrc=\"https://www.bing.com\""
},
"backend_log": {
"refresh": "重新整理"
},
@@ -2272,9 +2270,13 @@
},
"mobile_tab_switcher": {
"more_options": "更多選項",
"title_one": "{{count}} 個分頁"
"title_one": "{{count}} 個分頁",
"title_other": ""
},
"platform_indicator": {
"available_on": "可於 {{platform}} 使用"
},
"bookmark_buttons": {
"bookmarks": "書籤"
}
}

View File

@@ -1134,11 +1134,6 @@
"note_detail_render_help_1": "Ця довідка відображається, оскільки ця нотатка типу Render HTML не має необхідного зв'язку для належного функціонування.",
"note_detail_render_help_2": "Тип нотатки Render HTML використовується для <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">скриптів</a>. Коротше кажучи, у вас є нотатка з HTML-кодом (за бажанням з деяким JavaScript), і ця нотатка її відобразить. Щоб це запрацювало, вам потрібно визначити <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">відношення</a> під назвою \"renderNote\", яке вказує на нотатку HTML для відображення."
},
"web_view": {
"web_view": "Веб-перегляд",
"embed_websites": "Нотатка типу Веб-перегляд дозволяє вбудовувати веб-сайти в Trilium.",
"create_label": "Для початку створіть мітку з URL-адресою, яку ви хочете вбудувати, наприклад, #webViewSrc=\"https://www.google.com\""
},
"backend_log": {
"refresh": "Оновити"
},

View File

@@ -370,7 +370,33 @@ function showToast(type: "printing" | "exporting_pdf", progress: number = 0) {
}
function handlePrintReport(printReport?: PrintReport) {
if (printReport?.type === "collection" && printReport.ignoredNoteIds.length > 0) {
if (!printReport) return;
if (printReport.type === "error") {
toast.showPersistent({
id: "print-error",
icon: "bx bx-error-circle",
title: t("note_detail.print_report_error_title"),
message: printReport.message,
buttons: printReport.stack ? [
{
text: t("note_detail.print_report_collection_details_button"),
onClick(api) {
api.dismissToast();
dialog.info(<>
<p>{printReport.message}</p>
<details>
<summary>{t("note_detail.print_report_stack_trace")}</summary>
<pre style="font-size: 0.85em; overflow-x: auto;">{printReport.stack}</pre>
</details>
</>, {
title: t("note_detail.print_report_error_title")
});
}
}
] : undefined
});
} else if (printReport.type === "collection" && printReport.ignoredNoteIds.length > 0) {
toast.showPersistent({
id: "print-report",
icon: "bx bx-collection",

View File

@@ -16,6 +16,10 @@ body.mobile .promoted-attributes-widget {
display: table;
}
body.experimental-feature-new-layout .promoted-attributes-container {
max-height: unset;
}
.promoted-attribute-cell {
display: flex;
align-items: center;
@@ -94,4 +98,4 @@ body.mobile .promoted-attributes-widget {
background: rgba(0, 0, 0, 0.5);
transform: rotate(45deg);
pointer-events: none;
}
}

View File

@@ -19,14 +19,3 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
.note-list-widget video {
height: 100%;
}
/* #region Pagination */
.note-list-pager {
font-size: 1rem;
span.current-page {
text-decoration: underline;
font-weight: bold;
}
}
/* #endregion */

View File

@@ -4,6 +4,8 @@ import FNote from "../../entities/fnote";
import froca from "../../services/froca";
import { useNoteLabelInt } from "../react/hooks";
import { t } from "../../services/i18n";
import ActionButton from "../react/ActionButton";
import Button from "../react/Button";
interface PaginationContext {
page: number;
@@ -17,46 +19,79 @@ interface PaginationContext {
export function Pager({ page, pageSize, setPage, pageCount, totalNotes }: Omit<PaginationContext, "pageNotes">) {
if (pageCount < 2) return;
let lastPrinted = false;
let children: ComponentChildren[] = [];
for (let i = 1; i <= pageCount; i++) {
if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(page - i) <= 2) {
lastPrinted = true;
const startIndex = (i - 1) * pageSize + 1;
const endIndex = Math.min(totalNotes, i * pageSize);
if (i !== page) {
children.push((
<a
href="javascript:"
title={t("pagination.page_title", { startIndex, endIndex })}
onClick={() => setPage(i)}
>
{i}
</a>
))
} else {
// Current page
children.push(<span className="current-page">{i}</span>)
}
children.push(<>{" "}&nbsp;{" "}</>);
} else if (lastPrinted) {
children.push(<>{"... "}&nbsp;{" "}</>);
lastPrinted = false;
}
}
const children = createPageButtons(page, setPage, pageCount);
return (
<div class="note-list-pager">
{children}
<ActionButton
icon="bx bx-caret-left"
disabled={(page === 1)}
text={t("pagination.prev_page")}
onClick={() => {setPage(page - 1)}}
/>
{children}
<ActionButton
icon="bx bx-caret-right"
disabled={(page === pageCount)}
text={t("pagination.next_page")}
onClick={() => {setPage(page + 1)}}
/>
<span className="note-list-pager-total-count">({t("pagination.total_notes", { count: totalNotes })})</span>
</div>
)
}
function createPageButtons(page: number, setPage: Dispatch<StateUpdater<number>>, pageCount: number): ComponentChildren[] {
const maxButtonCount = 9;
const maxLeftRightSegmentLength = 2;
// The left-side segment
const leftLength = Math.min(pageCount, maxLeftRightSegmentLength);
const leftStart = 1;
// The middle segment
const middleMaxLength = maxButtonCount - maxLeftRightSegmentLength * 2;
const middleLength = Math.min(pageCount - leftLength, middleMaxLength);
let middleStart = page - Math.floor(middleLength / 2);
middleStart = Math.max(middleStart, leftLength + 1);
// The right-side segment
const rightLength = Math.min(pageCount - (middleLength + leftLength), maxLeftRightSegmentLength);
const rightStart = pageCount - rightLength + 1;
middleStart = Math.min(middleStart, rightStart - middleLength);
return [
...createSegment(leftStart, leftLength, page, setPage, false),
...createSegment(middleStart, middleLength, page, setPage, (middleStart - leftLength > 1)),
...createSegment(rightStart, rightLength, page, setPage, (rightStart - (middleStart + middleLength - 1) > 1)),
];
}
function createSegment(start: number, length: number, currentPage: number, setPage: Dispatch<StateUpdater<number>>, prependEllipsis: boolean): ComponentChildren[] {
const children: ComponentChildren[] = [];
if (prependEllipsis) {
children.push("...");
}
for (let i = 0; i < length; i++) {
const pageNum = start + i;
children.push((
<Button
text={pageNum.toString()}
disabled={(pageNum === currentPage)}
onClick={() => setPage(pageNum)}
/>
));
}
return children;
}
export function usePagination(note: FNote, noteIds: string[]): PaginationContext {
const [ page, setPage ] = useState(1);
const [ pageNotes, setPageNotes ] = useState<FNote[]>();

View File

@@ -7,10 +7,14 @@
> .collection-properties {
position: relative;
z-index: 2000;
z-index: 998;
}
}
body.mobile .geo-view > .collection-properties {
z-index: 2500;
}
.geo-map-container {
height: 100%;
overflow: hidden;
@@ -22,7 +26,7 @@
.leaflet-top,
.leaflet-bottom {
z-index: 997;
z-index: 997 !important;
}
.geo-view.placing-note .geo-map-container {

View File

@@ -22,7 +22,7 @@ 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 { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer";
import Marker, { GpxTrack } from "./marker";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
@@ -45,10 +45,11 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
const [ state, setState ] = useState(State.Normal);
const [ coordinates, setCoordinates ] = useState(viewConfig?.view?.center);
const [ zoom, setZoom ] = useState(viewConfig?.view?.zoom);
const [ layerName ] = useNoteLabel(note, "map:style");
const [ hasScale ] = useNoteLabelBoolean(note, "map:scale");
const [ hideLabels ] = useNoteLabelBoolean(note, "map:hideLabels");
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
const [ notes, setNotes ] = useState<FNote[]>([]);
const layerData = useLayerData(note);
const spacedUpdate = useSpacedUpdate(() => {
if (viewConfig) {
saveConfig(viewConfig);
@@ -152,7 +153,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
apiRef={apiRef} containerRef={containerRef}
coordinates={coordinates}
zoom={zoom}
layerName={layerName ?? DEFAULT_MAP_LAYER_NAME}
layerData={layerData}
viewportChanged={(coordinates, zoom) => {
if (!viewConfig) viewConfig = {};
viewConfig.view = { center: coordinates, zoom };
@@ -162,13 +163,35 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM
onContextMenu={onContextMenu}
scale={hasScale}
>
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} />)}
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)}
</Map>}
<GeoMapTouchBar state={state} map={apiRef.current} />
</div>
);
}
function useLayerData(note: FNote) {
const [ layerName ] = useNoteLabel(note, "map:style");
// Memo is needed because it would generate unnecessary reloads due to layer change.
const layerData = useMemo(() => {
// Custom layers.
if (layerName?.startsWith("http")) {
return {
name: "Custom",
type: "raster",
url: layerName,
attribution: ""
} satisfies MapLayer;
}
// Built-in layers.
const layerData = MAP_LAYERS[layerName ?? ""] ?? MAP_LAYERS[DEFAULT_MAP_LAYER_NAME];
return layerData;
}, [ layerName ]);
return layerData;
}
function ToggleReadOnlyButton({ note }: { note: FNote }) {
const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly");
@@ -179,22 +202,26 @@ function ToggleReadOnlyButton({ note }: { note: FNote }) {
/>;
}
function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) {
function NoteWrapper({ note, isReadOnly, hideLabels }: {
note: FNote,
isReadOnly: boolean,
hideLabels: boolean
}) {
const mime = useNoteProperty(note, "mime");
const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE);
if (mime === "application/gpx+xml") {
return <NoteGpxTrack note={note} />;
return <NoteGpxTrack note={note} hideLabels={hideLabels} />;
}
if (location) {
const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined;
if (!latLng) return;
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} />;
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} hideLabels={hideLabels} />;
}
}
function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean, latLng: [number, number] }) {
function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, editable: boolean, latLng: [number, number], hideLabels: boolean }) {
// React to changes
const [ color ] = useNoteLabel(note, "color");
const [ iconClass ] = useNoteLabel(note, "iconClass");
@@ -202,8 +229,9 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
const title = useNoteProperty(note, "title");
const icon = useMemo(() => {
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived]);
const titleOrNone = hideLabels ? undefined : title;
return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived);
}, [ iconClass, color, title, note.noteId, archived, hideLabels ]);
const onClick = useCallback(() => {
appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId });
@@ -235,7 +263,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean
/>;
}
function NoteGpxTrack({ note }: { note: FNote }) {
function NoteGpxTrack({ note, hideLabels }: { note: FNote, hideLabels?: boolean }) {
const [ xmlString, setXmlString ] = useState<string>();
const blob = useNoteBlob(note);
@@ -256,7 +284,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
const options = useMemo<GPXOptions>(() => ({
markers: {
startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title),
startIcon: buildIcon(note.getIcon(), note.getColorClass(), hideLabels ? undefined : note.title),
endIcon: buildIcon("bxs-flag-checkered"),
wptIcons: {
"": buildIcon("bx bx-pin")
@@ -265,7 +293,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
polyline_options: {
color: note.getLabelValue("color") ?? "blue"
}
}), [ color, iconClass ]);
}), [ color, iconClass, hideLabels ]);
return xmlString && <GpxTrack gpxXmlString={xmlString} options={options} />;
}

View File

@@ -1,7 +1,7 @@
import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks";
import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet";
import "leaflet/dist/leaflet.css";
import { MAP_LAYERS } from "./map_layer";
import { MAP_LAYERS, type MapLayer } from "./map_layer";
import { ComponentChildren, createContext, RefObject } from "preact";
import { useElementSize, useSyncedRef } from "../../react/hooks";
@@ -12,7 +12,7 @@ interface MapProps {
containerRef?: RefObject<HTMLDivElement>;
coordinates: LatLng | [number, number];
zoom: number;
layerName: string;
layerData: MapLayer;
viewportChanged: (coordinates: LatLng, zoom: number) => void;
children: ComponentChildren;
onClick?: (e: LeafletMouseEvent) => void;
@@ -21,7 +21,7 @@ interface MapProps {
scale: boolean;
}
export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
export default function Map({ coordinates, zoom, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) {
const mapRef = useRef<L.Map>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
@@ -49,8 +49,6 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
const [ layer, setLayer ] = useState<Layer>();
useEffect(() => {
async function load() {
const layerData = MAP_LAYERS[layerName];
if (layerData.type === "vector") {
const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style());
await import("@maplibre/maplibre-gl-leaflet");
@@ -68,7 +66,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
}
load();
}, [ layerName ]);
}, [ layerData ]);
// Attach layer to the map.
useEffect(() => {
@@ -139,7 +137,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
return (
<div
ref={containerRef}
className={`geo-map-container ${MAP_LAYERS[layerName].isDarkTheme ? "dark" : ""}`}
className={`geo-map-container ${layerData.isDarkTheme ? "dark" : ""}`}
>
<ParentMap.Provider value={mapRef.current}>
{children}

View File

@@ -1,20 +1,17 @@
export interface MapLayer {
name: string;
isDarkTheme?: boolean;
}
interface VectorLayer extends MapLayer {
export type MapLayer = ({
type: "vector";
style: string | (() => Promise<{}>)
}
interface RasterLayer extends MapLayer {
} | {
type: "raster";
url: string;
attribution: string;
}
}) & {
// Common properties
name: string;
isDarkTheme?: boolean;
};
export const MAP_LAYERS: Record<string, VectorLayer | RasterLayer> = {
export const MAP_LAYERS: Record<string, MapLayer> = {
"openstreetmap": {
name: "OpenStreetMap",
type: "raster",

View File

@@ -100,23 +100,206 @@
overflow: auto;
}
.note-expander {
font-size: x-large;
position: relative;
top: 3px;
cursor: pointer;
/* #region List view */
@keyframes note-preview-show {
from {
opacity: 0;
} to {
opacity: 1;
}
}
.note-list-pager {
text-align: center;
.nested-note-list {
--card-nested-section-indent: 25px;
&.search-results {
--card-nested-section-indent: 32px;
}
}
.note-list.list-view .note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
/* List item */
.nested-note-list-item {
h5 {
display: flex;
align-items: center;
font-size: 1em;
font-weight: normal;
margin: 0;
}
.note-expander {
margin-inline-end: 4px;
font-size: x-large;
cursor: pointer;
}
.tn-icon {
margin-inline-end: 8px;
color: var(--note-list-view-icon-color);
font-size: 1.2em;
}
.note-book-title {
--link-hover-background: transparent;
--link-hover-color: currentColor;
color: inherit;
font-weight: normal;
}
.note-path {
margin-left: 0.5em;
vertical-align: middle;
opacity: 0.5;
}
.note-list-attributes {
flex-grow: 1;
margin-inline-start: 1em;
text-align: right;
font-size: .75em;
opacity: .75;
}
.nested-note-list-item-menu {
margin-inline-start: 8px;
flex-shrink: 0;
}
&.archived {
span.tn-icon + span,
.tn-icon {
opacity: .6;
}
}
&.use-note-color {
span.tn-icon + span,
.nested-note-list:not(.search-results) & .tn-icon,
.rendered-note-attributes {
color: var(--custom-color);
}
}
}
.nested-note-list:not(.search-results) h5 {
span.tn-icon + span,
.note-list-attributes {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
/* List item (search results view) */
.nested-note-list.search-results .nested-note-list-item {
span.tn-icon + span > span {
display: flex;
flex-direction: column-reverse;
align-items: flex-start;
}
small {
line-height: .85em;
}
.note-path {
margin-left: 0;
font-size: .85em;
line-height: .85em;
font-weight: 500;
letter-spacing: .5pt;
}
.tn-icon {
display: flex;
flex-shrink: 0;
justify-content: center;
align-items: center;
width: 1.75em;
height: 1.75em;
margin-inline-end: 12px;
border-radius: 50%;
background: var(--note-icon-custom-background-color, var(--note-list-view-large-icon-background));
font-size: 1.2em;
color: var(--note-icon-custom-color, var(--note-list-view-large-icon-color));
}
h5 .ck-find-result {
background: var(--note-list-view-search-result-highlight-background);
color: var(--note-list-view-search-result-highlight-color);
font-weight: 600;
text-decoration: underline;
}
}
/* Note content preview */
.nested-note-list .note-book-content {
display: none;
outline: 1px solid var(--note-list-view-content-background);
border-radius: 8px;
background-color: var(--note-list-view-content-background);
overflow: hidden;
user-select: text;
font-size: .85rem;
animation: note-preview-show .25s ease-out;
will-change: opacity;
&.note-book-content-ready {
display: block;
}
> .rendered-content > *:last-child {
margin-bottom: 0;
}
&.type-text {
padding: 8px 24px;
.ck-content > *:last-child {
margin-bottom: 0;
}
}
&.type-protectedSession {
padding: 20px;
}
&.type-image {
padding: 0;
}
&.type-pdf {
iframe {
height: 50vh;
}
.file-footer {
padding: 8px;
}
}
&.type-webView {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 50vh;
}
.ck-find-result {
outline: 2px solid var(--note-list-view-content-search-result-highlight-background);
border-radius: 4px;
background: var(--note-list-view-content-search-result-highlight-background);
color: var(--note-list-view-content-search-result-highlight-color);
}
}
.note-content-preview:has(.note-book-content:empty) {
display: none;
}
/* #endregion */
/* #region Grid view */
.note-list.grid-view .note-list-container {
display: flex;
@@ -128,6 +311,10 @@
border: 1px solid transparent;
}
body.mobile .note-list.grid-view .note-book-card {
flex-basis: 150px;
}
.note-list.grid-view .note-book-card {
max-height: 300px;
}

View File

@@ -1,4 +1,5 @@
import "./ListOrGridView.css";
import { Card, CardSection } from "../../react/Card";
import { useEffect, useRef, useState } from "preact/hooks";
@@ -14,6 +15,11 @@ import NoteLink from "../../react/NoteLink";
import { ViewModeProps } from "../interface";
import { Pager, usePagination } from "../Pagination";
import { filterChildNotes, useFilteredNoteIds } from "./utils";
import { JSX } from "preact/jsx-runtime";
import { clsx } from "clsx";
import ActionButton from "../../react/ActionButton";
import linkContextMenuService from "../../../menus/link_context_menu";
import { TargetedMouseEvent } from "preact";
export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }: ViewModeProps<{}>) {
const expandDepth = useExpansionDepth(note);
@@ -33,7 +39,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
{ noteIds.length > 0 && <div class="note-list-wrapper">
{!hasCollectionProperties && <Pager {...pagination} />}
<div class="note-list-container use-tn-links">
<Card className={clsx("nested-note-list", {"search-results": (noteType === "search")})}>
{pageNotes?.map(childNote => (
<ListNoteCard
key={childNote.noteId}
@@ -41,7 +47,7 @@ export function ListView({ note, noteIds: unfilteredNoteIds, highlightedTokens }
expandDepth={expandDepth} highlightedTokens={highlightedTokens}
currentLevel={1} includeArchived={includeArchived} />
))}
</div>
</Card>
<Pager {...pagination} />
</div>}
@@ -93,27 +99,52 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan
// Reset expand state if switching to another note, or if user manually toggled expansion state.
useEffect(() => setExpanded(currentLevel <= expandDepth), [ note, currentLevel, expandDepth ]);
let subSections: JSX.Element | undefined = undefined;
if (isExpanded) {
subSections = <>
<CardSection className="note-content-preview">
<NoteContent note={note}
highlightedTokens={highlightedTokens}
noChildrenList
includeArchivedNotes={includeArchived} />
</CardSection>
<NoteChildren note={note}
parentNote={parentNote}
highlightedTokens={highlightedTokens}
currentLevel={currentLevel}
expandDepth={expandDepth}
includeArchived={includeArchived} />
</>
}
return (
<div
className={`note-book-card no-tooltip-preview ${isExpanded ? "expanded" : ""} ${note.isArchived ? "archived" : ""}`}
<CardSection
className={clsx("nested-note-list-item", "no-tooltip-preview", note.getColorClass(), {
"expanded": isExpanded,
"archived": note.isArchived
})}
subSections={subSections}
subSectionsVisible={isExpanded}
highlightOnHover
data-note-id={note.noteId}
>
<h5 className="note-book-header">
<span
className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}
/>
<h5>
<span className={`note-expander ${isExpanded ? "bx bx-chevron-down" : "bx bx-chevron-right"}`}
onClick={() => setExpanded(!isExpanded)}/>
<Icon className="note-icon" icon={note.getIcon()} />
<NoteLink className="note-book-title" notePath={notePath} noPreview showNotePath={parentNote.type === "search"} highlightedTokens={highlightedTokens} />
<NoteLink className="note-book-title"
notePath={notePath}
noPreview
showNotePath={parentNote.type === "search"}
highlightedTokens={highlightedTokens} />
<NoteAttributes note={note} />
<ActionButton className="nested-note-list-item-menu"
icon="bx bx-dots-vertical-rounded" text=""
onClick={(e) => openNoteMenu(notePath, e)}
/>
</h5>
{isExpanded && <>
<NoteContent note={note} highlightedTokens={highlightedTokens} noChildrenList includeArchivedNotes={includeArchived} />
<NoteChildren note={note} parentNote={parentNote} highlightedTokens={highlightedTokens} currentLevel={currentLevel} expandDepth={expandDepth} includeArchived={includeArchived} />
</>}
</div>
</CardSection>
);
}
@@ -165,6 +196,9 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
const contentRef = useRef<HTMLDivElement>(null);
const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens);
const [ready, setReady] = useState(false);
const [noteType, setNoteType] = useState<string>("none");
useEffect(() => {
content_renderer.getRenderedContent(note, {
trim,
@@ -179,17 +213,19 @@ export function NoteContent({ note, trim, noChildrenList, highlightedTokens, inc
} else {
contentRef.current.replaceChildren();
}
contentRef.current.classList.add(`type-${type}`);
highlightSearch(contentRef.current);
setNoteType(type);
setReady(true);
})
.catch(e => {
console.warn(`Caught error while rendering note '${note.noteId}' of type '${note.type}'`);
console.error(e);
contentRef.current?.replaceChildren(t("collections.rendering_error"));
setReady(true);
});
}, [ note, highlightedTokens ]);
return <div ref={contentRef} className="note-book-content" />;
return <div ref={contentRef} className={clsx("note-book-content", `type-${noteType}`, {"note-book-content-ready": ready})} />;
}
function NoteChildren({ note, parentNote, highlightedTokens, currentLevel, expandDepth, includeArchived }: {
@@ -238,3 +274,8 @@ function useExpansionDepth(note: FNote) {
return parseInt(expandDepth, 10);
}
function openNoteMenu(notePath, e: TargetedMouseEvent<HTMLElement>) {
linkContextMenuService.openContextMenu(notePath, e);
e.stopPropagation()
}

View File

@@ -19,6 +19,10 @@
}
}
body.mobile .scrolling-container {
--content-margin-inline: 8px;
}
.note-split.type-code:not(.mime-text-x-sqlite) {
&> .scrolling-container {
background-color: var(--code-background-color);

View File

@@ -32,6 +32,12 @@ body.mobile .modal.popup-editor-dialog .modal-dialog {
display: flex;
align-items: center;
margin-block: 0;
.note-icon-widget {
display: flex;
align-items: center;
margin-inline-start: 0;
}
}
.modal.popup-editor-dialog .modal-header .note-title-widget {

View File

@@ -67,10 +67,7 @@ export default function PopupEditor() {
<NoteContextContext.Provider value={noteContext}>
<DialogWrapper>
<Modal
title={<>
<TitleRow />
{isNewLayout && <NoteBadges />}
</>}
title={<TitleRow />}
customTitleBarButtons={[{
iconClassName: "bx-expand-alt",
title: t("popup-editor.maximize"),
@@ -123,6 +120,7 @@ export function TitleRow() {
<div className="title-row">
<NoteIcon />
<NoteTitleWidget />
{isNewLayout && <NoteBadges />}
</div>
);
}

View File

@@ -0,0 +1,282 @@
import { BUILTIN_ATTRIBUTES } from "@triliumnext/commons";
import clsx from "clsx";
import { useEffect, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import { openInAppHelpFromUrl } from "../../services/utils";
import { BadgeWithDropdown } from "../react/Badge";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import FormToggle from "../react/FormToggle";
import { useNoteContext, useTriliumEvent } from "../react/hooks";
import { BookProperty, ViewProperty } from "../react/NotePropertyMenu";
const NON_DANGEROUS_ACTIVE_CONTENT = [ "appCss", "appTheme" ];
const DANGEROUS_ATTRIBUTES = BUILTIN_ATTRIBUTES.filter(a => a.isDangerous || NON_DANGEROUS_ACTIVE_CONTENT.includes(a.name));
const activeContentLabels = [ "iconPack", "widget", "appCss", "appTheme" ] as const;
interface ActiveContentInfo {
type: "iconPack" | "backendScript" | "frontendScript" | "widget" | "appCss" | "renderNote" | "webView" | "appTheme";
isEnabled: boolean;
canToggleEnabled: boolean;
}
const executeOption: BookProperty = {
type: "button",
icon: "bx bx-play",
label: t("active_content_badges.menu_execute_now"),
onClick: context => context.triggerCommand("runActiveNote")
};
const typeMappings: Record<ActiveContentInfo["type"], {
title: string;
icon: string;
helpPage: string;
apiDocsPage?: string;
isExecutable?: boolean;
additionalOptions?: BookProperty[];
}> = {
iconPack: {
title: t("active_content_badges.type_icon_pack"),
icon: "bx bx-package",
helpPage: "g1mlRoU8CsqC",
},
backendScript: {
title: t("active_content_badges.type_backend_script"),
icon: "bx bx-server",
helpPage: "SPirpZypehBG",
apiDocsPage: "MEtfsqa5VwNi",
isExecutable: true,
additionalOptions: [
executeOption,
{
type: "combobox",
bindToLabel: "run",
label: t("active_content_badges.menu_run"),
icon: "bx bx-rss",
dropStart: true,
options: [
{ value: null, label: t("active_content_badges.menu_run_disabled") },
{ value: "backendStartup", label: t("active_content_badges.menu_run_backend_startup") },
{ value: "daily", label: t("active_content_badges.menu_run_daily") },
{ value: "hourly", label: t("active_content_badges.menu_run_hourly") }
]
}
]
},
frontendScript: {
title: t("active_content_badges.type_frontend_script"),
icon: "bx bx-window",
helpPage: "yIhgI5H7A2Sm",
apiDocsPage: "Q2z6av6JZVWm",
isExecutable: true,
additionalOptions: [
executeOption,
{
type: "combobox",
bindToLabel: "run",
label: t("active_content_badges.menu_run"),
icon: "bx bx-rss",
dropStart: true,
options: [
{ value: null, label: t("active_content_badges.menu_run_disabled") },
{ value: "frontendStartup", label: t("active_content_badges.menu_run_frontend_startup") },
{ value: "mobileStartup", label: t("active_content_badges.menu_run_mobile_startup") },
]
},
{ type: "separator" },
{
type: "button",
label: t("active_content_badges.menu_change_to_widget"),
icon: "bx bxs-widget",
onClick: ({ note }) => attributes.setLabel(note.noteId, "widget")
}
]
},
widget: {
title: t("active_content_badges.type_widget"),
icon: "bx bxs-widget",
helpPage: "MgibgPcfeuGz",
additionalOptions: [
{
type: "button",
label: t("active_content_badges.menu_change_to_frontend_script"),
icon: "bx bx-window",
onClick: ({ note }) => {
attributes.removeOwnedLabelByName(note, "widget");
attributes.removeOwnedLabelByName(note, "disabled:widget");
}
}
]
},
appCss: {
title: t("active_content_badges.type_app_css"),
icon: "bx bxs-file-css",
helpPage: "AlhDUqhENtH7"
},
renderNote: {
title: t("active_content_badges.type_render_note"),
icon: "bx bx-extension",
helpPage: "HcABDtFCkbFN"
},
webView: {
title: t("active_content_badges.type_web_view"),
icon: "bx bx-globe",
helpPage: "1vHRoWCEjj0L"
},
appTheme: {
title :t("active_content_badges.type_app_theme"),
icon: "bx bx-palette",
helpPage: "7NfNr5pZpVKV",
additionalOptions: [
{
type: "combobox",
bindToLabel: "appThemeBase",
label: t("active_content_badges.menu_theme_base"),
icon: "bx bx-layer",
dropStart: true,
options: [
{ label: t("theme.auto_theme"), value: null },
{ type: "separator" },
{ label: t("theme.triliumnext"), value: "next" },
{ label: t("theme.triliumnext-light"), value: "next-light" },
{ label: t("theme.triliumnext-dark"), value: "next-dark" }
]
}
]
}
};
export function ActiveContentBadges() {
const { note } = useNoteContext();
const info = useActiveContentInfo(note);
return (note && info &&
<>
{info.canToggleEnabled && <ActiveContentToggle info={info} note={note} />}
<ActiveContentBadge info={info} note={note} />
</>
);
}
function ActiveContentBadge({ info, note }: { note: FNote, info: ActiveContentInfo }) {
const { title, icon, helpPage, apiDocsPage, additionalOptions } = typeMappings[info.type];
return (
<BadgeWithDropdown
className={clsx("active-content-badge", info.canToggleEnabled && !info.isEnabled && "disabled")}
icon={icon}
text={title}
dropdownOptions={{
dropdownContainerClassName: "mobile-bottom-menu",
mobileBackdrop: true
}}
>
{additionalOptions?.length && (
<>
{additionalOptions?.map((property, i) => (
<ViewProperty key={i} note={note} property={property} />
))}
<FormDropdownDivider />
</>
)}
<FormListItem
icon="bx bx-help-circle"
onClick={() => openInAppHelpFromUrl(helpPage)}
>{t("active_content_badges.menu_docs")}</FormListItem>
{apiDocsPage && <FormListItem
icon="bx bx-book-content"
onClick={() => openInAppHelpFromUrl(apiDocsPage)}
>{t("code_buttons.trilium_api_docs_button_title")}</FormListItem>}
</BadgeWithDropdown>
);
}
function ActiveContentToggle({ note, info }: { note: FNote, info: ActiveContentInfo }) {
const { title } = typeMappings[info.type];
return info && <FormToggle
switchOnName="" switchOffName=""
currentValue={info.isEnabled}
switchOffTooltip={t("active_content_badges.toggle_tooltip_disable_tooltip", { type: title })}
switchOnTooltip={t("active_content_badges.toggle_tooltip_enable_tooltip", { type: title })}
onChange={async (willEnable) => {
await Promise.all(note.getOwnedAttributes()
.map(attr => ({ name: attributes.getNameWithoutDangerousPrefix(attr.name), type: attr.type }))
.filter(({ name, type }) => DANGEROUS_ATTRIBUTES.some(item => item.name === name && item.type === type))
.map(({ name, type }) => attributes.toggleDangerousAttribute(note, type, name, willEnable)));
}}
/>;
}
function useActiveContentInfo(note: FNote | null | undefined) {
const [ info, setInfo ] = useState<ActiveContentInfo | null>(null);
function refresh() {
let type: ActiveContentInfo["type"] | null = null;
let isEnabled = false;
let canToggleEnabled = false;
if (!note) {
setInfo(null);
return;
}
if (note.type === "render") {
type = "renderNote";
isEnabled = note.hasRelation("renderNote");
} else if (note.type === "webView") {
type = "webView";
isEnabled = note.hasLabel("webViewSrc");
} else if (note.type === "code" && note.mime === "application/javascript;env=backend") {
type = "backendScript";
for (const backendLabel of [ "run", "customRequestHandler", "customResourceProvider" ]) {
isEnabled ||= note.hasLabel(backendLabel);
if (!canToggleEnabled && note.hasLabelOrDisabled(backendLabel)) {
canToggleEnabled = true;
}
}
} else if (note.type === "code" && note.mime === "application/javascript;env=frontend") {
type = "frontendScript";
isEnabled = note.hasLabel("widget") || note.hasLabel("run");
canToggleEnabled = note.hasLabelOrDisabled("widget") || note.hasLabelOrDisabled("run");
} else if (note.type === "code" && note.hasLabelOrDisabled("appTheme")) {
isEnabled = note.hasLabel("appTheme");
canToggleEnabled = true;
}
for (const labelToCheck of activeContentLabels) {
if (note.hasLabel(labelToCheck)) {
type = labelToCheck;
isEnabled = true;
canToggleEnabled = true;
break;
} else if (note.hasLabel(`disabled:${labelToCheck}`)) {
type = labelToCheck;
isEnabled = false;
canToggleEnabled = true;
break;
}
}
if (type) {
setInfo({ type, isEnabled, canToggleEnabled });
} else {
setInfo(null);
}
}
// Refresh on note change.
useEffect(refresh, [ note ]);
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
refresh();
}
});
return info;
}

View File

@@ -37,6 +37,10 @@
pointer-events: none;
}
}
&.active-content-badge { --color: var(--badge-active-content-background-color); }
&.active-content-badge.disabled {
opacity: 0.5;
}
min-width: 0;
@@ -45,6 +49,11 @@
text-overflow: ellipsis;
min-width: 0;
}
.switch-button {
--switch-track-height: 8px;
--switch-track-width: 30px;
}
}
.dropdown-badge {

View File

@@ -10,6 +10,7 @@ import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useGetContextData, useIsNoteReadOnly, useNoteContext, useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { useShareState } from "../ribbon/BasicPropertiesTab";
import { useShareInfo } from "../shared_info";
import { ActiveContentBadges } from "./ActiveContentBadges";
export default function NoteBadges() {
return (
@@ -19,6 +20,7 @@ export default function NoteBadges() {
<ShareBadge />
<ClippedNoteBadge />
<ExecuteBadge />
<ActiveContentBadges />
</div>
);
}

View File

@@ -37,6 +37,10 @@
&:hover {
background: var(--input-background-color);
}
.text {
white-space: nowrap;
}
}
.status-bar-dropdown-button {

View File

@@ -35,11 +35,6 @@
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);

View File

@@ -2,18 +2,16 @@ import "./CollectionProperties.css";
import { t } from "i18next";
import { ComponentChildren } from "preact";
import { useContext, useRef } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
import { useRef } from "preact/hooks";
import FNote from "../../entities/fnote";
import { ViewTypeOptions } from "../collections/interface";
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "../react/FormList";
import FormTextBox from "../react/FormTextBox";
import { useNoteLabel, useNoteLabelBoolean, useNoteLabelWithDefault, useNoteProperty, useTriliumEvent } from "../react/hooks";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { 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 { CheckBoxProperty, ViewProperty } from "../react/NotePropertyMenu";
import { bookPropertiesConfig } from "../ribbon/collection-properties-config";
import { useViewType, VIEW_TYPE_MAPPINGS } from "../ribbon/CollectionPropertiesTab";
export const ICON_MAPPINGS: Record<ViewTypeOptions, string> = {
@@ -85,9 +83,11 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption
<Dropdown
buttonClassName="bx bx-cog icon-action"
hideToggleArrow
dropdownContainerClassName="mobile-bottom-menu"
mobileBackdrop
>
{properties.map(property => (
<ViewProperty key={property.label} note={note} property={property} />
{properties.map((property, index) => (
<ViewProperty key={index} note={note} property={property} />
))}
{properties.length > 0 && <FormDropdownDivider />}
@@ -107,127 +107,3 @@ function ViewOptions({ note, viewType }: { note: FNote, viewType: ViewTypeOption
</Dropdown>
);
}
function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) {
switch (property.type) {
case "button":
return <ButtonPropertyView note={note} property={property} />;
case "split-button":
return <SplitButtonPropertyView note={note} property={property} />;
case "checkbox":
return <CheckBoxPropertyView note={note} property={property} />;
case "number":
return <NumberPropertyView note={note} property={property} />;
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />;
}
}
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
const parentComponent = useContext(ParentComponent);
return (
<FormListItem
icon={property.icon}
title={property.title}
onClick={() => {
if (!parentComponent) return;
property.onClick({
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
});
}}
>{property.label}</FormListItem>
);
}
function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) {
const parentComponent = useContext(ParentComponent);
const ItemsComponent = property.items;
const clickContext = parentComponent && {
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
};
return (parentComponent &&
<FormDropdownSubmenu
icon={property.icon ?? "bx bx-empty"}
title={property.label}
onDropdownToggleClicked={() => clickContext && property.onClick(clickContext)}
>
<ItemsComponent note={note} parentComponent={parentComponent} />
</FormDropdownSubmenu>
);
}
function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) {
//@ts-expect-error Interop with text box which takes in string values even for numbers.
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
const disabled = property.disabled?.(note);
return (
<FormListItem
icon={property.icon}
disabled={disabled}
onClick={(e) => e.stopPropagation()}
>
{property.label}
<FormTextBox
type="number"
currentValue={value ?? ""} onChange={setValue}
style={{ width: (property.width ?? 100) }}
min={property.min ?? 0}
disabled={disabled}
/>
</FormListItem>
);
}
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
const [ value, setValue ] = useNoteLabelWithDefault(note, property.bindToLabel, property.defaultValue ?? "");
function renderItem(option: ComboBoxItem) {
return (
<FormListItem
key={option.value}
checked={value === option.value}
onClick={() => setValue(option.value)}
>
{option.label}
</FormListItem>
);
}
return (
<FormDropdownSubmenu
title={property.label}
icon={property.icon ?? "bx bx-empty"}
>
{(property.options).map((option, index) => {
if ("items" in option) {
return (
<Fragment key={option.title}>
<FormListItem key={option.title} disabled>{option.title}</FormListItem>
{option.items.map(renderItem)}
{index < property.options.length - 1 && <FormDropdownDivider />}
</Fragment>
);
}
return renderItem(option);
})}
</FormDropdownSubmenu>
);
}
function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
return (
<FormListToggleableItem
icon={property.icon}
title={property.label}
currentValue={value}
onChange={setValue}
/>
);
}

View File

@@ -9,7 +9,6 @@
border-radius: 0.5em;
font-size: 0.7rem;
font-weight: normal;
float: right;
vertical-align: middle;
}

View File

@@ -1992,7 +1992,7 @@ function buildEnhanceTitle() {
if (isSubtreeHidden && count > 0) {
const $badge = $(`<span class="note-indicator-icon subtree-hidden-badge">${count}</span>`);
$badge.attr("title", t("note_tree.subtree-hidden-tooltip", { count }));
$span.find(".fancytree-title").append($badge);
$span.append($badge);
}
};
}

View File

@@ -43,7 +43,8 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
},
protectedSession: {
view: () => import("./type_widgets/ProtectedSession"),
className: "protected-session-password-component"
className: "protected-session-password-component",
isFullHeight: true
},
book: {
view: () => import("./type_widgets/Book"),

View File

@@ -0,0 +1,47 @@
:where(.tn-card) {
--card-border-radius: 8px;
--card-padding-block: 8px;
--card-padding-inline: 16px;
--card-section-gap: 3px;
--card-nested-section-indent: 30px;
}
.tn-card-heading {
margin-bottom: 10px;
font-size: .75rem;
font-weight: 600;
letter-spacing: .4pt;
text-transform: uppercase;
}
.tn-card-body {
display: flex;
flex-direction: column;
gap: var(--card-section-gap);
.tn-card-section {
padding: var(--card-padding-block) var(--card-padding-inline);
border: 1px solid var(--card-border-color, var(--main-border-color));
background: var(--card-background-color);
&:first-of-type {
border-top-left-radius: var(--card-border-radius);
border-top-right-radius: var(--card-border-radius);
}
&:last-of-type {
border-bottom-left-radius: var(--card-border-radius);
border-bottom-right-radius: var(--card-border-radius);
}
&.tn-card-section-nested {
padding-left: calc(var(--card-padding-inline) + var(--card-nested-section-indent) * var(--tn-card-section-nesting-level));
background-color: color-mix(in srgb, var(--card-background-color) calc(100% / (var(--tn-card-section-nesting-level) + 1)) , transparent);
}
&.tn-card-section-highlight-on-hover:hover {
background-color: var(--card-background-hover-color);
transition: background-color .2s ease-out;
}
}
}

View File

@@ -0,0 +1,63 @@
import "./Card.css";
import { ComponentChildren, createContext } from "preact";
import { JSX } from "preact";
import { useContext } from "preact/hooks";
import clsx from "clsx";
// #region Card
export interface CardProps {
className?: string;
heading?: string;
}
export function Card(props: {children: ComponentChildren} & CardProps) {
return <div className={clsx("tn-card", props.className)}>
{props.heading && <h5 class="tn-card-heading">{props.heading}</h5>}
<div className="tn-card-body">
{props.children}
</div>
</div>;
}
// #endregion
// #region Card Section
export interface CardSectionProps {
className?: string;
subSections?: JSX.Element | JSX.Element[];
subSectionsVisible?: boolean;
highlightOnHover?: boolean;
onAction?: () => void;
}
interface CardSectionContextType {
nestingLevel: number;
}
const CardSectionContext = createContext<CardSectionContextType | undefined>(undefined);
export function CardSection(props: {children: ComponentChildren} & CardSectionProps) {
const parentContext = useContext(CardSectionContext);
const nestingLevel = (parentContext && parentContext.nestingLevel + 1) ?? 0;
return <>
<section className={clsx("tn-card-section", props.className, {
"tn-card-section-nested": nestingLevel > 0,
"tn-card-section-highlight-on-hover": props.highlightOnHover || props.onAction
})}
style={{"--tn-card-section-nesting-level": (nestingLevel) ? nestingLevel : null}}
onClick={props.onAction}>
{props.children}
</section>
{props.subSectionsVisible && props.subSections &&
<CardSectionContext.Provider value={{nestingLevel}}>
{props.subSections}
</CardSectionContext.Provider>
}
</>;
}
// #endregion

View File

@@ -3,10 +3,7 @@
line-height: 1em;
display: flex;
align-items: center;
appearance: none;
background: transparent;
border: 0;
color: inherit;
padding-inline-end: 12px;
.arrow {
font-size: 1.3em;

View File

@@ -57,7 +57,7 @@ export function ExternallyControlledCollapsible({ title, children, className, ex
"with-transition": transitionEnabled
})}>
<button
className="collapsible-title"
className="collapsible-title tn-low-profile"
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
aria-controls={contentId}

View File

@@ -0,0 +1,212 @@
import { FilterLabelsByType } from "@triliumnext/commons";
import { Fragment, VNode } from "preact";
import { useContext } from "preact/hooks";
import Component from "../../components/component";
import FNote from "../../entities/fnote";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem, FormListToggleableItem } from "./FormList";
import FormTextBox from "./FormTextBox";
import { useNoteLabel, useNoteLabelBoolean } from "./hooks";
import { ParentComponent } from "./react_utils";
export interface ClickContext {
note: FNote;
triggerCommand: NoteContextAwareWidget["triggerCommand"];
}
export interface CheckBoxProperty {
type: "checkbox",
label: string;
bindToLabel: FilterLabelsByType<boolean>;
icon?: string;
/** When true, the checkbox will be checked when the label value is false. Useful when the label represents a "hide" action, without exposing double negatives to the user. */
reverseValue?: boolean;
}
export interface ButtonProperty {
type: "button",
label: string;
title?: string;
icon?: string;
onClick(context: ClickContext): void;
}
export interface SplitButtonProperty extends Omit<ButtonProperty, "type"> {
type: "split-button";
items({ note, parentComponent }: { note: FNote, parentComponent: Component }): VNode;
}
export interface NumberProperty {
type: "number",
label: string;
bindToLabel: FilterLabelsByType<number>;
width?: number;
min?: number;
icon?: string;
disabled?: (note: FNote) => boolean;
}
export interface ComboBoxItem {
/**
* The value to set to the bound label, `null` has a special meaning which removes the label entirely.
*/
value: string | null;
label: string;
}
export interface ComboBoxGroup {
title: string;
items: ComboBoxItem[];
}
interface Separator {
type: "separator"
}
export interface ComboBoxProperty {
type: "combobox",
label: string;
icon?: string;
bindToLabel: FilterLabelsByType<string>;
/**
* The default value is used when the label is not set.
*/
defaultValue?: string;
options: (ComboBoxItem | Separator | ComboBoxGroup)[];
dropStart?: boolean;
}
export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty | ComboBoxProperty | SplitButtonProperty | Separator;
export function ViewProperty({ note, property }: { note: FNote, property: BookProperty }) {
switch (property.type) {
case "button":
return <ButtonPropertyView note={note} property={property} />;
case "split-button":
return <SplitButtonPropertyView note={note} property={property} />;
case "checkbox":
return <CheckBoxPropertyView note={note} property={property} />;
case "number":
return <NumberPropertyView note={note} property={property} />;
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />;
case "separator":
return <FormDropdownDivider />;
}
}
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
const parentComponent = useContext(ParentComponent);
return (
<FormListItem
icon={property.icon}
title={property.title}
onClick={() => {
if (!parentComponent) return;
property.onClick({
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
});
}}
>{property.label}</FormListItem>
);
}
function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) {
const parentComponent = useContext(ParentComponent);
const ItemsComponent = property.items;
const clickContext = parentComponent && {
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
};
return (parentComponent &&
<FormDropdownSubmenu
icon={property.icon ?? "bx bx-empty"}
title={property.label}
onDropdownToggleClicked={() => clickContext && property.onClick(clickContext)}
>
<ItemsComponent note={note} parentComponent={parentComponent} />
</FormDropdownSubmenu>
);
}
function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) {
//@ts-expect-error Interop with text box which takes in string values even for numbers.
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
const disabled = property.disabled?.(note);
return (
<FormListItem
icon={property.icon}
disabled={disabled}
onClick={(e) => e.stopPropagation()}
>
{property.label}
<FormTextBox
type="number"
currentValue={value ?? ""} onChange={setValue}
style={{ width: (property.width ?? 100) }}
min={property.min ?? 0}
disabled={disabled}
/>
</FormListItem>
);
}
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
const [ value, setValue ] = useNoteLabel(note, property.bindToLabel);
const valueWithDefault = value ?? property.defaultValue ?? null;
function renderItem(option: ComboBoxItem) {
return (
<FormListItem
key={option.value}
checked={valueWithDefault === option.value}
onClick={() => setValue(option.value)}
>
{option.label}
</FormListItem>
);
}
return (
<FormDropdownSubmenu
title={property.label}
icon={property.icon ?? "bx bx-empty"}
dropStart={property.dropStart}
>
{(property.options).map((option, index) => {
if ("items" in option) {
return (
<Fragment key={option.title}>
<FormListItem key={option.title} disabled>{option.title}</FormListItem>
{option.items.map(renderItem)}
{index < property.options.length - 1 && <FormDropdownDivider />}
</Fragment>
);
}
if ("type" in option) {
return <FormDropdownDivider key={index} />;
}
return renderItem(option);
})}
</FormDropdownSubmenu>
);
}
function CheckBoxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
return (
<FormListToggleableItem
icon={property.icon}
title={property.label}
currentValue={ property.reverseValue ? !value : value }
onChange={newValue => setValue(property.reverseValue ? !newValue : newValue)}
/>
);
}

View File

@@ -551,7 +551,12 @@ export function useNoteRelation(note: FNote | undefined | null, relationName: Re
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
for (const attr of loadResults.getAttributeRows()) {
if (attr.type === "relation" && attr.name === relationName && attributes.isAffecting(attr, note)) {
setRelationValue(attr.value ?? null);
if (!attr.isDeleted) {
setRelationValue(attr.value ?? null);
} else {
setRelationValue(null);
}
break;
}
}
});
@@ -601,6 +606,7 @@ export function useNoteLabel(note: FNote | undefined | null, labelName: FilterLa
} else {
setLabelValue(null);
}
break;
}
}
});

View File

@@ -1,18 +1,20 @@
import { useContext, useMemo } from "preact/hooks";
import { t } from "../../services/i18n";
import FormSelect, { FormSelectWithGroups } from "../react/FormSelect";
import { TabContext } from "./ribbon-interface";
import { mapToKeyValueArray } from "../../services/utils";
import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { bookPropertiesConfig, BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "./collection-properties-config";
import Button, { SplitButton } from "../react/Button";
import { ParentComponent } from "../react/react_utils";
import FNote from "../../entities/fnote";
import FormCheckbox from "../react/FormCheckbox";
import FormTextBox from "../react/FormTextBox";
import { ComponentChildren } from "preact";
import { ViewTypeOptions } from "../collections/interface";
import { useContext, useMemo } from "preact/hooks";
import FNote from "../../entities/fnote";
import { isExperimentalFeatureEnabled } from "../../services/experimental_features";
import { t } from "../../services/i18n";
import { mapToKeyValueArray } from "../../services/utils";
import { ViewTypeOptions } from "../collections/interface";
import Button, { SplitButton } from "../react/Button";
import FormCheckbox from "../react/FormCheckbox";
import FormSelect, { FormSelectWithGroups } from "../react/FormSelect";
import FormTextBox from "../react/FormTextBox";
import { useNoteLabel, useNoteLabelBoolean } from "../react/hooks";
import { BookProperty, ButtonProperty, CheckBoxProperty, ComboBoxGroup, ComboBoxItem, ComboBoxProperty, NumberProperty, SplitButtonProperty } from "../react/NotePropertyMenu";
import { ParentComponent } from "../react/react_utils";
import { bookPropertiesConfig } from "./collection-properties-config";
import { TabContext } from "./ribbon-interface";
export const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: t("book_properties.grid"),
@@ -50,70 +52,70 @@ export function useViewType(note: FNote | null | undefined) {
}
function CollectionTypeSwitcher({ viewType, setViewType }: { viewType: string, setViewType: (newValue: string) => void }) {
const collectionTypes = useMemo(() => mapToKeyValueArray(VIEW_TYPE_MAPPINGS), []);
const collectionTypes = useMemo(() => mapToKeyValueArray(VIEW_TYPE_MAPPINGS), []);
return (
<div style={{ display: "flex", alignItems: "baseline" }}>
<span style={{ whiteSpace: "nowrap" }}>{t("book_properties.view_type")}:&nbsp; &nbsp;</span>
<FormSelect
currentValue={viewType ?? "grid"} onChange={setViewType}
values={collectionTypes}
keyProperty="key" titleProperty="value"
/>
</div>
)
return (
<div style={{ display: "flex", alignItems: "baseline" }}>
<span style={{ whiteSpace: "nowrap" }}>{t("book_properties.view_type")}:&nbsp; &nbsp;</span>
<FormSelect
currentValue={viewType ?? "grid"} onChange={setViewType}
values={collectionTypes}
keyProperty="key" titleProperty="value"
/>
</div>
);
}
function BookProperties({ viewType, note, properties }: { viewType: ViewTypeOptions, note: FNote, properties: BookProperty[] }) {
return (
<>
{properties.map(property => (
<div className={`type-${property}`}>
{mapPropertyView({ note, property })}
</div>
))}
function BookProperties({ note, properties }: { viewType: ViewTypeOptions, note: FNote, properties: BookProperty[] }) {
return (
<>
{properties.map((property, index) => (
<div key={index} className={`type-${property}`}>
{mapPropertyView({ note, property })}
</div>
))}
<CheckboxPropertyView
note={note} property={{
bindToLabel: "includeArchived",
label: t("book_properties.include_archived_notes"),
type: "checkbox"
}}
/>
</>
)
<CheckboxPropertyView
note={note} property={{
bindToLabel: "includeArchived",
label: t("book_properties.include_archived_notes"),
type: "checkbox"
}}
/>
</>
);
}
function mapPropertyView({ note, property }: { note: FNote, property: BookProperty }) {
switch (property.type) {
case "button":
return <ButtonPropertyView note={note} property={property} />
case "split-button":
return <SplitButtonPropertyView note={note} property={property} />
case "checkbox":
return <CheckboxPropertyView note={note} property={property} />
case "number":
return <NumberPropertyView note={note} property={property} />
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />
}
switch (property.type) {
case "button":
return <ButtonPropertyView note={note} property={property} />;
case "split-button":
return <SplitButtonPropertyView note={note} property={property} />;
case "checkbox":
return <CheckboxPropertyView note={note} property={property} />;
case "number":
return <NumberPropertyView note={note} property={property} />;
case "combobox":
return <ComboBoxPropertyView note={note} property={property} />;
}
}
function ButtonPropertyView({ note, property }: { note: FNote, property: ButtonProperty }) {
const parentComponent = useContext(ParentComponent);
const parentComponent = useContext(ParentComponent);
return <Button
text={property.label}
title={property.title}
icon={property.icon}
onClick={() => {
if (!parentComponent) return;
property.onClick({
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
});
}}
/>
return <Button
text={property.label}
title={property.title}
icon={property.icon}
onClick={() => {
if (!parentComponent) return;
property.onClick({
note,
triggerCommand: parentComponent.triggerCommand.bind(parentComponent)
});
}}
/>;
}
function SplitButtonPropertyView({ note, property }: { note: FNote, property: SplitButtonProperty }) {
@@ -131,18 +133,18 @@ function SplitButtonPropertyView({ note, property }: { note: FNote, property: Sp
onClick={() => clickContext && property.onClick(clickContext)}
>
{parentComponent && <ItemsComponent note={note} parentComponent={parentComponent} />}
</SplitButton>
</SplitButton>;
}
function CheckboxPropertyView({ note, property }: { note: FNote, property: CheckBoxProperty }) {
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
const [ value, setValue ] = useNoteLabelBoolean(note, property.bindToLabel);
return (
<FormCheckbox
label={property.label}
currentValue={value} onChange={setValue}
/>
)
return (
<FormCheckbox
label={property.label}
currentValue={value} onChange={setValue}
/>
);
}
function NumberPropertyView({ note, property }: { note: FNote, property: NumberProperty }) {
@@ -160,7 +162,7 @@ function NumberPropertyView({ note, property }: { note: FNote, property: NumberP
disabled={disabled}
/>
</LabelledEntry>
)
);
}
function ComboBoxPropertyView({ note, property }: { note: FNote, property: ComboBoxProperty }) {
@@ -169,12 +171,12 @@ function ComboBoxPropertyView({ note, property }: { note: FNote, property: Combo
return (
<LabelledEntry label={property.label}>
<FormSelectWithGroups
values={property.options}
values={property.options.filter(i => !("type" in i)) as (ComboBoxItem | ComboBoxGroup)[]}
keyProperty="value" titleProperty="label"
currentValue={value ?? property.defaultValue} onChange={setValue}
/>
</LabelledEntry>
)
);
}
function LabelledEntry({ label, children }: { label: string, children: ComponentChildren }) {
@@ -186,5 +188,5 @@ function LabelledEntry({ label, children }: { label: string, children: Component
{children}
</label>
</>
)
);
}

View File

@@ -70,7 +70,6 @@ export default function NoteActionsCustom(props: NoteActionsCustomProps) {
>
<AddChildButton {...innerProps} />
<RunActiveNoteButton {...innerProps } />
<OpenTriliumApiDocsButton {...innerProps} />
<SwitchSplitOrientationButton {...innerProps} />
<ToggleReadOnlyButton {...innerProps} />
<SaveToNoteButton {...innerProps} />
@@ -230,15 +229,6 @@ function SaveToNoteButton({ note, noteMime }: NoteActionsCustomInnerProps) {
/>;
}
function OpenTriliumApiDocsButton({ noteMime }: NoteActionsCustomInnerProps) {
const isEnabled = noteMime.startsWith("application/javascript;env=");
return isEnabled && <NoteAction
icon="bx bx-help-circle"
text={t("code_buttons.trilium_api_docs_button_title")}
onClick={() => openInAppHelpFromUrl(noteMime.endsWith("frontend") ? "Q2z6av6JZVWm" : "MEtfsqa5VwNi")}
/>;
}
function InAppHelpButton({ note }: NoteActionsCustomInnerProps) {
const helpUrl = getHelpUrlForNote(note);
const isEnabled = !!helpUrl;

View File

@@ -1,4 +1,5 @@
import { AttributeType } from "@triliumnext/commons";
import clsx from "clsx";
import { ComponentChildren, VNode } from "preact";
import { useEffect, useMemo, useRef } from "preact/hooks";
@@ -7,6 +8,7 @@ import FNote from "../../entities/fnote";
import { removeOwnedAttributesByNameOrType } from "../../services/attributes";
import { t } from "../../services/i18n";
import server from "../../services/server";
import Admonition from "../react/Admonition";
import FormSelect from "../react/FormSelect";
import FormTextArea from "../react/FormTextArea";
import FormTextBox from "../react/FormTextBox";
@@ -105,8 +107,9 @@ export const SEARCH_OPTIONS: SearchOption[] = [
}
];
function SearchOption({ note, title, titleIcon, children, help, attributeName, attributeType, additionalAttributesToDelete }: {
function SearchOption({ note, className, title, titleIcon, children, help, attributeName, attributeType, additionalAttributesToDelete }: {
note: FNote;
className?: string;
title: string,
titleIcon?: string,
children?: ComponentChildren,
@@ -116,7 +119,7 @@ function SearchOption({ note, title, titleIcon, children, help, attributeName, a
additionalAttributesToDelete?: { type: "label" | "relation", name: string }[]
}) {
return (
<tr className={attributeName}>
<tr className={clsx(attributeName, className)}>
<td className="title-column">
{titleIcon && <><Icon icon={titleIcon} />{" "}</>}
{title}
@@ -154,64 +157,57 @@ function SearchStringOption({ note, refreshResults, error, ...restProps }: Searc
}
}, 1000);
// React to errors
const { showTooltip, hideTooltip } = useTooltip(inputRef, {
trigger: "manual",
title: `${t("search_string.error", { error: error?.message })}`,
html: true,
placement: "bottom"
});
// Auto-focus.
useEffect(() => inputRef.current?.focus(), []);
useEffect(() => {
if (error) {
showTooltip();
setTimeout(() => hideTooltip(), 4000);
} else {
hideTooltip();
}
}, [ error ]);
return <>
<SearchOption
title={t("search_string.title_column")}
className={clsx({ "has-error": !!error })}
help={<>
<strong>{t("search_string.search_syntax")}</strong> - {t("search_string.also_see")} <a href="#" data-help-page="search.html">{t("search_string.complete_help")}</a>
<ul style="marigin-bottom: 0;">
<li>{t("search_string.full_text_search")}</li>
<li><code>#abc</code> - {t("search_string.label_abc")}</li>
<li><code>#year = 2019</code> - {t("search_string.label_year")}</li>
<li><code>#rock #pop</code> - {t("search_string.label_rock_pop")}</li>
<li><code>#rock or #pop</code> - {t("search_string.label_rock_or_pop")}</li>
<li><code>#year &lt;= 2000</code> - {t("search_string.label_year_comparison")}</li>
<li><code>note.dateCreated &gt;= MONTH-1</code> - {t("search_string.label_date_created")}</li>
</ul>
</>}
note={note} {...restProps}
>
<FormTextArea
inputRef={inputRef}
className="search-string"
placeholder={t("search_string.placeholder")}
currentValue={searchString ?? ""}
onChange={text => {
currentValue.current = text;
spacedUpdate.scheduleUpdate();
}}
onKeyDown={async (e) => {
if (e.key === "Enter") {
e.preventDefault();
return <SearchOption
title={t("search_string.title_column")}
help={<>
<strong>{t("search_string.search_syntax")}</strong> - {t("search_string.also_see")} <a href="#" data-help-page="search.html">{t("search_string.complete_help")}</a>
<ul style="marigin-bottom: 0;">
<li>{t("search_string.full_text_search")}</li>
<li><code>#abc</code> - {t("search_string.label_abc")}</li>
<li><code>#year = 2019</code> - {t("search_string.label_year")}</li>
<li><code>#rock #pop</code> - {t("search_string.label_rock_pop")}</li>
<li><code>#rock or #pop</code> - {t("search_string.label_rock_or_pop")}</li>
<li><code>#year &lt;= 2000</code> - {t("search_string.label_year_comparison")}</li>
<li><code>note.dateCreated &gt;= MONTH-1</code> - {t("search_string.label_date_created")}</li>
</ul>
</>}
note={note} {...restProps}
>
<FormTextArea
inputRef={inputRef}
className="search-string"
placeholder={t("search_string.placeholder")}
currentValue={searchString ?? ""}
onChange={text => {
currentValue.current = text;
spacedUpdate.scheduleUpdate();
}}
onKeyDown={async (e) => {
if (e.key === "Enter") {
e.preventDefault();
// this also in effect disallows new lines in query string.
// on one hand, this makes sense since search string is a label
// on the other hand, it could be nice for structuring long search string. It's probably a niche case though.
await spacedUpdate.updateNowIfNecessary();
refreshResults();
}
}}
/>
</SearchOption>;
// this also in effect disallows new lines in query string.
// on one hand, this makes sense since search string is a label
// on the other hand, it could be nice for structuring long search string. It's probably a niche case though.
await spacedUpdate.updateNowIfNecessary();
refreshResults();
}
}}
/>
</SearchOption>
{error?.message && (
<tr>
<td colspan={3}>
<Admonition type="caution">{error.message}</Admonition>
</td>
</tr>
)}
</>;
}
function SearchScriptOption({ note, ...restProps }: SearchOptionProps) {

View File

@@ -4,6 +4,12 @@
width: 100%;
border-collapse: separate;
border-spacing: 10px;
.admonition {
margin-top: 0.25em;
margin-bottom: 1em;
text-wrap: wrap;
}
}
.search-setting-table div {
@@ -141,20 +147,26 @@ body.mobile .search-definition-widget {
gap: 0.5em;
}
.search-setting-table tr.searchString td:nth-of-type(2) {
flex-grow: 1;
}
.search-setting-table tr.searchString {
td:nth-of-type(2) {
flex-grow: 1;
}
.search-setting-table tr.searchString .button-column {
flex-grow: 0;
flex-shrink: 0;
width: 64px;
.button-column {
flex-grow: 0;
flex-shrink: 0;
width: 64px;
}
&.has-error {
border-bottom: 0;
}
}
.search-setting-table tr.ancestor > td > div {
flex-direction: column;
align-items: flex-start !important;
}
}
.search-actions tr {
border-bottom: 0;
@@ -171,4 +183,4 @@ body.mobile .search-definition-widget {
overflow: unset;
height: unset !important;
}
}
}

View File

@@ -0,0 +1,4 @@
.similar-notes-widget > .similar-notes-wrapper {
/* The font size of the links with the highest similarity score */
font-size: 17px;
}

View File

@@ -1,3 +1,5 @@
import "./SimilarNotesTab.css";
import { SimilarNoteResponse } from "@triliumnext/commons";
import { useEffect, useState } from "preact/hooks";
@@ -33,7 +35,7 @@ export default function SimilarNotesTab({ note }: Pick<TabContext, "note">) {
notePath={notePath}
noTnLink
style={{
"font-size": 20 * (1 - 1 / (1 + score))
"font-size": (1 - 1 / (1 + score)) + "em"
}}
/>
))}

View File

@@ -1,79 +1,19 @@
import { t } from "i18next";
import Component from "../../components/component";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../collections/geomap/map_layer";
import { ViewTypeOptions } from "../collections/interface";
import { FilterLabelsByType } from "@triliumnext/commons";
import { DEFAULT_THEME, getPresentationThemes } from "../collections/presentation/themes";
import { VNode } from "preact";
import { useNoteLabel } from "../react/hooks";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import Component from "../../components/component";
import { useNoteLabel } from "../react/hooks";
import { BookProperty, ClickContext, ComboBoxItem } from "../react/NotePropertyMenu";
interface BookConfig {
properties: BookProperty[];
}
export interface CheckBoxProperty {
type: "checkbox",
label: string;
bindToLabel: FilterLabelsByType<boolean>;
icon?: string;
}
export interface ButtonProperty {
type: "button",
label: string;
title?: string;
icon?: string;
onClick(context: BookContext): void;
}
export interface SplitButtonProperty extends Omit<ButtonProperty, "type"> {
type: "split-button";
items({ note, parentComponent }: { note: FNote, parentComponent: Component }): VNode;
}
export interface NumberProperty {
type: "number",
label: string;
bindToLabel: FilterLabelsByType<number>;
width?: number;
min?: number;
icon?: string;
disabled?: (note: FNote) => boolean;
}
export interface ComboBoxItem {
value: string;
label: string;
}
interface ComboBoxGroup {
title: string;
items: ComboBoxItem[];
}
export interface ComboBoxProperty {
type: "combobox",
label: string;
icon?: string;
bindToLabel: FilterLabelsByType<string>;
/**
* The default value is used when the label is not set.
*/
defaultValue?: string;
options: (ComboBoxItem | ComboBoxGroup)[];
}
export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty | ComboBoxProperty | SplitButtonProperty;
interface BookContext {
note: FNote;
triggerCommand: NoteContextAwareWidget["triggerCommand"];
}
export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
grid: {
properties: []
@@ -156,6 +96,13 @@ export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
icon: "bx bx-ruler",
type: "checkbox",
bindToLabel: "map:scale"
},
{
label: t("book_properties_config.show-labels"),
icon: "bx bx-label",
type: "checkbox",
bindToLabel: "map:hideLabels",
reverseValue: true
}
]
},
@@ -211,7 +158,7 @@ function ListExpandDepth(context: { note: FNote, parentComponent: Component }) {
<FormDropdownDivider />
<ListExpandDepthButton label={t("book_properties.expand_all_levels")} depth="all" checked={currentDepth === "all"} {...context} />
</>
)
);
}
function ListExpandDepthButton({ label, depth, note, parentComponent, checked }: { label: string, depth: number | "all", note: FNote, parentComponent: Component, checked?: boolean }) {
@@ -226,7 +173,7 @@ function ListExpandDepthButton({ label, depth, note, parentComponent, checked }:
}
function buildExpandListHandler(depth: number | "all") {
return async ({ note, triggerCommand }: BookContext) => {
return async ({ note, triggerCommand }: ClickContext) => {
const { noteId } = note;
const existingValue = note.getLabelValue("expanded");
@@ -236,5 +183,5 @@ function buildExpandListHandler(depth: number | "all") {
await attributes.setLabel(noteId, "expanded", newValue);
triggerCommand("refreshNoteList", { noteId });
}
};
}

View File

@@ -6,7 +6,7 @@
.attachment-list .links-wrapper {
font-size: larger;
margin-bottom: 15px;
margin-block: 12px;
display: flex;
justify-content: space-between;
align-items: baseline;

View File

@@ -1,16 +1,7 @@
.type-contentWidget .note-detail {
height: 100%;
}
.note-detail-content-widget {
height: 100%;
}
.note-detail-content-widget-content {
padding: 15px;
height: 100%;
}
.note-detail.full-height .note-detail-content-widget-content {
padding: 0;
}
}

View File

@@ -1,18 +1,20 @@
.note-detail-empty {
container-type: size;
padding-top: 50px;
min-width: 350px;
}
body.desktop {
.note-detail-empty {
container-type: size;
padding-top: 50px;
min-width: 350px;
}
.note-detail-empty > * {
margin-inline: auto;
max-width: 850px;
padding-inline: 50px;
}
@container (max-width: 600px) {
.note-detail-empty > * {
padding-inline: 20px;
margin-inline: auto;
max-width: 850px;
padding-inline: 50px;
}
@container (max-width: 600px) {
.note-detail-empty > * {
padding-inline: 20px;
}
}
}
@@ -24,10 +26,22 @@
}
.workspace-notes .workspace-note {
width: 130px;
text-align: center;
margin: 10px;
border: 1px transparent solid;
.workspace-icon {
text-align: center;
font-size: 250%;
}
@media (min-width: 992px) {
width: 130px;
.workspace-icon {
font-size: 500%;
}
}
}
.workspace-notes .workspace-note:hover {
@@ -37,8 +51,6 @@
}
.note-detail-empty-results .aa-dropdown-menu {
max-height: 50vh;
overflow: scroll;
border: var(--bs-border-width) solid var(--bs-border-color);
border-top: 0;
}
@@ -55,8 +67,3 @@
.empty-tab-search .input-clearer-button {
border-bottom-right-radius: 0;
}
.workspace-icon {
text-align: center;
font-size: 500%;
}

View File

@@ -1,9 +1,6 @@
.protected-session-password-component {
width: 300px;
margin: 30px auto auto;
}
.protected-session-password-component input,
.protected-session-password-component button {
margin-top: 12px;
display: flex;
margin-inline: 40px;
flex-direction: column;
justify-content: center;
}

View File

@@ -20,7 +20,9 @@ export default function ProtectedSession() {
}, [ passwordRef ]);
return (
<form class="protected-session-password-form" onSubmit={submitCallback}>
<form class="protected-session-password-form tn-centered-form" onSubmit={submitCallback}>
<span class="form-icon bx bx-key" />
<FormGroup name="protected-session-password-in-detail" label={t("protected_session.enter_password_instruction")}>
<FormTextBox
type="password"
@@ -37,4 +39,4 @@ export default function ProtectedSession() {
/>
</form>
)
}
}

View File

@@ -1,8 +1,7 @@
.note-detail-render {
position: relative;
}
.note-detail-render .note-detail-render-help {
margin: 50px;
padding: 20px;
}
&>.admonition {
margin: 1em;
}
}

View File

@@ -2,22 +2,59 @@ import "./Render.css";
import { useEffect, useRef, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import note_create from "../../services/note_create";
import render from "../../services/render";
import Alert from "../react/Alert";
import { useTriliumEvent } from "../react/hooks";
import RawHtml from "../react/RawHtml";
import toast from "../../services/toast";
import { getErrorMessage } from "../../services/utils";
import Admonition from "../react/Admonition";
import Button, { SplitButton } from "../react/Button";
import FormGroup from "../react/FormGroup";
import { FormListItem } from "../react/FormList";
import { useNoteRelation, useTriliumEvent } from "../react/hooks";
import NoteAutocomplete from "../react/NoteAutocomplete";
import { refToJQuerySelector } from "../react/react_utils";
import SetupForm from "./helpers/SetupForm";
import { TypeWidgetProps } from "./type_widget";
export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
const HELP_PAGE = "HcABDtFCkbFN";
const PREACT_SAMPLE = /*js*/`\
export default function() {
return <p>Hello world.</p>;
}
`;
const HTML_SAMPLE = /*html*/`\
<p>Hello world.</p>
`;
export default function Render(props: TypeWidgetProps) {
const { note } = props;
const [ renderNote ] = useNoteRelation(note, "renderNote");
const [ disabledRenderNote ] = useNoteRelation(note, "disabled:renderNote");
if (disabledRenderNote) {
return <DisabledRender {...props} />;
}
if (!renderNote) {
return <SetupRenderContent {...props} />;
}
return <RenderContent {...props} />;
}
function RenderContent({ note, noteContext, ntxId }: TypeWidgetProps) {
const contentRef = useRef<HTMLDivElement>(null);
const [ renderNotesFound, setRenderNotesFound ] = useState(false);
const [ error, setError ] = useState<unknown | null>(null);
function refresh() {
if (!contentRef) return;
render.render(note, refToJQuerySelector(contentRef)).then(setRenderNotesFound);
setError(null);
render.render(note, refToJQuerySelector(contentRef), setError);
}
useEffect(refresh, [ note ]);
@@ -49,14 +86,72 @@ export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
return (
<>
{!renderNotesFound && (
<Alert className="note-detail-render-help" type="warning">
<p><strong>{t("render.note_detail_render_help_1")}</strong></p>
<p><RawHtml html={t("render.note_detail_render_help_2")} /></p>
</Alert>
{error && (
<Admonition type="caution">
{getErrorMessage(error)}
</Admonition>
)}
<div ref={contentRef} className="note-detail-render-content" />
</>
);
}
function DisabledRender({ note }: TypeWidgetProps) {
return (
<SetupForm
icon="bx bx-extension"
inAppHelpPage={HELP_PAGE}
>
<p>{t("render.disabled_description")}</p>
<Button
text={t("render.disabled_button_enable")}
icon="bx bx-check-shield"
onClick={() => attributes.toggleDangerousAttribute(note, "relation", "renderNote", true)}
primary
/>
</SetupForm>
);
}
function SetupRenderContent({ note }: TypeWidgetProps) {
return (
<SetupForm
icon="bx bx-extension"
inAppHelpPage={HELP_PAGE}
>
<FormGroup name="render-target-note" label={t("render.setup_title")}>
<NoteAutocomplete noteIdChanged={noteId => {
if (!noteId) return;
setRenderNote(note, noteId);
}} />
</FormGroup>
<SplitButton
text={t("render.setup_create_sample_preact")}
icon="bx bxl-react"
onClick={() => setupSampleNote(note, "text/jsx", PREACT_SAMPLE)}
>
<FormListItem
icon="bx bxl-html5"
onClick={() => setupSampleNote(note, "text/html", HTML_SAMPLE)}
>{t("render.setup_create_sample_html")}</FormListItem>
</SplitButton>
</SetupForm>
);
}
async function setRenderNote(note: FNote, targetNoteUrl: string) {
await attributes.setRelation(note.noteId, "renderNote", targetNoteUrl);
}
async function setupSampleNote(parentNote: FNote, mime: string, content: string) {
const { note: codeNote } = await note_create.createNote(parentNote.noteId, {
type: "code",
mime,
content,
activate: false
});
if (!codeNote) return;
await setRenderNote(parentNote, codeNote.noteId);
toast.showMessage(t("render.setup_sample_created"));
}

View File

@@ -1,35 +1,103 @@
import { t } from "../../services/i18n";
import utils from "../../services/utils";
import Alert from "../react/Alert";
import { useNoteLabel } from "../react/hooks";
import { TypeWidgetProps } from "./type_widget";
import "./WebView.css";
import { useCallback, useState } from "preact/hooks";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import toast from "../../services/toast";
import utils from "../../services/utils";
import Button from "../react/Button";
import FormGroup from "../react/FormGroup";
import FormTextBox from "../react/FormTextBox";
import { useNoteLabel } from "../react/hooks";
import SetupForm from "./helpers/SetupForm";
import { TypeWidgetProps } from "./type_widget";
const isElectron = utils.isElectron();
const HELP_PAGE = "1vHRoWCEjj0L";
export default function WebView({ note }: TypeWidgetProps) {
const [ webViewSrc ] = useNoteLabel(note, "webViewSrc");
const [ disabledWebViewSrc ] = useNoteLabel(note, "disabled:webViewSrc");
return (webViewSrc
? <WebViewContent src={webViewSrc} />
: <WebViewHelp />
);
if (disabledWebViewSrc) {
return <DisabledWebView note={note} url={disabledWebViewSrc} />;
}
if (!webViewSrc) {
return <SetupWebView note={note} />;
}
return <WebViewContent src={webViewSrc} />;
}
function WebViewContent({ src }: { src: string }) {
if (!isElectron) {
return <iframe src={src} class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts allow-popups" />
} else {
return <webview src={src} class="note-detail-web-view-content" />
return <iframe src={src} class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts allow-popups" />;
}
return <webview src={src} class="note-detail-web-view-content" />;
}
function WebViewHelp() {
function SetupWebView({note}: {note: FNote}) {
const [ , setSrcLabel] = useNoteLabel(note, "webViewSrc");
const [ src, setSrc ] = useState("");
const submit = useCallback((url: string) => {
try {
// Validate URL
new URL(url);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (ex) {
toast.showErrorTitleAndMessage(t("web_view_setup.invalid_url_title"),
t("web_view_setup.invalid_url_message"));
return;
}
setSrcLabel(url);
}, [ setSrcLabel ]);
return (
<Alert className="note-detail-web-view-help" type="warning">
<h4>{t("web_view.web_view")}</h4>
<p>{t("web_view.embed_websites")}</p>
<p>{t("web_view.create_label")}</p>
</Alert>
)
<SetupForm
icon="bx bx-globe-alt" inAppHelpPage={HELP_PAGE}
onSubmit={() => submit(src)}
>
<FormGroup name="web-view-src-detail" label={t("web_view_setup.title")}>
<input className="form-control"
type="text"
value={src}
placeholder={t("web_view_setup.url_placeholder")}
onChange={(e) => {setSrc((e.target as HTMLInputElement)?.value);}}
/>
</FormGroup>
<Button
text={t("web_view_setup.create_button")}
primary
keyboardShortcut="Enter"
/>
</SetupForm>
);
}
function DisabledWebView({ note, url }: { note: FNote, url: string }) {
return (
<SetupForm icon="bx bx-globe-alt" inAppHelpPage={HELP_PAGE}>
<FormGroup name="web-view-src-detail" label={t("web_view_setup.disabled_description")}>
<FormTextBox
type="url"
currentValue={url}
disabled
/>
</FormGroup>
<Button
text={t("web_view_setup.disabled_button_enable")}
icon="bx bx-check-shield"
onClick={() => attributes.toggleDangerousAttribute(note, "label", "webViewSrc", true)}
primary
/>
</SetupForm>
);
}

View File

@@ -0,0 +1,22 @@
.setup-form {
height: 100%;
display: flex;
max-width: 600px;
margin: auto;
flex-direction: column;
justify-content: center;
padding-inline: 40px;
.form-icon {
margin-bottom: 12px;
}
.form-group {
width: 100%;
max-width: 600px;
}
.tn-link {
margin-top: 1em;
}
}

View File

@@ -0,0 +1,34 @@
import "./SetupForm.css";
import clsx from "clsx";
import { ComponentChildren } from "preact";
import { t } from "../../../services/i18n";
import { openInAppHelpFromUrl } from "../../../services/utils";
import LinkButton from "../../react/LinkButton";
interface SetupFormProps {
icon: string;
onSubmit?: () => void;
children: ComponentChildren;
inAppHelpPage?: string;
}
export default function SetupForm({ icon, children, onSubmit, inAppHelpPage }: SetupFormProps) {
return (
<div class="setup-form">
<form class="tn-centered-form" onSubmit={onSubmit}>
<span className={clsx(icon, "form-icon")} />
{children}
{inAppHelpPage && (
<LinkButton
text={t("setup_form.more_info")}
onClick={() => openInAppHelpFromUrl(inAppHelpPage)}
/>
)}
</form>
</div>
);
}

View File

@@ -0,0 +1,40 @@
.shortcuts-options-section {
> header {
position: sticky;
top: 0;
left: 0;
right: 0;
background: var(--main-background-color);
padding-block: 0.5em;
border-bottom: 1px solid var(--main-border-color);
}
> footer {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: var(--main-background-color);
padding-block: 0.5em;
border-top: 1px solid var(--main-border-color);
display: flex;
justify-content: space-between;
}
table {
width: 100%;
th {
width: 25%;
}
.separator {
background-color: var(--accented-background-color);
font-weight: bold;
&:first-of-type {
padding-top: 1em;
}
}
}
}

View File

@@ -2,7 +2,6 @@ import { ActionKeyboardShortcut, KeyboardShortcut, OptionNames } from "@triliumn
import { t } from "../../../services/i18n";
import { arrayEqual, reloadFrontendApp } from "../../../services/utils";
import Button from "../../react/Button";
import FormGroup from "../../react/FormGroup";
import FormText from "../../react/FormText";
import FormTextBox from "../../react/FormTextBox";
import RawHtml from "../../react/RawHtml";
@@ -12,6 +11,8 @@ import server from "../../../services/server";
import options from "../../../services/options";
import dialog from "../../../services/dialog";
import { useTriliumEvent } from "../../react/hooks";
import "./shortcuts.css";
import NoItems from "../../react/NoItems";
export default function ShortcutSettings() {
const [ keyboardShortcuts, setKeyboardShortcuts ] = useState<KeyboardShortcut[]>([]);
@@ -70,29 +71,29 @@ export default function ShortcutSettings() {
options.saveMany(optionsToSet);
}, [ keyboardShortcuts ]);
const filterLowerCase = filter?.toLowerCase() ?? "";
const filteredKeyboardShortcuts = filter ? keyboardShortcuts.filter((action) => filterKeyboardAction(action, filterLowerCase)) : keyboardShortcuts;
return (
<OptionsSection
className="shortcuts-options-section"
style={{ display: "flex", flexDirection: "column", height: "100%" }}
noCard
>
<FormText>
{t("shortcuts.multiple_shortcuts")}
{t("shortcuts.multiple_shortcuts")}{" "}
<RawHtml html={t("shortcuts.electron_documentation")} />
</FormText>
<FormGroup name="keyboard-shortcut-filter">
<header>
<FormTextBox
placeholder={t("shortcuts.type_text_to_filter")}
currentValue={filter} onChange={(value) => setFilter(value.toLowerCase())}
currentValue={filter} onChange={(value) => setFilter(value)}
/>
</FormGroup>
</header>
<div style={{overflow: "auto", flexGrow: 1, flexShrink: 1}}>
<KeyboardShortcutTable keyboardShortcuts={keyboardShortcuts} filter={filter} />
</div>
<KeyboardShortcutTable filteredKeyboardActions={filteredKeyboardShortcuts} filter={filter} />
<div style={{ display: "flex", justifyContent: "space-between", margin: "15px 15px 0 15px"}}>
<footer>
<Button
text={t("shortcuts.reload_app")}
onClick={reloadFrontendApp}
@@ -102,12 +103,17 @@ export default function ShortcutSettings() {
text={t("shortcuts.set_all_to_default")}
onClick={resetShortcuts}
/>
</div>
</footer>
</OptionsSection>
)
}
function filterKeyboardAction(action: ActionKeyboardShortcut, filter: string) {
function filterKeyboardAction(action: KeyboardShortcut, filter: string) {
// Hide separators when filtering is active.
if ("separator" in action) {
return !filter;
}
return action.actionName.toLowerCase().includes(filter) ||
(action.friendlyName && action.friendlyName.toLowerCase().includes(filter)) ||
(action.defaultShortcuts ?? []).some((shortcut) => shortcut.toLowerCase().includes(filter)) ||
@@ -115,7 +121,7 @@ function filterKeyboardAction(action: ActionKeyboardShortcut, filter: string) {
(action.description && action.description.toLowerCase().includes(filter));
}
function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string, keyboardShortcuts: KeyboardShortcut[] }) {
function KeyboardShortcutTable({ filteredKeyboardActions, filter }: { filteredKeyboardActions: KeyboardShortcut[], filter: string | undefined }) {
return (
<table class="keyboard-shortcut-table" cellPadding="10">
<thead>
@@ -127,16 +133,17 @@ function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string,
</tr>
</thead>
<tbody>
{keyboardShortcuts.map(action => (
{filteredKeyboardActions.length > 0
? filteredKeyboardActions.map(action => (
<tr>
{"separator" in action ? ( !filter &&
{"separator" in action ?
<td class="separator" colspan={4} style={{
backgroundColor: "var(--accented-background-color)",
fontWeight: "bold"
}}>
{action.separator}
</td>
) : ( (!filter || filterKeyboardAction(action, filter)) &&
: (
<>
<td>{action.friendlyName}</td>
<td>
@@ -147,7 +154,17 @@ function KeyboardShortcutTable({ filter, keyboardShortcuts }: { filter?: string,
</>
)}
</tr>
))}
))
: (
<tr>
<td colspan={4} class="text-center">
<NoItems
icon="bx bx-filter-alt"
text={t("shortcuts.no_results", { filter })}
/>
</td>
</tr>
)}
</tbody>
</table>
);

View File

@@ -65,7 +65,7 @@ export default function RelationMap({ note, noteContext, ntxId, parentComponent
};
},
onContentChange(content) {
let newData: MapData | null = null;
let newData: Partial<MapData> | null = null;
if (content) {
try {
@@ -75,7 +75,7 @@ export default function RelationMap({ note, noteContext, ntxId, parentComponent
}
}
if (!newData) {
if (!newData || !newData.notes || !newData.transform) {
newData = {
notes: [],
// it is important to have this exact value here so that initial transform is the same as this
@@ -90,8 +90,8 @@ export default function RelationMap({ note, noteContext, ntxId, parentComponent
};
}
setData(newData);
mapApiRef.current = new RelationMapApi(note, newData, (newData, refreshUi) => {
setData(newData as MapData);
mapApiRef.current = new RelationMapApi(note, newData as MapData, (newData, refreshUi) => {
if (refreshUi) {
setData(newData);
}

View File

@@ -95,7 +95,7 @@ export default defineConfig(() => ({
output: {
entryFileNames: (chunk) => {
// We enforce a hash in the main index file to avoid caching issues, this only works because we have the HTML entry point.
if (chunk.name === "index") {
if (chunk.name === "index" || chunk.name === "print") {
return "src/[name]-[hash].js";
}

View File

@@ -35,7 +35,7 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
"electron": "40.2.1",
"electron": "40.4.1",
"@electron-forge/cli": "7.11.1",
"@electron-forge/maker-deb": "7.11.1",
"@electron-forge/maker-dmg": "7.11.1",

View File

@@ -12,7 +12,7 @@
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
"electron": "40.2.1",
"electron": "40.4.1",
"fs-extra": "11.3.3"
},
"scripts": {

View File

@@ -6,6 +6,6 @@
"e2e": "playwright test"
},
"devDependencies": {
"dotenv": "17.2.3"
"dotenv": "17.3.1"
}
}

View File

@@ -1,4 +1,4 @@
FROM node:24.13.0-bullseye-slim AS builder
FROM node:24.13.1-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.13.0-bullseye-slim
FROM node:24.13.1-bullseye-slim
# Install only runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \

View File

@@ -1,4 +1,4 @@
FROM node:24.13.0-alpine AS builder
FROM node:24.13.1-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.13.0-alpine
FROM node:24.13.1-alpine
# Install runtime dependencies
RUN apk add --no-cache su-exec shadow

View File

@@ -1,4 +1,4 @@
FROM node:24.13.0-alpine AS builder
FROM node:24.13.1-alpine AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.13.0-alpine
FROM node:24.13.1-alpine
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -1,4 +1,4 @@
FROM node:24.13.0-bullseye-slim AS builder
FROM node:24.13.1-bullseye-slim AS builder
RUN corepack enable
# Install native dependencies since we might be building cross-platform.
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
# We have to use --no-frozen-lockfile due to CKEditor patches
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
FROM node:24.13.0-bullseye-slim
FROM node:24.13.1-bullseye-slim
# Create a non-root user with configurable UID/GID
ARG USER=trilium
ARG UID=1001

View File

@@ -35,7 +35,7 @@
"sucrase": "3.35.1"
},
"devDependencies": {
"@anthropic-ai/sdk": "0.72.1",
"@anthropic-ai/sdk": "0.74.0",
"@braintree/sanitize-url": "7.1.2",
"@electron/remote": "2.1.3",
"@triliumnext/commons": "workspace:*",
@@ -70,7 +70,7 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.4",
"axios": "1.13.5",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.1",
@@ -83,7 +83,7 @@
"debounce": "3.0.0",
"debug": "4.4.3",
"ejs": "4.0.1",
"electron": "40.2.1",
"electron": "40.4.1",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
@@ -99,7 +99,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "25.8.0",
"i18next": "25.8.7",
"i18next-fs-backend": "2.6.1",
"image-type": "6.0.0",
"ini": "6.0.0",
@@ -107,12 +107,12 @@
"is-svg": "6.1.0",
"jimp": "1.6.0",
"lorem-ipsum": "2.0.8",
"marked": "17.0.1",
"marked": "17.0.2",
"mime-types": "3.0.2",
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.3",
"openai": "6.17.0",
"openai": "6.22.0",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,118 @@
<p><em>Active content</em> is a generic name for powerful features in Trilium,
these range from customizing the UI to advanced scripting that can alter
your notes or even your PC.</p>
<h2>Safe import</h2>
<p>Active content problem of safety, especially when this active content
comes from a third-party such as if it is downloaded from a website and
then imported into Trilium.</p>
<p>When <a href="#root/_help_mHbBMPDPkVV5">importing</a> .zip archives into Trilium, <em>safe mode</em> is
active by default which will try to prevent untrusted code from executing.
For example, a <a href="#root/_help_MgibgPcfeuGz">custom widget</a> needs the
<code
spellcheck="false">#widget</code> <a href="#root/_help_HI6GBBIduIgv">label</a> in order to function;
safe import works by renaming that label to <code spellcheck="false">#disabled:widget</code>.</p>
<h2>Safe mode</h2>
<p>Sometimes active content can cause issues with the UI or the server, preventing
it from functioning properly.&nbsp;<a class="reference-link" href="#root/_help_64ZTlUPgEPtW">Safe mode</a>&nbsp;allows
starting Trilium in such a way that active content is not loaded by default
at start-up, allowing the user to fix the problematic scripts or widgets.</p>
<h2>Types of active content</h2>
<p>These are the types of active content in Trilium, along with a few examples
of what untrusted content of that type could cause:</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Disabled on a safe <a href="#root/_help_mHbBMPDPkVV5">import</a>
</th>
<th>Description</th>
<th>Potential risks of untrusted code</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="#root/_help_yIhgI5H7A2Sm">Front-end scripts</a>
</td>
<td>Yes</td>
<td>Allow running arbitrary code on the client (UI) of Trilium, which can
alter the user interface.</td>
<td>A malicious script can execute server-side code, access un-encrypted notes
or change their contents.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>
</td>
<td>Yes</td>
<td>Can add new UI features to Trilium, for example by adding a new section
in the&nbsp;<a class="reference-link" href="#root/_help_RnaPdbciOfeq">Right Sidebar</a>.</td>
<td>The UI can be altered in such a way that it can be used to extract sensitive
information or it can simply cause the application to crash.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_SPirpZypehBG">Backend scripts</a>
</td>
<td>Yes</td>
<td>Can run custom code on the server of Trilium (Node.js environment), with
full access to the notes and the database.</td>
<td>Has access to all the unencrypted notes, but with full access to the database
it can completely destroy the data. It also has access to execute other
applications or alter the files and folders on the server).</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_1vHRoWCEjj0L">Web View</a>
</td>
<td>Yes</td>
<td>Displays a website inside a note.</td>
<td>Can point to a phishing website which can collect the data (for example
on a log in page).</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
</td>
<td>Yes</td>
<td>Renders custom content inside a note, such as a dashboard or a new editor
that is not officially supported by Trilium.</td>
<td>Can affect the UI similar to front-end scripts or custom widgets since
the scripts are not completely encapsulated, or they can act similar to
a web view where they can collect data entered by the user.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_AlhDUqhENtH7">Custom app-wide CSS</a>
</td>
<td>No</td>
<td>Can alter the layout and style of the UI using CSS, applied regardless
of theme.</td>
<td>Generally less problematic than the rest of active content, but a badly
written CSS can affect the layout of the application, requiring the use
of&nbsp;<a class="reference-link" href="#root/_help_64ZTlUPgEPtW">Safe mode</a>&nbsp;to
be able to use the application.</td>
</tr>
<tr>
<td><a href="#root/_help_pKK96zzmvBGf">Custom themes</a>
</td>
<td>No</td>
<td>Can change the style of the entire UI.</td>
<td>Similar to custom app-wide CSS.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_gOKqSJgXLcIj">Icon Packs</a>
</td>
<td>No</td>
<td>Introduces new icons that can be used for notes.</td>
<td>Generally are more contained and less prone to cause issues, but they
can cause performance issues (for example if the icon pack has millions
of icons in it).</td>
</tr>
</tbody>
</table>
<h2>Active content badge</h2>
<p>Starting with v0.102.0, on the&nbsp;<a class="reference-link" href="#root/_help_IjZS7iK5EXtb">New Layout</a>&nbsp;a
badge will be displayed near the note title, indicating that an active
content is detected. Clicking the badge will reveal a menu with various
options related to that content type, for example to open the documentation
or to configure the execution of scripts.</p>
<p>For some active content types, such as backend scripts with custom triggering
conditions a toggle button will appear. This makes it possible to easily
disable scripts or widgets, but also to re-enable them if an import was
made with safe mode active.</p>

View File

@@ -29,10 +29,9 @@
<li>Ideally, create a dedicated spot in your note tree where to place the
icon packs.</li>
<li>Right click the note where to put it and select <em>Import into note</em>.</li>
<li
>Uncheck <em>Safe import</em>.</li>
<li>Select <em>Import</em>.</li>
<li><a href="#root/_help_s8alTXmpFR61">Refresh the application</a>.</li>
<li>Uncheck <em>Safe import</em>.</li>
<li>Select <em>Import</em>.</li>
<li><a href="#root/_help_s8alTXmpFR61">Refresh the application</a>.</li>
</ol>
<aside class="admonition warning">
<p>Since <em>Safe import</em> is disabled, make sure you trust the source as

View File

@@ -1,9 +1,8 @@
<aside class="admonition important">
<p><a class="reference-link" href="#root/_help_zEY4DaJG4YT5">Attributes</a><a class="reference-link"
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a><a class="reference-link"
href="#root/_help_zEY4DaJG4YT5">Attributes</a>Starting with Trilium v0.97.0,
the geo map has been converted from a standalone <a href="#root/_help_KSZ04uQ2D1St">note type</a> to
a type of view for the&nbsp;<a class="reference-link" href="#root/_help_0ESUbbAxVnoK">Note List</a>.&nbsp;</p>
<p>Starting with Trilium v0.97.0, the geo map has been converted from a standalone
<a
href="#root/_help_KSZ04uQ2D1St">note type</a>to a type of view for the&nbsp;<a class="reference-link"
href="#root/_help_0ESUbbAxVnoK">Note List</a>.&nbsp;</p>
</aside>
<figure class="image image-style-align-center">
<img style="aspect-ratio:892/675;" src="9_Geo Map_image.png"
@@ -13,38 +12,39 @@
on an attribute. It is also possible to add new notes at a specific location
using the built-in interface.</p>
<h2>Creating a new geo map</h2>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image">
<img style="aspect-ratio:483/413;" src="15_Geo Map_image.png"
width="483" height="413">
</figure>
</td>
<td>Right click on any note on the note tree and select <em>Insert child note</em><em>Geo Map (beta)</em>.</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:53.44%;">
<img style="aspect-ratio:1720/1396;" src="8_Geo Map_image.png"
width="1720" height="1396">
</figure>
</td>
<td>By default the map will be empty and will show the entire world.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image">
<img style="aspect-ratio:483/413;" src="15_Geo Map_image.png"
width="483" height="413">
</figure>
</td>
<td>Right click on any note on the note tree and select <em>Insert child note</em><em>Geo Map (beta)</em>.</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:53.44%;">
<img style="aspect-ratio:1720/1396;" src="8_Geo Map_image.png"
width="1720" height="1396">
</figure>
</td>
<td>By default the map will be empty and will show the entire world.</td>
</tr>
</tbody>
</table>
</figure>
<h2>Repositioning the map</h2>
<ul>
<li>Click and drag the map in order to move across the map.</li>
@@ -55,59 +55,60 @@
restored when visiting again the note.</p>
<h2>Adding a marker using the map</h2>
<h3>Adding a new note using the plus button</h3>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>To create a marker, first navigate to the desired point on the map. Then
press the
<img src="10_Geo Map_image.png">button in the&nbsp;<a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>&nbsp;(top-right)
area.&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>If the button is not visible, make sure the button section is visible
by pressing the chevron button (
<img src="17_Geo Map_image.png">) in the top-right of the map.</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png"
width="1730" height="416">
</td>
<td>Once pressed, the map will enter in the insert mode, as illustrated by
the notification.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Simply click the point on the map where to place the marker, or the Escape
key to cancel.</td>
</tr>
<tr>
<td>3</td>
<td>
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png"
width="1586" height="404">
</td>
<td>Enter the name of the marker/note to be created.</td>
</tr>
<tr>
<td>4</td>
<td>
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map_image.png"
width="1696" height="608">
</td>
<td>Once confirmed, the marker will show up on the map and it will also be
displayed as a child note of the map.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>To create a marker, first navigate to the desired point on the map. Then
press the
<img src="10_Geo Map_image.png">button in the&nbsp;<a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>&nbsp;(top-right)
area.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>If the button is not visible, make sure the button section is visible
by pressing the chevron button (
<img src="17_Geo Map_image.png">) in the top-right of the map.</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>2</td>
<td>
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png"
width="1730" height="416">
</td>
<td>Once pressed, the map will enter in the insert mode, as illustrated by
the notification.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Simply click the point on the map where to place the marker, or the Escape
key to cancel.</td>
</tr>
<tr>
<td>3</td>
<td>
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map_image.png"
width="1586" height="404">
</td>
<td>Enter the name of the marker/note to be created.</td>
</tr>
<tr>
<td>4</td>
<td>
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map_image.png"
width="1696" height="608">
</td>
<td>Once confirmed, the marker will show up on the map and it will also be
displayed as a child note of the map.</td>
</tr>
</tbody>
</table>
</figure>
<h3>Adding a new note using the contextual menu</h3>
<ol>
<li>Right click anywhere on the map, where to place the newly created marker
@@ -120,15 +121,18 @@
<h3>Adding an existing note on note from the note tree</h3>
<ol>
<li>Select the desired note in the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
<li>Hold the mouse on the note and drag it to the map to the desired location.</li>
<li>The map should be updated with the new marker.</li>
<li
>Hold the mouse on the note and drag it to the map to the desired location.</li>
<li
>The map should be updated with the new marker.</li>
</ol>
<p>This works for:</p>
<ul>
<li>Notes that are not part of the geo map, case in which a <a href="#root/_help_IakOLONlIfGI">clone</a> will
be created.</li>
<li>Notes that are a child of the geo map but not yet positioned on the map.</li>
<li>Notes that are a child of the geo map and also positioned, case in which
<li
>Notes that are a child of the geo map and also positioned, case in which
the marker will be relocated to the new position.</li>
</ul>
<aside class="admonition note">
@@ -138,8 +142,10 @@
<h2>How the location of the markers is stored</h2>
<p>The location of a marker is stored in the <code spellcheck="false">#geolocation</code> attribute
of the child notes:</p>
<img src="18_Geo Map_image.png"
width="1288" height="278">
<p>
<img src="18_Geo Map_image.png" width="1288"
height="278">
</p>
<p>This value can be added manually if needed. The value of the attribute
is made up of the latitude and longitude separated by a comma.</p>
<h2>Repositioning markers</h2>
@@ -160,7 +166,8 @@ width="1288" height="278">
</li>
<li>Middle-clicking the marker will open the note in a new tab.</li>
<li>Right-clicking the marker will open a contextual menu (as described below).</li>
<li>If the map is in read-only mode, clicking on a marker will open a&nbsp;
<li
>If the map is in read-only mode, clicking on a marker will open a&nbsp;
<a
class="reference-link" href="#root/_help_ZjLYv08Rp3qC">Quick edit</a>&nbsp;popup for the corresponding note.</li>
</ul>
@@ -206,211 +213,239 @@ width="1288" height="278">
<p>The value of the attribute is made up of the latitude and longitude separated
by a comma.</p>
<h3>Adding from Google Maps</h3>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:56.84%;">
<img style="aspect-ratio:732/918;" src="12_Geo Map_image.png"
width="732" height="918">
</figure>
</td>
<td>Go to Google Maps on the web and look for a desired location, right click
on it and a context menu will show up.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Simply click on the first item displaying the coordinates and they will
be copied to clipboard.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Then paste the value inside the text box into the <code spellcheck="false">#geolocation</code> attribute
of a child note of the map (don't forget to surround the value with a
<code
spellcheck="false">"</code>character).</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:518/84;" src="4_Geo Map_image.png"
width="518" height="84">
</figure>
</td>
<td>In Trilium, create a child note under the map.</td>
</tr>
<tr>
<td>3</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png"
width="1074" height="276">
</figure>
</td>
<td>And then go to Owned Attributes and type <code spellcheck="false">#geolocation="</code>,
then paste from the clipboard as-is and then add the ending <code spellcheck="false">"</code> character.
Press Enter to confirm and the map should now be updated to contain the
new note.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:56.84%;">
<img style="aspect-ratio:732/918;" src="12_Geo Map_image.png"
width="732" height="918">
</figure>
</td>
<td>Go to Google Maps on the web and look for a desired location, right click
on it and a context menu will show up.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Simply click on the first item displaying the coordinates and they will
be copied to clipboard.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Then paste the value inside the text box into the <code spellcheck="false">#geolocation</code> attribute
of a child note of the map (don't forget to surround the value with a
<code
spellcheck="false">"</code>character).</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:518/84;" src="4_Geo Map_image.png"
width="518" height="84">
</figure>
</td>
<td>In Trilium, create a child note under the map.</td>
</tr>
<tr>
<td>3</td>
<td>
<figure class="image image-style-align-center image_resized" style="width:100%;">
<img style="aspect-ratio:1074/276;" src="11_Geo Map_image.png"
width="1074" height="276">
</figure>
</td>
<td>And then go to Owned Attributes and type <code spellcheck="false">#geolocation="</code>,
then paste from the clipboard as-is and then add the ending <code spellcheck="false">"</code> character.
Press Enter to confirm and the map should now be updated to contain the
new note.</td>
</tr>
</tbody>
</table>
</figure>
<h3>Adding from OpenStreetMap</h3>
<p>Similarly to the Google Maps approach:</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png"
width="562" height="454">
</td>
<td>Go to any location on openstreetmap.org and right click to bring up the
context menu. Select the “Show address” item.</td>
</tr>
<tr>
<td>2</td>
<td>
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png"
width="696" height="480">
</td>
<td>The address will be visible in the top-left of the screen, in the place
of the search bar.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Select the coordinates and copy them into the clipboard.</td>
</tr>
<tr>
<td>3</td>
<td>
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png"
width="640" height="276">
</td>
<td>Simply paste the value inside the text box into the <code spellcheck="false">#geolocation</code> attribute
of a child note of the map and then it should be displayed on the map.</td>
</tr>
</tbody>
</table>
<figure class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png"
width="562" height="454">
</td>
<td>Go to any location on openstreetmap.org and right click to bring up the
context menu. Select the “Show address” item.</td>
</tr>
<tr>
<td>2</td>
<td>
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png"
width="696" height="480">
</td>
<td>The address will be visible in the top-left of the screen, in the place
of the search bar.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>Select the coordinates and copy them into the clipboard.</td>
</tr>
<tr>
<td>3</td>
<td>
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png"
width="640" height="276">
</td>
<td>Simply paste the value inside the text box into the <code spellcheck="false">#geolocation</code> attribute
of a child note of the map and then it should be displayed on the map.</td>
</tr>
</tbody>
</table>
</figure>
<h2>Adding GPS tracks (.gpx)</h2>
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:226/74;" src="3_Geo Map_image.png"
width="226" height="74">
</figure>
</td>
<td>To add a track, simply drag &amp; drop a .gpx file inside the geo map
in the note tree.</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:322/222;" src="14_Geo Map_image.png"
width="322" height="222">
</figure>
</td>
<td>In order for the file to be recognized as a GPS track, it needs to show
up as <code spellcheck="false">application/gpx+xml</code> in the <em>File type</em> field.</td>
</tr>
<tr>
<td>3</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:620/530;" src="6_Geo Map_image.png"
width="620" height="530">
</figure>
</td>
<td>When going back to the map, the track should now be visible.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>The start and end points of the track are indicated by the two blue markers.</td>
</tr>
</tbody>
</table>
<aside class="admonition note">
<p>The starting point of the track will be displayed as a marker, with the
name of the note underneath. The start marker will also respect the icon
and the <code spellcheck="false">color</code> of the note. The end marker
is displayed with a distinct icon.</p>
<p>If the GPX contains waypoints, they will also be displayed. If they have
a name, it is displayed when hovering over it with the mouse.</p>
</aside>
<h2>Read-only mode</h2>
<p>When a map is in read-only all editing features will be disabled such
as:</p>
<ul>
<li>The add button in the&nbsp;<a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
<li>Dragging markers.</li>
<li>Editing from the contextual menu (removing locations or adding new items).</li>
</ul>
<p>To enable read-only mode simply press the <em>Lock</em> icon from the&nbsp;
<a
class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
<h2>Configuration</h2>
<h3>Map Style</h3>
<p>The styling of the map can be adjusted in the <em>Collection Properties</em> tab
in the&nbsp;<a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>&nbsp;or
manually via the <code spellcheck="false">#map:style</code> attribute.</p>
<p>The geo map comes with two different types of styles:</p>
<ul>
<li>Raster styles
<ul>
<li>For these styles the map is represented as a grid of images at different
zoom levels. This is the traditional way OpenStreetMap used to work.</li>
<li>Zoom is slightly restricted.</li>
<li>Currently, the only raster theme is the original OpenStreetMap style.</li>
<figure
class="table">
<table>
<thead>
<tr>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:226/74;" src="3_Geo Map_image.png"
width="226" height="74">
</figure>
</td>
<td>To add a track, simply drag &amp; drop a .gpx file inside the geo map
in the note tree.</td>
</tr>
<tr>
<td>2</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:322/222;" src="14_Geo Map_image.png"
width="322" height="222">
</figure>
</td>
<td>In order for the file to be recognized as a GPS track, it needs to show
up as <code spellcheck="false">application/gpx+xml</code> in the <em>File type</em> field.</td>
</tr>
<tr>
<td>3</td>
<td>
<figure class="image image-style-align-center">
<img style="aspect-ratio:620/530;" src="6_Geo Map_image.png"
width="620" height="530">
</figure>
</td>
<td>When going back to the map, the track should now be visible.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<br>
<br>The start and end points of the track are indicated by the two blue markers.</td>
</tr>
</tbody>
</table>
</figure>
<aside class="admonition note">
<p>The starting point of the track will be displayed as a marker, with the
name of the note underneath. The start marker will also respect the icon
and the <code spellcheck="false">color</code> of the note. The end marker
is displayed with a distinct icon.</p>
<p>If the GPX contains waypoints, they will also be displayed. If they have
a name, it is displayed when hovering over it with the mouse.</p>
</aside>
<h2>Read-only mode</h2>
<p>When a map is in read-only all editing features will be disabled such
as:</p>
<ul>
<li>The add button in the&nbsp;<a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
<li
>Dragging markers.</li>
<li>Editing from the contextual menu (removing locations or adding new items).</li>
</ul>
<p>To enable read-only mode simply press the <em>Lock</em> icon from the&nbsp;
<a
class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
<h2>Configuration</h2>
<h3>Map Style</h3>
<p>The styling of the map can be adjusted in the <em>Collection Properties</em> (above
the map, by pressing on the Gear icon)&nbsp;or manually via the <code spellcheck="false">#map:style</code> attribute.</p>
<p>The geo map comes with two different types of styles:</p>
<ul>
<li>Raster styles
<ul>
<li>For these styles the map is represented as a grid of images at different
zoom levels. This is the traditional way OpenStreetMap used to work.</li>
<li
>Zoom is slightly restricted.</li>
<li>Currently, the only raster theme is the original OpenStreetMap style.</li>
</ul>
</li>
<li>Vector styles
<ul>
<li>Vector styles are not represented as images, but as geometrical shapes.
This makes the rendering much smoother, especially when zooming and looking
at the building edges, for example.</li>
<li>The map can be zoomed in much further.</li>
<li>These come both in a light and a dark version.</li>
<li>The vector styles come from <a href="https://versatiles.org/">VersaTiles</a>,
a free and open-source project providing map tiles based on OpenStreetMap.</li>
</ul>
</li>
</ul>
<aside class="admonition note">
<p>Currently it is not possible to use a custom map style.</p>
</aside>
<h3>Scale</h3>
<p>Activating this option via the&nbsp;<a class="reference-link" href="#root/_help_BlN9DFI679QC">Ribbon</a>&nbsp;or
manually via <code spellcheck="false">#map:scale</code> will display an indicator
in the bottom-left of the scale of the map.</p>
<h2>Troubleshooting</h2>
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
<img style="aspect-ratio:678/499;" src="13_Geo Map_image.png"
width="678" height="499">
</figure>
<h3>Grid-like artifacts on the map</h3>
<p>This occurs if the application is not at 100% zoom which causes the pixels
of the map to not render correctly due to fractional scaling. The only
possible solution is to set the UI zoom at 100% (default keyboard shortcut
is <kbd>Ctrl</kbd>+<kbd>0</kbd>).</p>
</li>
<li>Vector styles
<ul>
<li>Vector styles are not represented as images, but as geometrical shapes.
This makes the rendering much smoother, especially when zooming and looking
at the building edges, for example.</li>
<li>The map can be zoomed in much further.</li>
<li>These come both in a light and a dark version.</li>
<li>The vector styles come from <a href="https://versatiles.org/">VersaTiles</a>,
a free and open-source project providing map tiles based on OpenStreetMap.</li>
</ul>
</li>
</ul>
<h3>Custom map style / tiles</h3>
<p>Starting with v0.102.0 it is possible to use custom tile sets, but only
in raster format.</p>
<p>To do so, manually set the <code spellcheck="false">#map:style</code>
<a
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_HI6GBBIduIgv">label</a>to the URL of the tile set. For example, to use Esri.NatGeoWorldMap,
set the value to <a href="https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}."><code spellcheck="false">https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{z}/{y}/{x}</code>.</a>
</p>
<aside class="admonition note">
<p>For a list of tile sets, see the <a href="https://leaflet-extras.github.io/leaflet-providers/preview/">Leaflet Providers preview</a> page.
Select a desired tile set and just copy the URL from the <em>Plain JavaScript</em> example.</p>
</aside>
<p>Custom vector map support is planned, but not yet implemented.</p>
<h3>Other options</h3>
<p>The following options can be configured either via the Collection properties
pane above the map, by clicking on the settings (Gear icon). Alternatively,
each of these options also have a corresponding <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_HI6GBBIduIgv">label</a> that
can be set manually.</p>
<ul>
<li>
<p>Scale, which illustrates the scale of the map in kilometers and miles
in the bottom-left of the map.</p>
</li>
<li>
<p>The name of the markers is displayed by default underneath the pin on
the map. Since v0.102.0, it is possible to hide these labels which increases
the performance and decreases clutter when there are many markers on the
map.</p>
</li>
</ul>
<h2>Troubleshooting</h2>
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
<img style="aspect-ratio:678/499;" src="13_Geo Map_image.png"
width="678" height="499">
</figure>
<h3>Grid-like artifacts on the map</h3>
<p>This occurs if the application is not at 100% zoom which causes the pixels
of the map to not render correctly due to fractional scaling. The only
possible solution is to set the UI zoom at 100% (default keyboard shortcut
is <kbd>Ctrl</kbd>+<kbd>0</kbd>).</p>

View File

@@ -2,15 +2,20 @@
<img style="aspect-ratio:601/216;" src="Render Note_image.png"
width="601" height="216">
</figure>
<p>Render Note is used in&nbsp;<a class="reference-link" href="#root/_help_CdNpE2pqjmI6">Scripting</a>.
It works by displaying the HTML of a&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note,
via an attribute.</p>
<p>Render Note is a special case of <a href="#root/_help_yIhgI5H7A2Sm">front-end scripting</a> which
allows rendering custom content inside a note. This makes it possible to
create custom dashboards, or to use a custom note editor.</p>
<p>The content can either be a vanilla HTML, or Preact JSX.</p>
<h2>Creating a render note</h2>
<ol>
<li>Create a&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note
with the HTML language, with what needs to be displayed (for example
<code
spellcheck="false">&lt;p&gt;Hello world.&lt;/p&gt;</code>).</li>
with the:
<ol>
<li>HTML language for the legacy/vanilla method, with what needs to be displayed
(for example <code spellcheck="false">&lt;p&gt;Hello world.&lt;/p&gt;</code>).</li>
<li>JSX for the Preact-based approach (see below).</li>
</ol>
</li>
<li>Create a&nbsp;<a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>.</li>
<li>Assign the <code spellcheck="false">renderNote</code> <a href="#root/_help_zEY4DaJG4YT5">relation</a> to
point at the previously created code note.</li>
@@ -47,7 +52,7 @@ $dateEl.text(new Date());</code></pre>
<li>
<p>Create a child&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note
with JSX as the language.
<br>As an example, use the following content:</p><pre><code class="language-text-jsx">export default function() {
<br>As an example, use the following content:</p><pre><code class="language-text-x-trilium-auto">export default function() {
return (
&lt;&gt;
&lt;p&gt;Hello world.&lt;/p&gt;

View File

@@ -0,0 +1,29 @@
<p>Unlike <a href="#root/_help_yIhgI5H7A2Sm">front-end scripts</a> which run on the
client / browser-side, back-end scripts run directly on the Node.js environment
of the Trilium server.</p>
<p>Back-end scripts can be used both on a&nbsp;<a class="reference-link"
href="#root/_help_WOcw2SLH6tbX">Server Installation</a>&nbsp;(where it will run
on the device the server is running on), or on the&nbsp;<a class="reference-link"
href="#root/_help_poXkQfguuA0U">Desktop Installation</a>&nbsp;(where it will
run on the PC).</p>
<h2>Advantages of backend scripts</h2>
<p>The benefit of backend scripts is that they can be pretty powerful, for
example to have access to the underlying system, for example it can read
files or execute processes.</p>
<p>However, the main benefit of backend scripts is that they have easier
access to the notes since the information about them is already loaded
in memory. Whereas on the client, notes have to be manually loaded first.</p>
<h2>Creating a backend script</h2>
<p>Create a new&nbsp;<a class="reference-link" href="#root/_help_6f9hih2hXXZk">Code</a>&nbsp;note
and select the language <em>JS backend</em>.</p>
<h2>Running backend scripts</h2>
<p>Backend scripts can be either run manually (via the Execute button on
the script page), or they can be triggered on certain events.</p>
<p>In addition, scripts can be run automatically when the server starts up,
on a fixed time interval or when a certain event occurs (such as an attribute
being modified). For more information, see the dedicated&nbsp;<a class="reference-link"
href="#root/_help_GPERMystNGTB">Events</a>&nbsp;page.</p>
<h2>Script API</h2>
<p>Trilium exposes a set of APIs that can be directly consumed by scripts,
under the <code spellcheck="false">api</code> object. For a reference of
this API, see&nbsp;<a class="reference-link" href="#root/_help_MEtfsqa5VwNi">Backend API</a>.</p>

View File

@@ -14,32 +14,30 @@
</thead>
<tbody>
<tr>
<td><code>run</code>
<td><code spellcheck="false">run</code>
</td>
<td>
<p>Defines on which events script should run. Possible values are:</p>
<ul>
<li><code>frontendStartup</code> - when Trilium frontend starts up (or is refreshed),
but not on mobile.</li>
<li><code>mobileStartup</code> - when Trilium frontend starts up (or is refreshed),
on mobile.</li>
<li><code>backendStartup</code> - when Trilium backend starts up</li>
<li><code>hourly</code> - run once an hour. You can use additional label <code>runAtHour</code> to
specify at which hour, on the back-end.</li>
<li><code>daily</code> - run once a day, on the back-end</li>
<li><code spellcheck="false">backendStartup</code> - when Trilium backend starts
up</li>
<li><code spellcheck="false">hourly</code> - run once an hour. You can use
additional label <code spellcheck="false">runAtHour</code> to specify at
which hour, on the back-end.</li>
<li><code spellcheck="false">daily</code> - run once a day, on the back-end</li>
</ul>
</td>
</tr>
<tr>
<td><code>runOnInstance</code>
<td><code spellcheck="false">runOnInstance</code>
</td>
<td>Specifies that the script should only run on a particular&nbsp;<a class="reference-link"
href="#root/_help_c5xB8m4g2IY6">Trilium instance</a>.</td>
</tr>
<tr>
<td><code>runAtHour</code>
<td><code spellcheck="false">runAtHour</code>
</td>
<td>On which hour should this run. Should be used together with <code>#run=hourly</code>.
<td>On which hour should this run. Should be used together with <code spellcheck="false">#run=hourly</code>.
Can be defined multiple times for more runs during the day.</td>
</tr>
</tbody>

View File

@@ -1,92 +1,76 @@
<h2>Frontend API</h2>
<p>The frontend api supports two styles, regular scripts that are run with
the current app and note context, and widgets that export an object to
Trilium to be used in the UI. In both cases, the frontend api of Trilium
is available to scripts running in the frontend context as global variable
<code
spellcheck="false">api</code>. The members and methods of the api can be seen on the <a href="#root/_help_GLks18SNjxmC">Script API</a> page.</p>
<p>Front-end scripts are custom JavaScript notes that are run on the client
(browser environment)</p>
<p>There are four flavors of front-end scripts:</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>Regular scripts</td>
<td>These are run with the current app and note context. These can be run
either manually or automatically on start-up.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>
</td>
<td>These can introduce new UI elements in various positions, such as near
the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>,
content area or even the&nbsp;<a class="reference-link" href="#root/_help_RnaPdbciOfeq">Right Sidebar</a>.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_4Gn3psZKsfSm">Launch Bar Widgets</a>
</td>
<td>Similar to&nbsp;<a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
but dedicated to the&nbsp;<a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>.
These can simply introduce new buttons or graphical elements to the bar.</td>
</tr>
<tr>
<td><a class="reference-link" href="#root/_help_HcABDtFCkbFN">Render Note</a>
</td>
<td>This allows rendering custom content inside a note, using either HTML
or Preact JSX.</td>
</tr>
</tbody>
</table>
<p>For more advanced behaviors that do not require a user interface (e.g.
batch modifying notes), see&nbsp;<a class="reference-link" href="#root/_help_SPirpZypehBG">Backend scripts</a>.</p>
<h2>Scripts</h2>
<p>Scripts don't have any special requirements. They can be run at will using
the execute button in the UI or they can be configured to run at certain
times using <a href="#root/_help_zEY4DaJG4YT5">Attributes</a> on the note containing
the script.</p>
<h3>Global Events</h3>
<p>This attribute is called <code spellcheck="false">#run</code> and it can
have any of the following values:</p>
<p>Scripts don't have any special requirements. They can be run manually
using the <em>Execute</em> button on the code note or they can be run automatically;
to do so, set the <code spellcheck="false">run</code> <a href="#root/_help_HI6GBBIduIgv">label</a> to
either:</p>
<ul>
<li><code spellcheck="false">frontendStartup</code> - executes on frontend
upon startup.</li>
<li><code spellcheck="false">mobileStartup</code> - executes on mobile frontend
upon startup.</li>
<li><code spellcheck="false">backendStartup</code> - executes on backend upon
startup.</li>
<li><code spellcheck="false">hourly</code> - executes once an hour on backend.</li>
<li><code spellcheck="false">daily</code> - executes once a day on backend.</li>
</ul>
<h3>Entity Events</h3>
<p>These events are triggered by certain <a href="#root/_help_zEY4DaJG4YT5">relations</a> to
other notes. Meaning that the script is triggered only if the note has
this script attached to it through relations (or it can inherit it).</p>
<ul>
<li><code spellcheck="false">runOnNoteCreation</code> - executes when note
is created on backend.</li>
<li><code spellcheck="false">runOnNoteTitleChange</code> - executes when note
title is changed (includes note creation as well).</li>
<li><code spellcheck="false">runOnNoteContentChange</code> - executes when
note content is changed (includes note creation as well).</li>
<li><code spellcheck="false">runOnNoteChange</code> - executes when note is
changed (includes note creation as well).</li>
<li><code spellcheck="false">runOnNoteDeletion</code> - executes when note
is being deleted.</li>
<li><code spellcheck="false">runOnBranchCreation</code> - executes when a branch
is created. Branch is a link between parent note and child note and is
created e.g. when cloning or moving note.</li>
<li><code spellcheck="false">runOnBranchDeletion</code> - executes when a branch
is delete. Branch is a link between parent note and child note and is deleted
e.g. when moving note (old branch/link is deleted).</li>
<li><code spellcheck="false">runOnChildNoteCreation</code> - executes when
new note is created under this note.</li>
<li><code spellcheck="false">runOnAttributeCreation</code> - executes when
new attribute is created under this note.</li>
<li><code spellcheck="false">runOnAttributeChange</code> - executes when attribute
is changed under this note.</li>
<li><code spellcheck="false">frontendStartup</code> - when Trilium frontend
starts up (or is refreshed), but not on mobile.</li>
<li><code spellcheck="false">mobileStartup</code> - when Trilium frontend starts
up (or is refreshed), on mobile.</li>
</ul>
<aside class="admonition note">
<p>Backend scripts have more powerful triggering conditions, for example
they can run automatically on a hourly or daily basis, but also on events
such as when a note is created or an attribute is modified. See the server-side&nbsp;
<a
class="reference-link" href="#root/_help_GPERMystNGTB">Events</a>&nbsp;for more information.</p>
</aside>
<h2>Widgets</h2>
<p>Conversely to scripts, widgets do have some specific requirements in order
to work. A widget must:</p>
<p>Widgets require a certain format in order for Trilium to be able to integrate
them into the UI.</p>
<ul>
<li>Extend <a href="https://triliumnext.github.io/Notes/frontend_api/BasicWidget.html">BasicWidget</a> or
one of it's subclasses.</li>
<li>Create a new instance and assign it to <code spellcheck="false">module.exports</code>.</li>
<li>Define a <code spellcheck="false">parentWidget</code> member to determine
where it should be displayed.</li>
<li>Define a <code spellcheck="false">position</code> (integer) that determines
the location via sort order.</li>
<li>Have a <code spellcheck="false">#widget</code> attribute on the containing
note.</li>
<li>Create, render, and return your element in the render function.
<ul>
<li>For <a href="https://triliumnext.github.io/Notes/frontend_api/BasicWidget.html">BasicWidget</a> and
<a
href="https://triliumnext.github.io/Notes/frontend_api/NoteContextAwareWidget.html">NoteContextAwareWidget</a>you should create <code spellcheck="false">this.$widget</code> and
render it in <code spellcheck="false">doRender()</code>.</li>
<li>For <a href="https://triliumnext.github.io/Notes/frontend_api/RightPanelWidget.html">RightPanelWidget</a> the
<code
spellcheck="false">this.$widget</code>and <code spellcheck="false">doRender()</code> are already
handled and you should instead return the value in <code spellcheck="false">doRenderBody()</code>.</li>
</ul>
</li>
</ul>
<h3>parentWidget</h3>
<ul>
<li><code spellcheck="false">left-pane</code> - This renders the widget on
the left side of the screen where the note tree lives.</li>
<li><code spellcheck="false">center-pane</code> - This renders the widget in
the center of the layout in the same location that notes and splits appear.</li>
<li><code spellcheck="false">note-detail-pane</code> - This renders the widget <em>with</em> the
note in the center pane. This means it can appear multiple times with splits.</li>
<li><code spellcheck="false">right-pane</code> - This renders the widget to
the right of any opened notes.</li>
<li>For legacy widgets, the script note must export a <code spellcheck="false">BasicWidget</code> or
a derived one (see&nbsp;<a class="reference-link" href="#root/_help_GhurYZjh8e1V">Note context aware widget</a>&nbsp;or&nbsp;
<a
class="reference-link" href="#root/_help_M8IppdwVHSjG">Right pane widget</a>).</li>
<li>For Preact widgets, a built-in helper called <code spellcheck="false">defineWidget</code> needs
to be used.</li>
</ul>
<p>For more information, see&nbsp;<a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>.</p>
<h2>Script API</h2>
<p>The front-end API of Trilium is available to all scripts running in the
front-end context as global variable <code spellcheck="false">api</code>.
For a reference of the API, see&nbsp;<a class="reference-link" href="#root/_help_Q2z6av6JZVWm">Frontend API</a>.</p>
<h3>Tutorial</h3>
<p>For more information on building widgets, take a look at <a href="#root/_help_SynTBQiBsdYJ">Widget Basics</a>.</p>

View File

@@ -71,7 +71,7 @@ class="ck-table-resized">
</colgroup>
<thead>
<tr>
<th>Value for <code>parentWidget</code>
<th>Value for <code spellcheck="false">parentWidget</code>
</th>
<th>Description</th>
<th>Sample widget</th>
@@ -80,15 +80,15 @@ class="ck-table-resized">
</thead>
<tbody>
<tr>
<th><code>left-pane</code>
<th><code spellcheck="false">left-pane</code>
</th>
<td>Appears within the same pane that holds the&nbsp;<a class="reference-link"
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</td>
<td>Same as above, with only a different <code>parentWidget</code>.</td>
<td>Same as above, with only a different <code spellcheck="false">parentWidget</code>.</td>
<td>None.</td>
</tr>
<tr>
<th><code>center-pane</code>
<th><code spellcheck="false">center-pane</code>
</th>
<td>In the content area. If a split is open, the widget will span all of the
splits.</td>
@@ -96,7 +96,7 @@ class="ck-table-resized">
<td>None.</td>
</tr>
<tr>
<th><code>note-detail-pane</code>
<th><code spellcheck="false">note-detail-pane</code>
</th>
<td>
<p>In the content area, inside the note detail area. If a split is open,
@@ -107,16 +107,19 @@ class="ck-table-resized">
</td>
<td>
<ul>
<li>The widget must export a <code>class</code> and not an instance of the class
(e.g. <code>no new</code>) because it needs to be multiplied for each note,
so that splits work correctly.</li>
<li>Since the <code>class</code> is exported instead of an instance, the <code>parentWidget</code> getter
must be <code>static</code>, otherwise the widget is ignored.</li>
<li>The widget must export a <code spellcheck="false">class</code> and not an
instance of the class (e.g. <code spellcheck="false">no new</code>) because
it needs to be multiplied for each note, so that splits work correctly.</li>
<li
>Since the <code spellcheck="false">class</code> is exported instead of an
instance, the <code spellcheck="false">parentWidget</code> getter must be
<code
spellcheck="false">static</code>, otherwise the widget is ignored.</li>
</ul>
</td>
</tr>
<tr>
<th><code>right-pane</code>
<th><code spellcheck="false">right-pane</code>
</th>
<td>In the&nbsp;<a class="reference-link" href="#root/_help_RnaPdbciOfeq">Right Sidebar</a>,
as a dedicated section.</td>
@@ -124,8 +127,8 @@ class="ck-table-resized">
</td>
<td>
<ul>
<li>Although not mandatory, it's best to use a <code>RightPanelWidget</code> instead
of a <code>BasicWidget</code> or a <code>NoteContextAwareWidget</code>.</li>
<li>Although not mandatory, it's best to use a <code spellcheck="false">RightPanelWidget</code> instead
of a <code spellcheck="false">BasicWidget</code> or a <code spellcheck="false">NoteContextAwareWidget</code>.</li>
</ul>
</td>
</tr>
@@ -143,4 +146,13 @@ class="ck-table-resized">
to the&nbsp;<a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>.
See&nbsp;<a class="reference-link" href="#root/_help_4Gn3psZKsfSm">Launch Bar Widgets</a>&nbsp;for
more information.</p>
<h2>Custom position</h2>
<h2>Custom position</h2>
<p>The position of a custom widget is defined via a <code spellcheck="false">position</code> integer.</p>
<p>In legacy widgets:</p><pre><code class="language-text-x-trilium-auto">class MyWidget extends api.BasicWidget {
// [..
get position() { return 10; }
}</code></pre>
<p>In Preact widgets:</p><pre><code class="language-text-x-trilium-auto">export default defineWidget({
// [...]
position: 10
});</code></pre>

View File

@@ -5,16 +5,17 @@
<p>Unlike&nbsp;<a class="reference-link" href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>,
the process of setting up a launch bar widget is slightly different:</p>
<ol>
<li>Create a Code note of type <em>JavaScript (front-end)</em>.
<li>Create a Code note of type <em>JavaScript (front-end)</em> or JSX (for Preact-based
widgets).
<ul>
<li>The script itself uses the same concepts as&nbsp;<a class="reference-link"
href="#root/_help_MgibgPcfeuGz">Custom Widgets</a>, including the use of a
<code
spellcheck="false">NoteContextAwareWidget</code>or a <code spellcheck="false">BasicWidget</code> (according
to needs).</li>
<li>As examples, see&nbsp;<a class="reference-link" href="#root/_help_IPArqVfDQ4We">Note Title Widget</a>&nbsp;and&nbsp;
<a
class="reference-link" href="#root/_help_gcI7RPbaNSh3">Analog Watch</a>.</li>
<li>As examples in both legacy and Preact format, see&nbsp;<a class="reference-link"
href="#root/_help_IPArqVfDQ4We">Note Title Widget</a>&nbsp;and&nbsp;<a class="reference-link"
href="#root/_help_gcI7RPbaNSh3">Analog Watch</a>.</li>
</ul>
</li>
<li>Don't set <code spellcheck="false">#widget</code>, as that attribute is
@@ -25,5 +26,5 @@
<li>Give the newly created launcher a name (and optionally a name).</li>
<li>In the&nbsp;<a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>&nbsp;section,
modify the <em>widget</em> field to point to the newly created note.</li>
<li>Refresh the UI.</li>
<li><a href="#root/_help_s8alTXmpFR61">Refresh</a> the UI.</li>
</ol>

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