Compare commits

...

264 Commits

Author SHA1 Message Date
Elian Doran
06eb30c69d chore(release): prepare for v0.99.4 2025-11-09 21:29:21 +02:00
Elian Doran
b9b5c13d9c fix(client): ribbon adapter not working 2025-11-09 21:14:28 +02:00
Elian Doran
8e697d0578 fix(ribbon): not dismissing active tab after change 2025-11-09 20:17:13 +02:00
Elian Doran
6f245ec8d5 fix(ribbon): not refreshing if note is temporarily edited 2025-11-09 20:15:06 +02:00
Elian Doran
532df6559a fix(ribbon): formatting toolbar displayed in read-only notes 2025-11-09 20:15:06 +02:00
Adorian Doran
8589f7f164 style/note list widget: fix broken CSS selector 2025-11-09 19:50:48 +02:00
Adorian Doran
4eadf40e20 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-11-09 19:35:59 +02:00
Adorian Doran
7b9303b392 style/zen mode: do not show an empty toolbar container when the toolbar is not actually available 2025-11-09 19:35:47 +02:00
Elian Doran
521152ec0e Translations update from Hosted Weblate (#7661) 2025-11-09 17:50:54 +02:00
Hosted Weblate
2ff746253d 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/
2025-11-09 16:39:21 +01:00
Elian Doran
c270aef738 Translations update from Hosted Weblate (#7660) 2025-11-09 17:39:06 +02:00
Sir-Ivysaur
d0881c09ed Translated using Weblate (Indonesian)
Currently translated at 1.9% (3 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2025-11-09 16:15:11 +01:00
Adorian Doran
b905c1d03a style: different appearance tweaks 2025-11-09 17:10:23 +02:00
Adorian Doran
f03448bae4 style/gutter: tweak color on horizontal layout when background effects are enabled 2025-11-09 16:32:32 +02:00
Adorian Doran
f8ac09df38 style/right pane: tweak appearance 2025-11-09 16:16:37 +02:00
Adorian Doran
15c329c331 style/scrolling container widget: fix CSS selector 2025-11-09 15:51:29 +02:00
Adorian Doran
f6afc0b718 style/scrolling container widget: improve full-height widget handling 2025-11-09 15:46:57 +02:00
Adorian Doran
ecfa333491 style/note cards: remove shadow 2025-11-09 14:47:31 +02:00
Adorian Doran
21b0ef9554 style/note cards: improve style, remove no longer unused CSS variables 2025-11-09 14:43:59 +02:00
Adorian Doran
2f3be96dff style/info bar: improve style 2025-11-09 14:17:04 +02:00
Adorian Doran
4ba7907bee client/content header container: fix parent detection 2025-11-09 14:07:27 +02:00
Elian Doran
afa92551ea fix(tree): type issues & error if active node is empty 2025-11-09 11:16:35 +02:00
Elian Doran
cc0646e79c style(next): remove alignment of last toolbar group 2025-11-09 10:35:03 +02:00
Elian Doran
4c8f20be9a ignore toggle tray command if tray is disabled (#7654) 2025-11-09 09:45:37 +02:00
Elian Doran
3407528c03 chore(deps): update softprops/action-gh-release action to v2.4.2 (#7657) 2025-11-09 09:44:45 +02:00
Elian Doran
70575a00cb Translations update from Hosted Weblate (#7656) 2025-11-09 09:34:41 +02:00
renovate[bot]
74ba4b9ee5 chore(deps): update softprops/action-gh-release action to v2.4.2 2025-11-09 00:42:36 +00:00
Francis C.
706abeb307 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1621 of 1621 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-11-09 01:41:35 +01:00
Francis C.
7158c48831 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1621 of 1621 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-11-09 01:41:35 +01:00
Adorian Doran
78e2814068 UI improvements (#7655) 2025-11-09 02:41:20 +02:00
Adorian Doran
7659224e3a style/read-only note info bar: fix zen mode appearance 2025-11-09 02:26:37 +02:00
Adorian Doran
baff349fa2 client: refactor 2025-11-09 02:23:57 +02:00
Adorian Doran
50869d29db client/shared note info bar: improve appearance 2025-11-09 02:14:43 +02:00
Adorian Doran
c4603fce25 client/shared note message: convert to an info bar 2025-11-09 01:56:52 +02:00
Adorian Doran
220aab2b76 client/read-only note info bar: refactor 2025-11-09 01:35:33 +02:00
Adorian Doran
285a7253e3 client: make the info bar part of the scrollable content, prevent overlapping with the floating buttons 2025-11-09 01:19:45 +02:00
Adorian Doran
d8d80ed936 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-08 21:25:39 +02:00
Elian Doran
3463cb83a0 fix(collections/board): cloned notes appearing twice (closes #6786) 2025-11-08 20:17:56 +02:00
Elian Doran
b0bd60b9a4 feat(collections/calendar): use formatting locale 2025-11-08 18:01:58 +02:00
contributor
3d70a0534b ignore toggle tray command if tray is disabled 2025-11-08 13:03:42 +02:00
Elian Doran
53805e9c49 fix(server/notes): images not saved on duplication (fixes #7471) 2025-11-08 11:27:18 +02:00
Elian Doran
0e95610d4e fix(server): attachments not copied for templates (closes #7612) 2025-11-08 11:18:03 +02:00
Elian Doran
051e2b4eef Send global shortcut to current window (#7645) 2025-11-08 08:31:17 +02:00
Elian Doran
1d750bde64 chore(deps): update dependency vite to v7.2.2 (#7647) 2025-11-08 08:28:13 +02:00
Elian Doran
4476615d33 fix(deps): update dependency marked to v16.4.2 (#7649) 2025-11-08 08:27:36 +02:00
Elian Doran
bbcc16daab fix(deps): update dependency i18next to v25.6.1 (#7648) 2025-11-08 08:27:05 +02:00
Elian Doran
457dd070c6 chore(deps): update dependency rcedit to v5 (#7651) 2025-11-08 08:26:05 +02:00
Elian Doran
ce229dd6f5 chore(deps): update dependency electron to v38.6.0 (#7650) 2025-11-08 08:22:22 +02:00
renovate[bot]
2e59d9d7bc chore(deps): update dependency rcedit to v5 2025-11-08 01:39:22 +00:00
renovate[bot]
bbe96c3967 chore(deps): update dependency electron to v38.6.0 2025-11-08 01:39:17 +00:00
renovate[bot]
d95eb9f5d3 fix(deps): update dependency marked to v16.4.2 2025-11-08 01:38:37 +00:00
renovate[bot]
d75279316a fix(deps): update dependency i18next to v25.6.1 2025-11-08 01:37:54 +00:00
renovate[bot]
7f6be13a18 chore(deps): update dependency vite to v7.2.2 2025-11-08 01:37:08 +00:00
Adorian Doran
2f74b40095 client/read only note info bar: refactor 2025-11-07 23:45:48 +02:00
Adorian Doran
a844e1faab client: fix typo 2025-11-07 21:19:11 +02:00
contributor
f629f564cd add reusable showAndFocusWindow function 2025-11-07 21:17:34 +02:00
contributor
9a5f2f8d3b make global shortcut work with windows in system tray 2025-11-07 21:17:34 +02:00
Adorian Doran
9bccc72668 client/options/keep content centered: simplify the inner workings 2025-11-07 21:15:51 +02:00
contributor
a29597a4bf add safety check to ensure electron window is not destroyed 2025-11-07 20:25:16 +02:00
contributor
44b34d1ea0 send global shortcut to current window, not the first one 2025-11-07 20:25:09 +02:00
Adorian Doran
c617c84d86 style/options: properly align cards when content centering is enabled 2025-11-07 19:02:59 +02:00
Adorian Doran
b38780755a client: add a CSS class to identify option splits 2025-11-07 19:01:50 +02:00
Adorian Doran
78a54fa9f7 client: refactor 2025-11-07 18:23:30 +02:00
Adorian Doran
fa64ca2c93 client: add an option to center the content 2025-11-07 18:17:28 +02:00
Elian Doran
71d7403690 feat(print): support CSS for collections too 2025-11-07 17:48:37 +02:00
Elian Doran
e4cd946ea8 docs(user): fix wrong page for documentation 2025-11-07 17:31:50 +02:00
Elian Doran
cedd1c4789 docs(user): document custom print CSS 2025-11-07 17:25:14 +02:00
Elian Doran
7a677cff5f feat(print): allow custom CSS (closes #7608) 2025-11-07 17:25:05 +02:00
Adorian Doran
2d03dd22e3 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-07 17:06:21 +02:00
Elian Doran
e28da416ba Translations update from Hosted Weblate (#7639) 2025-11-06 22:59:32 +02:00
green
28c0ef52f3 Translated using Weblate (Japanese)
Currently translated at 100.0% (1621 of 1621 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-11-06 20:28:01 +01:00
Unknown
9464a64d64 Translated using Weblate (Spanish)
Currently translated at 99.7% (1617 of 1621 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2025-11-06 20:28:00 +01:00
SngAbc
c3af6a6aa2 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.7% (1617 of 1621 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-11-06 20:28:00 +01:00
Elian Doran
a4281fe26f fix(deps): update ckeditor monorepo to v47.2.0 (#7634) 2025-11-06 09:04:07 +02:00
Adorian Doran
5c0d6e1fef style/alert bar: update message 2025-11-06 08:55:08 +02:00
Adorian Doran
23f1103822 style/alert bar: update message 2025-11-06 08:45:53 +02:00
renovate[bot]
b45ee6879c fix(deps): update ckeditor monorepo to v47.2.0 2025-11-06 06:34:53 +00:00
Elian Doran
a8116aa264 chore(deps): update dependency vite to v7.2.1 (#7633) 2025-11-06 08:31:51 +02:00
Elian Doran
db3960a23e fix(deps): update dependency mind-elixir to v5.3.5 (#7632) 2025-11-06 08:31:14 +02:00
Elian Doran
a43e08500e chore(deps): update dependency openai to v6.8.1 (#7631) 2025-11-06 08:30:45 +02:00
Adorian Doran
9011d648b5 client: refactor 2025-11-06 08:26:21 +02:00
Adorian Doran
30c1708979 style/alert bar: update the background color for the light color scheme 2025-11-06 08:15:59 +02:00
Adorian Doran
1a55d3433d client/floating buttons: hide the "Edit note" button for the moment 2025-11-06 08:08:56 +02:00
Adorian Doran
bec47c0bb2 client/note context menu: add "Edit note" for read-only notes 2025-11-06 07:49:57 +02:00
Adorian Doran
4fdb502a19 client: allow the isNoteReadOnly hook operate on a note and context other of the current one 2025-11-06 07:48:11 +02:00
renovate[bot]
e9ccd52fd5 chore(deps): update dependency vite to v7.2.1 2025-11-06 04:54:41 +00:00
renovate[bot]
dcd30972bd fix(deps): update dependency mind-elixir to v5.3.5 2025-11-06 02:49:14 +00:00
renovate[bot]
4578541fa8 chore(deps): update dependency openai to v6.8.1 2025-11-06 02:48:32 +00:00
Adorian Doran
914fa3625f client/read only note info bar: style tweaks for zen mode 2025-11-06 02:01:30 +02:00
Adorian Doran
5e3ffc12ce style/zen mode: fix content refusing to get narrower than the max content width preference 2025-11-06 02:01:03 +02:00
Adorian Doran
aa3a8d19ae client: increase the default size limit for auto read-only 2025-11-06 01:46:55 +02:00
Adorian Doran
a57c237c69 client/read only note info bar: style tweaks for zen mode 2025-11-06 01:34:39 +02:00
Adorian Doran
b52e615f0c client/read only note info bar: add support for zen mode 2025-11-06 01:08:26 +02:00
Adorian Doran
95e5c2563e client/read only note info bar: refactor 2025-11-06 00:45:16 +02:00
Adorian Doran
33be7f828b client/read only note info bar: fix file name 2025-11-06 00:24:27 +02:00
Adorian Doran
d23d37baac client/read only note info bar: add translation string 2025-11-06 00:22:00 +02:00
Adorian Doran
dda8b2795b client/read only note info bar: tweak style 2025-11-06 00:17:14 +02:00
Adorian Doran
0b9eb6c532 client/read only note info bar: shorten link caption 2025-11-05 23:56:18 +02:00
Adorian Doran
728f574eac Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-05 23:52:53 +02:00
Adorian Doran
8e90826aef client: add a read-only note info bar 2025-11-05 23:52:38 +02:00
Elian Doran
5e5e0afcf0 Translations update from Hosted Weblate (#7630) 2025-11-05 23:36:01 +02:00
kamykO
de85d1f0df Translated using Weblate (Polish)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pl/
2025-11-05 21:17:29 +00:00
Elian Doran
b287b892e1 Merge branch 'main' of github.com:TriliumNext/Trilium 2025-11-05 23:17:19 +02:00
Elian Doran
a0edf00caa feat(collections/calendar): context menu option to delete event 2025-11-05 23:17:15 +02:00
Elian Doran
218b9404fc fix(print): missing admonition icons 2025-11-05 22:08:31 +02:00
Elian Doran
54af120e96 fix(search): resolve issue when using = operator in search (#7268) 2025-11-05 21:51:44 +02:00
Elian Doran
ba61ab18ff docs: sync 2025-11-05 21:01:39 +02:00
Elian Doran
2e14522f86 Merge remote-tracking branch 'origin/main' 2025-11-05 21:00:28 +02:00
Elian Doran
4ae38ac5d6 docs(user): widget basics missing template (closes #6148) 2025-11-05 18:32:34 +02:00
Adorian Doran
d5cb6a86c8 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-05 17:35:10 +02:00
Elian Doran
a577fd45e2 fix(quick_search): enable Numpad Enter to trigger quick search (#7624) 2025-11-05 14:49:01 +02:00
Elian Doran
977284fe57 docs(user): document read-only database (closes #4341) 2025-11-05 10:20:35 +02:00
SiriusXT
26ea43d604 chore(test): add vitest for NumpadEnter 2025-11-05 16:01:26 +08:00
SiriusXT
4cb328bdb3 Merge branch 'main' into quick_search 2025-11-05 15:41:13 +08:00
SiriusXT
16785a5c0b fix(quick_search): enable Numpad Enter to trigger quick search 2025-11-05 15:40:12 +08:00
Elian Doran
d271fe7fdd docs(demo): clean up expansion state 2025-11-05 09:26:14 +02:00
Elian Doran
88b9709f15 docs(user): launch bar clarifications in the LLM doc 2025-11-05 08:26:34 +02:00
Elian Doran
f55edabe92 chore(edit-docs): set different port to edit-demo 2025-11-05 08:19:37 +02:00
Elian Doran
2b983f871e docs(demo): link to old documentation (closes #5211) 2025-11-05 08:19:25 +02:00
Elian Doran
ab298cbb3b docs(user): how to set data dir on Windows (closes #4853) 2025-11-05 08:09:15 +02:00
Elian Doran
abeeea584f chore(deps): update dependency sax to v1.4.3 (#7622) 2025-11-05 07:47:19 +02:00
Elian Doran
4d5597cc75 chore(deps): update dependency @redocly/cli to v2.11.0 (#7623) 2025-11-05 07:46:39 +02:00
Elian Doran
c684712141 chore(deps): update dependency axios to v1.13.2 (#7621) 2025-11-05 07:45:33 +02:00
renovate[bot]
a8bb301296 chore(deps): update dependency @redocly/cli to v2.11.0 2025-11-05 01:34:43 +00:00
renovate[bot]
d5bfa466a2 chore(deps): update dependency sax to v1.4.3 2025-11-05 01:34:00 +00:00
renovate[bot]
7651c58c47 chore(deps): update dependency axios to v1.13.2 2025-11-05 01:33:17 +00:00
Elian Doran
c2ce36d963 docs(user): mention structure difference 2025-11-04 23:13:34 +02:00
Elian Doran
3359ff5470 docs(dev): update database structure 2025-11-04 22:53:42 +02:00
Elian Doran
421c1f257e fix(electron): port-in-use dialog shown when opening a new window | (#7595) 2025-11-04 19:29:46 +02:00
Elian Doran
97e87741ee Add multi-branch prefix editing support (#7598) 2025-11-04 19:17:31 +02:00
Elian Doran
193caf8c21 chore: clean up generated type definitions 2025-11-04 19:07:42 +02:00
Elian Doran
d521bda6ab Add comprehensive technical and architectural documentation (#7600) 2025-11-04 18:50:48 +02:00
Elian Doran
b80cb22985 chore(deps): clean up package lock 2025-11-04 18:20:12 +02:00
Elian Doran
7131d44d03 docs(dev): integrate rest of the documentation 2025-11-04 18:16:20 +02:00
Elian Doran
7369f9d532 docs(dev): integrate architecture guide 2025-11-04 17:24:38 +02:00
Elian Doran
58ac325634 docs(dev): integrate some of the architecture notes 2025-11-04 15:51:54 +02:00
Elian Doran
579b2ce76e Merge remote-tracking branch 'origin/main' into copilot/add-technical-documentation 2025-11-04 15:19:49 +02:00
copilot-swe-agent[bot]
0494032fb5 Convert ASCII diagrams to Mermaid.js format
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-04 12:35:56 +00:00
Elian Doran
48853555f5 Fix documentation errors, broken links, and standardize to American English (#7599) 2025-11-04 14:31:39 +02:00
Elian Doran
0cd6f0d267 Merge branch 'main' into copilot/improve-user-documentation 2025-11-04 14:30:51 +02:00
Elian Doran
0ae4defc6d docs(dev): reorganize and clean up technical documentation 2025-11-04 10:55:48 +02:00
Elian Doran
db644f20ed docs(dev): releasing a new version 2025-11-04 09:23:56 +02:00
Elian Doran
59a2ef7527 chore(build-docs): add preview entrypoint 2025-11-04 09:15:27 +02:00
Elian Doran
757a046474 feat(build-docs): add root 404 page 2025-11-04 09:13:12 +02:00
Elian Doran
aeb0f44a43 chore(website): fix typecheck issue 2025-11-04 08:55:05 +02:00
Elian Doran
5186ea3fff chore(deps): duplicate dependency in lock 2025-11-04 08:51:54 +02:00
Elian Doran
70a4feff50 edited notes: remove comma for flexible styling (#7609) 2025-11-04 08:43:27 +02:00
Elian Doran
91f85e6675 chore(deps): update typescript-eslint monorepo to v8.46.3 (#7614) 2025-11-04 08:41:51 +02:00
Elian Doran
0cb989e74f chore(deps): update dependency openai to v6.8.0 (#7618) 2025-11-04 07:42:44 +02:00
Elian Doran
d4e31e9d98 fix(deps): update dependency eslint-linter-browserify to v9.39.1 (#7615) 2025-11-04 07:40:52 +02:00
Elian Doran
6c3b5314c8 chore(deps): update dependency sax to v1.4.2 (#7613) 2025-11-04 07:40:20 +02:00
renovate[bot]
3faac9f26e chore(deps): update typescript-eslint monorepo to v8.46.3 2025-11-04 05:35:53 +00:00
Elian Doran
c31ac1a6ee fix(deps): update dependency react-i18next to v16.2.4 (#7616) 2025-11-04 07:34:35 +02:00
Elian Doran
9eff6ad4c2 fix(deps): update eslint monorepo to v9.39.1 (#7617) 2025-11-04 07:33:26 +02:00
Elian Doran
9deb7ba4e9 Translations update from Hosted Weblate (#7619) 2025-11-04 07:30:59 +02:00
Francis C.
93d77ca06e Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/zh_Hant/
2025-11-04 01:51:28 +00:00
Giovi
a42daccc2e Translated using Weblate (Italian)
Currently translated at 99.3% (151 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/it/
2025-11-04 01:51:26 +00:00
Giovi
33c64b604e Translated using Weblate (Italian)
Currently translated at 99.7% (386 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/it/
2025-11-04 01:51:21 +00:00
Giovi
f89c14b35a Translated using Weblate (Italian)
Currently translated at 100.0% (1617 of 1617 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-11-04 01:51:16 +00:00
Francis C.
1fa3420abe Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (387 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/zh_Hant/
2025-11-04 01:51:14 +00:00
renovate[bot]
f8b8edd5aa chore(deps): update dependency openai to v6.8.0 2025-11-04 01:42:30 +00:00
renovate[bot]
4c90319c9e fix(deps): update eslint monorepo to v9.39.1 2025-11-04 01:41:49 +00:00
renovate[bot]
3b531544a3 fix(deps): update dependency react-i18next to v16.2.4 2025-11-04 01:41:09 +00:00
renovate[bot]
25d9695db0 fix(deps): update dependency eslint-linter-browserify to v9.39.1 2025-11-04 01:40:28 +00:00
renovate[bot]
caf88473f6 chore(deps): update dependency sax to v1.4.2 2025-11-04 01:38:35 +00:00
SngAbc
b32dc18cf6 fix: incorrect options description 2025-11-04 09:36:34 +08:00
SiriusXT
5a7349121a fix: incorrect options description 2025-11-04 09:24:18 +08:00
Adorian Doran
dcc5b9f422 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-03 23:44:18 +02:00
Adorian Doran
0b01890a11 client: add a launcher to open the command palette 2025-11-03 23:44:06 +02:00
Elian Doran
686c8936cb docs(dev): integrate more of the old documentation 2025-11-03 22:48:14 +02:00
Elian Doran
d9071f2d8e docs(dev): update recent technical documentation 2025-11-03 22:16:15 +02:00
perf3ct
2a0472ae42 fix(search): resolve compilation issue due to variables with the same name 2025-11-03 11:44:43 -08:00
Adorian Doran
8c2354df71 client: format code 2025-11-03 18:16:41 +02:00
Adorian Doran
d650b801e6 client: add a launcher for zen mode 2025-11-03 17:52:08 +02:00
Adorian Doran
fbb27b512e style/zen mode/translucent title bar: fix broken title bar in multi-split windows 2025-11-03 16:09:05 +02:00
Adorian Doran
3ae7bd59ec style/zen mode: disable translucency for the title bar when backdrop effects are turned off 2025-11-03 15:59:34 +02:00
Adorian Doran
55f7a26634 style/zen mode: use translucency for the title bar 2025-11-03 15:53:05 +02:00
Adorian Doran
aa7f01313a style/zen mode: improve scrolling for text notes 2025-11-03 15:24:09 +02:00
Adorian Doran
6f82c283e9 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-03 15:13:21 +02:00
contributor
aa7ecaf684 edited notes: space instead of comma for flexible styling 2025-11-03 15:13:06 +02:00
copilot-swe-agent[bot]
50a69248a7 Fix keyboard shortcut support and address code review feedback
- Add editBranchPrefixCommand in note_tree.ts to support F2 keyboard shortcut with multi-selection
- Extract CSS to separate branch_prefix.css file
- Remove hard-coded color, use CSS class instead
- Fix translation key usage to keep t() calls visible in IDE
- Remove all inline styles

Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-03 08:01:56 +00:00
SngAbc
5782e58db1 Merge branch 'main' into electron_newwindow 2025-11-03 09:49:46 +08:00
Adorian Doran
e8bf12c4ab style/zen mode: tweak text vertical centering offset 2025-11-03 00:40:00 +02:00
Adorian Doran
344f2d819e style/zen mode: hide the shared note info bar 2025-11-03 00:19:40 +02:00
Adorian Doran
d2e9101675 style/zen mode/fixed formatting toolbar: allow items to overflow if narrow window 2025-11-03 00:14:44 +02:00
copilot-swe-agent[bot]
82e5de2261 Add input validation for prefix to address security concerns
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:06:14 +00:00
copilot-swe-agent[bot]
ff4cd7eae5 Fix grammar: 'type of notes' → 'type of note' in Collections.md
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:04:34 +00:00
copilot-swe-agent[bot]
a62c9a1a2f Fix broken wiki-style link in Scripting.md
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:03:29 +00:00
copilot-swe-agent[bot]
cf406383c3 Standardize spelling to American English (behavior, categorizing)
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:01:44 +00:00
copilot-swe-agent[bot]
07fe42d04e Add quick reference guide for technical documentation
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:01:17 +00:00
copilot-swe-agent[bot]
5b8bb8587d Address code review feedback - add logging and constant for virtual branches
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 22:00:54 +00:00
copilot-swe-agent[bot]
154492e454 Add comprehensive technical and architectural documentation
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:59:29 +00:00
copilot-swe-agent[bot]
b1729ad7ec Fix additional clarity and grammar issues
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:59:22 +00:00
copilot-swe-agent[bot]
daec11b981 Fix additional grammar and clarity issues in documentation
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:57:26 +00:00
copilot-swe-agent[bot]
7cdd8ffbe2 Add gitignore for build artifacts and verify tests pass
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:56:46 +00:00
copilot-swe-agent[bot]
25ac9e2aa1 Fix spelling, grammar, and broken links in documentation
Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:54:40 +00:00
copilot-swe-agent[bot]
4fc434a52e Implement multi-branch prefix editing functionality
- Add setPrefixBatch API endpoint to handle batch prefix updates
- Update branch_prefix dialog to support multiple branches
- Remove noSelectedNotes constraint from edit branch prefix menu
- Add translations for multi-branch prefix editing

Co-authored-by: eliandoran <21236836+eliandoran@users.noreply.github.com>
2025-11-02 21:52:02 +00:00
copilot-swe-agent[bot]
3e0d1bfa44 Initial plan 2025-11-02 21:47:57 +00:00
copilot-swe-agent[bot]
3b02eb8851 Initial plan 2025-11-02 21:47:17 +00:00
copilot-swe-agent[bot]
4c5b2a7c75 Initial plan 2025-11-02 21:39:44 +00:00
Adorian Doran
e6d2009605 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 23:00:20 +02:00
Adorian Doran
5d706a88d8 style/zen mode/fixed formatting toolbar: fix the position in multi-split mode 2025-11-02 23:00:00 +02:00
Adorian Doran
c7c7e05106 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 21:12:36 +02:00
Adorian Doran
e250107202 style/zen mode: fix collections not showing up 2025-11-02 21:12:23 +02:00
Adorian Doran
c141cbcf07 style/zen mode/fixed formatting toolbar: improve pointer events handling 2025-11-02 19:08:59 +02:00
Adorian Doran
8e83562e6a style/zen mode/fixed formatting toolbar: fix backdrop blur 2025-11-02 18:37:02 +02:00
Adorian Doran
08197f56d0 style/zen mode/fixed formatting toolbar: add entrance animation 2025-11-02 18:22:07 +02:00
Adorian Doran
ff0d8a70ad style/zen mode/fixed formatting toolbar: add backdrop blur 2025-11-02 18:08:05 +02:00
Adorian Doran
605b317f29 style/zen mode: restyle the fixed formatting toolbar, relocate at the bottom of the screen 2025-11-02 17:21:27 +02:00
SngAbc
6f0a264869 Merge branch 'main' into electron_newwindow 2025-11-02 23:06:58 +08:00
Adorian Doran
6a89e096e5 style/zen mode: hide promoted attributes 2025-11-02 16:37:19 +02:00
Adorian Doran
e4e0f7619b Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-02 16:34:18 +02:00
SiriusXT
f3f07cdd28 fix(electron): port-in-use dialog shown when opening a new window 2025-11-02 20:30:01 +08:00
SiriusXT
ef82c3d48b fix(electron): port-in-use dialog shown when opening a new window | 2025-11-02 16:48:05 +08:00
Adorian Doran
214ba5265a Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-11-01 18:20:08 +02:00
Adorian Doran
ecb6dc7923 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-31 11:33:07 +02:00
Adorian Doran
131fb43ab7 style/zen mode: tweak 2025-10-30 02:48:38 +02:00
Adorian Doran
8a76fdb8d1 style/zen mode: center content, increase font size 2025-10-30 02:30:51 +02:00
Adorian Doran
8e3dbb2f65 client: remove redundant code 2025-10-30 02:27:57 +02:00
Adorian Doran
8b4fee1680 client: make code notes full-width 2025-10-30 01:03:22 +02:00
Adorian Doran
a370b52614 client: remove no longer used translation strings 2025-10-30 00:57:27 +02:00
Adorian Doran
4063229982 client: allow changing the max content width option without a frontend reload 2025-10-30 00:49:02 +02:00
Adorian Doran
1ef03b7a77 client: rework max content width handling 2025-10-30 00:23:41 +02:00
Adorian Doran
e510653edb Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 23:38:05 +02:00
Adorian Doran
a2c523def1 style: fix a bug preventing background effects to work properly 2025-10-29 20:28:53 +02:00
Adorian Doran
e3604edad7 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 19:59:30 +02:00
Adorian Doran
426d8296be Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-29 09:30:50 +02:00
Adorian Doran
947e43a615 style: allow custom themes to turn off background effects 2025-10-29 03:20:25 +02:00
Adorian Doran
0424fe4fba client/css var utility: add support for parsing boolean values 2025-10-29 03:13:16 +02:00
Adorian Doran
f789b69506 style: disable tab switching animation while background effects are active, improve performance 2025-10-29 02:39:25 +02:00
Adorian Doran
5df512a69c style: use background effects for empty note, refactor 2025-10-29 01:32:47 +02:00
Adorian Doran
2a5f329ada style: darken the main background color 2025-10-29 00:45:40 +02:00
Adorian Doran
4fe3944585 style/right panel: use own background color when background effects are active 2025-10-29 00:34:20 +02:00
Adorian Doran
98b8e97fd9 style/right panel: add translucent background when background effects are active 2025-10-29 00:20:50 +02:00
Adorian Doran
38a1cd0d35 style: tweak gutters 2025-10-28 23:55:51 +02:00
Adorian Doran
ae544a80c2 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-28 23:43:55 +02:00
Jon Fuller
8d88411fda Merge branch 'main' into fix/fix-equals-operator-in-search 2025-10-28 08:41:47 -07:00
Adorian Doran
ea45024559 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-28 09:51:32 +02:00
Adorian Doran
64d3589b40 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-25 22:42:20 +03:00
Adorian Doran
638cb4281e style/center pane: optimize the identification of options pages 2025-10-25 11:28:26 +03:00
Adorian Doran
1568908982 style/center pane: allow distinct background colors for note splits 2025-10-25 11:12:00 +03:00
Adorian Doran
4459561308 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-25 10:20:28 +03:00
Adorian Doran
3341e59a80 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-24 02:43:52 +03:00
Adorian Doran
74a805056b style/settings: use Mica for settings pages 2025-10-24 01:03:28 +03:00
Adorian Doran
f42e870de1 style/dropdowns: increase the radius of the backdrop blur 2025-10-23 20:28:36 +03:00
Adorian Doran
ca3964f8b7 style/floating buttons: tweak position 2025-10-23 20:18:52 +03:00
Adorian Doran
ddafda5f4e style/quick edit dialog: tweak the colors for the dark color scheme 2025-10-23 20:01:29 +03:00
Adorian Doran
40b08e1828 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-23 19:43:17 +03:00
Adorian Doran
5141f0a0d5 style/quick edit dialog: tweak the colors for the dark color scheme 2025-10-23 19:43:06 +03:00
Adorian Doran
8c165c0401 style/quick edit dialog: add support for the dark color scheme 2025-10-23 10:57:01 +03:00
Adorian Doran
b4dd40e128 style/quick edit dialog: refactor 2025-10-23 10:48:04 +03:00
Adorian Doran
535b960b76 style/quick edit dialog: ignore monochromatic custom colors 2025-10-23 10:44:23 +03:00
Adorian Doran
b58f37cd4a style/quick edit dialog: tint the dialog background, border and promoted attributes card according to the note's custom color 2025-10-23 10:18:52 +03:00
Adorian Doran
a01fb39599 client/quick edit dialog: make available the CSS variables with the custom color of the edited note 2025-10-23 10:16:18 +03:00
Adorian Doran
be15934b22 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-23 10:03:03 +03:00
Adorian Doran
96b3464f00 Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui-improvements 2025-10-22 21:53:07 +03:00
Adorian Doran
2470b0b334 style: tweak the appearance of the promoted attributes cards 2025-10-22 20:06:06 +03:00
Adorian Doran
4344687303 style: tweak the appearance of the promoted attributes cards 2025-10-22 19:58:42 +03:00
Adorian Doran
b4fe46eba3 style: tweak the appearance of option cards 2025-10-22 19:41:49 +03:00
perf3ct
8e227a6146 fix(search): make sure to highlight exact search results too 2025-10-21 14:35:31 -07:00
Jon Fuller
b03cb1ce1b Merge branch 'main' into fix/fix-equals-operator-in-search 2025-10-21 11:30:05 -07:00
perf3ct
fb0d971e48 fix(search): also support exact phrase matching such as ='test phrase' 2025-10-21 10:12:14 -07:00
perf3ct
4fa4112840 feat(search): also support the use of ="exact match search string" 2025-10-10 12:23:57 -07:00
perf3ct
50f0b88eff fix(search): resolve issue when using = operator in search 2025-10-10 09:51:52 -07:00
350 changed files with 10309 additions and 7637 deletions

View File

@@ -77,7 +77,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Publish release
uses: softprops/action-gh-release@v2.4.1
uses: softprops/action-gh-release@v2.4.2
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false
@@ -118,7 +118,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2.4.1
uses: softprops/action-gh-release@v2.4.2
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false

View File

@@ -127,7 +127,7 @@ jobs:
path: upload
- name: Publish stable release
uses: softprops/action-gh-release@v2.4.1
uses: softprops/action-gh-release@v2.4.2
with:
draft: false
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md

View File

@@ -41,12 +41,12 @@
"@types/node": "24.10.0",
"@types/yargs": "17.0.34",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.39.0",
"eslint": "9.39.1",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.5",
"lorem-ipsum": "2.0.8",
"rcedit": "4.0.1",
"rcedit": "5.0.0",
"rimraf": "6.1.0",
"tslib": "2.8.1"
},

View File

@@ -11,7 +11,7 @@
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.20.0",
"devDependencies": {
"@redocly/cli": "2.10.0",
"@redocly/cli": "2.11.0",
"archiver": "7.0.1",
"fs-extra": "11.3.2",
"react": "19.2.0",

View File

@@ -22,8 +22,9 @@ async function main() {
buildSwagger(context);
buildScriptApi(context);
// Copy index file.
// Copy index and 404 files.
cpSync(join(__dirname, "index.html"), join(context.baseDir, "index.html"));
cpSync(join(context.baseDir, "user-guide/404.html"), join(context.baseDir, "404.html"));
}
main();

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.99.3",
"version": "0.99.4",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -15,7 +15,7 @@
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.39.0",
"@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
@@ -43,7 +43,7 @@
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "16.5.0",
"i18next": "25.6.0",
"i18next": "25.6.1",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery.fancytree": "2.38.5",
@@ -53,13 +53,13 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "16.4.1",
"marked": "16.4.2",
"mermaid": "11.12.1",
"mind-elixir": "5.3.4",
"mind-elixir": "5.3.5",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"react-i18next": "16.2.3",
"react-i18next": "16.2.4",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",

View File

@@ -499,6 +499,10 @@ type EventMappings = {
noteIds: string[];
};
refreshData: { ntxId: string | null | undefined };
contentSafeMarginChanged: {
top: number;
noteContext: NoteContext;
}
};
export type EventListener<T extends EventNames> = {

View File

@@ -7,7 +7,6 @@ import protectedSessionService from "../services/protected_session.js";
import options from "../services/options.js";
import froca from "../services/froca.js";
import utils from "../services/utils.js";
import LlmChatPanel from "../widgets/llm_chat_panel.js";
import toastService from "../services/toast.js";
import noteCreateService from "../services/note_create.js";
@@ -171,7 +170,8 @@ export default class RootCommandExecutor extends Component {
}
toggleTrayCommand() {
if (!utils.isElectron()) return;
if (!utils.isElectron() || options.is("disableTray")) return;
const { BrowserWindow } = utils.dynamicRequire("@electron/remote");
const windows = BrowserWindow.getAllWindows() as Electron.BaseWindow[];
const isVisible = windows.every((w) => w.isVisible());

View File

@@ -1,47 +1,49 @@
import FlexContainer from "../widgets/containers/flex_container.js";
import TabRowWidget from "../widgets/tab_row.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteDetailWidget from "../widgets/note_detail.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
import SpacerWidget from "../widgets/spacer.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import ScrollPadding from "../widgets/scroll_padding.js";
import options from "../services/options.js";
import utils from "../services/utils.js";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import { applyModals } from "./layout_commons.js";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import SearchResult from "../widgets/search_result.jsx";
import ApiLog from "../widgets/api_log.jsx";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import ContentHeader from "../widgets/containers/content-header.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import FindWidget from "../widgets/find.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import GlobalMenu from "../widgets/buttons/global_menu.jsx";
import HighlightsListWidget from "../widgets/highlights_list.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import options from "../services/options.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import ScrollPadding from "../widgets/scroll_padding.js";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import SpacerWidget from "../widgets/spacer.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TabRowWidget from "../widgets/tab_row.js";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import ApiLog from "../widgets/api_log.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import NoteList from "../widgets/collections/NoteList.jsx";
import TocWidget from "../widgets/toc.js";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import utils from "../services/utils.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
export default class DesktopLayout {
@@ -129,12 +131,15 @@ export default class DesktopLayout {
.child(<CreatePaneButton />)
)
.child(<Ribbon />)
.child(<SharedInfo />)
.child(new WatchedFileUpdateStatusWidget())
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
.child(
new ScrollingContainer()
.filling()
.child(new ContentHeader()
.child(<ReadOnlyNoteInfoBar />)
.child(<SharedInfo />)
)
.child(new PromotedAttributesWidget())
.child(<SqlTableSchemas />)
.child(new NoteDetailWidget())

View File

@@ -1,32 +1,34 @@
import { applyModals } from "./layout_commons.js";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import FlexContainer from "../widgets/containers/flex_container.js";
import NoteTitleWidget from "../widgets/note_title.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import SharedInfoWidget from "../widgets/shared_info.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import type AppContext from "../components/app_context.js";
import TabRowWidget from "../widgets/tab_row.js";
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
import { applyModals } from "./layout_commons.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import MobileEditorToolbar from "../widgets/type_widgets/ckeditor/mobile_editor_toolbar.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteList from "../widgets/collections/NoteList.jsx";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import ContentHeader from "../widgets/containers/content-header.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import PromotedAttributesWidget from "../widgets/promoted_attributes.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import RootContainer from "../widgets/containers/root_container.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfoWidget from "../widgets/shared_info.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import TabRowWidget from "../widgets/tab_row.js";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import type AppContext from "../components/app_context.js";
const MOBILE_CSS = `
<style>
@@ -149,13 +151,16 @@ export default class MobileLayout {
.child(<NoteTitleWidget />)
.child(<MobileDetailMenu />)
)
.child(<SharedInfoWidget />)
.child(<FloatingButtons items={MOBILE_FLOATING_BUTTONS} />)
.child(new PromotedAttributesWidget())
.child(
new ScrollingContainer()
.filling()
.contentSized()
.child(new ContentHeader()
.child(<ReadOnlyNoteInfoBar />)
.child(<SharedInfoWidget />)
)
.child(new NoteDetailWidget())
.child(<NoteList media="screen" />)
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)

View File

@@ -137,7 +137,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
command: "editBranchPrefix",
keyboardShortcut: "editBranchPrefix",
uiIcon: "bx bx-rename",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
enabled: isNotRoot && parentNotSearch && notOptionsOrHelp
},
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },

View File

@@ -1,3 +1,5 @@
@import "boxicons/css/boxicons.min.css";
:root {
--print-font-size: 11pt;
--ck-content-color-image-caption-background: transparent !important;

View File

@@ -70,6 +70,9 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) {
});
})
);
// Check custom CSS.
await loadCustomCss(note);
}
load().then(() => requestAnimationFrame(onReady))
@@ -89,7 +92,10 @@ function CollectionRenderer({ note, onReady }: RendererProps) {
ntxId="print"
highlightedTokens={null}
media="print"
onReady={onReady}
onReady={async () => {
await loadCustomCss(note);
onReady();
}}
/>;
}
@@ -102,4 +108,25 @@ function Error404({ noteId }: { noteId: string }) {
)
}
async function loadCustomCss(note: FNote) {
const printCssNotes = await note.getRelationTargets("printCss");
let loadPromises: JQueryPromise<void>[] = [];
for (const printCssNote of printCssNotes) {
if (!printCssNote || (printCssNote.type !== "code" && printCssNote.mime !== "text/css")) continue;
const linkEl = document.createElement("link");
linkEl.href = `/api/notes/${printCssNote.noteId}/download`;
linkEl.rel = "stylesheet";
const promise = $.Deferred();
loadPromises.push(promise.promise());
linkEl.onload = () => promise.resolve();
document.head.appendChild(linkEl);
}
await Promise.allSettled(loadPromises);
}
main();

View File

@@ -159,7 +159,7 @@ describe("shortcuts", () => {
expect(matchesShortcut(event, "Shift+F1")).toBeTruthy();
// Special keys
for (const keyCode of [ "Delete", "Enter" ]) {
for (const keyCode of [ "Delete", "Enter", "NumpadEnter" ]) {
event = createKeyboardEvent({ key: keyCode, code: keyCode });
expect(matchesShortcut(event, keyCode), `Key ${keyCode}`).toBeTruthy();
}

View File

@@ -46,6 +46,7 @@ for (let i = 1; i <= 19; i++) {
const KEYCODES_WITH_NO_MODIFIER = new Set([
"Delete",
"Enter",
"NumpadEnter",
...functionKeyCodes
]);

View File

@@ -841,7 +841,7 @@ export function arrayEqual<T>(a: T[], b: T[]) {
return true;
}
type Indexed<T extends object> = T & { index: number };
export type Indexed<T extends object> = T & { index: number };
/**
* Given an object array, alters every object in the array to have an index field assigned to it.

View File

@@ -5,7 +5,6 @@
.note-detail-relation-map {
height: 100%;
overflow: hidden !important;
padding: 10px;
position: relative;
}

View File

@@ -1104,7 +1104,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
.card {
color: inherit !important;
background-color: inherit !important;
border-color: var(--main-border-color) !important;
}
@@ -1759,10 +1758,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
flex-direction: column;
margin-inline-start: 10px;
margin-inline-end: 5px;
background: transparent;
}
#right-pane .card-header {
background: inherit;
padding: 6px 0 3px 0;
width: 99%; /* to give minimal right margin */
background-color: var(--button-background-color);
@@ -1809,12 +1808,15 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
}
.note-split {
/* Limits the maximum width of the note */
--max-content-width: var(--preferred-max-content-width);
margin-inline-start: auto;
margin-inline-end: auto;
}
.note-split.full-content-width {
max-width: 999999px;
--max-content-width: unset;
}
button.close:hover {
@@ -2034,13 +2036,16 @@ body.zen #right-pane,
body.zen #mobile-sidebar-wrapper,
body.zen .tab-row-container,
body.zen .tab-row-widget,
body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action,
body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button {
body.zen .action-button,
body.zen .note-list-widget:not(.full-height) {
display: none !important;
}
@@ -2084,12 +2089,121 @@ body.zen .note-title-widget,
body.zen .note-title-widget input {
font-size: 1rem !important;
background: transparent !important;
pointer-events: none;
}
body.zen #detail-container {
width: 100%;
}
body.zen .note-split:not(.full-content-width) .scrolling-container {
display: flex;
flex-direction: column;
scroll-behavior: unset !important;
}
body.zen .note-split:not(.full-content-width) .note-detail {
margin: auto;
padding-bottom: 25vh;
max-width: var(--max-content-width);
width: 100%;
}
body.zen .note-split:not(.full-content-width) .scroll-padding-widget {
display: none;
}
body.zen .note-split.type-text {
position: relative;
font-size: 1.15em;
}
body.zen:not(.backdrop-effects-disabled) .note-split.type-text .title-row {
--start-color: var(--main-background-color);
position: absolute;
width: 100%;
background: linear-gradient(var(--start-color) 30%, transparent 100%);
z-index: 1000;
}
@supports (background: color-mix(in srgb, white, transparent)) {
body.zen .note-split.type-text .title-row {
--start-color: color-mix(in srgb, var(--main-background-color), transparent 10%);
}
}
body.zen .note-split.type-text .scrolling-container {
--padding-bottom: 130px; /* Should be enough to avoid caret being hidden by the formatting toolbar */
/* (Usually) keeps the caret above the fixed toolbar */
scroll-padding-bottom: var(--padding-bottom);
}
body.zen:not(.backdrop-effects-disabled) .note-split.type-text .scrolling-container {
--padding-top: 50px; /* Should be enough to cover the title row */
padding-top: var(--padding-top);
scroll-padding-top: var(--padding-top);
}
/* Fixed formatting toolbar */
body.zen .note-split .ribbon-container {
position: fixed;
left: 0;
bottom: 20px;
width: 100%;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
}
body.zen .note-split:focus-within .ribbon-container {
opacity: 1; /* Show when the note split is focused */
}
body.zen .note-split .ribbon-container .ribbon-body {
border: 0;
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
margin: auto;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color);
}
body.zen .note-split .ribbon-container .classic-toolbar-widget:not(:has(> .ck-toolbar)) {
/* Hide the toolbar wrapper if the toolbar is missing */
display: none;
}
body.zen .note-split:focus-within .ribbon-container .classic-toolbar-widget {
pointer-events: all;
}
@media (max-width: 1300px) {
body.zen .note-split .ribbon-container .classic-toolbar-widget {
/* Set the toolbar to full with */
width: 100%;
}
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
}
}
/* Content renderer */
footer.file-footer,
@@ -2406,7 +2520,7 @@ footer.webview-footer button {
transform: rotate(180deg);
}
/* CK Edito */
/* CK Editor */
/* Insert text snippet: limit the width of the listed items to avoid overly long names */
:root body.desktop div.ck-template-form li.ck-list__item .ck-template-form__text-part > span {
@@ -2436,4 +2550,18 @@ iframe.print-iframe {
.excalidraw.theme--dark canvas {
--theme-filter: invert(100%) hue-rotate(180deg);
}
/* Scrolling container */
.scrolling-container:has(> :is(.note-detail.full-height, .note-list-widget.full-height)) {
display: flex;
flex-direction: column;
}
.scrolling-container > .note-detail.full-height,
.scrolling-container > .note-list-widget.full-height {
position: relative;
flex-grow: 1;
width: 100%;
}

View File

@@ -15,7 +15,7 @@
--native-titlebar-background: #00000000;
--window-background-color-bgfx: transparent; /* When background effects enabled */
--main-background-color: #272727;
--main-background-color: #242424;
--main-text-color: #ccc;
--main-border-color: #454545;
--subtle-border-color: #313131;
@@ -166,6 +166,9 @@
--protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871;
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
@@ -192,9 +195,9 @@
--badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #000000b3;
--promoted-attribute-card-background-color: #ffffff21;
--promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000080;
--floating-button-background-color: #494949d2;
--floating-button-color: var(--button-text-color);
@@ -208,6 +211,8 @@
--floating-button-hide-button-background: #00000029;
--floating-button-hide-button-color: #ffffff63;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: #0c0c0c24; /* Only for the vertical layout */
--right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white;
@@ -225,10 +230,9 @@
--code-block-box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);
--card-background-color: #ffffff12;
--card-background-hover-color: #3c3c3c;
--card-background-press-color: #464646;
--card-border-color: #222222;
--card-box-shadow: 0 0 12px rgba(0, 0, 0, 0.15);
--card-background-hover-color: #ffffff20;
--card-border-color: transparent;
--card-box-shadow: none;
--calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color);
@@ -294,4 +298,10 @@ body ::-webkit-calendar-picker-indicator {
body .todo-list input[type="checkbox"]:not(:checked):before {
border-color: var(--muted-text-color) !important;
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 8.8%, 11.2%);
--modal-border-color: hsl(var(--custom-color-hue), 9.4%, 25.1%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 13.2%, 20.8%);
}

View File

@@ -159,6 +159,9 @@
--protected-session-active-icon-color: #16b516;
--sync-status-error-pulse-color: #ff5528;
--center-pane-vert-layout-background-color-bgfx: #ffffff75;
--center-pane-horiz-layout-background-color-bgfx: #ffffffd6;
--right-pane-heading-color: gray;
--root-background: var(--left-pane-background-color);
@@ -180,13 +183,13 @@
--inactive-tab-hover-background-color: #00000016;
--inactive-tab-text-color: #4e4e4e;
--alert-bar-background: #32637b29;
--alert-bar-background: #f9cf2b29;
--badge-background-color: #00000011;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #00000033;
--promoted-attribute-card-background-color: #00000014;
--promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000042;
--floating-button-background-color: #eaeaeacc;
@@ -207,7 +210,9 @@
--new-tab-button-hover-background: white;
--new-tab-button-hover-color: black;
--right-pane-item-hover-background: #ececec;
--right-pane-background-color: var(--main-background-color);
--right-pane-background-color-bgfx: #ffffff9e; /* Only for the vertical layout */
--right-pane-item-hover-background: #00000013;
--right-pane-item-hover-color: inherit;
--scrollbar-thumb-color: #0000005c;
@@ -223,12 +228,11 @@
--code-block-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.1), 0px 0px 2px rgba(0, 0, 0, 0.2);
--card-background-color: var(--accented-background-color);
--card-background-hover-color: #f9f9f9;
--card-background-press-color: #efefef;
--card-border-color: #eaeaea;
--card-background-color: #0000000d;
--card-background-hover-color: #0000001c;
--card-border-color: transparent;
--card-shadow-color: rgba(0, 0, 0, 0.1);
--card-box-shadow: 0 0 12px var(--card-shadow-color);
--card-box-shadow: none;
--calendar-color: var(--menu-text-color);
--calendar-weekday-labels-color: var(--muted-text-color);
@@ -270,4 +274,10 @@
* The --custom-color-hue variable contains the hue of the user-selected note color.
* This value is unset for gray tones. */
--custom-bg-color: hsl(var(--custom-color-hue), 37%, 89%, 1);
}
.tinted-quick-edit-dialog {
--modal-background-color: hsl(var(--custom-color-hue), 56%, 96%);
--modal-border-color: hsl(var(--custom-color-hue), 33%, 41%);
--promoted-attribute-card-background-color: hsl(var(--custom-color-hue), 40%, 88%);
}

View File

@@ -82,6 +82,7 @@
/* Theme capabilities */
--tab-note-icons: true;
--allow-background-effects: true;
/* To ensure that a tree item's custom color remains sufficiently contrasted and readable,
* the color is adjusted based on the current color scheme (light or dark). The lightness
@@ -131,7 +132,8 @@ body.mobile .dropdown-menu .dropdown-menu {
body.desktop .dropdown-menu::before,
:root .ck.ck-dropdown__panel::before,
:root .excalidraw .popover::before {
:root .excalidraw .popover::before,
body.zen .note-split .ribbon-container .classic-toolbar-widget::before {
content: "";
backdrop-filter: var(--dropdown-backdrop-filter);
border-radius: var(--dropdown-border-radius);
@@ -485,13 +487,21 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
--note-list-vertical-padding: 15px;
background-color: var(--card-background-color);
border: 1px solid var(--card-border-color) !important;
box-shadow: 2px 3px 4px var(--card-shadow-color);
border-radius: 12px;
user-select: none;
padding: 0;
margin: 5px 10px 5px 0;
}
:root .note-list .note-book-card:hover {
background-color: var(--card-background-hover-color);
transition: background-color 200ms ease-out;
}
:root .note-list .note-book-card:active {
transform: scale(.98);
}
.note-list.list-view .note-book-card {
box-shadow: 0 0 3px var(--card-shadow-color);
}
@@ -500,10 +510,6 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
vertical-align: middle;
}
.note-list-wrapper .note-book-card:active {
background-color: var(--card-background-press-color);
}
.note-list-wrapper .note-book-card a {
color: inherit !important;
}
@@ -585,7 +591,6 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
}
.note-list.grid-view .note-book-card:hover {
background: var(--card-background-color) !important;
filter: contrast(105%);
}

View File

@@ -258,11 +258,6 @@
border-inline-start: 1px solid var(--ck-color-toolbar-border);
}
/* The last separator of the toolbar */
:root .classic-toolbar-widget .ck.ck-toolbar__separator:last-of-type {
flex-grow: 1;
}
/* Heading dropdown */
:root .ck.ck-dropdown.ck-heading-dropdown .ck-dropdown__panel .ck-list__item {
@@ -679,4 +674,17 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
.ck-content a.reference-link > span {
text-decoration: underline;
}
/*
* Read-only text content
*/
.note-detail-readonly-text:focus-visible {
outline: 2px solid var(--input-focus-outline-color);
border-radius: 4px;
}
.note-list-widget {
outline: 0 !important;
}

View File

@@ -101,7 +101,7 @@
.sql-table-schemas-widget .sql-table-schemas button:hover,
.sql-table-schemas-widget .sql-table-schemas button:active,
.sql-table-schemas-widget .sql-table-schemas button:focus-visible {
--background: var(--card-background-press-color);
--background: var(--card-background-hover-color);
--color: var(--main-text-color);
}
@@ -148,7 +148,7 @@ div.note-detail-empty {
--options-card-min-width: 500px;
--options-card-max-width: 900px;
--options-card-padding: 17px;
--options-title-font-size: 1rem;
--options-title-font-size: .75rem;
--options-title-offset: 13px;
}
/* Create a gap at the top of the option pages */
@@ -173,16 +173,19 @@ div.note-detail-empty {
}
.options-section:not(.tn-no-card) {
margin: auto;
border-radius: 12px;
border: 1px solid var(--card-border-color) !important;
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
box-shadow: var(--card-box-shadow);
border: 1px solid var(--card-border-color) !important;
border-radius: 8px;
background: var(--card-background-color);
padding: var(--options-card-padding);
margin-bottom: calc(var(--options-title-offset) + 26px) !important;
}
body.desktop .option-section:not(.tn-no-card) {
body.prefers-centered-content .options-section:not(.tn-no-card) {
margin-inline: auto;
}
body.desktop .options-section:not(.tn-no-card) {
min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width);
}
@@ -193,9 +196,16 @@ body.desktop .option-section:not(.tn-no-card) {
padding-bottom: var(--default-padding);
}
.options-section:not(.tn-no-card) h4,
.options-section:not(.tn-no-card) h5 {
text-transform: uppercase;
letter-spacing: .4pt;
}
.options-section:not(.tn-no-card) h4 {
font-size: var(--options-title-font-size);
font-weight: bold;
font-weight: 600;
color: var(--launcher-pane-text-color);
margin-top: calc(-1 * var(--options-card-padding) - var(--options-title-font-size) - var(--options-title-offset)) !important;
margin-bottom: calc(var(--options-title-offset) + var(--options-card-padding)) !important;

View File

@@ -34,6 +34,7 @@
div.promoted-attributes-container {
margin-top: 8px;
margin-bottom: 8px;
margin-inline-start: 12px;
}
/*

View File

@@ -8,7 +8,7 @@
}
:root {
--dropdown-backdrop-filter: blur(10px) saturate(6);
--dropdown-backdrop-filter: blur(20px) saturate(6);
--dropdown-border-radius: 10px;
}
@@ -35,30 +35,53 @@ body.mobile {
}
/* #region Mica */
body.background-effects.platform-win32 {
/* Quirk: --background-material is read before "theme-supports-background-effects" class
* is applied. Apply the matterial even if the theme doesn't support it. */
--background-material: tabbed;
}
body.background-effects.theme-supports-background-effects.platform-win32 {
--launcher-pane-horiz-border-color: var(--launcher-pane-horiz-border-color-bgfx);
--launcher-pane-horiz-background-color: var(--launcher-pane-horiz-background-color-bgfx);
--launcher-pane-vert-background-color: var(--launcher-pane-vert-background-color-bgfx);
--tab-background-color: var(--window-background-color-bgfx);
--new-tab-button-background: var(--window-background-color-bgfx);
--active-tab-background-color: var(--launcher-pane-horiz-background-color);
--root-background: transparent;
}
body.background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--background-material: mica;
}
body.background-effects.platform-win32,
body.background-effects.platform-win32 #root-widget {
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical {
--left-pane-background-color: var(--window-background-color-bgfx);
--center-pane-background-color-bgfx: var(--center-pane-vert-layout-background-color-bgfx);
--right-pane-background-color: var(--right-pane-background-color-bgfx);
}
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal {
--center-pane-background-color-bgfx: var(--center-pane-horiz-layout-background-color-bgfx);
--gutter-color: var(--left-pane-background-color);
}
body.background-effects.theme-supports-background-effects.platform-win32,
body.background-effects.theme-supports-background-effects.platform-win32 #root-widget {
background: var(--window-background-color-bgfx) !important;
}
body.background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.platform-win32.layout-vertical #vertical-main-container {
body.background-effects.theme-supports-background-effects.platform-win32.layout-horizontal #horizontal-main-container,
body.background-effects.theme-supports-background-effects.platform-win32.layout-vertical #vertical-main-container {
background-color: var(--root-background);
}
/* Note split with background effects */
body.background-effects.theme-supports-background-effects.platform-win32 #center-pane .note-split.bgfx {
--note-split-background-color: var(--center-pane-background-color-bgfx);
}
/* #endregion */
/* Matches when the left pane is collapsed */
@@ -72,9 +95,21 @@ body.layout-vertical #horizontal-main-container.left-pane-hidden #launcher-pane.
border-inline-end: 2px solid var(--left-pane-collapsed-border-color);
}
body.background-effects.zen #root-widget {
--main-background-color: transparent;
--root-background: transparent;
/*
* Zen mode
*/
@keyframes zen-formatting-toolbar-entrance {
from {
transform: translateY(200%);
} to {
transform: translateY(0);
}
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
position: relative;
animation: zen-formatting-toolbar-entrance 300ms ease-out;
}
/*
@@ -1171,23 +1206,18 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
* CENTER PANE
*/
#center-pane {
background: var(--main-background-color);
}
.vertical-layout #center-pane {
/* The first visible note split */
.vertical-layout #center-pane .note-split:not(.visible ~ .visible) {
border-radius: var(--center-pane-border-radius) 0 0 0;
}
.note-split {
#center-pane .note-split {
padding-top: 2px;
animation: note-entrance 100ms linear;
/* will-change: opacity; -- causes some weird artifacts to the note menu in split view */
background-color: var(--note-split-background-color, var(--main-background-color));
}
.split-note-container-widget > .gutter {
background: var(--root-background) !important;
transition: background 150ms ease-out;
body:not(.background-effects) #center-pane .note-split {
animation: note-entrance 100ms linear;
}
/*
@@ -1200,9 +1230,9 @@ body.layout-vertical .tab-row-widget-is-sorting .note-tab.note-tab-is-dragging .
@keyframes note-entrance {
from {
opacity: 0;
filter: opacity(0);
} to {
opacity: 1;
filter: opacity(1);
}
}
@@ -1328,8 +1358,7 @@ div.promoted-attribute-cell {
--pa-card-padding-inline-end: 2px;
--input-background-color: transparent;
box-shadow: 1px 1px 2px var(--promoted-attribute-card-shadow-color);
box-shadow: var(--promoted-attribute-card-shadow);
display: inline-flex;
margin: 0;
border-radius: 8px;
@@ -1716,7 +1745,7 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
*/
#right-pane {
background: var(--main-background-color);
background: var(--right-pane-background-color);
}
#right-pane div.card-header {

View File

@@ -520,9 +520,7 @@
"max_content_width": {
"max_width_unit": "بكسل",
"title": "عرض المحتوى",
"reload_button": "اعادة تحميل الواجهة",
"max_width_label": "اقصى عرض للمحتوى",
"reload_description": "تغييرات من خيارات المظهر"
"max_width_label": "اقصى عرض للمحتوى"
},
"native_title_bar": {
"enabled": "مفعل",

View File

@@ -39,7 +39,10 @@
"help_on_tree_prefix": "有关树前缀的帮助",
"prefix": "前缀: ",
"save": "保存",
"branch_prefix_saved": "分支前缀已保存。"
"branch_prefix_saved": "分支前缀已保存。",
"edit_branch_prefix_multiple": "编辑 {{count}} 个分支的前缀",
"branch_prefix_saved_multiple": "已为 {{count}} 个分支保存分支前缀。",
"affected_branches": "受影响的分支 {{count}}:"
},
"bulk_actions": {
"bulk_actions": "批量操作",
@@ -1107,9 +1110,6 @@
"title": "内容宽度",
"default_description": "Trilium默认会限制内容的最大宽度以提高在宽屏中全屏时的可读性。",
"max_width_label": "内容最大宽度(像素)",
"apply_changes_description": "要应用内容宽度更改,请点击",
"reload_button": "重载前端",
"reload_description": "来自外观选项的更改",
"max_width_unit": "像素"
},
"native_title_bar": {
@@ -1917,7 +1917,7 @@
},
"custom_date_time_format": {
"title": "自定义日期/时间格式",
"description": "通过<shortcut />或工具栏的方式可自定义日期和时间格式有关日期/时间格式字符串中各个字符的含义,请参阅<doc>Day.js docs</doc>。",
"description": "自定义通过 <shortcut /> 或工具栏插入的日期和时间格式有关日期/时间格式字符串中各个字符的含义,请参阅<doc>Day.js docs</doc>。",
"format_string": "日期/时间格式字符串:",
"formatted_time": "格式化后日期/时间:"
},
@@ -2079,5 +2079,8 @@
"edit-slide": "编辑此幻灯片",
"start-presentation": "开始演示",
"slide-overview": "切换幻灯片概览"
},
"calendar_view": {
"delete_note": "删除笔记..."
}
}

View File

@@ -1104,9 +1104,6 @@
"title": "Inhaltsbreite",
"default_description": "Trilium begrenzt standardmäßig die maximale Inhaltsbreite, um die Lesbarkeit für maximierte Bildschirme auf Breitbildschirmen zu verbessern.",
"max_width_label": "Maximale Inhaltsbreite in Pixel",
"apply_changes_description": "Um Änderungen an der Inhaltsbreite anzuwenden, klicke auf",
"reload_button": "Frontend neu laden",
"reload_description": "Änderungen an den Darstellungsoptionen",
"max_width_unit": "Pixel"
},
"native_title_bar": {

View File

@@ -36,10 +36,13 @@
},
"branch_prefix": {
"edit_branch_prefix": "Edit branch prefix",
"edit_branch_prefix_multiple": "Edit branch prefix for {{count}} branches",
"help_on_tree_prefix": "Help on Tree prefix",
"prefix": "Prefix: ",
"save": "Save",
"branch_prefix_saved": "Branch prefix has been saved."
"branch_prefix_saved": "Branch prefix has been saved.",
"branch_prefix_saved_multiple": "Branch prefix has been saved for {{count}} branches.",
"affected_branches": "Affected branches ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "Bulk actions",
@@ -1108,9 +1111,7 @@
"default_description": "Trilium by default limits max content width to improve readability for maximized screens on wide screens.",
"max_width_label": "Max content width",
"max_width_unit": "pixels",
"apply_changes_description": "To apply content width changes, click on",
"reload_button": "reload frontend",
"reload_description": "changes from appearance options"
"centerContent": "Keep content centered"
},
"native_title_bar": {
"title": "Native Title Bar (requires app restart)",
@@ -1636,6 +1637,12 @@
"shared_locally": "This note is shared locally on {{- link}}.",
"help_link": "For help visit <a href=\"https://triliumnext.github.io/Docs/Wiki/sharing.html\">wiki</a>."
},
"read-only-info": {
"read-only-note": "Currently viewing a read-only note.",
"auto-read-only-note": "This note is shown in a read-only mode for faster loading.",
"auto-read-only-learn-more": "Learn more",
"edit-note": "Edit note"
},
"note_types": {
"text": "Text",
"code": "Code",
@@ -2034,6 +2041,9 @@
"start-presentation": "Start presentation",
"slide-overview": "Toggle an overview of the slides"
},
"calendar_view": {
"delete_note": "Delete note..."
},
"command_palette": {
"tree-action-name": "Tree: {{name}}",
"export_note_title": "Export Note",

View File

@@ -1107,10 +1107,7 @@
"title": "Ancho del contenido",
"default_description": "Trilium limita de forma predeterminada el ancho máximo del contenido para mejorar la legibilidad de ventanas maximizadas en pantallas anchas.",
"max_width_label": "Ancho máximo del contenido en píxeles",
"max_width_unit": "píxeles",
"apply_changes_description": "Para aplicar cambios en el ancho del contenido, haga clic en",
"reload_button": "recargar la interfaz",
"reload_description": "cambios desde las opciones de apariencia"
"max_width_unit": "píxeles"
},
"native_title_bar": {
"title": "Barra de título nativa (requiere reiniciar la aplicación)",
@@ -1592,7 +1589,7 @@
"tree-context-menu": {
"open-in-a-new-tab": "Abrir en nueva pestaña",
"open-in-a-new-split": "Abrir en nueva división",
"insert-note-after": "Insertar nota después de",
"insert-note-after": "Insertar nota contigua",
"insert-child-note": "Insertar subnota",
"delete": "Eliminar",
"search-in-subtree": "Buscar en subárbol",

View File

@@ -1106,9 +1106,6 @@
"title": "Largeur du contenu",
"default_description": "Trilium limite par défaut la largeur maximale du contenu pour améliorer la lisibilité sur des écrans larges.",
"max_width_label": "Largeur maximale du contenu en pixels",
"apply_changes_description": "Pour appliquer les modifications de largeur du contenu, cliquez sur",
"reload_button": "recharger l'interface",
"reload_description": "changements par rapport aux options d'apparence",
"max_width_unit": "Pixels"
},
"native_title_bar": {

View File

@@ -109,7 +109,8 @@
"export_type_single": "Solo questa nota, senza le sottostanti",
"format_opml": "OPML - formato per scambio informazioni outline. Formattazione, immagini e files non sono inclusi.",
"opml_version_1": "OPML v.1.0 - solo testo semplice",
"opml_version_2": "OPML v2.0 - supporta anche HTML"
"opml_version_2": "OPML v2.0 - supporta anche HTML",
"share-format": "HTML per la pubblicazione sul web - utilizza lo stesso tema utilizzato per le note condivise, ma può essere pubblicato come sito web statico."
},
"password_not_set": {
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
@@ -1569,10 +1570,7 @@
"title": "Larghezza del contenuto",
"default_description": "Per impostazione predefinita, Trilium limita la larghezza massima del contenuto per migliorare la leggibilità sugli schermi più grandi.",
"max_width_label": "Larghezza massima del contenuto",
"max_width_unit": "pixel",
"apply_changes_description": "Per applicare le modifiche alla larghezza del contenuto, fare clic su",
"reload_button": "ricarica frontend",
"reload_description": "modifiche dalle opzioni di aspetto"
"max_width_unit": "pixel"
},
"native_title_bar": {
"title": "Barra del titolo nativa (richiede il riavvio dell'app)",

View File

@@ -39,7 +39,10 @@
"edit_branch_prefix": "ブランチ接頭辞の編集",
"help_on_tree_prefix": "ツリー接頭辞に関するヘルプ",
"prefix": "接頭辞: ",
"branch_prefix_saved": "ブランチの接頭辞が保存されました。"
"branch_prefix_saved": "ブランチの接頭辞が保存されました。",
"edit_branch_prefix_multiple": "{{count}} ブランチのブランチ接頭辞を編集",
"branch_prefix_saved_multiple": "{{count}} 個のブランチのブランチ接頭辞が保存されました。",
"affected_branches": "影響を受けるブランチ {{count}}:"
},
"global_menu": {
"menu": "メニュー",
@@ -830,13 +833,10 @@
"theme_defined": "テーマが定義されました"
},
"max_content_width": {
"reload_button": "フロントエンドをリロード",
"title": "コンテンツ幅",
"default_description": "Triliumは、ワイドスクリーンで最大化された画面での可読性を向上させるために、デフォルトでコンテンツの最大幅を制限しています。",
"max_width_label": "最大コンテンツ幅",
"max_width_unit": "ピクセル",
"apply_changes_description": "コンテンツ幅の変更を適用するには、クリックしてください",
"reload_description": "外観設定から変更"
"max_width_unit": "ピクセル"
},
"theme": {
"title": "アプリのテーマ",
@@ -2079,5 +2079,8 @@
"edit-slide": "このスライドを編集",
"start-presentation": "プレゼンテーションを開始",
"slide-overview": "スライドの概要を切り替え"
},
"calendar_view": {
"delete_note": "ノートを削除..."
}
}

View File

@@ -1464,10 +1464,7 @@
"title": "Szerokość zawartości",
"default_description": "Trilium domyślnie ogranicza maksymalną szerokość zawartości, aby poprawić czytelność na zmaksymalizowanych ekranach o dużej szerokości.",
"max_width_label": "Maksymalna szerokość zawartości",
"max_width_unit": "piksele",
"apply_changes_description": "Aby zastosować zmiany szerokości zawartości, kliknij na",
"reload_button": "przeładuj frontend",
"reload_description": "zmiany z opcji wyglądu"
"max_width_unit": "piksele"
},
"native_title_bar": {
"title": "Natywny pasek tytułu (wymaga ponownego uruchomienia aplikacji)",

View File

@@ -1082,10 +1082,7 @@
"title": "Largura do Conteúdo",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em ecrãs largos.",
"max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels",
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
"reload_button": "recarregar frontend",
"reload_description": "alterações de opções de aparência"
"max_width_unit": "pixels"
},
"native_title_bar": {
"title": "Barra de Título Nativa (requer recarregar a app)",

View File

@@ -1304,9 +1304,6 @@
"title": "Largura do Conteúdo",
"max_width_label": "Largura máxima do conteúdo",
"max_width_unit": "pixels",
"apply_changes_description": "Para aplicar as alterações de largura do conteúdo, clique em",
"reload_button": "recarregar frontend",
"reload_description": "alterações de opções de aparência",
"default_description": "Por padrão, o Trilium limita a largura máxima do conteúdo para melhorar a legibilidade em janelas maximizadas em telas wide."
},
"native_title_bar": {

View File

@@ -796,12 +796,9 @@
"modal_body_text": "Din cauza limitărilor la nivel de navigator, nu este posibilă citirea clipboard-ului din JavaScript. Inserați Markdown-ul pentru a-l importa în caseta de mai jos și dați clic pe butonul Import"
},
"max_content_width": {
"apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe",
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
"max_width_label": "Lungimea maximă a conținutului",
"max_width_unit": "pixeli",
"reload_button": "reîncarcă interfața",
"reload_description": "schimbări din opțiunile de afișare",
"title": "Lățime conținut"
},
"mobile_detail_menu": {

View File

@@ -1203,11 +1203,8 @@
"max_content_width": {
"max_width_unit": "пикселей",
"title": "Ширина контентной области",
"reload_button": "перезагрузить интерфейс",
"default_description": "Trilium по умолчанию ограничивает максимальную ширину контента, чтобы улучшить читаемость на широких экранах.",
"max_width_label": "Максимальная ширина контентной области",
"apply_changes_description": "Чтобы применить изменения, нажмите на",
"reload_description": "изменения в параметрах внешнего вида"
"max_width_label": "Максимальная ширина контентной области"
},
"native_title_bar": {
"enabled": "включено",

View File

@@ -39,7 +39,10 @@
"help_on_tree_prefix": "有關樹前綴的說明",
"prefix": "前綴: ",
"save": "儲存",
"branch_prefix_saved": "已儲存分支前綴。"
"branch_prefix_saved": "已儲存分支前綴。",
"edit_branch_prefix_multiple": "編輯 {{count}} 個分支的前綴",
"branch_prefix_saved_multiple": "已為 {{count}} 個分支儲存分支前綴。",
"affected_branches": "受影響的分支 ({{count}}):"
},
"bulk_actions": {
"bulk_actions": "批次操作",
@@ -1104,9 +1107,6 @@
"title": "內容寬度",
"default_description": "Trilium 預設會限制內容的最大寬度以提高在寬螢幕中全螢幕時的可讀性。",
"max_width_label": "內容最大寬度(像素)",
"apply_changes_description": "要套用內容寬度更改,請點擊",
"reload_button": "重新載入前端",
"reload_description": "來自外觀選項的更改",
"max_width_unit": "像素"
},
"native_title_bar": {
@@ -2079,5 +2079,8 @@
"edit-slide": "編輯此投影片",
"start-presentation": "開始簡報",
"slide-overview": "切換投影片概覽"
},
"calendar_view": {
"delete_note": "刪除筆記…"
}
}

View File

@@ -1204,10 +1204,7 @@
"title": "Ширина вмісту",
"default_description": "Trilium за замовчуванням обмежує максимальну ширину вмісту, щоб поліпшити читабельність на широкоформатних екранах у режимі максимального розширення.",
"max_width_label": "Максимальна ширина вмісту",
"max_width_unit": "пікселів",
"apply_changes_description": "Щоб застосувати зміни ширини вмісту, натисніть на",
"reload_button": "перезавантажити інтерфейс",
"reload_description": "зміни в параметрах зовнішнього вигляду"
"max_width_unit": "пікселів"
},
"native_title_bar": {
"title": "Нативний рядок заголовка (потрібен перезапуск)",

View File

@@ -215,6 +215,30 @@ declare namespace Fancytree {
enableUpdate(enabled: boolean): void;
}
interface FancytreeNodeData {
noteId: string;
parentNoteId: string;
branchId: string;
isProtected: boolean;
noteType: NoteType;
}
interface FancytreeNewNode extends FancytreeNodeData {
title: string;
extraClasses: string;
icon: string;
refKey: string;
/** True if this node is loaded on demand, i.e. on first expansion. */
lazy: boolean;
/** Folder nodes have different default icons and click behavior. Note: Also non-folders may have children. */
folder: boolean;
/** Use isExpanded(), setExpanded() to access this property. */
expanded: boolean;
/** Node id (must be unique inside the tree) */
key: string;
children?: FancytreeNewNode[];
}
/** A FancytreeNode represents the hierarchical data model and operations. */
interface FancytreeNode {
// #region Properties
@@ -227,7 +251,7 @@ declare namespace Fancytree {
/** Display name (may contain HTML) */
title: string;
/** Contains all extra data that was passed on node creation */
data: any;
data: FancytreeNodeData;
/** Array of child nodes. For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array to define a node that has no children. */
children: FancytreeNode[];
/** Use isExpanded(), setExpanded() to access this property. */

View File

@@ -23,6 +23,24 @@ export class CssVarReader {
return (!isNaN(number.valueOf()) ? number.valueOf() : defaultValue)
}
asBoolean(defaultValue?: boolean) {
let value = this.value.toLocaleLowerCase().trim();
let result: boolean | undefined;
switch (value) {
case "true":
case "1":
result = true;
break;
case "false":
case "0":
result = false;
break;
}
return (result !== undefined) ? result : defaultValue;
}
asEnum<T>(enumType: T, defaultValue?: T[keyof T]): T[keyof T] | undefined {
let result: T[keyof T] | undefined;

View File

@@ -6,7 +6,7 @@
.floating-buttons-children,
.show-floating-buttons {
position: absolute;
top: var(--floating-buttons-vert-offset, 10px);
top: var(--floating-buttons-vert-offset, 14px);
inset-inline-end: var(--floating-buttons-horiz-offset, 10px);
display: flex;
flex-direction: row;

View File

@@ -1,6 +1,6 @@
import { t } from "i18next";
import "./FloatingButtons.css";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean } from "./react/hooks";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useTriliumEvent } from "./react/hooks";
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { ParentComponent } from "./react/react_utils";
import { EventData, EventNames } from "../components/app_context";
@@ -20,6 +20,7 @@ interface FloatingButtonsProps {
* properly handle rounded corners, as defined by the --border-radius CSS variable.
*/
export default function FloatingButtons({ items }: FloatingButtonsProps) {
const [ top, setTop ] = useState(0);
const { note, noteContext } = useNoteContext();
const parentComponent = useContext(ParentComponent);
const [ viewType ] = useNoteLabel(note, "viewType");
@@ -47,8 +48,14 @@ export default function FloatingButtons({ items }: FloatingButtonsProps) {
const [ visible, setVisible ] = useState(true);
useEffect(() => setVisible(true), [ note ]);
useTriliumEvent("contentSafeMarginChanged", (e) => {
if (e.noteContext === noteContext) {
setTop(e.top);
}
});
return (
<div className="floating-buttons no-print">
<div className="floating-buttons no-print" style={{top}}>
<div className={`floating-buttons-children ${!visible ? "temporarily-hidden" : ""}`}>
{context && items.map((Component) => (
<Component {...context} />

View File

@@ -4,7 +4,7 @@ import Component from "../components/component";
import NoteContext from "../components/note_context";
import FNote from "../entities/fnote";
import ActionButton, { ActionButtonProps } from "./react/ActionButton";
import { useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
import { useIsNoteReadOnly, useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
import server from "../services/server";
@@ -13,8 +13,6 @@ import toast from "../services/toast";
import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image";
import tree from "../services/tree";
import protected_session_holder from "../services/protected_session_holder";
import options from "../services/options";
import { getHelpUrlForNote } from "../services/in_app_help";
import froca from "../services/froca";
import NoteLink from "./react/NoteLink";
@@ -101,48 +99,26 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingBut
/>
}
function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const [ animationClass, setAnimationClass ] = useState("");
const [ isEnabled, setIsEnabled ] = useState(false);
function EditButton({ note, noteContext }: FloatingButtonContext) {
const [animationClass, setAnimationClass] = useState("");
const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
const isReadOnlyInfoBarDismissed = false; // TODO
useEffect(() => {
noteContext.isReadOnly().then(isReadOnly => {
setIsEnabled(
isDefaultViewMode
&& (!note.isProtected || protected_session_holder.isProtectedSessionAvailable())
&& !options.is("databaseReadonly")
&& isReadOnly
);
});
}, [ note ]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsEnabled(false);
}
});
// make the edit button stand out on the first display, otherwise
// it's difficult to notice that the note is readonly
useEffect(() => {
if (isEnabled) {
if (isReadOnly) {
setAnimationClass("bx-tada bx-lg");
setTimeout(() => {
setAnimationClass("");
}, 1700);
}
}, [ isEnabled ]);
}, [ isReadOnly ]);
return isEnabled && <FloatingButton
return !!isReadOnly && isReadOnlyInfoBarDismissed && <FloatingButton
text={t("edit_button.edit_this_note")}
icon="bx bx-pencil"
className={animationClass}
onClick={() => {
if (noteContext.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
}
}}
onClick={() => enableEditing()}
/>
}

View File

@@ -0,0 +1,19 @@
body.zen div.read-only-note-info-bar-widget {
width: fit-content;
max-width: var(--max-content-width);
border-radius: 8px;
border: unset;
margin: 0 auto 10px auto;
}
.read-only-note-info-bar-widget-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
:root div.read-only-note-info-bar-widget button {
white-space: nowrap;
padding: 2px 8px;
}

View File

@@ -0,0 +1,36 @@
import "./ReadOnlyNoteInfoBar.css";
import { t } from "../services/i18n";
import { useIsNoteReadOnly, useNoteContext, useTriliumEvent } from "./react/hooks"
import Button from "./react/Button";
import InfoBar from "./react/InfoBar";
export default function ReadOnlyNoteInfoBar(props: {}) {
const {note, noteContext} = useNoteContext();
const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
const isExplicitReadOnly = note?.isLabelTruthy("readOnly");
return <InfoBar className="read-only-note-info-bar-widget"
type={(isExplicitReadOnly ? "subtle" : "prominent")}
style={{display: (!isReadOnly) ? "none" : undefined}}>
<div class="read-only-note-info-bar-widget-content">
{(isExplicitReadOnly) ? (
<div>{t("read-only-info.read-only-note")}</div>
) : (
<div>
{t("read-only-info.auto-read-only-note")}
&nbsp;
<a class="tn-link"
href="https://docs.triliumnotes.org/user-guide/concepts/notes/read-only-notes#automatic-read-only-mode">
{t("read-only-info.auto-read-only-learn-more")}
</a>
</div>
)}
<Button text={t("read-only-info.edit-note")}
icon="bx-pencil" onClick={() => enableEditing()} />
</div>
</InfoBar>
}

View File

@@ -1,9 +1,16 @@
.note-list-widget {
min-height: 0;
max-width: var(--max-content-width); /* Inherited from .note-split */
overflow: auto;
contain: none !important;
}
body.prefers-centered-content .note-list-widget:not(.full-height) {
/* Horizontally center the widget in its parent when the "Keep content centered" option is on */
margin-inline: auto;
}
.note-list-widget .note-list {
padding: 10px;
}

View File

@@ -0,0 +1,32 @@
import { it, describe, expect } from "vitest";
import { buildNote } from "../../../test/easy-froca";
import { getBoardData } from "./data";
import FBranch from "../../../entities/fbranch";
import froca from "../../../services/froca";
describe("Board data", () => {
it("deduplicates cloned notes", async () => {
const parentNote = buildNote({
title: "Board",
"#collection": "",
"#viewType": "board",
children: [
{ id: "note1", title: "First note", "#status": "To Do" },
{ id: "note2", title: "Second note", "#status": "In progress" },
{ id: "note3", title: "Third note", "#status": "Done" }
]
});
const branch = new FBranch(froca, {
branchId: "note1_note2",
notePosition: 10,
fromSearchNote: false,
noteId: "note2",
parentNoteId: "note1"
});
froca.branches["note1_note2"] = branch;
froca.getNoteFromCache("note1").addChild("note2", "note1_note2", false);
const data = await getBoardData(parentNote, "status", {}, false);
const noteIds = Array.from(data.byColumn.values()).flat().map(item => item.note.noteId);
expect(noteIds.length).toBe(3);
});
});

View File

@@ -11,7 +11,7 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per
const byColumn: ColumnMap = new Map();
// First, scan all notes to find what columns actually exist
await recursiveGroupBy(parentNote.getChildBranches(), byColumn, groupByColumn, includeArchived);
await recursiveGroupBy(parentNote.getChildBranches(), byColumn, groupByColumn, includeArchived, new Set<string>());
// Get all columns that exist in the notes
const columnsFromNotes = [...byColumn.keys()];
@@ -61,26 +61,28 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per
};
}
async function recursiveGroupBy(branches: FBranch[], byColumn: ColumnMap, groupByColumn: string, includeArchived: boolean) {
async function recursiveGroupBy(branches: FBranch[], byColumn: ColumnMap, groupByColumn: string, includeArchived: boolean, seenNoteIds: Set<string>) {
for (const branch of branches) {
const note = await branch.getNote();
if (!note || (!includeArchived && note.isArchived)) continue;
if (note.type !== "search" && note.hasChildren()) {
await recursiveGroupBy(note.getChildBranches(), byColumn, groupByColumn, includeArchived);
await recursiveGroupBy(note.getChildBranches(), byColumn, groupByColumn, includeArchived, seenNoteIds);
}
const group = note.getLabelValue(groupByColumn);
if (!group) {
if (!group || seenNoteIds.has(note.noteId)) {
continue;
}
if (!byColumn.has(group)) {
byColumn.set(group, []);
}
byColumn.get(group)!.push({
branch,
note
});
seenNoteIds.add(note.noteId);
}
}

View File

@@ -0,0 +1,28 @@
import FNote from "../../../entities/fnote";
import contextMenu, { ContextMenuEvent } from "../../../menus/context_menu";
import link_context_menu from "../../../menus/link_context_menu";
import branches from "../../../services/branches";
import { t } from "../../../services/i18n";
export function openCalendarContextMenu(e: ContextMenuEvent, noteId: string, parentNote: FNote) {
e.preventDefault();
e.stopPropagation();
contextMenu.show({
x: e.pageX,
y: e.pageY,
items: [
...link_context_menu.getItems(),
{ kind: "separator" },
{
title: t("calendar_view.delete_note"),
uiIcon: "bx bx-trash",
handler: async () => {
const branchId = parentNote.childToBranch[noteId];
await branches.deleteNotes([ branchId ], false, false);
}
}
],
selectMenuItemHandler: ({ command }) => link_context_menu.handleLinkContextMenuItem(command, noteId),
})
}

View File

@@ -1,6 +1,7 @@
.calendar-view {
overflow: hidden;
position: relative;
outline: 0;
height: 100%;
user-select: none;
padding: 10px;
@@ -67,6 +68,7 @@
}
body.desktop:not(.zen) .calendar-view .calendar-header {
padding-block-start: 4px;
padding-inline-end: 5em;
}

View File

@@ -20,6 +20,7 @@ import Button, { ButtonGroup } from "../../react/Button";
import ActionButton from "../../react/ActionButton";
import { RefObject } from "preact";
import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar";
import { openCalendarContextMenu } from "./context_menu";
interface CalendarViewData {
@@ -106,7 +107,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
const plugins = usePlugins(isEditable, isCalendarRoot);
const locale = useLocale();
const { eventDidMount } = useEventDisplayCustomization();
const { eventDidMount } = useEventDisplayCustomization(note);
const editingProps = useEditing(note, isEditable, isCalendarRoot);
// React to changes.
@@ -196,11 +197,11 @@ function usePlugins(isEditable: boolean, isCalendarRoot: boolean) {
}
function useLocale() {
const [ locale ] = useTriliumOption("locale");
const [ formattingLocale ] = useTriliumOption("formattingLocale");
const [ calendarLocale, setCalendarLocale ] = useState<LocaleInput>();
useEffect(() => {
const correspondingLocale = LOCALE_MAPPINGS[locale];
const correspondingLocale = LOCALE_MAPPINGS[formattingLocale];
if (correspondingLocale) {
correspondingLocale().then((locale) => setCalendarLocale(locale.default));
} else {
@@ -253,7 +254,7 @@ function useEditing(note: FNote, isEditable: boolean, isCalendarRoot: boolean) {
};
}
function useEventDisplayCustomization() {
function useEventDisplayCustomization(parentNote: FNote) {
const eventDidMount = useCallback((e: EventMountArg) => {
const { iconClass, promotedAttributes } = e.event.extendedProps;
@@ -302,6 +303,11 @@ function useEventDisplayCustomization() {
}
$(mainContainer ?? e.el).append($(promotedAttributesHtml));
}
e.el.addEventListener("contextmenu", (contextMenuEvent) => {
const noteId = e.event.extendedProps.noteId;
openCalendarContextMenu(contextMenuEvent, noteId, parentNote);
});
}, []);
return { eventDidMount };
}

View File

@@ -0,0 +1,62 @@
import { EventData } from "../../components/app_context";
import BasicWidget from "../basic_widget";
import Container from "./container";
import NoteContext from "../../components/note_context";
export default class ContentHeader extends Container<BasicWidget> {
noteContext?: NoteContext;
thisElement?: HTMLElement;
parentElement?: HTMLElement;
resizeObserver: ResizeObserver;
currentHeight: number = 0;
currentSafeMargin: number = NaN;
constructor() {
super();
this.css("contain", "unset");
this.resizeObserver = new ResizeObserver(this.onResize.bind(this));
}
setNoteContextEvent({ noteContext }: EventData<"setNoteContext">) {
this.noteContext = noteContext;
this.init();
}
init() {
this.parentElement = this.parent?.$widget.get(0);
if (!this.parentElement) {
console.warn("No parent set for <ContentHeader>.");
return;
}
this.thisElement = this.$widget.get(0)!;
this.resizeObserver.observe(this.thisElement);
this.parentElement.addEventListener("scroll", this.updateSafeMargin.bind(this));
}
updateSafeMargin() {
const newSafeMargin = Math.max(this.currentHeight - this.parentElement!.scrollTop, 0);
if (newSafeMargin !== this.currentSafeMargin) {
this.currentSafeMargin = newSafeMargin;
this.triggerEvent("contentSafeMarginChanged", {
top: newSafeMargin,
noteContext: this.noteContext!
});
}
}
onResize(entries: ResizeObserverEntry[]) {
for (const entry of entries) {
if (entry.target === this.thisElement) {
this.currentHeight = entry.contentRect.height;
this.updateSafeMargin();
}
}
}
}

View File

@@ -1,9 +1,10 @@
import { EventData } from "../../components/app_context.js";
import { LOCALES } from "@triliumnext/commons";
import { readCssVar } from "../../utils/css-var.js";
import FlexContainer from "./flex_container.js";
import options from "../../services/options.js";
import type BasicWidget from "../basic_widget.js";
import utils from "../../services/utils.js";
import { LOCALES } from "@triliumnext/commons";
/**
* The root container is the top-most widget/container, from which the entire layout derives.
@@ -30,9 +31,11 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
window.visualViewport?.addEventListener("resize", () => this.#onMobileResize());
}
this.#setMotion(options.is("motionEnabled"));
this.#setShadows(options.is("shadowsEnabled"));
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
this.#setMaxContentWidth();
this.#setMotion();
this.#setShadows();
this.#setBackdropEffects();
this.#setThemeCapabilities();
this.#setLocaleAndDirection(options.get("locale"));
return super.render();
@@ -40,15 +43,21 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.isOptionReloaded("motionEnabled")) {
this.#setMotion(options.is("motionEnabled"));
this.#setMotion();
}
if (loadResults.isOptionReloaded("shadowsEnabled")) {
this.#setShadows(options.is("shadowsEnabled"));
this.#setShadows();
}
if (loadResults.isOptionReloaded("backdropEffectsEnabled")) {
this.#setBackdropEffects(options.is("backdropEffectsEnabled"));
this.#setBackdropEffects();
}
if (loadResults.isOptionReloaded("maxContentWidth")
|| loadResults.isOptionReloaded("centerContent")) {
this.#setMaxContentWidth();
}
}
@@ -58,19 +67,38 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
}
#setMotion(enabled: boolean) {
#setMaxContentWidth() {
const width = Math.max(options.getInt("maxContentWidth") || 0, 640);
document.body.style.setProperty("--preferred-max-content-width", `${width}px`);
document.body.classList.toggle("prefers-centered-content", options.is("centerContent"));
}
#setMotion() {
const enabled = options.is("motionEnabled");
document.body.classList.toggle("motion-disabled", !enabled);
jQuery.fx.off = !enabled;
}
#setShadows(enabled: boolean) {
#setShadows() {
const enabled = options.is("shadowsEnabled");
document.body.classList.toggle("shadows-disabled", !enabled);
}
#setBackdropEffects(enabled: boolean) {
#setBackdropEffects() {
const enabled = options.is("backdropEffectsEnabled");
document.body.classList.toggle("backdrop-effects-disabled", !enabled);
}
#setThemeCapabilities() {
// Supports background effects
const useBgfx = readCssVar(document.documentElement, "allow-background-effects")
.asBoolean(false);
document.body.classList.toggle("theme-supports-background-effects", useBgfx);
}
#setLocaleAndDirection(locale: string) {
const correspondingLocale = LOCALES.find(l => l.id === locale);
document.body.lang = locale;

View File

@@ -0,0 +1,13 @@
.branch-prefix-dialog .branch-prefix-notes-list {
margin-top: 10px;
}
.branch-prefix-dialog .branch-prefix-notes-list ul {
max-height: 200px;
overflow: auto;
margin-top: 5px;
}
.branch-prefix-dialog .branch-prefix-current {
opacity: 0.6;
}

View File

@@ -10,53 +10,86 @@ import Button from "../react/Button.jsx";
import FormGroup from "../react/FormGroup.js";
import { useTriliumEvent } from "../react/hooks.jsx";
import FBranch from "../../entities/fbranch.js";
import type { ContextMenuCommandData } from "../../components/app_context.js";
import "./branch_prefix.css";
// Virtual branches (e.g., from search results) start with this prefix
const VIRTUAL_BRANCH_PREFIX = "virt-";
export default function BranchPrefixDialog() {
const [ shown, setShown ] = useState(false);
const [ branch, setBranch ] = useState<FBranch>();
const [ branches, setBranches ] = useState<FBranch[]>([]);
const [ prefix, setPrefix ] = useState("");
const branchInput = useRef<HTMLInputElement>(null);
useTriliumEvent("editBranchPrefix", async () => {
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
useTriliumEvent("editBranchPrefix", async (data?: ContextMenuCommandData) => {
let branchIds: string[] = [];
if (data?.selectedOrActiveBranchIds && data.selectedOrActiveBranchIds.length > 0) {
// Multi-select mode from tree context menu
branchIds = data.selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith(VIRTUAL_BRANCH_PREFIX));
} else {
// Single branch mode from keyboard shortcut or when no selection
const notePath = appContext.tabManager.getActiveContextNotePath();
if (!notePath) {
return;
}
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
}
const branchId = await froca.getBranchId(parentNoteId, noteId);
if (!branchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
branchIds = [branchId];
}
if (branchIds.length === 0) {
return;
}
const { noteId, parentNoteId } = tree.getNoteIdAndParentIdFromUrl(notePath);
const newBranches = branchIds
.map(id => froca.getBranch(id))
.filter((branch): branch is FBranch => branch !== null);
if (!noteId || !parentNoteId) {
if (newBranches.length === 0) {
return;
}
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
if (!newBranchId) {
return;
}
const parentNote = await froca.getNote(parentNoteId);
if (!parentNote || parentNote.type === "search") {
return;
}
const newBranch = froca.getBranch(newBranchId);
setBranch(newBranch);
setPrefix(newBranch?.prefix ?? "");
setBranches(newBranches);
// Use the prefix of the first branch as the initial value
setPrefix(newBranches[0]?.prefix ?? "");
setShown(true);
});
async function onSubmit() {
if (!branch) {
if (branches.length === 0) {
return;
}
savePrefix(branch.branchId, prefix);
if (branches.length === 1) {
await savePrefix(branches[0].branchId, prefix);
} else {
await savePrefixBatch(branches.map(b => b.branchId), prefix);
}
setShown(false);
}
const isSingleBranch = branches.length === 1;
return (
<Modal
className="branch-prefix-dialog"
title={t("branch_prefix.edit_branch_prefix")}
title={isSingleBranch ? t("branch_prefix.edit_branch_prefix") : t("branch_prefix.edit_branch_prefix_multiple", { count: branches.length })}
size="lg"
onShown={() => branchInput.current?.focus()}
onHidden={() => setShown(false)}
@@ -69,9 +102,27 @@ export default function BranchPrefixDialog() {
<div class="input-group">
<input class="branch-prefix-input form-control" value={prefix} ref={branchInput}
onChange={(e) => setPrefix((e.target as HTMLInputElement).value)} />
<div class="branch-prefix-note-title input-group-text"> - {branch && branch.getNoteFromCache().title}</div>
{isSingleBranch && branches[0] && (
<div class="branch-prefix-note-title input-group-text"> - {branches[0].getNoteFromCache().title}</div>
)}
</div>
</FormGroup>
{!isSingleBranch && (
<div className="branch-prefix-notes-list">
<strong>{t("branch_prefix.affected_branches", { count: branches.length })}</strong>
<ul>
{branches.map((branch) => {
const note = branch.getNoteFromCache();
return (
<li key={branch.branchId}>
{branch.prefix && <span className="branch-prefix-current">{branch.prefix} - </span>}
{note.title}
</li>
);
})}
</ul>
</div>
)}
</Modal>
);
}
@@ -80,3 +131,8 @@ async function savePrefix(branchId: string, prefix: string) {
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved"));
}
async function savePrefixBatch(branchIds: string[], prefix: string) {
await server.put("branches/set-prefix-batch", { branchIds, prefix });
toast.showMessage(t("branch_prefix.branch_prefix_saved_multiple", { count: branchIds.length }));
}

View File

@@ -57,17 +57,19 @@ const TPL = /*html*/`\
}
</style>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<!-- This is where the first child will be injected -->
<div class="quick-edit-dialog-wrapper">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<!-- This is where the first child will be injected -->
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- This is where all but the first child will be injected. -->
<div class="modal-body">
<!-- This is where all but the first child will be injected. -->
</div>
</div>
</div>
</div>
@@ -79,6 +81,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
private noteContext: NoteContext;
private $modalHeader!: JQuery<HTMLElement>;
private $modalBody!: JQuery<HTMLElement>;
private $wrapper!: JQuery<HTMLDivElement>;
constructor() {
super();
@@ -93,6 +96,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
const $newWidget = $(TPL);
this.$modalHeader = $newWidget.find(".modal-title");
this.$modalBody = $newWidget.find(".modal-body");
this.$wrapper = $newWidget.find(".quick-edit-dialog-wrapper");
const children = this.$widget.children();
this.$modalHeader.append(children[0]);
@@ -112,6 +116,21 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
}
});
const colorClass = this.noteContext.note?.getColorClass();
const wrapperElement = this.$wrapper.get(0)!;
if (colorClass) {
wrapperElement.className = "quick-edit-dialog-wrapper " + colorClass;
} else {
wrapperElement.className = "quick-edit-dialog-wrapper";
}
const customHue = getComputedStyle(wrapperElement).getPropertyValue("--custom-color-hue");
if (customHue) {
/* Apply the tinted-dialog class only if the custom color CSS class specifies a hue */
wrapperElement.classList.add("tinted-quick-edit-dialog");
}
const activeEl = document.activeElement;
if (activeEl && "blur" in activeEl) {
(activeEl as HTMLElement).blur();

View File

@@ -39,12 +39,14 @@ const TPL = /*html*/`
<div class="note-detail">
<style>
.note-detail {
max-width: var(--max-content-width); /* Inherited from .note-split */
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
}
.note-detail.full-height {
height: 100%;
body.prefers-centered-content .note-detail {
/* Horizontally center the widget in its parent when the "Keep content centered" option is on */
margin-inline: auto;
}
</style>
</div>

View File

@@ -173,14 +173,6 @@ interface ExpandedSubtreeResponse {
branchIds: string[];
}
interface Node extends Fancytree.NodeData {
noteId: string;
parentNoteId: string;
branchId: string;
isProtected: boolean;
noteType: NoteType;
}
interface RefreshContext {
noteIdsToUpdate: Set<string>;
noteIdsToReload: Set<string>;
@@ -769,7 +761,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
prepareChildren(parentNote: FNote) {
utils.assertArguments(parentNote);
const noteList: Node[] = [];
const noteList: Fancytree.FancytreeNewNode[] = [];
const hideArchivedNotes = this.hideArchivedNotes;
@@ -837,7 +829,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const isFolder = note.isFolder();
const node: Node = {
const node: Fancytree.FancytreeNewNode = {
noteId: note.noteId,
parentNoteId: branch.parentNoteId,
branchId: branch.branchId,
@@ -849,7 +841,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
refKey: note.noteId,
lazy: true,
folder: isFolder,
expanded: branch.isExpanded && note.type !== "search",
expanded: !!branch.isExpanded && note.type !== "search",
key: utils.randomString(12) // this should prevent some "duplicate key" errors
};
@@ -911,7 +903,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return extraClasses.join(" ");
}
/** @returns {FancytreeNode[]} */
getSelectedNodes(stopOnParents = false) {
return this.tree.getSelectedNodes(stopOnParents);
}
@@ -1532,7 +1523,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
// Automatically expand the hoisted note by default
const node = this.getActiveNode();
if (node?.data.noteId === this.noteContext.hoistedNoteId){
if (node && node.data.noteId === this.noteContext.hoistedNoteId){
this.setExpanded(node.data.branchId, true);
}
}
@@ -1591,6 +1582,20 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.clearSelectedNodes();
}
async editBranchPrefixCommand({ node }: CommandListenerData<"editBranchPrefix">) {
const branchIds = this.getSelectedOrActiveBranchIds(node).filter((branchId) => !branchId.startsWith("virt-"));
if (!branchIds.length) {
return;
}
// Trigger the event with the selected branch IDs
appContext.triggerEvent("editBranchPrefix", {
selectedOrActiveBranchIds: branchIds,
node: node
});
}
canBeMovedUpOrDown(node: Fancytree.FancytreeNode) {
if (node.data.noteId === "root") {
return false;

View File

@@ -52,6 +52,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
const note = this.noteContext?.note;
if (!note) {
this.$widget.addClass("bgfx");
return;
}
@@ -61,7 +62,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
this.$widget.addClass(utils.getNoteTypeClass(note.type));
this.$widget.addClass(utils.getMimeTypeClass(note.mime));
this.$widget.toggleClass(["bgfx", "options"], note.isOptions());
this.$widget.toggleClass("protected", note.isProtected);
const noteLanguage = note?.getLabelValue("language");
@@ -70,7 +71,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
}
#isFullWidthNote(note: FNote) {
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
if (["code", "image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
return true;
}

View File

@@ -0,0 +1,23 @@
.info-bar {
--link-color: currentColor;
margin-top: 4px;
contain: unset !important;
padding: 8px 20px;
color: var(--read-only-note-info-bar-color);
font-size: .9em;
cursor: default;
user-select: none;
}
.info-bar-prominent {
background: var(--alert-bar-background, var(--accented-background-color));
}
.info-bar-subtle {
color: var(--muted-text-color);
border-bottom: 1px solid var(--main-border-color);
margin-block: 0;
margin-inline: 10px;
padding-inline: 12px;
}

View File

@@ -0,0 +1,19 @@
import "./InfoBar.css";
import { ComponentChildren, CSSProperties } from "preact";
export type InfoBarParams = {
type: "prominent" | "subtle",
className: string;
style: CSSProperties
children: ComponentChildren;
};
export default function InfoBar(props: InfoBarParams) {
return <div className={`info-bar ${props.className} info-bar-${props.type}`} style={props.style}>
{props?.children}
</div>
}
InfoBar.defaultProps = {
type: "prominent"
} as InfoBarParams

View File

@@ -1,25 +1,26 @@
import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { CommandListenerData, EventData, EventNames } from "../../components/app_context";
import { ParentComponent } from "./react_utils";
import SpacedUpdate from "../../services/spaced_update";
import { CSSProperties } from "preact/compat";
import { DragData } from "../note_tree";
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
import options, { type OptionValue } from "../../services/options";
import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils";
import NoteContext from "../../components/note_context";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import FBlob from "../../entities/fblob";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { ParentComponent } from "./react_utils";
import { RefObject, VNode } from "preact";
import { Tooltip } from "bootstrap";
import { CSSProperties } from "preact/compat";
import { ViewMode } from "../../services/link";
import appContext, { CommandListenerData, EventData, EventNames } from "../../components/app_context";
import attributes from "../../services/attributes";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import Component from "../../components/component";
import FBlob from "../../entities/fblob";
import FNote from "../../entities/fnote";
import keyboard_actions from "../../services/keyboard_actions";
import Mark from "mark.js";
import { DragData } from "../note_tree";
import Component from "../../components/component";
import NoteContext from "../../components/note_context";
import NoteContextAwareWidget from "../note_context_aware_widget";
import options, { type OptionValue } from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import SpacedUpdate from "../../services/spaced_update";
import toast, { ToastOptions } from "../../services/toast";
import { ViewMode } from "../../services/link";
import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils";
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
const parentComponent = useContext(ParentComponent);
@@ -198,6 +199,7 @@ export function useNoteContext() {
const [ notePath, setNotePath ] = useState<string | null | undefined>();
const [ note, setNote ] = useState<FNote | null | undefined>();
const [ , setViewMode ] = useState<ViewMode>();
const [ isReadOnlyTemporarilyDisabled, setIsReadOnlyTemporarilyDisabled ] = useState<boolean | null | undefined>(noteContext?.viewScope?.isReadOnly);
const [ refreshCounter, setRefreshCounter ] = useState(0);
useEffect(() => {
@@ -217,6 +219,11 @@ export function useNoteContext() {
setRefreshCounter(refreshCounter + 1);
}
});
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
if (eventNoteContext.ntxId === noteContext?.ntxId) {
setIsReadOnlyTemporarilyDisabled(eventNoteContext?.viewScope?.readOnlyTemporarilyDisabled);
}
});
const parentComponent = useContext(ParentComponent) as ReactWrappedWidget;
useDebugValue(() => `notePath=${notePath}, ntxId=${noteContext?.ntxId}`);
@@ -230,7 +237,8 @@ export function useNoteContext() {
viewScope: noteContext?.viewScope,
componentId: parentComponent.componentId,
noteContext,
parentComponent
parentComponent,
isReadOnlyTemporarilyDisabled
};
}
@@ -701,3 +709,51 @@ export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => v
return () => observer.disconnect();
}, [ callback, ref ]);
}
/**
* Indicates that the current note is in read-only mode, while an editing mode is available,
* and provides a way to switch to editing mode.
*/
export function useIsNoteReadOnly(note: FNote | null | undefined, noteContext: NoteContext | undefined) {
const [isReadOnly, setIsReadOnly] = useState<boolean | undefined>(undefined);
const enableEditing = useCallback(() => {
if (noteContext?.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
}
}, [noteContext]);
useEffect(() => {
if (note && noteContext) {
isNoteReadOnly(note, noteContext).then((readOnly) => {
setIsReadOnly(readOnly);
});
}
}, [note, noteContext]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsReadOnly(false);
}
});
return {isReadOnly, enableEditing};
}
async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
if (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) {
return false;
}
if (options.is("databaseReadonly")) {
return false;
}
if (noteContext.viewScope?.viewMode !== "default" || !await noteContext.isReadOnly()) {
return false;
}
return true;
}

View File

@@ -13,8 +13,8 @@ export default function EditedNotesTab({ note }: TabContext) {
useEffect(() => {
if (!note) return;
server.get<EditedNotesResponse>(`edited-notes/${note.getLabelValue("dateNote")}`).then(async editedNotes => {
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
editedNotes = editedNotes.filter((n) => n.noteId !== note.noteId);
const noteIds = editedNotes.flatMap((n) => n.noteId);
await froca.getNotes(noteIds, true); // preload all at once
setEditedNotes(editedNotes);
});
@@ -41,11 +41,11 @@ export default function EditedNotesTab({ note }: TabContext) {
)}
</span>
)
}))}
}), " ")}
</div>
) : (
<div className="no-edited-notes-found">{t("edited_notes.no_edited_notes_found")}</div>
)}
</div>
)
)
}

View File

@@ -1,19 +1,20 @@
import { ConvertToAttachmentResponse } from "@triliumnext/commons";
import appContext, { CommandNames } from "../../components/app_context";
import FNote from "../../entities/fnote"
import dialog from "../../services/dialog";
import { t } from "../../services/i18n"
import server from "../../services/server";
import toast from "../../services/toast";
import ws from "../../services/ws";
import ActionButton from "../react/ActionButton"
import Dropdown from "../react/Dropdown";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils";
import { ParentComponent } from "../react/react_utils";
import { t } from "../../services/i18n"
import { useContext } from "preact/hooks";
import NoteContext from "../../components/note_context";
import { useIsNoteReadOnly } from "../react/hooks";
import ActionButton from "../react/ActionButton"
import appContext, { CommandNames } from "../../components/app_context";
import branches from "../../services/branches";
import dialog from "../../services/dialog";
import Dropdown from "../react/Dropdown";
import FNote from "../../entities/fnote"
import NoteContext from "../../components/note_context";
import server from "../../services/server";
import toast from "../../services/toast";
import ws from "../../services/ws";
interface NoteActionsProps {
note?: FNote;
@@ -52,6 +53,7 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
const isMac = getIsMac();
const hasSource = ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type);
const isSearchOrBook = ["search", "book"].includes(note.type);
const {isReadOnly, enableEditing} = useIsNoteReadOnly(note, noteContext);
return (
<Dropdown
@@ -59,8 +61,14 @@ function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: Not
className="note-actions"
hideToggleArrow
noSelectButtonStyle
iconAction
>
iconAction>
{isReadOnly && <>
<CommandItem icon="bx bx-pencil" text={t("read-only-info.edit-note")}
command={() => enableEditing()} />
<FormDropdownDivider />
</>}
{canBeConvertedToAttachment && <ConvertToAttachment note={note} /> }
{note.type === "render" && <CommandItem command="renderActiveNote" icon="bx bx-extension" text={t("note_actions.re_render_note")} />}
<CommandItem command="findInText" icon="bx bx-search" disabled={!isSearchable} text={t("note_actions.search_in_note")} />

View File

@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"
import { useNoteContext, useNoteProperty, useStaticTooltipWithKeyboardShortcut, useTriliumEvents } from "../react/hooks";
import "./style.css";
import { numberObjectsInPlace } from "../../services/utils";
import { Indexed, numberObjectsInPlace } from "../../services/utils";
import { EventNames } from "../../components/app_context";
import NoteActions from "./NoteActions";
import { KeyboardActionNames } from "@triliumnext/commons";
@@ -11,30 +11,47 @@ import { TabConfiguration, TitleContext } from "./ribbon-interface";
const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>(RIBBON_TAB_DEFINITIONS);
interface ComputedTab extends Indexed<TabConfiguration> {
shouldShow: boolean;
}
export default function Ribbon() {
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId, isReadOnlyTemporarilyDisabled } = useNoteContext();
const noteType = useNoteProperty(note, "type");
const titleContext: TitleContext = { note };
const [ activeTabIndex, setActiveTabIndex ] = useState<number | undefined>();
const computedTabs = useMemo(
() => TAB_CONFIGURATION.map(tab => {
const shouldShow = typeof tab.show === "boolean" ? tab.show : tab.show?.(titleContext);
return {
const [ computedTabs, setComputedTabs ] = useState<ComputedTab[]>();
const titleContext: TitleContext = useMemo(() => ({
note,
noteContext
}), [ note, noteContext ]);
async function refresh() {
const computedTabs: ComputedTab[] = [];
for (const tab of TAB_CONFIGURATION) {
const shouldShow = await shouldShowTab(tab.show, titleContext);
computedTabs.push({
...tab,
shouldShow
}
}),
[ titleContext, note, noteType ]);
shouldShow: !!shouldShow
});
}
setComputedTabs(computedTabs);
}
useEffect(() => {
refresh();
}, [ note, noteType, isReadOnlyTemporarilyDisabled ]);
// Automatically activate the first ribbon tab that needs to be activated whenever a note changes.
useEffect(() => {
if (!computedTabs) return;
const tabToActivate = computedTabs.find(tab => tab.shouldShow && (typeof tab.activate === "boolean" ? tab.activate : tab.activate?.(titleContext)));
setActiveTabIndex(tabToActivate?.index);
}, [ note?.noteId ]);
}, [ computedTabs, note?.noteId ]);
// Register keyboard shortcuts.
const eventsToListenTo = useMemo(() => TAB_CONFIGURATION.filter(config => config.toggleCommand).map(config => config.toggleCommand) as EventNames[], []);
useTriliumEvents(eventsToListenTo, useCallback((e, toggleCommand) => {
if (!computedTabs) return;
const correspondingTab = computedTabs.find(tab => tab.toggleCommand === toggleCommand);
if (correspondingTab) {
if (activeTabIndex !== correspondingTab.index) {
@@ -51,7 +68,7 @@ export default function Ribbon() {
<>
<div className="ribbon-top-row">
<div className="ribbon-tab-container">
{computedTabs.map(({ title, icon, index, toggleCommand, shouldShow }) => (
{computedTabs && computedTabs.map(({ title, icon, index, toggleCommand, shouldShow }) => (
shouldShow && <RibbonTab
icon={icon}
title={typeof title === "string" ? title : title(titleContext)}
@@ -74,7 +91,7 @@ export default function Ribbon() {
</div>
<div className="ribbon-body-container">
{computedTabs.map(tab => {
{computedTabs && computedTabs.map(tab => {
const isActive = tab.index === activeTabIndex;
if (!isActive && !tab.stayInDom) {
return;
@@ -129,3 +146,9 @@ function RibbonTab({ icon, title, active, onClick, toggleCommand }: { icon: stri
)
}
export async function shouldShowTab(showConfig: boolean | ((context: TitleContext) => Promise<boolean | null | undefined> | boolean | null | undefined), context: TitleContext) {
if (showConfig === null || showConfig === undefined) return true;
if (typeof showConfig === "boolean") return showConfig;
if ("then" in showConfig) return await showConfig(context);
return showConfig(context);
}

View File

@@ -21,7 +21,9 @@ export const RIBBON_TAB_DEFINITIONS: TabConfiguration[] = [
{
title: t("classic_editor_toolbar.title"),
icon: "bx bx-text",
show: ({ note }) => note?.type === "text" && options.get("textNoteEditorType") === "ckeditor-classic",
show: async ({ note, noteContext }) => note?.type === "text"
&& options.get("textNoteEditorType") === "ckeditor-classic"
&& !(await noteContext?.isReadOnly()),
toggleCommand: "toggleRibbonTabClassicEditor",
content: FormattingToolbar,
activate: true,

View File

@@ -1,8 +1,9 @@
import { ComponentChildren } from "preact";
import { useNoteContext } from "../../react/hooks";
import { TabContext, TitleContext } from "../ribbon-interface";
import { TabContext } from "../ribbon-interface";
import { useEffect, useMemo, useState } from "preact/hooks";
import { RIBBON_TAB_DEFINITIONS } from "../RibbonDefinition";
import { shouldShowTab } from "../Ribbon";
interface StandaloneRibbonAdapterProps {
component: (props: TabContext) => ComponentChildren;
@@ -16,10 +17,11 @@ export default function StandaloneRibbonAdapter({ component }: StandaloneRibbonA
const Component = component;
const { note, ntxId, hoistedNoteId, notePath, noteContext, componentId } = useNoteContext();
const definition = useMemo(() => RIBBON_TAB_DEFINITIONS.find(def => def.content === component), [ component ]);
const [ shown, setShown ] = useState(unwrapShown(definition?.show, { note }));
const [ shown, setShown ] = useState<boolean | null | undefined>(false);
useEffect(() => {
setShown(unwrapShown(definition?.show, { note }));
if (!definition) return;
shouldShowTab(definition.show, { note, noteContext }).then(setShown);
}, [ note ]);
return (
@@ -35,9 +37,3 @@ export default function StandaloneRibbonAdapter({ component }: StandaloneRibbonA
/>
);
}
function unwrapShown(value: boolean | ((context: TitleContext) => boolean | null | undefined) | undefined, context: TitleContext) {
if (!value) return true;
if (typeof value === "boolean") return value;
return !!value(context);
}

View File

@@ -16,13 +16,14 @@ export interface TabContext {
export interface TitleContext {
note: FNote | null | undefined;
noteContext: NoteContext | undefined;
}
export interface TabConfiguration {
title: string | ((context: TitleContext) => string);
icon: string;
content: (context: TabContext) => VNode | false;
show: boolean | ((context: TitleContext) => boolean | null | undefined);
show: boolean | ((context: TitleContext) => Promise<boolean | null | undefined> | boolean | null | undefined);
toggleCommand?: KeyboardActionNames;
activate?: boolean | ((context: TitleContext) => boolean);
/**

View File

@@ -0,0 +1,7 @@
.shared-info-widget {
display: flex;
}
.shared-info-widget button {
margin-inline-start: 8px;
}

View File

@@ -1,11 +1,12 @@
import { useEffect, useState } from "preact/hooks";
import "./shared_info.css";
import { t } from "../services/i18n";
import Alert from "./react/Alert";
import { useEffect, useState } from "preact/hooks";
import { useNoteContext, useTriliumEvent, useTriliumOption } from "./react/hooks";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import RawHtml from "./react/RawHtml";
import FNote from "../entities/fnote";
import HelpButton from "./react/HelpButton";
import InfoBar from "./react/InfoBar";
import RawHtml from "./react/RawHtml";
export default function SharedInfo() {
const { note } = useNoteContext();
@@ -35,7 +36,7 @@ export default function SharedInfo() {
link = `${location.protocol}//${host}${location.pathname}share/${shareId}`;
}
setLink(`<a href="${link}" class="external">${link}</a>`);
setLink(`<a href="${link}" class="external tn-link">${link}</a>`);
}
useEffect(refresh, [ note ]);
@@ -48,20 +49,14 @@ export default function SharedInfo() {
});
return (
<Alert className="shared-info-widget" type="warning" style={{
contain: "none",
margin: "10px",
padding: "10px",
fontWeight: "bold",
display: !link ? "none" : undefined
}}>
<InfoBar className="shared-info-widget" type="subtle" style={{display: (!link) ? "none" : undefined}}>
{link && (
<RawHtml html={syncServerHost
? t("shared_info.shared_publicly", { link })
: t("shared_info.shared_locally", { link })} />
)}
<HelpButton helpPage="R9pX4DGra2Vt" style={{ width: "24px", height: "24px" }} />
</Alert>
</InfoBar>
)
}

View File

@@ -13,6 +13,10 @@ import protected_session_holder from "../../services/protected_session_holder.js
const TPL = /*html*/`
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
<style>
.canvas-widget {
height: 100%;
}
.excalidraw .App-menu_top .buttonList {
display: flex;
}

View File

@@ -284,7 +284,8 @@ function SmoothScrollEnabledOption() {
}
function MaxContentWidth() {
const [ maxContentWidth, setMaxContentWidth ] = useTriliumOption("maxContentWidth");
const [maxContentWidth, setMaxContentWidth] = useTriliumOption("maxContentWidth");
const [centerContent, setCenterContent] = useTriliumOptionBool("centerContent");
return (
<OptionsSection title={t("max_content_width.title")}>
@@ -300,9 +301,9 @@ function MaxContentWidth() {
</FormGroup>
</Column>
<p>
{t("max_content_width.apply_changes_description")} <Button text={t("max_content_width.reload_button")} size="micro" onClick={reloadFrontendApp} />
</p>
<FormCheckbox label={t("max_content_width.centerContent")}
currentValue={centerContent}
onChange={setCenterContent} />
</OptionsSection>
)
}

View File

@@ -97,7 +97,7 @@ function TokenList({ tokens }: { tokens: EtapiToken[] }) {
return (
tokens.length ? (
<div style={{ overflow: "auto", height: "500px"}}>
<div style={{ overflow: "auto"}}>
<table className="table table-stripped">
<thead>
<tr>

View File

@@ -72,8 +72,8 @@ function EditorFeatures() {
return (
<OptionsSection title={t("editorfeatures.title")}>
<EditorFeature name="emoji-completion-enabled" optionName="textNoteEmojiCompletionEnabled" label={t("editorfeatures.emoji_completion_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="note-completion-enabled" optionName="textNoteCompletionEnabled" label={t("editorfeatures.note_completion_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="slash-commands-enabled" optionName="textNoteSlashCommandsEnabled" label={t("editorfeatures.slash_commands_enabled")} description={t("editorfeatures.emoji_completion_description")} />
<EditorFeature name="note-completion-enabled" optionName="textNoteCompletionEnabled" label={t("editorfeatures.note_completion_enabled")} description={t("editorfeatures.note_completion_description")} />
<EditorFeature name="slash-commands-enabled" optionName="textNoteSlashCommandsEnabled" label={t("editorfeatures.slash_commands_enabled")} description={t("editorfeatures.slash_commands_description")} />
</OptionsSection>
);
}

View File

@@ -33,6 +33,7 @@ const TPL = /*html*/`
body.heading-style-underline .note-detail-readonly-text h6 { border-bottom: 1px solid var(--main-border-color); }
.note-detail-readonly-text {
padding-inline-start: 24px;
padding-top: 10px;
font-family: var(--detail-font-family);

View File

@@ -103,7 +103,7 @@ const linkOverlays = [
];
const TPL = /*html*/`
<div class="note-detail-relation-map note-detail-printable">
<div class="note-detail-relation-map full-height note-detail-printable">
<div class="relation-map-wrapper">
<div class="relation-map-container"></div>
</div>

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/desktop",
"version": "0.99.3",
"version": "0.99.4",
"description": "Build your personal knowledge base with Trilium Notes",
"private": true,
"main": "src/main.ts",
@@ -35,7 +35,7 @@
"@triliumnext/commons": "workspace:*",
"@triliumnext/server": "workspace:*",
"copy-webpack-plugin": "13.0.1",
"electron": "38.5.0",
"electron": "38.6.0",
"@electron-forge/cli": "7.10.2",
"@electron-forge/maker-deb": "7.10.2",
"@electron-forge/maker-dmg": "7.10.2",

View File

@@ -1,6 +1,6 @@
{
"formatVersion": 2,
"appVersion": "0.99.2",
"appVersion": "0.99.3",
"files": [
{
"isClone": false,
@@ -2700,6 +2700,7 @@
}
],
"format": "html",
"dataFileName": "Note Types.html",
"attachments": [],
"dirFileName": "Note Types",
"children": [

View File

@@ -270,7 +270,7 @@
</li>
</ul>
</li>
<li>Note Types
<li><a href="root/Trilium%20Demo/Note%20Types.html" target="detail">Note Types</a>
<ul>
<li><a href="root/Trilium%20Demo/Note%20Types/Canvas.json" target="detail">Canvas</a>
</li>

View File

@@ -14,6 +14,7 @@
<div class="ck-content">
<h2>☑️ Tasks</h2>
<ul>
<li data-list-item-id="e4b26220d6ce48997f1116dc1d1d83dc0">[…]</li>
</ul>

View File

@@ -14,11 +14,10 @@
<div class="ck-content">
<figure class="image image-style-align-right image_resized" style="width:29.84%;">
<img style="aspect-ratio:150/150;" src="Trilium Demo_icon-color.svg" width="150"
height="150">
<img style="aspect-ratio:150/150;" src="Trilium Demo_icon-color.svg"
width="150" height="150">
</figure>
<p><strong>Welcome to Trilium Notes!</strong>
</p>
<p>This is a "demo" document packaged with Trilium to showcase some of its
features and also give you some ideas on how you might structure your notes.
@@ -26,22 +25,17 @@
you wish.</p>
<p>If you need any help, visit <a href="https://triliumnotes.org">triliumnotes.org</a> or
our <a href="https://github.com/TriliumNext">GitHub repository</a>
</p>
<h2>Cleanup</h2>
<p>Once you're finished with experimenting and want to cleanup these pages,
you can simply delete them all.</p>
<h2>Formatting</h2>
<p>Trilium supports classic formatting like <em>italic</em>, <strong>bold</strong>, <em><strong>bold and italic</strong></em>.
You can add links pointing to <a href="https://triliumnotes.org/">external pages</a> or&nbsp;
<a
class="reference-link" href="Trilium%20Demo/Formatting%20examples">Formatting examples</a>.</p>
<h3>Lists</h3>
<p><strong>Ordered:</strong>
</p>
<ol>
<li data-list-item-id="e877cc655d0239b8bb0f38696ad5d8abb">First Item</li>
@@ -56,7 +50,6 @@
</li>
</ol>
<p><strong>Unordered:</strong>
</p>
<ul>
<li data-list-item-id="e68bf4b518a16671c314a72073c3d900a">Item</li>
@@ -67,7 +60,6 @@
</li>
</ul>
<h3>Block quotes</h3>
<blockquote>
<p>Whereof one cannot speak, thereof one must be silent”</p>
<p> Ludwig Wittgenstein</p>
@@ -75,9 +67,9 @@
<hr>
<p>See also other examples like <a href="Trilium%20Demo/Formatting%20examples/School%20schedule.html">tables</a>,
<a
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists,</a> <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>,
href="Trilium%20Demo/Formatting%20examples/Checkbox%20lists.html">checkbox lists,</a> <a href="Trilium%20Demo/Formatting%20examples/Highlighting.html">highlighting</a>, <a href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>and
<a
href="Trilium%20Demo/Formatting%20examples/Code%20blocks.html">code blocks</a>and <a href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
href="Trilium%20Demo/Formatting%20examples/Math.html">math examples</a>.</p>
</div>
</div>
</body>

View File

@@ -21,8 +21,12 @@
language, should that fail it is possible to manually adjust it. The color
scheme for the syntax highlighting is adjustable in settings.&nbsp;</p><pre><code class="language-application-javascript-env-frontend">function helloWorld() {
alert("Hello world");
}</code></pre>
<p>For larger pieces of code it is better to use a code note, which uses
a fully-fledged code editor (CodeMirror). For an example of a code note,

View File

@@ -0,0 +1,21 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../../style.css">
<base target="_parent">
<title data-trilium-title>Note Types</title>
</head>
<body>
<div class="content">
<h1 data-trilium-h1>Note Types</h1>
<div class="ck-content">
<p>T</p>
</div>
</div>
</body>
</html>

View File

@@ -13,9 +13,8 @@
<h1 data-trilium-h1>Task manager</h1>
<div class="ck-content">
<p>This is a simple TODO/Task manager. You can see some description and explanation
here: <a href="https://github.com/zadam/trilium/wiki/Task-manager">https://github.com/zadam/trilium/wiki/Task-manager</a>
</p>
<p>This is a simple TODO/Task manager. See the <a href="https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases/task-manager">Trilium documentation</a> for
information on how it works.</p>
<p>Please note that this is meant as scripting example only and feature/bug
support is very limited.</p>
</div>

View File

@@ -16,18 +16,32 @@
<p>Documentation: <a href="http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html">http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_02.html</a>
</p><pre><code class="language-text-x-sh">#!/bin/bash
# This script opens 4 terminal windows.
i="0"
while [ $i -lt 4 ]
do
xterm &amp;
i=$[$i+1]
done</code></pre>
</div>
</div>

View File

@@ -12,11 +12,11 @@
"@triliumnext/desktop": "workspace:*",
"@types/fs-extra": "11.0.4",
"copy-webpack-plugin": "13.0.1",
"electron": "38.5.0",
"electron": "38.6.0",
"fs-extra": "11.3.2"
},
"scripts": {
"edit-docs": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-docs.ts",
"edit-demo": "cross-env TRILIUM_PORT=37741 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
"edit-demo": "cross-env TRILIUM_PORT=37744 TRILIUM_DATA_DIR=data TRILIUM_INTEGRATION_TEST=memory-no-store DOCS_ROOT=../../../docs USER_GUIDE_ROOT=\"../../server/src/assets/doc_notes/en/User Guide\" tsx ../../scripts/electron-start.mts src/edit-demo.ts"
}
}

View File

@@ -0,0 +1,502 @@
import { test, expect } from "@playwright/test";
import App from "./support/app";
const BASE_URL = "http://127.0.0.1:8082";
/**
* E2E tests for exact search functionality using the leading "=" operator.
*
* These tests validate the GitHub issue:
* - Searching for "pagio" returns many false positives (e.g., "page", "pages")
* - Searching for "=pagio" should return ONLY exact matches for "pagio"
*/
test.describe("Exact Search with Leading = Operator", () => {
let csrfToken: string;
let createdNoteIds: string[] = [];
test.beforeEach(async ({ page, context }) => {
const app = new App(page, context);
await app.goto();
// Get CSRF token
csrfToken = await page.evaluate(() => {
return (window as any).glob.csrfToken;
});
expect(csrfToken).toBeTruthy();
// Create test notes with specific content patterns
// Note 1: Contains exactly "pagio" in title
const note1 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Test Note with pagio",
content: "This note contains the word pagio in the content.",
type: "text"
}
});
expect(note1.ok()).toBeTruthy();
const note1Data = await note1.json();
createdNoteIds.push(note1Data.note.noteId);
// Note 2: Contains "page" (not exact match)
const note2 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Test Note with page",
content: "This note contains the word page in the content.",
type: "text"
}
});
expect(note2.ok()).toBeTruthy();
const note2Data = await note2.json();
createdNoteIds.push(note2Data.note.noteId);
// Note 3: Contains "pages" (plural, not exact match)
const note3 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Test Note with pages",
content: "This note contains the word pages in the content.",
type: "text"
}
});
expect(note3.ok()).toBeTruthy();
const note3Data = await note3.json();
createdNoteIds.push(note3Data.note.noteId);
// Note 4: Contains "homepage" (contains "page", not exact match)
const note4 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Homepage Note",
content: "This note is about homepage content.",
type: "text"
}
});
expect(note4.ok()).toBeTruthy();
const note4Data = await note4.json();
createdNoteIds.push(note4Data.note.noteId);
// Note 5: Another note with exact "pagio" in content
const note5 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Another pagio Note",
content: "This is another note with pagio content for testing exact matches.",
type: "text"
}
});
expect(note5.ok()).toBeTruthy();
const note5Data = await note5.json();
createdNoteIds.push(note5Data.note.noteId);
// Note 6: Contains "pagio" in title only
const note6 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "pagio",
content: "This note has pagio as the title.",
type: "text"
}
});
expect(note6.ok()).toBeTruthy();
const note6Data = await note6.json();
createdNoteIds.push(note6Data.note.noteId);
// Wait a bit for indexing
await page.waitForTimeout(500);
});
test.afterEach(async ({ page }) => {
// Clean up created notes
for (const noteId of createdNoteIds) {
try {
const taskId = `cleanup-${Math.random().toString(36).substr(2, 9)}`;
await page.request.delete(`${BASE_URL}/api/notes/${noteId}?taskId=${taskId}&last=true`, {
headers: { "x-csrf-token": csrfToken }
});
} catch (e) {
console.error(`Failed to delete note ${noteId}:`, e);
}
}
createdNoteIds = [];
});
test("Quick search without = operator returns all partial matches", async ({ page }) => {
// Test the /quick-search endpoint without the = operator
const response = await page.request.get(`${BASE_URL}/api/quick-search/pag`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
// Should return multiple notes including "page", "pages", "homepage"
expect(data.searchResultNoteIds).toBeDefined();
expect(data.searchResults).toBeDefined();
// Filter to only our test notes
const testResults = data.searchResults.filter((result: any) =>
result.noteTitle.includes("page") ||
result.noteTitle.includes("pagio") ||
result.noteTitle.includes("Homepage")
);
// Should find at least "page", "pages", "homepage", and "pagio" notes
expect(testResults.length).toBeGreaterThanOrEqual(4);
console.log("Quick search 'pag' found:", testResults.length, "matching notes");
console.log("Note titles:", testResults.map((r: any) => r.noteTitle));
});
test("Quick search with = operator returns only exact matches", async ({ page }) => {
// Test the /quick-search endpoint WITH the = operator
const response = await page.request.get(`${BASE_URL}/api/quick-search/=pagio`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
// Should return only notes with exact "pagio" match
expect(data.searchResultNoteIds).toBeDefined();
expect(data.searchResults).toBeDefined();
// Filter to only our test notes
const testResults = data.searchResults.filter((result: any) =>
createdNoteIds.includes(result.notePath.split("/").pop() || "")
);
console.log("Quick search '=pagio' found:", testResults.length, "matching notes");
console.log("Note titles:", testResults.map((r: any) => r.noteTitle));
// Should find exactly 3 notes: "Test Note with pagio", "Another pagio Note", "pagio"
expect(testResults.length).toBe(3);
// Verify that none of the results contain "page" or "pages" (only "pagio")
for (const result of testResults) {
const title = result.noteTitle.toLowerCase();
const hasPageNotPagio = (title.includes("page") && !title.includes("pagio"));
expect(hasPageNotPagio).toBe(false);
}
});
test("Full search API without = operator returns partial matches", async ({ page }) => {
// Test the /search endpoint without the = operator
const response = await page.request.get(`${BASE_URL}/api/search/pag`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
// Should return an array of note IDs
expect(Array.isArray(data)).toBe(true);
// Filter to only our test notes
const testNoteIds = data.filter((id: string) => createdNoteIds.includes(id));
console.log("Full search 'pag' found:", testNoteIds.length, "matching notes from our test set");
// Should find at least 4 notes
expect(testNoteIds.length).toBeGreaterThanOrEqual(4);
});
test("Full search API with = operator returns only exact matches", async ({ page }) => {
// Test the /search endpoint WITH the = operator
const response = await page.request.get(`${BASE_URL}/api/search/=pagio`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
// Should return an array of note IDs
expect(Array.isArray(data)).toBe(true);
// Filter to only our test notes
const testNoteIds = data.filter((id: string) => createdNoteIds.includes(id));
console.log("Full search '=pagio' found:", testNoteIds.length, "matching notes from our test set");
// Should find exactly 3 notes with exact "pagio" match
expect(testNoteIds.length).toBe(3);
});
test("Exact search operator works with content search", async ({ page }) => {
// Create a note with "test" in title but different content
const noteWithTest = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Testing Content",
content: "This note contains the exact word test in content.",
type: "text"
}
});
expect(noteWithTest.ok()).toBeTruthy();
const noteWithTestData = await noteWithTest.json();
const testNoteId = noteWithTestData.note.noteId;
createdNoteIds.push(testNoteId);
// Create a note with "testing" (not exact match)
const noteWithTesting = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Testing More",
content: "This note has testing in the content.",
type: "text"
}
});
expect(noteWithTesting.ok()).toBeTruthy();
const noteWithTestingData = await noteWithTesting.json();
createdNoteIds.push(noteWithTestingData.note.noteId);
await page.waitForTimeout(500);
// Search with exact operator
const response = await page.request.get(`${BASE_URL}/api/quick-search/=test`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
const ourTestNotes = data.searchResults.filter((result: any) => {
const noteId = result.notePath.split("/").pop();
return noteId === testNoteId || noteId === noteWithTestingData.note.noteId;
});
console.log("Exact search '=test' found our test notes:", ourTestNotes.length);
console.log("Note titles:", ourTestNotes.map((r: any) => r.noteTitle));
// Should find the note with exact "test" match, but not "testing"
// Note: This test may fail if the implementation doesn't properly handle exact matching in content
expect(ourTestNotes.length).toBeGreaterThan(0);
});
test("Exact search is case-insensitive", async ({ page }) => {
// Create notes with different case variations
const noteUpper = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "EXACT MATCH",
content: "This note has EXACT in uppercase.",
type: "text"
}
});
expect(noteUpper.ok()).toBeTruthy();
const noteUpperData = await noteUpper.json();
createdNoteIds.push(noteUpperData.note.noteId);
const noteLower = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "exact match",
content: "This note has exact in lowercase.",
type: "text"
}
});
expect(noteLower.ok()).toBeTruthy();
const noteLowerData = await noteLower.json();
createdNoteIds.push(noteLowerData.note.noteId);
await page.waitForTimeout(500);
// Search with exact operator in lowercase
const response = await page.request.get(`${BASE_URL}/api/quick-search/=exact`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
const ourTestNotes = data.searchResults.filter((result: any) => {
const noteId = result.notePath.split("/").pop();
return noteId === noteUpperData.note.noteId || noteId === noteLowerData.note.noteId;
});
console.log("Case-insensitive exact search found:", ourTestNotes.length, "notes");
// Should find both uppercase and lowercase versions
expect(ourTestNotes.length).toBe(2);
});
test("Exact phrase matching with multi-word searches", async ({ page }) => {
// Create notes with various phrase patterns
const note1 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "exact phrase",
content: "This note contains the exact phrase.",
type: "text"
}
});
expect(note1.ok()).toBeTruthy();
const note1Data = await note1.json();
createdNoteIds.push(note1Data.note.noteId);
const note2 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "exact phrase match",
content: "This note has exact phrase followed by more words.",
type: "text"
}
});
expect(note2.ok()).toBeTruthy();
const note2Data = await note2.json();
createdNoteIds.push(note2Data.note.noteId);
const note3 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "phrase exact",
content: "This note has the words in reverse order.",
type: "text"
}
});
expect(note3.ok()).toBeTruthy();
const note3Data = await note3.json();
createdNoteIds.push(note3Data.note.noteId);
const note4 = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "this exact and that phrase",
content: "Words are separated but both present.",
type: "text"
}
});
expect(note4.ok()).toBeTruthy();
const note4Data = await note4.json();
createdNoteIds.push(note4Data.note.noteId);
await page.waitForTimeout(500);
// Search for exact phrase "exact phrase"
const response = await page.request.get(`${BASE_URL}/api/quick-search/='exact phrase'`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
const ourTestNotes = data.searchResults.filter((result: any) => {
const noteId = result.notePath.split("/").pop();
return [note1Data.note.noteId, note2Data.note.noteId, note3Data.note.noteId, note4Data.note.noteId].includes(noteId || "");
});
console.log("Exact phrase search '=\"exact phrase\"' found:", ourTestNotes.length, "notes");
console.log("Note titles:", ourTestNotes.map((r: any) => r.noteTitle));
// Should find only notes 1 and 2 (consecutive "exact phrase")
// Should NOT find note 3 (reversed order) or note 4 (words separated)
expect(ourTestNotes.length).toBe(2);
const foundTitles = ourTestNotes.map((r: any) => r.noteTitle);
expect(foundTitles).toContain("exact phrase");
expect(foundTitles).toContain("exact phrase match");
expect(foundTitles).not.toContain("phrase exact");
expect(foundTitles).not.toContain("this exact and that phrase");
});
test("Exact phrase matching respects word order", async ({ page }) => {
// Create notes to test word order sensitivity
const noteForward = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Testing Order",
content: "This is a test sentence for verification.",
type: "text"
}
});
expect(noteForward.ok()).toBeTruthy();
const noteForwardData = await noteForward.json();
createdNoteIds.push(noteForwardData.note.noteId);
const noteReverse = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Order Testing",
content: "A sentence test is this for verification.",
type: "text"
}
});
expect(noteReverse.ok()).toBeTruthy();
const noteReverseData = await noteReverse.json();
createdNoteIds.push(noteReverseData.note.noteId);
await page.waitForTimeout(500);
// Search for exact phrase "test sentence"
const response = await page.request.get(`${BASE_URL}/api/quick-search/='test sentence'`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
const ourTestNotes = data.searchResults.filter((result: any) => {
const noteId = result.notePath.split("/").pop();
return noteId === noteForwardData.note.noteId || noteId === noteReverseData.note.noteId;
});
console.log("Exact phrase search '=\"test sentence\"' found:", ourTestNotes.length, "notes");
console.log("Note titles:", ourTestNotes.map((r: any) => r.noteTitle));
// Should find only the forward order note
expect(ourTestNotes.length).toBe(1);
expect(ourTestNotes[0].noteTitle).toBe("Testing Order");
});
test("Multi-word exact search without quotes", async ({ page }) => {
// Test that multi-word search with = but without quotes also does exact phrase matching
const notePhrase = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Quick Test Note",
content: "A simple note for multi word testing.",
type: "text"
}
});
expect(notePhrase.ok()).toBeTruthy();
const notePhraseData = await notePhrase.json();
createdNoteIds.push(notePhraseData.note.noteId);
const noteScattered = await page.request.post(`${BASE_URL}/api/notes/root/children?target=into&targetBranchId=`, {
headers: { "x-csrf-token": csrfToken },
data: {
title: "Word Multi Testing",
content: "Words are multi scattered in this testing example.",
type: "text"
}
});
expect(noteScattered.ok()).toBeTruthy();
const noteScatteredData = await noteScattered.json();
createdNoteIds.push(noteScatteredData.note.noteId);
await page.waitForTimeout(500);
// Search for "=multi word" without quotes (parser tokenizes as two words)
const response = await page.request.get(`${BASE_URL}/api/quick-search/=multi word`, {
headers: { "x-csrf-token": csrfToken }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
const ourTestNotes = data.searchResults.filter((result: any) => {
const noteId = result.notePath.split("/").pop();
return noteId === notePhraseData.note.noteId || noteId === noteScatteredData.note.noteId;
});
console.log("Multi-word exact search '=multi word' found:", ourTestNotes.length, "notes");
console.log("Note titles:", ourTestNotes.map((r: any) => r.noteTitle));
// Should find only the note with consecutive "multi word" phrase
expect(ourTestNotes.length).toBe(1);
expect(ourTestNotes[0].noteTitle).toBe("Quick Test Note");
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/server",
"version": "0.99.3",
"version": "0.99.4",
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
"private": true,
"main": "./src/main.ts",
@@ -67,7 +67,7 @@
"@types/xml2js": "0.4.14",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"axios": "1.13.1",
"axios": "1.13.2",
"bindings": "1.5.0",
"bootstrap": "5.3.8",
"chardet": "2.1.1",
@@ -81,7 +81,7 @@
"debounce": "3.0.0",
"debug": "4.4.3",
"ejs": "3.1.10",
"electron": "38.5.0",
"electron": "38.6.0",
"electron-debug": "4.1.0",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
@@ -97,7 +97,7 @@
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "7.0.6",
"i18next": "25.6.0",
"i18next": "25.6.1",
"i18next-fs-backend": "2.6.0",
"image-type": "6.0.0",
"ini": "6.0.0",
@@ -105,17 +105,17 @@
"is-svg": "6.1.0",
"jimp": "1.6.0",
"js-yaml": "4.1.0",
"marked": "16.4.1",
"marked": "16.4.2",
"mime-types": "3.0.1",
"multer": "2.0.2",
"normalize-strings": "1.1.1",
"ollama": "0.6.2",
"openai": "6.7.0",
"openai": "6.8.1",
"rand-token": "1.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.17.0",
"sax": "1.4.1",
"sax": "1.4.3",
"serve-favicon": "2.5.1",
"stream-throttle": "0.1.3",
"strip-bom": "5.0.0",
@@ -126,7 +126,7 @@
"tmp": "0.2.5",
"turndown": "7.2.2",
"unescape": "1.0.1",
"vite": "7.1.12",
"vite": "7.2.2",
"ws": "8.18.3",
"xml2js": "0.6.2",
"yauzl": "3.2.0"

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@ class="image image_resized" style="width:74.04%;">
<p>To see what embedding models Ollama has available, you can check out
<a
href="https://ollama.com/search?c=embedding">this search</a>on their website, and then <code>pull</code> whichever one
you want to try out. As of 4/15/25, my personal favorite is <code>mxbai-embed-large</code>.</p>
you want to try out. A popular choice is <code>mxbai-embed-large</code>.</p>
<p>First, we'll need to select the Ollama provider from the tabs of providers,
then we will enter in the Base URL for our Ollama. Since our Ollama is
running on our local machine, our Base URL is <code>http://localhost:11434</code>.
@@ -145,17 +145,18 @@ class="image image_resized" style="width:74.04%;">
<p>You don't need to tell the LLM to execute a certain tool, it should “smartly”
call tools and automatically execute them as needed.</p>
<h2>Overview</h2>
<p>Now that you know about embeddings and tools, you can just go ahead and
use the “Chat with Notes” button, where you can go ahead and start chatting!:</p>
<figure
class="image image_resized" style="width:60.77%;">
<p>To start, simply press the <em>Chat with Notes</em> button in the&nbsp;
<a
class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>.</p>
<figure class="image image_resized" style="width:60.77%;">
<img style="aspect-ratio:1378/539;" src="2_AI_image.png"
width="1378" height="539">
</figure>
<p>If you don't see the “Chat with Notes” button on your side launchbar,
you might need to move it from the Available Launchers section to the
Visible Launchers section:</p>
<figure class="image image_resized" style="width:69.81%;">
<img style="aspect-ratio:1765/1287;" src="9_AI_image.png"
width="1765" height="1287">
</figure>
</figure>
<p>If you don't see the button in the&nbsp;<a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>,
you might need to move it from the <em>Available Launchers</em> section to
the <em>Visible Launchers</em> section:</p>
<figure class="image image_resized"
style="width:69.81%;">
<img style="aspect-ratio:1765/1287;" src="9_AI_image.png"
width="1765" height="1287">
</figure>

View File

@@ -8,7 +8,7 @@
<li>
<p><a class="reference-link" href="#root/_help_HI6GBBIduIgv">Labels</a>&nbsp;can
be used for a variety of purposes, such as storing metadata or configuring
the behaviour of notes. Labels are also searchable, enhancing note retrieval.</p>
the behavior of notes. Labels are also searchable, enhancing note retrieval.</p>
<p>For more information, including predefined labels, see&nbsp;<a class="reference-link"
href="#root/_help_HI6GBBIduIgv">Labels</a>.</p>
</li>
@@ -21,7 +21,7 @@
class="reference-link" href="#root/_help_Cq5X6iKQop6R">Relations</a>.</p>
</li>
</ol>
<p>These attributes play a crucial role in organizing, categorising, and
<p>These attributes play a crucial role in organizing, categorizing, and
enhancing the functionality of notes.</p>
<h2>Viewing the list of attributes</h2>
<p>Both the labels and relations for the current note are displayed in the <em>Owned Attributes</em> section

View File

@@ -11,7 +11,7 @@ const {secret, title, content} = req.body;
if (req.method == 'POST' &amp;&amp; secret === 'secret-password') {
// notes must be saved somewhere in the tree hierarchy specified by a parent note.
// This is defined by a relation from this code note to the "target" parent note
// alternetively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note)
// alternatively you can just use constant noteId for simplicity (get that from "Note Info" dialog of the desired parent note)
const targetParentNoteId = api.currentNote.getRelationValue('targetNote');
const {note} = api.createTextNote(targetParentNoteId, title, content);
@@ -30,7 +30,7 @@ else {
be saved</li>
</ul>
<h3>Explanation</h3>
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">POST http://my.trilium.org/custom/create-note
<p>Let's test this by using an HTTP client to send a request:</p><pre><code class="language-text-x-trilium-auto">POST http://your-trilium-server/custom/create-note
Content-Type: application/json
{
@@ -64,12 +64,12 @@ Content-Type: application/json
can always look into its <a href="https://expressjs.com/en/api.html">documentation</a> for
details.</p>
<h3>Parameters</h3>
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://my.trilium.org/custom/notes/123</code></pre>
<p>REST request paths often contain parameters in the URL, e.g.:</p><pre><code class="language-text-x-trilium-auto">http://your-trilium-server/custom/notes/123</code></pre>
<p>The last part is dynamic so the matching of the URL must also be dynamic
- for this reason the matching is done with regular expressions. Following <code>customRequestHandler</code> value
would match it:</p><pre><code class="language-text-x-trilium-auto">notes/([0-9]+)</code></pre>
<p>Additionally, this also defines a matching group with the use of parenthesis
which then makes it easier to extract the value. The matched groups are
available in <code>api.pathParams</code>:</p><pre><code class="language-text-x-trilium-auto">const noteId = api.pathParams[0];</code></pre>
<p>Often you also need query params (as in e.g. <code>http://my.trilium.org/custom/notes?noteId=123</code>),
<p>Often you also need query params (as in e.g. <code>http://your-trilium-server/custom/notes?noteId=123</code>),
you can get those with standard express <code>req.query.noteId</code>.</p>

View File

@@ -0,0 +1,35 @@
<p>Nightly releases are versions built every day, containing the latest improvements
and bugfixes, directly from the main development branch. These versions
are generally useful in preparation for a release, to ensure that there
are no significant bugs that need to be addressed first, or they can be
used to confirm whether a particular bug is fixed or feature is well implemented.</p>
<h2>Regarding the stability</h2>
<p>Despite being on a development branch, generally the main branch is pretty
stable since PRs are tested before they are merged. If you notice any issues,
feel free to report them either via a ticket or via the Matrix.</p>
<h2>Downloading the nightly release manually</h2>
<p>Go to <a href="https://github.com/TriliumNext/Trilium/releases/tag/nightly">github.com/TriliumNext/Trilium/releases/tag/nightly</a> and
look for the artifacts starting with <code>TriliumNotes-main</code>. Choose
the appropriate one for your platform (e.g. <code>windows-x64.zip</code>).</p>
<p>Depending on your use case, you can either test the portable version or
even use the installer.</p>
<aside class="admonition note">
<p>If you choose the installable version (e.g. the .exe on Windows), it will
replace your stable installation.</p>
</aside>
<aside class="admonition important">
<p>By default, the nightly uses the same database as the production version.
Generally you could easily downgrade if needed. However, if there are changes
to the database or sync version, it will not be possible to downgrade without
having to restore from a backup.</p>
</aside>
<h2>Automatically download and install the latest nightly</h2>
<p>This is pretty useful if you are a beta tester that wants to periodically
update their version:</p>
<p>On Ubuntu:</p><pre><code class="language-text-x-trilium-auto">#!/usr/bin/env bash
name=TriliumNotes-linux-x64-nightly.deb
rm -f $name*
wget https://github.com/TriliumNext/Trilium/releases/download/nightly/$name
sudo apt-get install ./$name
rm $name</code></pre>

View File

@@ -0,0 +1,41 @@
<aside class="admonition warning">
<p>This functionality is still in preview, expect possible issues or even
the feature disappearing completely.
<br>Feel free to <a href="#root/_help_wy8So3yZZlH9">report</a> any issues you might
have.</p>
</aside>
<p>The read-only database is an alternative to&nbsp;<a class="reference-link"
href="#root/_help_R9pX4DGra2Vt">Sharing</a>&nbsp;notes. Although the share functionality
works pretty well to publish pages to the Internet in a wiki, blog-like
format it does not offer the full functionality behind Trilium (such as
the advanced&nbsp;<a class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a>&nbsp;or
the interactivity behind&nbsp;<a class="reference-link" href="#root/_help_GTwFsgaA0lCt">Collections</a>&nbsp;or
the various&nbsp;<a class="reference-link" href="#root/_help_KSZ04uQ2D1St">Note Types</a>).</p>
<p>When the database is in read-only mode, the Trilium application can be
used as normal, but editing is disabled and changes are made in-memory
only.</p>
<h2>What it does</h2>
<ul>
<li>All notes are read-only, without the possibility of editing them.</li>
<li>Features that would normally alter the database such as the list of recent
notes are disabled.</li>
</ul>
<h2>Limitations</h2>
<ul>
<li>Some features might “slip through” and still end up creating a note, for
example.
<ul>
<li>However, the database is still read-only, so all modifications will be
reset if the server is restarted.</li>
<li>Whenever this occurs, <code>ERROR: read-only DB ignored</code> will be shown
in the logs.</li>
</ul>
</li>
</ul>
<h2>Setting a database as read-only</h2>
<p>First, make sure the database is initialized (e.g. the first set up is
complete). Then modify the <a href="#root/_help_Gzjqa934BdH4">config.ini</a> by
looking for the <code>[General]</code> section and adding a new <code>readOnly</code> field:</p><pre><code class="language-text-x-trilium-auto">[General]
readOnly=true</code></pre>
<p>If your server is already running, restart it to apply the changes.</p>
<p>Similarly, to disable read-only remove the line or set it to <code>false</code>.</p>

View File

@@ -0,0 +1,12 @@
<p>Safe mode is triggered by setting the <code>TRILIUM_SAFE_MODE</code> environment
variable to a truthy value, usually <code>1</code>.</p>
<p>In each artifact there is a <code>trilium-safe-mode.sh</code> (or <code>.bat</code>)
script to enable it.</p>
<p>What it does:</p>
<ul>
<li>Disables <code>customWidget</code> launcher types in <code>app/widgets/containers/launcher.js</code>.</li>
<li>Disables the running of <code>mobileStartup</code> or <code>frontendStartup</code> scripts.</li>
<li>Displays the root note instead of the previously saved session.</li>
<li>Disables the running of <code>backendStartup</code>, <code>hourly</code>, <code>daily</code> scripts
and checks for the hidden subtree.</li>
</ul>

View File

@@ -41,6 +41,15 @@
<li>The export requires a functional web server as the pages will not render
properly if accessed locally via a web browser due to the use of module
scripts.</li>
<li>The directory structure is also slightly different:
<ul>
<li>A normal HTML export results in an index file and a single directory.</li>
<li>Instead, for static exporting the top-root level becomes the index file
and the child directories are on the root instead.</li>
<li>This makes it possible to easily publish to a website, without forcing
everything but the root note to be in a sub-directory.</li>
</ul>
</li>
</ul>
<h2>Testing locally</h2>
<p>As mentioned previously, the exported static pages require a website to

View File

@@ -21,7 +21,7 @@
<ol>
<li>Set the text to search for in the <em>Search string</em> field.
<ol>
<li>Apart from searching for words ad-literam, there is also the possibility
<li>Apart from searching for words literally, there is also the possibility
to search for attributes or properties of notes.</li>
<li>See the examples below for more information.</li>
</ol>

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