Compare commits

..

1329 Commits

Author SHA1 Message Date
Elian Doran
0b5ce95093 fix(standalone): some sql queries not executing properly 2026-03-22 15:48:40 +02:00
Elian Doran
77971a10d1 feat(core): integrate special notes with route 2026-03-22 14:30:33 +02:00
Elian Doran
28a56ff7bf feat(core): integrate search with route 2026-03-22 14:03:48 +02:00
Elian Doran
d7d28bcf58 chore(standalone): align version with the rest 2026-03-22 13:37:52 +02:00
Elian Doran
682e1549f8 fix(standalone): failing due to type error 2026-03-22 13:03:54 +02:00
Elian Doran
d7d2b21935 feat(standalone): improve error handling on initialization 2026-03-22 13:02:50 +02:00
Elian Doran
1b7d2da6cb Merge remote-tracking branch 'origin/main' into standalone
; Conflicts:
;	apps/client/src/layouts/mobile_layout.tsx
;	apps/client/src/services/promoted_attribute_definition_parser.ts
;	apps/server/package.json
;	apps/server/src/becca/entities/bnote.ts
;	apps/server/src/etapi/etapi_utils.ts
;	apps/server/src/etapi/notes.ts
;	apps/server/src/routes/api/clipper.ts
;	apps/server/src/routes/api/export.ts
;	apps/server/src/routes/api/files.ts
;	apps/server/src/routes/api/image.ts
;	apps/server/src/routes/api/import.ts
;	apps/server/src/routes/api/note_map.ts
;	apps/server/src/routes/api/search.ts
;	apps/server/src/routes/api/similar_notes.ts
;	apps/server/src/routes/api/sync.ts
;	apps/server/src/routes/error_handlers.ts
;	apps/server/src/routes/index.ts
;	apps/server/src/routes/route_api.ts
;	apps/server/src/routes/routes.ts
;	apps/server/src/services/anonymization.ts
;	apps/server/src/services/app_info.ts
;	apps/server/src/services/builtin_attributes.ts
;	apps/server/src/services/export/zip.ts
;	apps/server/src/services/hidden_subtree.ts
;	apps/server/src/services/llm/ai_service_manager.ts
;	apps/server/src/services/llm/context/modules/context_formatter.ts
;	apps/server/src/services/llm/context/note_content.ts
;	apps/server/src/services/llm/formatters/base_formatter.ts
;	apps/server/src/services/llm/formatters/ollama_formatter.ts
;	apps/server/src/services/llm/formatters/openai_formatter.ts
;	apps/server/src/services/llm/tools/read_note_tool.ts
;	apps/server/src/services/note_types.ts
;	apps/server/src/services/notes.ts
;	apps/server/src/services/options.ts
;	apps/server/src/services/options_init.ts
;	apps/server/src/services/search/expressions/note_content_fulltext.ts
;	apps/server/src/services/utils.ts
;	apps/server/src/services/ws.ts
;	apps/server/src/share/content_renderer.ts
;	packages/commons/src/lib/builtin_attributes.ts
;	packages/commons/src/lib/rows.ts
;	packages/trilium-core/src/routes/api/attachments.ts
;	packages/trilium-core/src/routes/api/attributes.ts
;	packages/trilium-core/src/routes/api/branches.ts
;	packages/trilium-core/src/routes/api/notes.ts
;	packages/trilium-core/src/routes/api/recent_changes.ts
;	packages/trilium-core/src/routes/api/revisions.ts
;	packages/trilium-core/src/routes/api/sql.ts
;	packages/trilium-core/src/routes/api/stats.ts
;	packages/trilium-core/src/services/attributes.ts
;	packages/trilium-core/src/services/builtin_attributes.ts
;	packages/trilium-core/src/services/promoted_attribute_definition_parser.ts
;	pnpm-lock.yaml
2026-03-22 12:56:14 +02:00
Elian Doran
76fc9eaeb0 chore(deps): update dependency ws to v8.20.0 (#9136) 2026-03-22 11:40:00 +02:00
Elian Doran
a4b7f54c64 fix(nix): build failing due to rolldown optional deps 2026-03-22 11:37:05 +02:00
Elian Doran
53192d202d chore(nix): add electron & python to shell 2026-03-22 11:37:05 +02:00
Elian Doran
6896ed2c70 chore(nix): update flake lock for new Electron version 2026-03-22 11:37:05 +02:00
Elian Doran
5a96b9c48d fix(deps): update dependency i18next to v25.10.3 (#9135) 2026-03-22 10:56:13 +02:00
renovate[bot]
6113bfc57f fix(deps): update dependency i18next to v25.10.3 2026-03-22 08:49:05 +00:00
Elian Doran
9d7bc20f26 fix(deps): update dependency react-i18next to v16.6.0 (#9137) 2026-03-22 10:47:18 +02:00
renovate[bot]
79788937b9 fix(deps): update dependency react-i18next to v16.6.0 2026-03-22 01:08:10 +00:00
renovate[bot]
66873f16f2 chore(deps): update dependency ws to v8.20.0 2026-03-22 01:07:33 +00:00
Elian Doran
532e001ef0 chore(deps): update dependency stylelint to v17.5.0 (#9115) 2026-03-21 19:29:30 +02:00
Elian Doran
17991bf31f chore(deps): update dependency @preact/preset-vite to v2.10.5 (#9125) 2026-03-21 19:28:47 +02:00
renovate[bot]
2b21b1f75e chore(deps): update dependency @preact/preset-vite to v2.10.5 2026-03-21 17:28:07 +00:00
Elian Doran
dae1f9302c chore(deps): update dependency @redocly/cli to v2.24.1 (#9126) 2026-03-21 19:27:55 +02:00
Elian Doran
33365cdaf1 Translations update from Hosted Weblate (#9124) 2026-03-21 19:25:38 +02:00
green
3ac66ffe72 Translated using Weblate (Japanese)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-03-21 18:24:53 +01:00
Francis C.
81baf13720 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-03-21 18:24:52 +01:00
AggelosPnS
e0e96350d6 Translated using Weblate (Greek)
Currently translated at 2.8% (49 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/el/
2026-03-21 18:24:52 +01:00
Elian Doran
c539c21ced chore(deps): update dependency eslint to v10.1.0 (#9130) 2026-03-21 19:24:44 +02:00
Elian Doran
3f7f6cf982 fix(deps): update dependency i18next to v25.10.2 (#9113) 2026-03-21 19:23:13 +02:00
Elian Doran
271d87ae33 fix(deps): update dependency katex to v0.16.40 (#9127) 2026-03-21 19:22:03 +02:00
Elian Doran
533a77e606 fix(deps): update dependency marked to v17.0.5 (#9128) 2026-03-21 19:21:19 +02:00
Elian Doran
77cf2d4dd9 fix(deps): update dependency sanitize-filename to v1.6.4 (#9129) 2026-03-21 19:20:42 +02:00
Elian Doran
890cb247c1 fix(deps): update dependency eslint-linter-browserify to v10.1.0 (#9131) 2026-03-21 19:19:18 +02:00
renovate[bot]
8d7f4dd0fa fix(deps): update dependency i18next to v25.10.2 2026-03-21 16:55:05 +00:00
Elian Doran
00c4933344 fix(collections/grid): full-width images are too small in preview (closes #9116) 2026-03-21 09:15:13 +02:00
Elian Doran
cd9b46e1c7 fix(attributes): attribute detail not showing up for first item (closes #6948) 2026-03-21 09:06:21 +02:00
Elian Doran
b356b355ca fix(layout): attribute details not visible in new layout (closes #9005) 2026-03-21 08:58:13 +02:00
renovate[bot]
d1aebb7bb0 fix(deps): update dependency eslint-linter-browserify to v10.1.0 2026-03-21 02:04:29 +00:00
renovate[bot]
6cbb595ae8 chore(deps): update dependency eslint to v10.1.0 2026-03-21 02:03:50 +00:00
renovate[bot]
fcf238bc35 fix(deps): update dependency sanitize-filename to v1.6.4 2026-03-21 02:03:10 +00:00
renovate[bot]
8c82468ecc fix(deps): update dependency marked to v17.0.5 2026-03-21 02:02:32 +00:00
renovate[bot]
965905ce00 fix(deps): update dependency katex to v0.16.40 2026-03-21 02:01:52 +00:00
renovate[bot]
ed280775bd chore(deps): update dependency @redocly/cli to v2.24.1 2026-03-21 02:01:10 +00:00
Elian Doran
8834899012 fix(math): limit size of popup and add back overflow (closes #9117) 2026-03-20 20:57:07 +02:00
Elian Doran
55dea474e9 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v55.2.0 (#9099) 2026-03-20 13:45:51 +02:00
Elian Doran
bc74455a64 chore(deps): update dependency @smithy/middleware-retry to v4.4.44 (#9111) 2026-03-20 13:45:21 +02:00
Elian Doran
2d0b28367f chore(deps): update dependency vite to v8.0.1 (#9112) 2026-03-20 13:45:00 +02:00
Elian Doran
7d8a3e2811 fix(deps): update dependency katex to v0.16.39 (#9114) 2026-03-20 13:44:32 +02:00
renovate[bot]
79e5d9595a chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v55.2.0 2026-03-20 00:11:04 +00:00
renovate[bot]
1f0fa57218 chore(deps): update dependency stylelint to v17.5.0 2026-03-20 00:09:32 +00:00
renovate[bot]
0310626025 fix(deps): update dependency katex to v0.16.39 2026-03-20 00:08:50 +00:00
renovate[bot]
fefbb40c03 chore(deps): update dependency vite to v8.0.1 2026-03-20 00:07:33 +00:00
renovate[bot]
12f89078b8 chore(deps): update dependency @smithy/middleware-retry to v4.4.44 2026-03-20 00:06:57 +00:00
Elian Doran
8d873c5869 fix(share): prevent crash when accessing /share on uninitialized setup (#9088) 2026-03-19 20:13:32 +02:00
Elian Doran
27f4ac1d03 Textarea Label Support (#9077) 2026-03-19 20:02:11 +02:00
Elian Doran
d533360903 chore(attributes): rename textarea to multiline text 2026-03-19 20:01:55 +02:00
Elian Doran
49f5dc1c26 fix(table): text jumping when editing multiline text 2026-03-19 20:00:43 +02:00
Elian Doran
16419ed4ac Merge remote-tracking branch 'origin/main' into textarea-labels 2026-03-19 19:54:16 +02:00
Elian Doran
1595d1b5c9 Fix setup page error (#9102) 2026-03-19 19:50:07 +02:00
Elian Doran
50eb11997c fix(setup): contrast issue in alert (closes #8915) 2026-03-19 19:49:32 +02:00
Elian Doran
d974dfbc31 Translations update from Hosted Weblate (#9109) 2026-03-19 19:26:35 +02:00
Hosted Weblate
e9b63e50d4 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-03-19 18:08:13 +01:00
Elian Doran
b6efb7c9ab feat(relation_map): add common note opening options to context menu 2026-03-19 19:07:54 +02:00
Elian Doran
f84479b1c2 fix(deps): update univer monorepo to v0.18.0 (#9101) 2026-03-19 17:48:23 +02:00
JYC333
8597fa560b Merge branch 'main' into fix/share-uninitialized-crash 2026-03-19 11:49:55 +00:00
JYC333
b29ab93fd5 fix: limit the scope of DB check only for share page 2026-03-19 11:46:35 +00:00
JYC333
225cdaff46 Update apps/client/src/setup.ts
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-19 11:34:53 +00:00
JYC333
61dfba8c32 fix: remove knockout dependency 2026-03-19 11:24:45 +00:00
JYC333
4fee91d219 fix: change html to use DOM status 2026-03-19 11:24:09 +00:00
JYC333
12e4f76a8b fix: move setup to DOM implementation to fix knockout issue 2026-03-19 11:23:06 +00:00
JYC333
ec30598397 chore(deps): update dependency eslint-plugin-playwright to v2.10.1 (#9097) 2026-03-19 10:55:01 +00:00
JYC333
fdd2cc77e6 Merge branch 'main' into renovate/eslint-plugin-playwright-2.x 2026-03-19 10:30:16 +00:00
JYC333
6f5c618bcd chore(deps): update dependency @redocly/cli to v2.24.0 (#9100) 2026-03-19 10:29:43 +00:00
JYC333
8041112414 chore(deps): update dependency sanitize-html to v2.17.2 (#9098) 2026-03-19 10:28:18 +00:00
renovate[bot]
40ab2bc798 fix(deps): update univer monorepo to v0.18.0 2026-03-19 00:46:10 +00:00
renovate[bot]
3a3029cf3a chore(deps): update dependency @redocly/cli to v2.24.0 2026-03-19 00:45:28 +00:00
renovate[bot]
e7d1c75cdb chore(deps): update dependency sanitize-html to v2.17.2 2026-03-19 00:44:03 +00:00
renovate[bot]
bc907ee6ad chore(deps): update dependency eslint-plugin-playwright to v2.10.1 2026-03-19 00:43:25 +00:00
Elian Doran
0bff3f1fbc chore(deps): update dependency electron to v41.0.3 (#9090) 2026-03-18 20:18:44 +02:00
Elian Doran
377864b2c6 chore(deps): update dependency wxt to v0.20.20 (#9091) 2026-03-18 19:50:35 +02:00
Elian Doran
3db13df245 fix(deps): update dependency @zumer/snapdom to v2.5.0 (#9093) 2026-03-18 19:48:36 +02:00
Elian Doran
a2e09b40fa chore(deps): update dependency sax to v1.6.0 (#9092) 2026-03-18 19:43:21 +02:00
Elian Doran
aa590753d5 Translations update from Hosted Weblate (#9095) 2026-03-18 13:28:23 +02:00
Hosted Weblate
1c8519d7ec Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-18 12:26:56 +01:00
Elian Doran
686a614bfa Translations update from Hosted Weblate (#9085) 2026-03-18 13:26:47 +02:00
Elian Doran
01261220e3 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-18 13:26:25 +02:00
허석
1bf92ab19a Translated using Weblate (Korean)
Currently translated at 93.9% (109 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ko/
2026-03-18 12:15:30 +01:00
Yunho Park
c0d0d1868d Translated using Weblate (Korean)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2026-03-18 12:15:30 +01:00
허석
da2dec16cc Translated using Weblate (Korean)
Currently translated at 7.3% (126 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ko/
2026-03-18 12:15:29 +01:00
Ulices
787ef7d513 Translated using Weblate (Spanish)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-03-18 12:15:28 +01:00
허석
dcb0ce79e6 Translated using Weblate (Korean)
Currently translated at 51.4% (199 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ko/
2026-03-18 12:15:27 +01:00
Elian Doran
9d8202539d chore(deps): update pnpm/action-setup action to v5 (#9094) 2026-03-18 13:15:19 +02:00
renovate[bot]
c46af4bca9 chore(deps): update pnpm/action-setup action to v5 2026-03-18 01:26:54 +00:00
renovate[bot]
610f6652df fix(deps): update dependency @zumer/snapdom to v2.5.0 2026-03-18 01:26:47 +00:00
renovate[bot]
b33635381b chore(deps): update dependency sax to v1.6.0 2026-03-18 01:26:06 +00:00
renovate[bot]
f20af8cac9 chore(deps): update dependency wxt to v0.20.20 2026-03-18 01:25:25 +00:00
renovate[bot]
29f3b987aa chore(deps): update dependency electron to v41.0.3 2026-03-18 01:24:43 +00:00
argusagent
e9987b40e6 fix(share): wire assertShareDbReady into router middleware — address code review 2026-03-17 20:27:04 -04:00
argusagent
1990a990c3 fix(share): return 503 when app is still initializing (#5677) 2026-03-17 20:22:06 -04:00
argusagent
d1159d3af9 fix(share): guard against uninitialized DB connection on /share routes (#5677) 2026-03-17 20:22:05 -04:00
JYC333
723dada78a chore(deps): update typescript-eslint monorepo to v8.57.1 (#9080) 2026-03-17 21:56:25 +00:00
JYC333
0e089af677 chore(deps): update dependency @smithy/middleware-retry to v4.4.43 (#9079) 2026-03-17 20:47:42 +00:00
renovate[bot]
f4aed5d012 chore(deps): update typescript-eslint monorepo to v8.57.1 2026-03-17 20:42:50 +00:00
Elian Doran
860f953962 chore(deps): update dependency @redocly/cli to v2.22.1 (#9081) 2026-03-17 22:37:46 +02:00
Elian Doran
ded692ead7 chore(deps): update ckeditor5 config packages to v14 (major) (#9082) 2026-03-17 22:37:20 +02:00
renovate[bot]
4f5413ebbe chore(deps): update ckeditor5 config packages to v14 2026-03-17 01:14:47 +00:00
renovate[bot]
8f3e210740 chore(deps): update dependency @redocly/cli to v2.22.1 2026-03-17 01:14:03 +00:00
renovate[bot]
783cb8b4e9 chore(deps): update dependency @smithy/middleware-retry to v4.4.43 2026-03-17 01:12:33 +00:00
Mystler
2b94d96930 fix(labels): Code review issue 2026-03-16 18:21:08 +01:00
Elian Doran
ca349e03f2 feat(editor): add catppuccin theme to highlightjs (#9075) 2026-03-16 18:39:40 +02:00
Mystler
5b5222b846 feat(labels): Add textarea label type with Table support 2026-03-16 17:07:37 +01:00
Giulia Ye
850f8ad939 feat(editor): make theme selector scoped to code tag replace regex more robust
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-16 11:55:06 +01:00
Giulia Ye
50e5f89e9a feat(editor): make theme selector scoped to code tag regex more robust
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-16 11:54:14 +01:00
giuxtaposition
603b47f9b0 feat(editor): add catppuccin theme to highlightjs 2026-03-16 10:35:49 +01:00
Elian Doran
92227c364e chore(deps): update dependency @preact/preset-vite to v2.10.4 (#9070) 2026-03-16 08:05:58 +02:00
Elian Doran
10ac18a7cc chore(deps): update dependency webdriverio to v9.26.1 (#9071) 2026-03-16 08:04:54 +02:00
Elian Doran
e06123e4bd chore(deps): update softprops/action-gh-release action to v2.6.1 (#9072) 2026-03-16 08:04:23 +02:00
Elian Doran
b44bd544cd chore(deps): update dependency node-abi to v4.28.0 (#9073) 2026-03-16 08:01:45 +02:00
renovate[bot]
4c3a448330 chore(deps): update softprops/action-gh-release action to v2.6.1 2026-03-16 01:31:50 +00:00
renovate[bot]
7f07c249af chore(deps): update dependency node-abi to v4.28.0 2026-03-16 01:31:44 +00:00
renovate[bot]
51958d2ac0 chore(deps): update dependency webdriverio to v9.26.1 2026-03-16 00:10:44 +00:00
renovate[bot]
67f474d794 chore(deps): update dependency @preact/preset-vite to v2.10.4 2026-03-16 00:10:12 +00:00
Adorian Doran
e6e8ebd881 pdf.js: add ability to comment selected text (#9068) 2026-03-16 02:08:31 +02:00
Elian Doran
b138fedd35 CSRF fixes (#9067) 2026-03-15 20:13:46 +02:00
contributor
a92d846b57 pdf.js: add ability to comment selected text 2026-03-15 20:05:59 +02:00
Elian Doran
7a544482d1 chore(server): request bootstrap with no cache 2026-03-15 19:39:27 +02:00
Elian Doran
53739ee8d4 e2e(server): address flaky test 2026-03-15 19:33:21 +02:00
Elian Doran
495145e033 chore(server): use random UUID for session ID 2026-03-15 19:33:06 +02:00
Elian Doran
6701d09df5 feat(client): refresh CSRF if request fails 2026-03-15 19:02:28 +02:00
Elian Doran
e36d7121f1 fix(desktop): broken due to CSRF failing 2026-03-15 18:54:37 +02:00
JYC333
9290a60b23 chore(deps): update dependency csrf-csrf to v4.0.3 (#9061) 2026-03-15 15:08:17 +00:00
Elian Doran
761de79a8c chore(deps): update dependency eslint-plugin-playwright to v2.10.0 (#9064) 2026-03-15 16:37:03 +02:00
Elian Doran
b8e9beff1b chore(deps): update dependency lint-staged to v16.4.0 (#9065) 2026-03-15 16:23:52 +02:00
Elian Doran
5b13e0ba4f chore(deps): update softprops/action-gh-release action to v2.5.3 (#9063) 2026-03-15 16:23:07 +02:00
Elian Doran
b0bab18d00 chore(deps): update dependency wxt to v0.20.19 (#9062) 2026-03-15 16:21:31 +02:00
Elian Doran
b4fa1392de Translations update from Hosted Weblate (#9066) 2026-03-15 16:07:45 +02:00
Elian Doran
4e58002e13 Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-15 16:07:18 +02:00
Aindriú Mac Giolla Eoin
adc149aac8 Translated using Weblate (Irish)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-03-15 11:09:59 +00:00
Marcel
4c02773ddc Translated using Weblate (German)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-03-15 11:09:56 +00:00
green
892abe1d70 Translated using Weblate (Japanese)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ja/
2026-03-15 11:09:54 +00:00
green
71c23d33ff Translated using Weblate (Japanese)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-03-15 11:09:52 +00:00
msnx0
9c110e896e Translated using Weblate (Polish)
Currently translated at 100.0% (1719 of 1719 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2026-03-15 11:09:50 +00:00
renovate[bot]
afe7a748e1 chore(deps): update softprops/action-gh-release action to v2.5.3 2026-03-15 05:14:23 +00:00
renovate[bot]
325e582593 chore(deps): update dependency lint-staged to v16.4.0 2026-03-15 00:53:10 +00:00
renovate[bot]
ccebe6a423 chore(deps): update dependency eslint-plugin-playwright to v2.10.0 2026-03-15 00:52:28 +00:00
renovate[bot]
f7c92fa4b2 chore(deps): update dependency wxt to v0.20.19 2026-03-15 00:51:42 +00:00
renovate[bot]
d07c2d118f chore(deps): update dependency csrf-csrf to v4.0.3 2026-03-15 00:51:00 +00:00
Elian Doran
94a09edd1d Renovate/csrf csrf 4.x (#5831) 2026-03-15 00:19:23 +02:00
Elian Doran
f6f939c245 chore(server): address requested changes 2026-03-14 23:49:36 +02:00
Elian Doran
0d889426e8 refactor(server): use different approach to handling the CSRF token 2026-03-14 23:48:06 +02:00
Elian Doran
c8a546ef1e fix(server): uninitialized sessions causing bad CSRF errors 2026-03-14 23:31:17 +02:00
Elian Doran
693919b21a Merge remote-tracking branch 'origin/main' into renovate/csrf-csrf-4.x 2026-03-14 22:48:43 +02:00
Elian Doran
7c1a2039b1 feat(editor): add catppuccin theme to codemirror (#9060) 2026-03-14 22:05:50 +02:00
Elian Doran
c66e4e0475 pdf.js floating highlight button (#9048) 2026-03-14 22:03:55 +02:00
Elian Doran
6bdfbf0d7d chore(deps): fix package lock 2026-03-14 21:52:55 +02:00
Giulia Ye
61393bca90 fix(editor): catppuccin theme declared following alphabetical order based by id
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-14 16:03:03 +01:00
giuxtaposition
c41b649bff feat(editor): add catppuccin theme to codemirror 2026-03-14 15:58:37 +01:00
Elian Doran
ba87487714 Space mobile launcher container evenly (#9031) 2026-03-14 13:49:20 +02:00
Elian Doran
3e73f38ae2 chore(deps): update dependency vite to v8 (#9043) 2026-03-14 13:45:25 +02:00
Elian Doran
51dd55c3fd Merge remote-tracking branch 'origin/main' into renovate/vite-8.x
; Conflicts:
;	pnpm-lock.yaml
2026-03-14 12:54:04 +02:00
Elian Doran
de49ca37b9 fix(deps): update ckeditor monorepo (#8884) 2026-03-14 12:51:11 +02:00
Elian Doran
8d8080ee09 Merge remote-tracking branch 'origin/main' into renovate/ckeditor-monorepo
; Conflicts:
;	pnpm-lock.yaml
2026-03-14 12:38:35 +02:00
Elian Doran
f3613ccb25 chore(client): typecheck failing due to changes in JSX handling 2026-03-14 12:35:44 +02:00
Elian Doran
ad7b5700f3 chore(client): tests not running due to change in Vite 2026-03-14 12:29:57 +02:00
Elian Doran
0d17c62d02 chore(client): remove deprecated vite manual chunks 2026-03-14 12:15:43 +02:00
Elian Doran
826690982a chore(deps): update dependency pdfjs-dist to v5.5.207 (#8874) 2026-03-14 12:10:41 +02:00
Elian Doran
b438ff9c62 chore(deps): update dependency electron to v41 (#9025) 2026-03-14 12:08:15 +02:00
Elian Doran
43fb9d1a23 chore(deps): update docker/setup-buildx-action action to v4 (#8931) 2026-03-14 12:07:53 +02:00
Elian Doran
fb17ce8c8a Merge remote-tracking branch 'origin/main' into renovate/ckeditor-monorepo
; Conflicts:
;	pnpm-lock.yaml
2026-03-14 12:06:40 +02:00
Elian Doran
3a8e12535e fix(ckeditor5): version misalignment 2026-03-14 12:04:32 +02:00
Elian Doran
ce0caa3f6d chore(deps): update dependency @smithy/middleware-retry to v4.4.42 (#9035) 2026-03-14 12:00:14 +02:00
Elian Doran
00a0315f12 chore(pdfjs): fix type error after update 2026-03-14 11:59:57 +02:00
renovate[bot]
da38d56dc7 chore(deps): update docker/setup-buildx-action action to v4 2026-03-14 09:55:13 +00:00
renovate[bot]
83e47cba2c chore(deps): update dependency electron to v41 2026-03-14 09:54:14 +00:00
Elian Doran
f4d1eebed4 fix(deps): update dependency knockout to v3.5.2 (#8970) 2026-03-14 11:47:51 +02:00
Elian Doran
f27b394099 Merge remote-tracking branch 'origin/main' into renovate/ckeditor-monorepo
; Conflicts:
;	pnpm-lock.yaml
2026-03-14 11:47:16 +02:00
renovate[bot]
a66c9ccc1f chore(deps): update dependency @smithy/middleware-retry to v4.4.42 2026-03-14 09:45:48 +00:00
Elian Doran
0bc6a830c8 chore(pdfjs): update viewer to latest 2026-03-14 11:42:39 +02:00
Elian Doran
f3008b29af chore(deps): bump yauzl from 2.10.0 to 3.2.1 (#9047) 2026-03-14 11:33:15 +02:00
Elian Doran
5b16ff8be1 chore(client): fix type error after update of knockout 2026-03-14 11:32:44 +02:00
Elian Doran
86621e3388 fix(deps): update dependency reveal.js to v6 (#9028) 2026-03-14 11:30:40 +02:00
Elian Doran
1beb0668c3 fix(deps): update codemirror (#9039) 2026-03-14 11:30:21 +02:00
Elian Doran
34b09f90fb fix(deps): wrong ckeditor version 2026-03-14 11:24:49 +02:00
Elian Doran
a6f964925b chore(presentation): fix paths to themes 2026-03-14 11:17:21 +02:00
Elian Doran
196416bb9f chore(presentation): fix type issues 2026-03-14 11:15:04 +02:00
Elian Doran
1535db9f7d chore(presentation): remove now redundant type definitions 2026-03-14 11:13:50 +02:00
renovate[bot]
ae38ac4de8 fix(deps): update dependency knockout to v3.5.2 2026-03-14 09:11:59 +00:00
Elian Doran
e0aa8d8ecf fix(codemirror): version misalignment causing type errors 2026-03-14 11:09:48 +02:00
renovate[bot]
23c1eacf2b fix(deps): update ckeditor monorepo 2026-03-14 09:03:13 +00:00
contributor
10e28789e2 add optional chaining to access window.parent in callback 2026-03-14 11:03:10 +02:00
contributor
940f7f77f5 add automatic removal of the event 2026-03-14 11:03:10 +02:00
renovate[bot]
83f8b4fcb4 fix(deps): update codemirror 2026-03-14 09:02:35 +00:00
Elian Doran
42dc801ddf fix(deps): update univer monorepo to v0.17.0 (#9057) 2026-03-14 11:01:08 +02:00
renovate[bot]
5e6e5bfbec chore(deps): update dependency vite to v8 2026-03-14 09:00:57 +00:00
Elian Doran
28fc99dd45 fix(deps): update dependency mermaid to v11.13.0 (#8988) 2026-03-14 10:59:16 +02:00
Elian Doran
9ef501c399 Mermaid diagram switcher (#9058) 2026-03-14 10:54:47 +02:00
Elian Doran
6bba908654 feat(mermaid): add new sample diagrams for Ishikawa & Venn 2026-03-14 10:54:33 +02:00
Elian Doran
d423e43312 Merge branch 'feature/mermaid_diagram_switcher' into renovate/mermaid-11.x 2026-03-14 10:50:33 +02:00
Elian Doran
91718f218b Merge remote-tracking branch 'origin/main' into renovate/mermaid-11.x 2026-03-14 10:50:18 +02:00
Elian Doran
e623e91a82 docs(user): mention changes to Mermaid diagrams 2026-03-14 10:44:55 +02:00
Elian Doran
dce9f50911 fix(mermaid): not recentering when using the sample switcher 2026-03-14 10:37:26 +02:00
Elian Doran
5f1486cf6a feat(mermaid): use custom placeholder 2026-03-14 10:31:28 +02:00
Elian Doran
2c6bdc79af chore(mermaid): rounded corners for editor only on desktop 2026-03-14 10:25:59 +02:00
Elian Doran
a6b89cfa30 chore(mermaid): use translations for sample names 2026-03-14 10:22:35 +02:00
Elian Doran
21d1cd395b feat(mermaid): add a text for sample diagram switcher 2026-03-14 10:14:21 +02:00
Elian Doran
981466cbe8 fix(deps): update dependency better-sqlite3 to v12.8.0 (#9056) 2026-03-14 10:08:38 +02:00
Elian Doran
0209573fce chore(mermaid): remove default placeholder content 2026-03-14 10:03:15 +02:00
Elian Doran
92f8459f28 feat(mermaid): show switcher only when note is empty 2026-03-14 10:02:05 +02:00
Elian Doran
da193b456b fix(mermaid): error when diagram is empty 2026-03-14 10:00:18 +02:00
Elian Doran
6c151afca3 fix(mermaid): error text not selectable 2026-03-14 09:58:45 +02:00
Elian Doran
aba6750c18 chore(mermaid): add rounded corners to code editor 2026-03-14 09:44:06 +02:00
Elian Doran
b9a8e4e4ba feat(mermaid): add all official samples 2026-03-14 09:42:35 +02:00
Elian Doran
4134c4ddd0 feat(mermaid): replace note content on switch 2026-03-14 09:29:28 +02:00
Elian Doran
72038fb2ec chore(mermaid): basic logic for content switcher 2026-03-14 09:26:14 +02:00
Elian Doran
069d8b1ae4 refactor(mermaid): move into own directory 2026-03-14 09:20:07 +02:00
Elian Doran
ce71068f6d chore(mermaid): add a bottom section for switching between samples 2026-03-14 09:18:38 +02:00
renovate[bot]
dc298a44e1 fix(deps): update univer monorepo to v0.17.0 2026-03-14 06:54:06 +00:00
renovate[bot]
306bfd7673 fix(deps): update dependency better-sqlite3 to v12.8.0 2026-03-14 06:53:12 +00:00
Elian Doran
25cf23f507 chore(deps): update dependency http-proxy-agent to v8 (#9026) 2026-03-14 08:39:04 +02:00
Elian Doran
096d5f7c65 chore: remove old references to svelte 2026-03-14 08:06:46 +02:00
Elian Doran
0e2dee1609 chore(renovate): group univerjs packages 2026-03-14 08:05:50 +02:00
Elian Doran
ec927d25a9 chore(deps): update dependency @redocly/cli to v2.21.1 (#8905) 2026-03-14 07:44:45 +02:00
renovate[bot]
7c8aefb4ef chore(deps): update dependency http-proxy-agent to v8 2026-03-14 05:44:33 +00:00
Elian Doran
e840769dd6 fix(deps): update dependency react-i18next to v16.5.8 (#9014) 2026-03-14 07:44:00 +02:00
Elian Doran
4a5d3f01b8 chore(deps): update dependency node-abi to v4.27.0 (#9015) 2026-03-14 07:43:26 +02:00
Elian Doran
71d975f339 fix(deps): update dependency force-graph to v1.51.2 (#9050) 2026-03-14 07:42:10 +02:00
Elian Doran
a4051fc372 chore(deps): update pnpm to v10.32.1 (#9012) 2026-03-14 07:41:44 +02:00
Elian Doran
359b2a68b8 chore(deps): update dependency https-proxy-agent to v8 (#9027) 2026-03-14 07:41:11 +02:00
Elian Doran
8124e4e589 chore(deps): update dependency electron to v40.8.2 (#9049) 2026-03-14 07:40:06 +02:00
Elian Doran
5f699996f8 chore(deps): update dependency rollup-plugin-webpack-stats to v3.1.0 (#9051) 2026-03-14 07:35:09 +02:00
Elian Doran
c7c0bc4185 chore(deps): update marocchino/sticky-pull-request-comment action to v3 (#9053) 2026-03-14 07:34:47 +02:00
Elian Doran
d422ac7bc5 chore(deps): update dependency vite-plugin-static-copy to v3.3.0 (#9052) 2026-03-14 07:33:52 +02:00
Elian Doran
0bd912d18a Translations update from Hosted Weblate (#9054) 2026-03-14 07:33:19 +02:00
Ulices
252f2fe72c Translated using Weblate (Spanish)
Currently translated at 100.0% (1693 of 1693 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-03-14 06:09:49 +01:00
renovate[bot]
7f29c347e2 chore(deps): update marocchino/sticky-pull-request-comment action to v3 2026-03-14 00:40:57 +00:00
renovate[bot]
7a3dc824d6 chore(deps): update dependency vite-plugin-static-copy to v3.3.0 2026-03-14 00:40:47 +00:00
renovate[bot]
8550c62771 chore(deps): update dependency rollup-plugin-webpack-stats to v3.1.0 2026-03-14 00:39:43 +00:00
renovate[bot]
66cd657cd8 fix(deps): update dependency force-graph to v1.51.2 2026-03-14 00:38:45 +00:00
renovate[bot]
beac80e175 chore(deps): update dependency electron to v40.8.2 2026-03-14 00:37:41 +00:00
contributor
5ae9952ba1 use pagehide event instead of unload 2026-03-14 01:30:59 +02:00
contributor
d4bc1ec444 add typings 2026-03-14 01:24:50 +02:00
contributor
d52f529b24 extract handler setup into a function 2026-03-14 01:24:50 +02:00
contributor
9a9cfdec2b add unload handler 2026-03-14 01:24:50 +02:00
contributor
6ab421ffa0 check target iframe 2026-03-14 01:24:50 +02:00
contributor
53b0aafb98 disable preferences warning 2026-03-14 01:24:49 +02:00
contributor
6b02ad8421 enable floating highlight button 2026-03-14 01:24:43 +02:00
renovate[bot]
242ebfccc0 chore(deps): update dependency https-proxy-agent to v8 2026-03-13 22:26:27 +00:00
renovate[bot]
545cc0782f fix(deps): update dependency react-i18next to v16.5.8 2026-03-13 22:24:39 +00:00
renovate[bot]
bf6a2917cd fix(deps): update dependency reveal.js to v6 2026-03-13 22:22:26 +00:00
renovate[bot]
eaba2a8395 fix(deps): update dependency mermaid to v11.13.0 2026-03-13 22:19:14 +00:00
Elian Doran
c581fb17bc chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v55 (#9042) 2026-03-14 00:14:41 +02:00
renovate[bot]
9b05b95d77 chore(deps): update dependency pdfjs-dist to v5.5.207 2026-03-13 22:12:07 +00:00
renovate[bot]
b3ba18ddd0 chore(deps): update dependency node-abi to v4.27.0 2026-03-13 22:11:25 +00:00
renovate[bot]
bb2a633ba7 chore(deps): update dependency @redocly/cli to v2.21.1 2026-03-13 22:10:44 +00:00
renovate[bot]
913efdef03 chore(deps): update pnpm to v10.32.1 2026-03-13 22:07:56 +00:00
Elian Doran
bf0bea18b1 fix(deps): update dependency i18next to v25.8.18 (#9013) 2026-03-14 00:06:31 +02:00
Elian Doran
cdebd1f63a chore(deps): update dependency esbuild to v0.27.4 (#9037) 2026-03-14 00:06:07 +02:00
Elian Doran
a6a2635836 chore(deps): update dependency happy-dom to v20.8.4 (#9038) 2026-03-14 00:05:38 +02:00
dependabot[bot]
024b57c2b4 chore(deps): bump yauzl from 2.10.0 to 3.2.1
Bumps [yauzl](https://github.com/thejoshwolfe/yauzl) from 2.10.0 to 3.2.1.
- [Commits](https://github.com/thejoshwolfe/yauzl/compare/2.10.0...3.2.1)

---
updated-dependencies:
- dependency-name: yauzl
  dependency-version: 3.2.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-13 20:13:10 +00:00
renovate[bot]
6fbc85cbc7 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v55 2026-03-13 05:51:53 +00:00
renovate[bot]
5f8a0aee13 chore(deps): update dependency happy-dom to v20.8.4 2026-03-13 05:47:50 +00:00
renovate[bot]
0c67b292ef chore(deps): update dependency esbuild to v0.27.4 2026-03-13 05:46:42 +00:00
Elian Doran
ba663e6162 chore(deps): update dependency electron to v40.8.1 (#9036) 2026-03-13 07:42:35 +02:00
Elian Doran
14925266cf fix(deps): update dependency dayjs to v1.11.20 (#9040) 2026-03-13 07:41:36 +02:00
Elian Doran
702e29bd8c chore(deps): update vitest monorepo to v4.1.0 (#9041) 2026-03-13 07:41:10 +02:00
Elian Doran
27ac3e58c5 fix(deps): update dependency sqlite3 to v6 (#9044) 2026-03-13 07:40:07 +02:00
Elian Doran
86e268c06d Translations update from Hosted Weblate (#9046) 2026-03-13 07:39:12 +02:00
Ulices
6e4b231319 Translated using Weblate (Spanish)
Currently translated at 99.2% (1681 of 1693 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-03-13 05:09:49 +01:00
green
041eff6cbd Translated using Weblate (Japanese)
Currently translated at 100.0% (1693 of 1693 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-03-13 05:09:48 +01:00
renovate[bot]
1a3471a516 fix(deps): update dependency sqlite3 to v6 2026-03-13 01:10:11 +00:00
renovate[bot]
57c8727bb1 chore(deps): update vitest monorepo to v4.1.0 2026-03-13 01:06:52 +00:00
renovate[bot]
bd451d0738 fix(deps): update dependency dayjs to v1.11.20 2026-03-13 01:05:48 +00:00
renovate[bot]
ced062842d chore(deps): update dependency electron to v40.8.1 2026-03-13 01:01:51 +00:00
Elian Doran
a1bf7bfa08 Protected note tweaks (#9033) 2026-03-12 22:24:10 +02:00
Elian Doran
2a67c93c20 fix(deps): update dependency mathlive to v0.109.0 (#9024) 2026-03-12 21:30:13 +02:00
Elian Doran
b51bfdfb33 chore(client): address requested change 2026-03-12 21:09:30 +02:00
Elian Doran
9aa84877ee fix(tree): not reacting to protected state changes 2026-03-12 21:03:12 +02:00
Elian Doran
9e99670b19 fix(collections): displaying note list even if session is not unlocked 2026-03-12 20:53:19 +02:00
Elian Doran
744b93dd98 fix(board): does not respect protected note of parent 2026-03-12 20:50:56 +02:00
Elian Doran
5abb77242c feat(map): create pins atomically 2026-03-12 20:49:34 +02:00
Elian Doran
4ab3b0dd2b fix(map): does not respect protected note of parent 2026-03-12 20:47:43 +02:00
Elian Doran
a6a1594265 fix(table): does not respect protected note of parent 2026-03-12 20:44:25 +02:00
Elian Doran
b06cdd442d fix(calendar): does not respect protected note of parent 2026-03-12 20:41:53 +02:00
Mystler
f7067fb968 fix(mobile): Space mobile launcher container evenly 2026-03-12 19:24:27 +01:00
renovate[bot]
cf0f5ba4c4 fix(deps): update dependency mathlive to v0.109.0 2026-03-12 01:25:15 +00:00
renovate[bot]
309a81a0fe fix(deps): update dependency i18next to v25.8.18 2026-03-12 01:14:10 +00:00
Elian Doran
caa428c1a2 Translations update from Hosted Weblate (#8990) 2026-03-11 21:44:21 +02:00
Hosted Weblate
517c721664 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-03-11 20:39:34 +01:00
green
a8cdaa69f7 Translated using Weblate (Japanese)
Currently translated at 100.0% (1693 of 1693 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-03-11 20:39:33 +01:00
Luk On
53d221ef34 Translated using Weblate (Polish)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2026-03-11 20:39:33 +01:00
pythaac
5450fde472 Translated using Weblate (Korean)
Currently translated at 93.1% (108 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ko/
2026-03-11 20:39:33 +01:00
pythaac
808446cef5 Translated using Weblate (Korean)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2026-03-11 20:39:33 +01:00
ibs-allaow
921c663199 Translated using Weblate (Arabic)
Currently translated at 57.2% (959 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ar/
2026-03-11 20:39:33 +01:00
Микола Копитін
1b8a75b615 Translated using Weblate (Ukrainian)
Currently translated at 98.2% (114 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/uk/
2026-03-11 20:39:33 +01:00
Микола Копитін
f78ced5bc3 Translated using Weblate (Ukrainian)
Currently translated at 99.3% (157 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/uk/
2026-03-11 20:39:33 +01:00
JYC333
81bf5f4f3b Translated using Weblate (Swedish)
Currently translated at 17.6% (21 of 119 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/sv/
2026-03-11 20:39:33 +01:00
Hosted Weblate
aaed368670 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-11 20:39:33 +01:00
Elian Doran
5e8de14721 Audio player improvements (#9008) 2026-03-11 21:38:58 +02:00
Elian Doran
634ab5b5c0 chore(media): address requested changes 2026-03-11 21:26:24 +02:00
Elian Doran
906889a035 fix(video): auto-hide no longer working 2026-03-11 21:07:48 +02:00
Elian Doran
ab9d50b905 feat(video): add blur for controls 2026-03-11 21:06:08 +02:00
Elian Doran
e61b7c7cfc feat(audio): add background effects 2026-03-11 20:58:49 +02:00
Elian Doran
1c628fba4c fix(video): playing button not working 2026-03-11 20:53:08 +02:00
Elian Doran
f8b4c6cb15 fix(audio): styling on light theme 2026-03-11 20:41:37 +02:00
Elian Doran
3edd8f6c5a chore(media): solve linter warnings 2026-03-11 20:38:53 +02:00
Elian Doran
7777f72893 chore(media): change translations prefix 2026-03-11 20:36:51 +02:00
Elian Doran
9af85b767b feat(audio): add an icon placeholder 2026-03-11 20:35:41 +02:00
Elian Doran
73260b91eb feat(audio): add mime to unsupported format 2026-03-11 20:29:34 +02:00
Elian Doran
2858f63873 feat(audio): report unsupported format 2026-03-11 19:30:12 +02:00
Elian Doran
15ca328727 feat(audio): make player full-width 2026-03-11 19:26:17 +02:00
Elian Doran
5b3fbecc0f feat(audio): introduce keyboard shortcuts 2026-03-11 19:24:47 +02:00
Elian Doran
365d0f0aac feat(audio): introduce playback speed 2026-03-11 19:17:14 +02:00
Elian Doran
e86d84c463 feat(audio): introduce loop button 2026-03-11 19:14:52 +02:00
Elian Doran
6b974c2ac7 feat(audio): introduce skip buttons 2026-03-11 19:12:09 +02:00
Elian Doran
d2afcbb98d feat(audio): introduce volume slider 2026-03-11 19:10:18 +02:00
Elian Doran
68a122fcf5 chore(audio): reintroduce some styles 2026-03-11 19:06:59 +02:00
Elian Doran
92f0144b48 feat(audio): reintroduce seek bar 2026-03-11 19:03:09 +02:00
Elian Doran
a5a345728c feat(audio): reintroduce play button 2026-03-11 18:56:43 +02:00
Elian Doran
23890e64e9 refactor(audio): extract to separate file 2026-03-11 18:51:20 +02:00
Elian Doran
3de712aca4 fix(server/search): invalid canvas crashing search (closes #9004) 2026-03-11 18:32:53 +02:00
Elian Doran
cb5b4d870f refactor(server/search): extract fulltext preprocessing to separate file 2026-03-11 18:29:36 +02:00
Elian Doran
f81aef2de5 docs(user): specify spreadsheets 2026-03-11 18:01:22 +02:00
Elian Doran
06aed16ea1 refactor(spreadsheet): simplify the checks for popups 2026-03-11 12:11:00 +02:00
Elian Doran
aa2d8af15c fix(spreadsheet): popups show up and hide 2026-03-11 12:10:46 +02:00
Elian Doran
dc7b91433b docs(user): mention changes to video player 2026-03-11 09:44:24 +02:00
Elian Doran
72951386b1 Video player improvements (#8992) 2026-03-11 08:33:04 +02:00
Elian Doran
db8df01d82 fix(deps): update dependency eslint-linter-browserify to v10.0.3 (#8948) 2026-03-11 08:32:06 +02:00
Elian Doran
98713ed111 chore(deps): update dependency lint-staged to v16.3.3 (#8997) 2026-03-11 08:31:29 +02:00
Elian Doran
3e88fecb15 chore(deps): update dependency yauzl to v3.2.1 (#8998) 2026-03-11 08:31:12 +02:00
Elian Doran
fe4255f2fc fix(deps): update dependency @codemirror/view to v6.39.17 (#8999) 2026-03-11 08:31:00 +02:00
Elian Doran
c046a57654 chore(deps): update dependency webdriverio to v9.25.0 (#9000) 2026-03-11 08:30:47 +02:00
Elian Doran
d8fc0d45a8 fix(deps): update dependency @zumer/snapdom to v2.1.0 (#9001) 2026-03-11 08:30:01 +02:00
Elian Doran
567b96cfb4 fix(deps): update dependency preact to v10.29.0 (#9002) 2026-03-11 08:29:34 +02:00
renovate[bot]
d25849d280 fix(deps): update dependency preact to v10.29.0 2026-03-11 00:06:32 +00:00
renovate[bot]
d4d73995db fix(deps): update dependency @zumer/snapdom to v2.1.0 2026-03-11 00:05:36 +00:00
renovate[bot]
f4657b5da9 chore(deps): update dependency webdriverio to v9.25.0 2026-03-11 00:04:47 +00:00
renovate[bot]
614f43cb8a fix(deps): update dependency @codemirror/view to v6.39.17 2026-03-11 00:04:00 +00:00
renovate[bot]
ca2fbf8dba chore(deps): update dependency yauzl to v3.2.1 2026-03-11 00:03:14 +00:00
renovate[bot]
a421513442 chore(deps): update dependency lint-staged to v16.3.3 2026-03-11 00:02:25 +00:00
JYC333
a9599c471a Merge branch 'main' into renovate/eslint-linter-browserify-10.x 2026-03-10 23:56:13 +00:00
JYC333
415bcac641 chore(deps): update dependency lightningcss to v1.32.0 (#8985) 2026-03-10 23:54:00 +00:00
JYC333
9527017314 fix(deps): update dependency mind-elixir to v5.9.3 (#8984) 2026-03-10 23:53:12 +00:00
JYC333
1d3d7c77f8 fix(deps): update dependency i18next to v25.8.17 (#8983) 2026-03-10 23:52:55 +00:00
Elian Doran
e868615fd5 chore(client): address requested changes 2026-03-10 22:19:50 +02:00
Elian Doran
80493a52be feat(video_player): move loop to center section 2026-03-10 20:46:28 +02:00
Elian Doran
3fed2ba42e feat(video_player): add zoom to fit button 2026-03-10 20:44:32 +02:00
Elian Doran
82592ada54 fix(video_player): unreadable controls on light theme 2026-03-10 20:36:03 +02:00
Elian Doran
5528701744 feat(video_player): indicate unsupported file formats 2026-03-10 20:33:47 +02:00
Elian Doran
0ca665fb85 chore(video_player): mention keys 2026-03-10 20:24:16 +02:00
Elian Doran
7eb452ed8b refactor(video_player): use translations 2026-03-10 20:22:03 +02:00
Elian Doran
d81dec94a9 feat(video_player): add keyboard shortcuts for toggling volume 2026-03-10 20:18:16 +02:00
Elian Doran
6631a4a806 feat(video_player): add shortcuts to just to beginning/end 2026-03-10 20:16:53 +02:00
Elian Doran
12f817c896 feat(video_player): add keyboard shortcut to toggle mute 2026-03-10 20:16:04 +02:00
Elian Doran
87229600d2 feat(video_player): keyboard shortcut to toggle full-screen 2026-03-10 20:15:10 +02:00
Elian Doran
471a46a030 feat(video_player): flash controls when pressing shortcuts 2026-03-10 20:14:11 +02:00
Elian Doran
41220eebd5 feat(video_player): arrow keys to seek 2026-03-10 20:11:56 +02:00
Elian Doran
755872277b feat(video_player): space to toggle play/pause 2026-03-10 20:10:40 +02:00
Elian Doran
2cb54d7021 fix(video_player): loop can get out of sync with external control 2026-03-10 20:09:33 +02:00
Elian Doran
5a16bafbbf fix(video_player): playback speed can get out of sync with external control 2026-03-10 20:08:17 +02:00
Elian Doran
fc6e9d89d9 fix(video_player): volume can get out of sync with external control 2026-03-10 20:07:45 +02:00
Elian Doran
8af35da279 feat(video_player): add loop button 2026-03-10 20:05:40 +02:00
Elian Doran
7107fec1a4 feat(video_player): add rotate button 2026-03-10 20:03:58 +02:00
Elian Doran
4bb662c5fb feat(video_player): button to toggle PIP 2026-03-10 20:00:38 +02:00
Elian Doran
89297b92f8 feat(video_player): click toggles play/pause instead of controls 2026-03-10 19:53:24 +02:00
Elian Doran
e019271e74 feat(video_player): hide immediately on play 2026-03-10 19:50:31 +02:00
Elian Doran
f6d61eefcc feat(video_player): don't hide controls if not playing 2026-03-10 19:48:21 +02:00
Elian Doran
fabc07be42 refactor(video_player): extract hiding visibility to hook 2026-03-10 19:47:25 +02:00
Elian Doran
bccfa7956c refactor(video_player): extract more buttons into separate components 2026-03-10 19:45:42 +02:00
Elian Doran
42a05f411b feat(video_player): basic toggle of the controls 2026-03-10 19:42:54 +02:00
Elian Doran
7ba7b98f5f feat(video_player): add playback speed indicator 2026-03-10 19:38:15 +02:00
Elian Doran
2132c2ab38 refactor(video_player): extract full screen to separate component 2026-03-10 19:29:00 +02:00
Elian Doran
2ce4d512e7 feat(video_player): add full screen button 2026-03-10 19:23:45 +02:00
Elian Doran
1258d32820 feat(video_player): add skip left/right buttons 2026-03-10 19:22:29 +02:00
Elian Doran
db763ba229 feat(video_player): improve style of bottom bar 2026-03-10 19:20:49 +02:00
Elian Doran
951fdaec70 chore(video_player): change button alignment 2026-03-10 19:17:51 +02:00
Elian Doran
4303f3687e refactor(video_player): extract seek bar & volume control 2026-03-10 19:12:52 +02:00
Elian Doran
540b0e0b83 feat(video_player): volume changer 2026-03-10 19:11:08 +02:00
Elian Doran
08a0326cb0 feat(video_player): add elapsed/remaining time 2026-03-10 19:05:59 +02:00
Elian Doran
8b0a45e4fd feat(video_player): add a trackbar for seeking the video 2026-03-10 18:57:58 +02:00
Elian Doran
0e0ad2ed73 feat(video_player): single play/pause button 2026-03-10 18:56:20 +02:00
Elian Doran
4c73f31aca feat(video_player): start adding custom controls (play/pause) 2026-03-10 18:54:53 +02:00
Elian Doran
6b2ae8fd12 feat(video_player): black background 2026-03-10 18:49:36 +02:00
Elian Doran
88d84fae1e refactor(video_player): extract to separate file 2026-03-10 18:48:54 +02:00
Elian Doran
cdc46faaad fix(board): add column not snappable on mobile 2026-03-10 18:41:53 +02:00
Elian Doran
24dbc79961 fix(board): clipped on horizontal scroll 2026-03-10 18:40:17 +02:00
Elian Doran
8cb58dcc45 fix(icon_packs): missing empty icon 2026-03-10 18:35:20 +02:00
Elian Doran
fe70b8aee6 fix(note_badges): saved indicator not disappearing if reduced motion was activated 2026-03-10 18:32:31 +02:00
Elian Doran
00f66cfb49 fix(popup_editor): note content no longer rendering
The commit f44b47ec added a hasTabBeenActive guard in NoteDetail that defers rendering until the tab has been active at least once. It initializes via noteContext?.isActive() and then listens for activeNoteChanged events.

The popup editor creates its own NoteContext("_popup-editor") which is never the activeNtxId in the tab manager — isActive() always returns false, and activeNoteChanged never fires for it. So hasTabBeenActive stays false forever, and the if (!type || !hasTabBeenActive) return guard at NoteDetail.tsx:64 prevents the note type widget from ever loading.
2026-03-10 18:32:31 +02:00
Elian Doran
3a4b080765 Table of contents fixes (#8933) 2026-03-10 18:31:24 +02:00
Elian Doran
41269ef987 chore(deps): update dependency express-rate-limit to v8.3.1 (#8981) 2026-03-10 08:30:06 +02:00
Elian Doran
e521c6a386 fix(deps): update dependency @mermaid-js/layout-elk to v0.2.1 (#8982) 2026-03-10 08:29:41 +02:00
Elian Doran
1c35a557c1 chore(deps): update pnpm to v10.32.0 (#8986) 2026-03-10 08:29:20 +02:00
Elian Doran
99eb8389c5 chore(deps): update typescript-eslint monorepo to v8.57.0 (#8987) 2026-03-10 08:29:03 +02:00
renovate[bot]
c5e560ef5b chore(deps): update typescript-eslint monorepo to v8.57.0 2026-03-10 02:13:50 +00:00
renovate[bot]
a7d7a078b1 chore(deps): update pnpm to v10.32.0 2026-03-10 02:12:47 +00:00
renovate[bot]
a06fa5222f chore(deps): update dependency lightningcss to v1.32.0 2026-03-10 02:12:35 +00:00
renovate[bot]
8d3e40a28a fix(deps): update dependency mind-elixir to v5.9.3 2026-03-10 02:11:34 +00:00
renovate[bot]
8e32f99790 fix(deps): update dependency i18next to v25.8.17 2026-03-10 02:10:34 +00:00
renovate[bot]
57bce62e48 fix(deps): update dependency @mermaid-js/layout-elk to v0.2.1 2026-03-10 02:09:36 +00:00
renovate[bot]
1c873394d5 chore(deps): update dependency express-rate-limit to v8.3.1 2026-03-10 02:08:32 +00:00
JYC333
d652f67364 Translations update from Hosted Weblate (#8977) 2026-03-09 10:43:00 +00:00
JYC333
5e54d098c5 Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 10:23:14 +00:00
JYC333
ec95303c31 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 10:22:31 +00:00
Hosted Weblate
07aafe7e89 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-09 11:17:40 +01:00
Giovi
dc7acbb70e Translated using Weblate (Italian)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-03-09 11:17:32 +01:00
JYC333
0dcb8b3ff8 Translations update from Hosted Weblate (#8975) 2026-03-09 10:17:22 +00:00
JYC333
e4ddff01ca Update docs/README-sv.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 10:13:43 +00:00
JYC333
015c1161d4 Update docs/README-sv.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-03-09 10:11:43 +00:00
Robert Magnusson
ca0c6076c5 Translated using Weblate (Swedish)
Currently translated at 5.4% (21 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/sv/
2026-03-09 05:47:59 +00:00
Robert Magnusson
80a02f88be Translated using Weblate (Swedish)
Currently translated at 1.3% (22 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/sv/
2026-03-09 05:47:58 +00:00
Robert Magnusson
430833bedb Translated using Weblate (Swedish)
Currently translated at 13.2% (21 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/sv/
2026-03-09 05:47:57 +00:00
Hosted Weblate
dc80d83964 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-09 05:47:56 +00:00
Elian Doran
5f7ade45f4 fix(deps): update dependency katex to v0.16.38 (#8969) 2026-03-09 07:47:35 +02:00
Elian Doran
8b36a7ab1e Spreadsheet experiment v0.5 (#8966) 2026-03-09 07:47:08 +02:00
Elian Doran
fd18276693 fix(deps): update dependency @preact/signals to v2.8.2 (#8968) 2026-03-09 07:46:47 +02:00
Elian Doran
0becfc16ba chore(deps): update pnpm to v10.31.0 (#8971) 2026-03-09 07:46:02 +02:00
renovate[bot]
d480d1f6ba chore(deps): update pnpm to v10.31.0 2026-03-09 01:36:51 +00:00
renovate[bot]
f5c9a71ba0 fix(deps): update dependency katex to v0.16.38 2026-03-09 01:35:41 +00:00
renovate[bot]
c177a8a464 fix(deps): update dependency @preact/signals to v2.8.2 2026-03-09 01:34:42 +00:00
Elian Doran
c826564c9e chore(spreadsheet): address requested changes 2026-03-08 23:25:47 +02:00
Elian Doran
ccb13fa6b9 fix(commons): typecheck 2026-03-08 23:19:23 +02:00
Elian Doran
69e374138f fix(spreadsheet): missing some CSS imports 2026-03-08 23:07:48 +02:00
Elian Doran
3156b2cb59 feat(spreadsheet): enable conditional formatting 2026-03-08 23:02:54 +02:00
Elian Doran
d6217ffed4 feat(spreadsheet): enable data validation 2026-03-08 22:59:41 +02:00
Elian Doran
fc90c6af9d feat(spreadsheet): enable sorting 2026-03-08 22:56:11 +02:00
Elian Doran
a1118419ec feat(spreadsheet): enable filtering 2026-03-08 22:53:04 +02:00
Elian Doran
8599785ee8 refactor(spreadsheet): use multiple modules 2026-03-08 22:39:43 +02:00
Elian Doran
99ba192a44 feat(spreadsheet): allow triggering find/replace from context menu 2026-03-08 22:35:08 +02:00
Elian Doran
b86d3587ac feat(spreadsheet): basic integration of find/replace 2026-03-08 22:24:03 +02:00
Elian Doran
b2a0baf56a fix(spreadsheet): jumping when editing in another split 2026-03-08 22:15:29 +02:00
Elian Doran
22f37817e5 fix(spreadsheet): fix The column width is less than 0 when switching tabs 2026-03-08 22:01:45 +02:00
Elian Doran
6b4fe03625 fix(spreadsheet): mitigate The column width is less than 0, need to adjust page width to make it great than 0 when changing an inactive tab 2026-03-08 21:57:26 +02:00
Elian Doran
f44b47ec23 fix(client): tabs still rendering in the background 2026-03-08 21:48:45 +02:00
Elian Doran
8d667e838a feat(spreadsheet): hide cell protection mechanism 2026-03-08 21:28:12 +02:00
Elian Doran
f32385de2e feat(spreadsheet): hide toolbars while in read-only 2026-03-08 21:24:24 +02:00
Elian Doran
90796fc4fa feat(spreadsheet): basic read-only support 2026-03-08 21:09:11 +02:00
Elian Doran
4960c49cb2 feat(spreadsheet): add note plugin 2026-03-08 20:39:07 +02:00
Elian Doran
b112e8b56b feat(spreadsheet): basic support for note revision using image 2026-03-08 20:30:24 +02:00
Elian Doran
83095130f6 feat(spreadsheet): basic rendering as HTML for share 2026-03-08 20:04:14 +02:00
Elian Doran
d005c0ef2d feat(spreadsheet): basic note list preview using SVG 2026-03-08 19:49:53 +02:00
Elian Doran
c135578626 fix(spreadsheet): not focusing on tab switch 2026-03-08 13:05:47 +02:00
Elian Doran
9a6e20029e fix(client): all tabs loaded in the background 2026-03-08 12:59:57 +02:00
Elian Doran
39bd4ccea1 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-03-08 12:59:44 +02:00
Elian Doran
aac4774326 Merge remote-tracking branch 'origin/main' into feature/toc_improvements 2026-03-08 12:20:53 +02:00
Elian Doran
ea7aac2030 v0.102.1 (#8961) 2026-03-08 12:13:12 +02:00
Elian Doran
e7f98f08d0 Merge remote-tracking branch 'origin/main' into stable 2026-03-08 12:12:52 +02:00
Elian Doran
8ac9daa5d3 chore(release): prepare for v0.102.1 2026-03-08 10:43:59 +02:00
Elian Doran
0b506c6327 chore(pdfjs): bump pdfjs viewer version 2026-03-08 10:41:21 +02:00
Elian Doran
d2b62540ec fix(ci): migrate all the jank docker ci to use crane instead (#8869) 2026-03-08 10:37:49 +02:00
Elian Doran
64418c7fec docs(release): prepare for v0.102.1 2026-03-08 10:36:06 +02:00
Elian Doran
8c1a58e64f fix(pdf): cache buster not working in all circumstances 2026-03-08 10:29:57 +02:00
Adorian Doran
b27fd31c1f style/pdf viewer: fix some layout issues in toolbar 2026-03-08 10:25:05 +02:00
Elian Doran
f18a531924 fix(mindmap): crashing on auto-switch to dark theme (closes #8879) 2026-03-08 10:22:21 +02:00
Elian Doran
3cabb4b661 fix(pdf): not accessible on Nginx Proxy Manager with block common exploits (closes #8877) 2026-03-08 09:30:27 +02:00
Elian Doran
5c88b1c6b8 chore(server): add infrastructure for running Nginx Proxy Manager 2026-03-08 09:01:47 +02:00
Elian Doran
c2adc43780 chore(deps): update dependency @types/multer to v2.1.0 (#8921) 2026-03-07 23:17:45 +02:00
Elian Doran
7eaa5352ba Translations update from Hosted Weblate (#8956) 2026-03-07 23:17:15 +02:00
Patric Siesing
17e3e3187b Translated using Weblate (Swedish)
Currently translated at 4.6% (18 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/sv/
2026-03-07 22:15:57 +01:00
Robert Magnusson
2ad7cd3a49 Translated using Weblate (Swedish)
Currently translated at 4.6% (18 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/sv/
2026-03-07 22:15:56 +01:00
Patric Siesing
39aa8d61c2 Translated using Weblate (Swedish)
Currently translated at 11.3% (18 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/sv/
2026-03-07 22:15:55 +01:00
Robert Magnusson
1a3ea977b7 Translated using Weblate (Swedish)
Currently translated at 11.3% (18 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/sv/
2026-03-07 22:15:54 +01:00
Robert Magnusson
4cd8f9a1e6 Translated using Weblate (Swedish)
Currently translated at 1.0% (18 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/sv/
2026-03-07 22:15:53 +01:00
Hosted Weblate
87ce6d1231 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-07 22:15:52 +01:00
Elian Doran
8fdbeacf77 fix(deps): update dependency katex to v0.16.37 (#8935) 2026-03-07 23:15:44 +02:00
Elian Doran
f4f775a1c9 chore(deps): update dependency @smithy/middleware-retry to v4.4.40 (#8945) 2026-03-07 23:13:13 +02:00
Elian Doran
fe1154cb2d chore(deps): update dependency @types/sanitize-html to v2.16.1 (#8946) 2026-03-07 23:12:53 +02:00
Elian Doran
638f479ff3 chore(deps): update dependency eslint to v10.0.3 (#8947) 2026-03-07 23:12:27 +02:00
Elian Doran
70436bdb04 fix(deps): update dependency react-i18next to v16.5.6 (#8949) 2026-03-07 23:12:05 +02:00
Elian Doran
575ecaae07 fix(deps): update dependency tabulator-tables to v6.4.0 (#8950) 2026-03-07 23:11:37 +02:00
Elian Doran
d277e6db94 chore(deps): update actions/upload-artifact action to v7 (#8951) 2026-03-07 08:29:42 +02:00
renovate[bot]
25efcd12d0 chore(deps): update actions/upload-artifact action to v7 2026-03-07 02:18:00 +00:00
renovate[bot]
10129321be fix(deps): update dependency tabulator-tables to v6.4.0 2026-03-07 02:17:54 +00:00
renovate[bot]
72710a8f6b chore(deps): update dependency @types/multer to v2.1.0 2026-03-07 02:17:10 +00:00
renovate[bot]
6a7c5c04d8 fix(deps): update dependency react-i18next to v16.5.6 2026-03-07 02:16:21 +00:00
renovate[bot]
7f32fe5ef7 fix(deps): update dependency eslint-linter-browserify to v10.0.3 2026-03-07 02:15:20 +00:00
renovate[bot]
5d89591dea chore(deps): update dependency eslint to v10.0.3 2026-03-07 02:14:21 +00:00
renovate[bot]
a88bf5a87b chore(deps): update dependency @types/sanitize-html to v2.16.1 2026-03-07 02:13:18 +00:00
renovate[bot]
bbe5d3506e chore(deps): update dependency @smithy/middleware-retry to v4.4.40 2026-03-07 02:12:12 +00:00
renovate[bot]
c2993d4e7d fix(deps): update dependency katex to v0.16.37 2026-03-06 21:42:06 +00:00
Elian Doran
17ba479182 chore(deps): update dependency @smithy/middleware-retry to v4.4.39 (#8906) 2026-03-06 19:01:41 +02:00
Elian Doran
a465014bbe fix(deps): update codemirror (#8885) 2026-03-06 19:01:13 +02:00
Elian Doran
5dfe253ef6 chore(deps): update imjasonh/setup-crane action to v0.5 (#8910) 2026-03-06 19:00:14 +02:00
Elian Doran
ae7ca6021f Translations update from Hosted Weblate (#8919) 2026-03-06 18:57:49 +02:00
noobhjy
c389697acd Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-03-06 16:50:15 +00:00
Aleksandr Reid
c13c3e0f4a Translated using Weblate (Russian)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2026-03-06 16:50:14 +00:00
Ulices
82c042d045 Translated using Weblate (Spanish)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-03-06 16:50:14 +00:00
Aleksandr Reid
9145ba1690 Translated using Weblate (Russian)
Currently translated at 100.0% (387 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/
2026-03-06 16:50:13 +00:00
Marcel
d60653ee17 Translated using Weblate (German)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/de/
2026-03-06 16:50:12 +00:00
Marcel
dae8613b4e Translated using Weblate (German)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-03-06 16:50:12 +00:00
Aindriú Mac Giolla Eoin
2f8e2c40be Translated using Weblate (Irish)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-03-06 16:50:11 +00:00
Francis C.
d85225a0dc Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-03-06 16:50:11 +00:00
green
0cb66df2b2 Translated using Weblate (Japanese)
Currently translated at 100.0% (1676 of 1676 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-03-06 16:50:10 +00:00
Aleksandr Reid
92e0578cb6 Translated using Weblate (Russian)
Currently translated at 100.0% (158 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2026-03-06 16:50:09 +00:00
Elian Doran
2eee06786e chore(deps): update dependency lint-staged to v16.3.2 (#8908) 2026-03-06 18:50:00 +02:00
Elian Doran
19053dcb3b fix(deps): update dependency mind-elixir to v5.9.2 (#8909) 2026-03-06 18:49:24 +02:00
JYC333
e10c30c59f fix(deps): update dependency i18next to v25.8.14 (#8922) 2026-03-06 14:12:55 +00:00
Elian Doran
c356159664 fix(deps): update dependency marked to v17.0.4 (#8923) 2026-03-06 15:45:12 +02:00
Elian Doran
579be68ca1 chore(deps): update dependency electron to v40.8.0 (#8924) 2026-03-06 15:28:24 +02:00
Elian Doran
a6326a682e chore(deps): update dependency @types/node to v24.12.0 (#8934) 2026-03-06 15:27:19 +02:00
renovate[bot]
4595a3a5dd fix(deps): update dependency i18next to v25.8.14 2026-03-06 12:42:27 +00:00
renovate[bot]
ee21185e64 chore(deps): update dependency electron to v40.8.0 2026-03-06 12:39:17 +00:00
Elian Doran
6d0676c37d chore(deps): update docker/login-action action to v4 (#8925) 2026-03-06 14:38:39 +02:00
Elian Doran
1d4768a581 chore(deps): update docker/setup-qemu-action action to v4 (#8926) 2026-03-06 14:38:14 +02:00
Elian Doran
d086bb7fcb chore(deps): update dependency multer to v2.1.1 [security] (#8929) 2026-03-06 14:37:33 +02:00
Elian Doran
2607c4a32e fix(deps): update dependency react-i18next to v16.5.5 (#8936) 2026-03-06 14:37:12 +02:00
Elian Doran
624333a2ef chore(deps): update dependency express-rate-limit to v8.3.0 (#8937) 2026-03-06 14:36:43 +02:00
Elian Doran
d4acb37f21 chore(deps): update dependency ejs to v5 (#8938) 2026-03-06 14:36:24 +02:00
Elian Doran
6c1a1e9812 chore(deps): update docker/build-push-action action to v7 (#8939) 2026-03-06 13:19:16 +02:00
Elian Doran
9a13641f9b chore(deps): update docker/metadata-action action to v6 (#8940) 2026-03-06 13:18:33 +02:00
renovate[bot]
699e0624c9 chore(deps): update docker/setup-qemu-action action to v4 2026-03-06 06:58:29 +00:00
renovate[bot]
47ceb0d4d2 chore(deps): update docker/metadata-action action to v6 2026-03-06 06:58:27 +00:00
renovate[bot]
15c42f4a09 chore(deps): update docker/login-action action to v4 2026-03-06 06:58:24 +00:00
renovate[bot]
bf8401bb26 chore(deps): update docker/build-push-action action to v7 2026-03-06 06:58:21 +00:00
renovate[bot]
f234433c63 chore(deps): update dependency ejs to v5 2026-03-06 06:58:18 +00:00
renovate[bot]
1b70101123 chore(deps): update imjasonh/setup-crane action to v0.5 2026-03-06 06:57:50 +00:00
renovate[bot]
d610c63c28 chore(deps): update dependency express-rate-limit to v8.3.0 2026-03-06 06:57:17 +00:00
renovate[bot]
5e820a407f chore(deps): update dependency @types/node to v24.12.0 2026-03-06 06:56:18 +00:00
renovate[bot]
62610979b7 fix(deps): update dependency react-i18next to v16.5.5 2026-03-06 06:55:50 +00:00
renovate[bot]
700e99e854 fix(deps): update dependency mind-elixir to v5.9.2 2026-03-06 06:55:19 +00:00
renovate[bot]
7767116b3d fix(deps): update dependency marked to v17.0.4 2026-03-06 06:54:40 +00:00
renovate[bot]
0206e8247b fix(deps): update codemirror 2026-03-06 06:52:48 +00:00
renovate[bot]
5476fe3df9 chore(deps): update dependency lint-staged to v16.3.2 2026-03-06 06:50:46 +00:00
renovate[bot]
d9a4581d37 chore(deps): update dependency @smithy/middleware-retry to v4.4.39 2026-03-06 06:49:46 +00:00
renovate[bot]
8d9c888481 chore(deps): update dependency multer to v2.1.1 [security] 2026-03-06 06:46:38 +00:00
Elian Doran
11e4b672d1 Fix CI test issues (#8932) 2026-03-06 08:43:51 +02:00
Elian Doran
bace3daadc Update apps/server/src/routes/session_parser.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-06 08:43:31 +02:00
Elian Doran
dee5380e60 fix(ci): sequential tests ended up run in parallel 2026-03-06 08:20:17 +02:00
Elian Doran
bc6a6fd860 Revert "test(server): reset ws module"
This reverts commit 0212398815.
2026-03-05 23:44:24 +02:00
Elian Doran
e928337fe9 test(server): adjust timeout 2026-03-05 23:40:43 +02:00
Elian Doran
432f86ea4b Revert "test(server): switch to forks with 2 max workers"
This reverts commit 4ac22678df.
2026-03-05 23:37:28 +02:00
Elian Doran
5d2daecee0 test(server): switch to forks with 6 max workers 2026-03-05 23:35:15 +02:00
Elian Doran
7c8eb311af test(server): switch to forks with 3 max workers 2026-03-05 23:31:54 +02:00
Elian Doran
4ac22678df test(server): switch to forks with 2 max workers 2026-03-05 23:25:45 +02:00
Elian Doran
5057c02176 test(server): fix errors due to database already existing 2026-03-05 22:52:26 +02:00
Elian Doran
d301e56216 refactor(server): don't set up other timers on module init 2026-03-05 22:19:04 +02:00
Elian Doran
3c22ab8c9c refactor(server): don't set up session timer on module init 2026-03-05 22:17:19 +02:00
Elian Doran
0212398815 test(server): reset ws module 2026-03-05 22:14:34 +02:00
Elian Doran
db0c515bad test(server): fake timers not restored 2026-03-05 22:11:51 +02:00
Elian Doran
9b4f8c5003 feat(ci/client): HTML output 2026-03-05 22:07:11 +02:00
Elian Doran
85d8c4c8fa feat(ci/server): HTML output 2026-03-05 22:06:46 +02:00
Elian Doran
5afab6938a test(server): reduce max workers to 1 2026-03-05 21:54:30 +02:00
Elian Doran
a437169ad5 test(server): increase hook timeout 2026-03-05 21:20:12 +02:00
Elian Doran
f632d3aeb6 Merge remote-tracking branch 'origin/main' into fix/ci 2026-03-05 21:14:57 +02:00
Elian Doran
513fffcb1a ci(dev): escape test filter 2026-03-05 21:14:21 +02:00
Elian Doran
d3337eab9c Merge branch 'main' into feature/toc_improvements 2026-03-05 21:05:17 +02:00
Elian Doran
8128a8192a refactor(ckeditor): address requested changes 2026-03-05 19:28:52 +02:00
Elian Doran
c80bb9657c fix(mindmap): crashing on auto-switch to dark theme 2026-03-05 19:25:07 +02:00
Elian Doran
65514a6fd7 fix(toc): title is extracted before changes are made 2026-03-05 19:08:56 +02:00
Elian Doran
93a7f8c711 fix(toc): not reacting to attribute changes in CKEditor 2026-03-05 19:03:32 +02:00
Elian Doran
0ca179f990 ci(test): quote command 2026-03-05 18:40:24 +02:00
Elian Doran
9d104015f3 ci(test): quote command 2026-03-05 18:30:08 +02:00
Elian Doran
2c4cf2dcf1 ci(test): separate running of heavy tests to avoid OOM issues 2026-03-05 18:28:27 +02:00
Elian Doran
d2e0124962 chore(deps): update dependency fs-extra to v11.3.4 (#8907) 2026-03-05 16:51:11 +02:00
renovate[bot]
cd59c75c04 chore(deps): update dependency fs-extra to v11.3.4 2026-03-04 01:13:39 +00:00
Elian Doran
caa9143591 chore(deps): update dependency happy-dom to v20.8.3 (#8887) 2026-03-03 22:15:58 +02:00
renovate[bot]
7e53810c02 chore(deps): update dependency happy-dom to v20.8.3 2026-03-03 19:42:03 +00:00
Elian Doran
12efa8dc0b chore(deps): update dependency eslint-plugin-playwright to v2.9.0 (#8886) 2026-03-03 21:21:22 +02:00
Elian Doran
4d0ccac7b5 fix(deps): update dependency node-html-parser to v7.1.0 (#8888) 2026-03-03 21:20:21 +02:00
Elian Doran
8b023a55d0 chore(deps): update dependency copy-webpack-plugin to v14 (#8889) 2026-03-03 21:10:22 +02:00
Elian Doran
b4df5fcbd9 chore(deps): update dependency rollup-plugin-webpack-stats to v3 (#8890) 2026-03-03 20:58:24 +02:00
renovate[bot]
6fbe5718e9 chore(deps): update dependency rollup-plugin-webpack-stats to v3 2026-03-03 18:54:56 +00:00
Elian Doran
908bafca63 Translations update from Hosted Weblate (#8901) 2026-03-03 20:54:00 +02:00
Elian Doran
d7313efd67 fix(ci): migrate all the jank docker ci to use crane instead (#8869) 2026-03-03 20:48:42 +02:00
Микола Копитін
a51e15c9b8 Translated using Weblate (Ukrainian)
Currently translated at 90.0% (1508 of 1675 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/uk/
2026-03-03 18:44:20 +00:00
Hosted Weblate
37e9c7d639 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-03-03 18:44:20 +00:00
Elian Doran
2d00ac4dfb Univer Sheets v0 (#8902) 2026-03-03 20:44:06 +02:00
Elian Doran
6aec7eae00 chore(server): increase sync version to avoid data loss due to unsupported note type 2026-03-03 20:25:33 +02:00
Elian Doran
6bfbc2d35e chore(spreadsheet): use better clean up mechanism 2026-03-03 20:12:54 +02:00
Elian Doran
2ffc854ce6 chore(spreadsheet): mark note type as beta 2026-03-03 19:59:12 +02:00
Elian Doran
ddd4a374e4 chore(client): fix some whitespace issues 2026-03-03 19:52:15 +02:00
Elian Doran
0d6e2fc00f chore(client): fix typecheck 2026-03-03 19:41:51 +02:00
Elian Doran
366a8e8726 fix(spreadsheet): persistence hook on every render 2026-03-03 19:24:04 +02:00
Elian Doran
7f0aa0697a fix(spreadsheet): error due to duplicate unit IDs 2026-03-03 19:20:25 +02:00
Elian Doran
d123ce33b8 feat(spreadsheet): restore from JSON 2026-03-03 19:09:33 +02:00
Elian Doran
55588f5962 feat(spreadsheet): restore from JSON 2026-03-03 19:05:01 +02:00
Elian Doran
f32130d5c2 feat(spreadsheet): allow source to be viewed 2026-03-03 19:00:23 +02:00
Elian Doran
03f4ff9e7c feat(spreadsheet): save spreadsheet to JSON 2026-03-03 19:00:14 +02:00
Elian Doran
6de78c7154 refactor(spreadsheet): make use of hooks 2026-03-03 18:48:45 +02:00
Elian Doran
d331e418d4 feat(spreadsheet): support dark mode 2026-03-03 18:42:26 +02:00
Elian Doran
4ace74bcb8 feat(spreadsheet): make full-width 2026-03-03 18:36:17 +02:00
Elian Doran
1d4a336256 feat(spreadsheet): integrate spreadsheet with full-height 2026-03-03 18:34:46 +02:00
Elian Doran
ee6c192ab9 chore(spreadsheet): create new note type 2026-03-03 18:24:55 +02:00
Elian Doran
b220bdce9c fix(note_list): affected by floating images (closes #8899) 2026-03-03 18:14:43 +02:00
Elian Doran
4d86c6c4f1 feat(import/single): trim extension for audio files + default icon 2026-03-03 16:19:44 +02:00
Elian Doran
4fd68bf12d feat(import/single): trim extension for video files 2026-03-03 14:29:18 +02:00
Elian Doran
3ffe34964f feat(notes): add default icon for videos 2026-03-03 14:26:45 +02:00
Elian Doran
faaf26c174 fix(quick_edit): save indicator not shown 2026-03-03 14:19:24 +02:00
Elian Doran
f9c7518db2 fix(spaced_update): triggering events too often while typing 2026-03-03 14:19:24 +02:00
Elian Doran
8357c2a39c chore(pdfjs): version not updated for releases 2026-03-03 14:19:24 +02:00
renovate[bot]
793dcee562 chore(deps): update dependency copy-webpack-plugin to v14 2026-03-03 02:02:49 +00:00
renovate[bot]
00368fc131 fix(deps): update dependency node-html-parser to v7.1.0 2026-03-03 02:01:49 +00:00
renovate[bot]
f81b686f41 chore(deps): update dependency eslint-plugin-playwright to v2.9.0 2026-03-03 02:00:00 +00:00
Elian Doran
4c5aada5d3 chore(deps): update dependency @types/express-serve-static-core to v5.1.1 (#8346) 2026-03-02 22:42:10 +02:00
Elian Doran
05551cec9e chore(deps): update dependency sax to v1.5.0 (#8875) 2026-03-02 22:41:20 +02:00
Elian Doran
6300a8c8d1 chore(deps): update dependency @redocly/cli to v2.20.2 (#8853) 2026-03-02 22:34:22 +02:00
Elian Doran
ca4d15727d Merge branch 'main' into renovate/express-serve-static-core-5.x 2026-03-02 22:30:43 +02:00
renovate[bot]
2fe076086e chore(deps): update dependency sax to v1.5.0 2026-03-02 20:25:56 +00:00
Elian Doran
56b65ddfae Translations update from Hosted Weblate (#8870) 2026-03-02 22:22:37 +02:00
Hasan Kara
fcf6673825 Translated using Weblate (Turkish)
Currently translated at 16.3% (19 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/tr/
2026-03-02 20:54:15 +01:00
Hasan Kara
9eda264f52 Translated using Weblate (Turkish)
Currently translated at 5.1% (20 of 387 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/tr/
2026-03-02 20:54:14 +01:00
Hasan Kara
fe1270c679 Translated using Weblate (Turkish)
Currently translated at 4.2% (71 of 1675 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/tr/
2026-03-02 20:54:13 +01:00
Hasan Kara
679e1ac678 Translated using Weblate (Turkish)
Currently translated at 12.0% (19 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/tr/
2026-03-02 20:54:12 +01:00
ibs-allaow
e309ff2d17 Translated using Weblate (Arabic)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ar/
2026-03-02 20:54:12 +01:00
Francis C.
c910335155 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1675 of 1675 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-03-02 20:54:11 +01:00
Yatrik Patel
5606cde506 Translated using Weblate (Hindi)
Currently translated at 100.0% (1675 of 1675 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-03-02 20:54:10 +01:00
Yatrik Patel
0e2f4f4e13 Translated using Weblate (Hindi)
Currently translated at 38.6% (61 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-03-02 20:54:09 +01:00
renovate[bot]
1f6c6f2acd chore(deps): update dependency @redocly/cli to v2.20.2 2026-03-02 18:09:58 +00:00
Elian Doran
37d2e9f14b fix(deps): update dependency globals to v17.4.0 (#8876) 2026-03-02 16:44:44 +02:00
Adorian Doran
0fcf30a3b8 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-03-02 11:22:08 +02:00
Adorian Doran
8712e7dd16 style/pdf viewer: fix some layout issues in toolbar 2026-03-02 11:21:47 +02:00
renovate[bot]
2ee4e9cc14 fix(deps): update dependency globals to v17.4.0 2026-03-02 01:18:53 +00:00
perfectra1n
b257b75be2 fix(ci): remove fragile jq where possible 2026-03-01 13:49:45 -08:00
perfectra1n
2de2709420 fix(ci): migrate all the jank docker ci to use crane instead 2026-03-01 13:47:18 -08:00
Elian Doran
3e0ddd90a1 chore(docker): image not properly pushed to DockerHub 2026-03-01 22:20:00 +02:00
Elian Doran
b96b004262 fix(electron-forge): wrong import 2026-03-01 20:15:04 +02:00
Elian Doran
e1ad48b42a feat(electron-forge): clear signing logs on failure 2026-03-01 20:02:08 +02:00
Elian Doran
420f0917be chore: formatting for CJS files 2026-03-01 20:00:23 +02:00
Elian Doran
31d8287e1b feat(electron-forge): introduce basic expotential back-off for signing 2026-03-01 20:00:23 +02:00
Elian Doran
4433d034db fix(ci): unblock signing not working due to bad shell & missing variable 2026-03-01 17:57:06 +02:00
Elian Doran
c96114992e fix(ci): sanity check does not account for version prefix 2026-03-01 17:43:41 +02:00
Elian Doran
b35e0b906f fix(ci): missing dependencies 2026-03-01 17:38:27 +02:00
Elian Doran
104c9ec64a fix(ci): prevent release if sanity checks fail 2026-03-01 17:34:45 +02:00
Elian Doran
6e4b18b57b fix(ci): missing pnpm 2026-03-01 17:34:05 +02:00
Elian Doran
f9460c8f41 feat(ci): unblock signing for Electron 2026-03-01 17:25:06 +02:00
Elian Doran
02601f37d8 fix(ci): version consistency missing pnpm 2026-03-01 17:21:43 +02:00
Elian Doran
0feab6d4ed chore(release): prepare for v0.102.0 2026-03-01 16:54:05 +02:00
Elian Doran
5a9d3499d8 docs(release): add v0.102.0 release notes 2026-03-01 16:49:06 +02:00
Elian Doran
dce0988409 chore(deps): update dependency lint-staged to v16.3.1 (#8860) 2026-03-01 16:37:50 +02:00
renovate[bot]
b2d378db6b chore(deps): update dependency lint-staged to v16.3.1 2026-03-01 10:43:11 +00:00
Elian Doran
7ecff88da7 chore(deps): update github artifact actions (major) (#8847) 2026-03-01 12:41:40 +02:00
Adorian Doran
b434bf8804 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-03-01 09:01:14 +02:00
Adorian Doran
441484629a style/quick edit: fix the layout of collections 2026-03-01 09:01:04 +02:00
renovate[bot]
37a1bd6b25 chore(deps): update github artifact actions 2026-02-28 21:15:12 +00:00
Elian Doran
fe3dfc418f test(client): broken test due to circular dependency 2026-02-28 21:47:02 +02:00
Elian Doran
34ca7912fc Merge remote-tracking branch 'origin/main' into renovate/express-serve-static-core-5.x 2026-02-28 19:11:57 +02:00
Adorian Doran
1d698106da Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-28 19:03:35 +02:00
Adorian Doran
b9ce83165f client/content renderer/pdf: make the container element full height 2026-02-28 19:03:25 +02:00
Elian Doran
34670dd69c Add Support for Recurrence In Calendar Collection (#8799) 2026-02-28 19:01:30 +02:00
Elian Doran
54be58ba1f feat(client/menus): add Copy as Markdown (#8808) 2026-02-28 18:59:40 +02:00
Elian Doran
b9abdcb189 chore(electron_context_menu): use upper case for Markdown 2026-02-28 18:58:25 +02:00
Adorian Doran
9f0f9b8315 style/list view: tweak the size of the PDF preview 2026-02-28 18:57:53 +02:00
Adorian Doran
63ee60ffc7 client/pdf viewer: add a rudimentary support for a read-only view 2026-02-28 18:51:36 +02:00
Adorian Doran
3b2cd5dca1 style/note paths: fix the border radius for a single item listing 2026-02-28 18:47:17 +02:00
Adorian Doran
388a6943e9 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-02-28 18:43:59 +02:00
Adorian Doran
50301be093 client/pdf viewer: add a rudimentary support for a read-only view 2026-02-28 18:43:51 +02:00
Elian Doran
123e1ada90 Merge branch 'main' into main 2026-02-28 18:41:48 +02:00
Elian Doran
5c42dbba12 chore(deps): update dependency @types/node to v24.11.0 (#8844) 2026-02-28 18:36:43 +02:00
Elian Doran
a549fd228e chore(deps): update pnpm to v10.30.3 (#8845) 2026-02-28 18:36:33 +02:00
Elian Doran
6e352e5b22 chore(deps): update dependency axios to v1.13.6 (#8851) 2026-02-28 18:36:16 +02:00
Elian Doran
1d3df69bb7 fix(deps): update dependency codemirror-lang-elixir to v4.0.1 (#8852) 2026-02-28 18:35:53 +02:00
Elian Doran
c6394698fa chore(deps): update dependency eslint-plugin-playwright to v2.8.0 (#8854) 2026-02-28 18:35:45 +02:00
Elian Doran
d94f2ac093 chore(deps): update dependency multer to v2.1.0 (#8855) 2026-02-28 18:35:32 +02:00
Elian Doran
740974fbfd fix(next): tab bar offset on horizontal layout 2026-02-28 18:35:15 +02:00
Elian Doran
f0b9e0e48b Translations update from Hosted Weblate (#8849) 2026-02-28 18:35:04 +02:00
Elian Doran
deee50f2b4 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-28 18:34:54 +02:00
BeatLink
2c9d5368a1 Merge branch 'main' into main 2026-02-28 09:45:53 -05:00
Luk On
819432b4ab Translated using Weblate (Polish)
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2026-02-28 15:42:25 +01:00
Giovi
7efbb4d945 Translated using Weblate (Italian)
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-02-28 15:42:24 +01:00
Adorian Doran
64d1b33a4c UI: restyle PDF viewer (#8857) 2026-02-28 16:42:10 +02:00
Adorian Doran
326adc0196 style/pdf viewer: fixes 2026-02-28 16:35:51 +02:00
Adorian Doran
b1b9a4461e style/pdf viewer: several tweaks 2026-02-28 14:45:35 +02:00
Adorian Doran
d70f5d3ed6 style/pdf viewer: several tweaks 2026-02-28 13:33:48 +02:00
Adorian Doran
d1fac8f0e2 client/pdf viewer: remove the CSS variable whitelist 2026-02-28 10:18:25 +02:00
Adorian Doran
5f7c26eed3 style/pdf viewer: tweak document properties dialog button 2026-02-28 08:56:42 +02:00
Adorian Doran
bf748cee24 style/pdf viewer: tweak toolbar 2026-02-28 08:44:50 +02:00
Adorian Doran
79439f6435 style/pdf viewer: tweak the find bar 2026-02-28 08:28:37 +02:00
Adorian Doran
2a875f8386 style/pdf viewer: restyle text boxes 2026-02-28 08:03:27 +02:00
Adorian Doran
08e69d405c style/pdf viewer: tweak the find bar 2026-02-28 07:38:49 +02:00
renovate[bot]
471f380756 chore(deps): update dependency @types/node to v24.11.0 2026-02-28 04:53:58 +00:00
renovate[bot]
700edbe2c8 chore(deps): update dependency multer to v2.1.0 2026-02-28 01:31:50 +00:00
renovate[bot]
d60c117c62 chore(deps): update dependency eslint-plugin-playwright to v2.8.0 2026-02-28 01:30:58 +00:00
renovate[bot]
4ae395ded5 fix(deps): update dependency codemirror-lang-elixir to v4.0.1 2026-02-28 01:29:07 +00:00
renovate[bot]
f311db1cb4 chore(deps): update dependency axios to v1.13.6 2026-02-28 01:28:18 +00:00
BeatLink
40be94804c Merge branch 'main' into main 2026-02-27 18:09:22 -05:00
BeatLink
5719819947 Update Recurrence Docs 2026-02-27 18:08:13 -05:00
BeatLink
c938bcc657 Remove unnecessary error checks 2026-02-27 18:01:23 -05:00
BeatLink
e029379194 Add recurrence testing, use dayjs for calendar 2026-02-27 17:39:12 -05:00
Adorian Doran
d56f106964 UI: general improvements (#8837) 2026-02-27 22:47:22 +02:00
Adorian Doran
676dea33e1 client: fix different issues 2026-02-27 22:45:52 +02:00
Adorian Doran
945b00030b style/pdf viewer: restyle UI 2026-02-27 22:37:07 +02:00
Adorian Doran
a47de6c65c Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui/general-improvements 2026-02-27 19:34:41 +02:00
Adorian Doran
7c89c66526 style/pdf viewer: restyle UI 2026-02-27 19:34:26 +02:00
BeatLink
8554dc249c Fix recurrence error management 2026-02-27 11:43:23 -05:00
BeatLink
bfcbfac5bb Add recurrence error handling 2026-02-27 10:47:56 -05:00
Zexin Yuan
59d8a98eea feat(client/menus): add Copy as Markdown to electron context menu 2026-02-27 14:22:04 +08:00
renovate[bot]
c7d74e8b00 chore(deps): update pnpm to v10.30.3 2026-02-27 01:08:42 +00:00
Elian Doran
dc3de5bf36 chore(server): address requested changes 2026-02-27 00:05:54 +02:00
Elian Doran
680cd8118f fix(desktop+server): use exec in launcher scripts (#8809) 2026-02-26 23:38:14 +02:00
Elian Doran
1041bf70e1 test(express-partial-content): fix type errors 2026-02-26 21:11:22 +02:00
Elian Doran
0c6326b678 refactor(server): use strong typing for routes 2026-02-26 21:08:54 +02:00
Adorian Doran
f4d91e48ba style/pdf viewer: fix color scheme 2026-02-26 20:46:55 +02:00
renovate[bot]
fd805a5279 chore(deps): update dependency @types/express-serve-static-core to v5.1.1 2026-02-26 18:18:55 +00:00
Elian Doran
9374694a0c Merge branch 'main' into exec-in-launcher-scripts 2026-02-26 20:17:53 +02:00
Elian Doran
61460daaea chore(deps): update dependency stylelint to v17.4.0 (#8835) 2026-02-26 20:17:04 +02:00
Elian Doran
d64c1f6b0e chore(deps): update dependency @types/node to v24.10.14 (#8833) 2026-02-26 20:16:47 +02:00
Elian Doran
0334166029 feat(turndown-plugin-gfm): adopt MD060 compact style for table cells (#8840) 2026-02-26 20:15:27 +02:00
Adorian Doran
7fac172ce2 style: remove redundant style 2026-02-26 19:54:42 +02:00
Adorian Doran
3b9f765c24 style/tree items: display a fallback icon when the specified icon pack is missing 2026-02-26 19:52:20 +02:00
Adorian Doran
655b016efa Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/ui/general-improvements 2026-02-26 19:18:09 +02:00
Adorian Doran
aa247ef06c Theming: include the current theme ID as an attribute for the body element (#8842) 2026-02-26 19:14:37 +02:00
Zexin Yuan
9e653a87b8 feat(turndown-plugin-gfm): adopt MD060 compact style for table cells
Style compact avoids extra padding with a single space around cell
content:

| Character | Meaning |
| --- | --- |
| Y | Yes |
| N | No |

Closes #8795
2026-02-27 00:23:04 +08:00
Adorian Doran
dfb44def2b client/theming: include the current theme ID as an attribute on the body element 2026-02-26 17:50:23 +02:00
Elian Doran
f0e8c0f79d chore(deps): update dependency @types/supertest to v7 (#8836) 2026-02-26 16:18:12 +02:00
Adorian Doran
28fe73911f Theming: color scheme selectors (#8839) 2026-02-26 16:16:52 +02:00
Adorian Doran
a971640ffc client: add some documentation 2026-02-26 16:14:39 +02:00
Adorian Doran
44dc8cf00d Close #7932 2026-02-26 15:52:27 +02:00
Adorian Doran
59ca270880 Merge branch 'main' into feat/ui/general-improvements 2026-02-26 10:43:34 +02:00
Adorian Doran
abb5fe5b0f style/tree items: restyle the bulk actions button 2026-02-26 10:27:13 +02:00
Adorian Doran
af0e4088a6 style/tree items: fix hover color for the active item 2026-02-26 09:35:29 +02:00
Adorian Doran
6b82943871 style/tree items: restyle selection 2026-02-26 09:31:39 +02:00
renovate[bot]
d7aa744bad chore(deps): update dependency @types/supertest to v7 2026-02-26 01:54:26 +00:00
renovate[bot]
f417a2e126 chore(deps): update dependency stylelint to v17.4.0 2026-02-26 01:53:38 +00:00
renovate[bot]
b937f474e4 chore(deps): update dependency @types/node to v24.10.14 2026-02-26 01:52:41 +00:00
Elian Doran
6a3c4fec98 Mobile fixes (#8832) 2026-02-25 23:17:50 +02:00
Elian Doran
6bda7837d6 chore(deps): update dependency @redocly/cli to v2.19.2 (#8826) 2026-02-25 23:07:13 +02:00
Elian Doran
ab9144972b Merge branch 'main' into feature/mobile_fixes 2026-02-25 23:06:23 +02:00
Elian Doran
bcb646f42b chore(deps): update dependency @smithy/middleware-retry to v4.4.37 (#8827) 2026-02-25 23:02:37 +02:00
Elian Doran
0db75a6cdb chore(deps): update dependency electron to v40.6.1 (#8828) 2026-02-25 23:01:37 +02:00
Elian Doran
76c7b8692d fix(deps): update dependency mind-elixir to v5.9.1 (#8829) 2026-02-25 23:00:36 +02:00
Elian Doran
1061aba212 chore(deps): update node.js to v24.14.0 (#8830) 2026-02-25 22:55:03 +02:00
Elian Doran
d4eb6f07bd chore(deps): update dependency @wxt-dev/auto-icons to v1.1.1 (#8800) 2026-02-25 22:54:42 +02:00
Elian Doran
f1a83124a8 Merge branch 'main' into feature/mobile_fixes 2026-02-25 22:42:41 +02:00
Elian Doran
2ad30c6a3d Other bugfixes (#8824) 2026-02-25 22:35:29 +02:00
Elian Doran
0f1533d0a0 chore(mobile): remove redundant style 2026-02-25 20:57:20 +02:00
Elian Doran
bf5caaebb5 fix(mobile/text): formatting toolbar doesn't go back to the right position on iOS 2026-02-25 20:36:37 +02:00
Adorian Doran
b24d2c65a1 style: tweak layout 2026-02-25 20:33:33 +02:00
Elian Doran
2d989dcfe3 fix(mobile/text): formatting toolbar wrongly positioned if dragging on it 2026-02-25 20:18:04 +02:00
Elian Doran
8e8e6f9ed1 fix(mobile/text): floating toolbar mispositioned on iOS 2026-02-25 20:15:07 +02:00
Elian Doran
e1de98c4ae fix(mobile/text): formatting toolbar missing background on iOS 2026-02-25 20:02:15 +02:00
Adorian Doran
aba7f35d9f style/text: tweak dividers 2026-02-25 19:34:31 +02:00
Adorian Doran
8c9ad575ef style: tweak layout 2026-02-25 19:26:06 +02:00
Elian Doran
fc59ee6e93 chore(mobile): fix warnings in mobile_editor_toolbar 2026-02-25 19:14:32 +02:00
Elian Doran
5ec9770b07 fix(mobile): confusing shift when opening keyboard in split on iOS 2026-02-25 19:08:20 +02:00
Elian Doran
1c1895b2eb fix(mobile): confusing shift when opening keyboard in split on iOS 2026-02-25 19:05:01 +02:00
Adorian Doran
7861bc41f9 style/calendar: tweak layout 2026-02-25 18:56:24 +02:00
Adorian Doran
843e4d45e6 style: tweak layout 2026-02-25 18:55:57 +02:00
Elian Doran
416825c9c2 fix(mobile): toast not respecting safe area 2026-02-25 18:54:42 +02:00
Elian Doran
6762539b4d fix(client): note context active indicator disappears after typing 2026-02-25 18:47:00 +02:00
Elian Doran
ae9827c436 fix(mobile): virtual keyboard detection not working on Android 2026-02-25 18:35:01 +02:00
Adorian Doran
4b4b427d2b style: tweak layout 2026-02-25 18:33:53 +02:00
Elian Doran
33622cd3fe fix(hooks): unnecessary recreation of media listener 2026-02-25 18:21:53 +02:00
Adorian Doran
807b054a95 UI: grid view extra improvements (#8831) 2026-02-25 18:21:35 +02:00
Adorian Doran
b223b931ab ui/grid view/tables: fix scroll direction 2026-02-25 11:35:39 +02:00
Adorian Doran
6759fef827 ui/grid view/tables: tweak 2026-02-25 11:04:26 +02:00
Adorian Doran
896d50b1f8 ui/grid view: hide the scrollbar of tables 2026-02-25 11:01:45 +02:00
Adorian Doran
2049c49fdb ui/grid view: horizontally scroll tables 2026-02-25 10:58:38 +02:00
Adorian Doran
b3c397e847 ui/grid view: fade out overflowing content 2026-02-25 09:40:05 +02:00
renovate[bot]
e69b85a988 chore(deps): update node.js to v24.14.0 2026-02-25 00:56:05 +00:00
renovate[bot]
aabc9ec4df fix(deps): update dependency mind-elixir to v5.9.1 2026-02-25 00:55:58 +00:00
renovate[bot]
598501e3f6 chore(deps): update dependency electron to v40.6.1 2026-02-25 00:55:05 +00:00
renovate[bot]
bfa344a839 chore(deps): update dependency @smithy/middleware-retry to v4.4.37 2026-02-25 00:54:08 +00:00
renovate[bot]
c466d7ee9f chore(deps): update dependency @redocly/cli to v2.19.2 2026-02-25 00:53:13 +00:00
renovate[bot]
2e9c07d3d6 chore(deps): update dependency @wxt-dev/auto-icons to v1.1.1 2026-02-24 20:24:58 +00:00
Elian Doran
9651ca99f3 chore(deps): update dependency @smithy/middleware-retry to v4.4.36 (#8813) 2026-02-24 22:22:53 +02:00
Elian Doran
a6e87f5724 chore(deps): update dependency eslint to v10.0.2 (#8815) 2026-02-24 22:22:25 +02:00
renovate[bot]
cf994dac5a chore(deps): update dependency eslint to v10.0.2 2026-02-24 17:49:42 +00:00
Elian Doran
95c9c375c9 chore(deps): update dependency wxt to v0.20.18 (#8801) 2026-02-24 19:45:35 +02:00
renovate[bot]
f9804eda8e chore(deps): update dependency @smithy/middleware-retry to v4.4.36 2026-02-24 17:44:47 +00:00
Elian Doran
88c2bedbd7 chore(deps): update dependency eslint-plugin-playwright to v2.7.1 (#8816) 2026-02-24 19:44:34 +02:00
Elian Doran
49bf49c967 chore(deps): update pnpm to v10.30.2 (#8817) 2026-02-24 19:44:13 +02:00
Elian Doran
506b5c44af chore(deps): update typescript-eslint monorepo to v8.56.1 (#8818) 2026-02-24 19:43:38 +02:00
Elian Doran
22ea59a63b fix(deps): update dependency eslint-linter-browserify to v10.0.2 (#8819) 2026-02-24 19:43:17 +02:00
Elian Doran
934aaaf045 chore(deps): update dependency @types/jquery to v4 (#8820) 2026-02-24 19:42:37 +02:00
Elian Doran
c6bfcea79f Translations update from Hosted Weblate (#8823) 2026-02-24 19:42:19 +02:00
Aindriú Mac Giolla Eoin
6e39fb12e2 Translated using Weblate (Irish)
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-24 17:14:11 +00:00
Elian Doran
dbe534d8f8 Translations update from Hosted Weblate (#8811) 2026-02-24 19:14:01 +02:00
Elian Doran
04e5197d00 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-24 19:13:42 +02:00
Elian Doran
3a15878629 fix(mindmap): not reacting to switch between light/dark mode 2026-02-24 18:46:46 +02:00
Elian Doran
021a908c9c fix(canvas): not reacting to switch between light/dark mode 2026-02-24 18:35:36 +02:00
Elian Doran
c09ef3af80 fix(note_title): help pages have editable title 2026-02-24 18:14:13 +02:00
Elian Doran
3151e6dafc fix(client/layout): scroll padding enabled when note is protected 2026-02-24 18:10:51 +02:00
noobhjy
e9dc97daf8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-02-24 09:16:45 +01:00
green
3a3cc565ea Translated using Weblate (Japanese)
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-24 09:16:45 +01:00
Toto Yullian
fcf4ffb445 Translated using Weblate (Indonesian)
Currently translated at 4.6% (78 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-02-24 09:16:44 +01:00
Giovi
b34c27af1f Translated using Weblate (Italian)
Currently translated at 100.0% (1674 of 1674 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-02-24 09:16:44 +01:00
Adorian Doran
64371a6b9c UI: tweak grid & list collections (#8804) 2026-02-24 10:16:39 +02:00
Adorian Doran
182afab12c Merge branch 'main' into feat/ui/tweak-list-and-grid-collections 2026-02-24 10:16:17 +02:00
Adorian Doran
57e888911d ui/list & grid view: refactor 2026-02-24 10:15:51 +02:00
Adorian Doran
535054b2d2 style/card: fix hover color on nested items 2026-02-24 10:07:26 +02:00
Adorian Doran
d40191257c ui/grid view: tweak the height of video previews 2026-02-24 09:53:35 +02:00
Adorian Doran
87ee1185f2 ui/list & grid view: add bottom margin if the pager is not visible 2026-02-24 09:33:28 +02:00
Adorian Doran
f4b0f810bd ui/list & grid view: increase content entrance transition duration 2026-02-24 09:24:55 +02:00
renovate[bot]
0934e33af7 chore(deps): update dependency @types/jquery to v4 2026-02-24 01:55:37 +00:00
renovate[bot]
b3e88f5a44 fix(deps): update dependency eslint-linter-browserify to v10.0.2 2026-02-24 01:54:41 +00:00
renovate[bot]
2f23db0c64 chore(deps): update typescript-eslint monorepo to v8.56.1 2026-02-24 01:53:41 +00:00
renovate[bot]
ca2f39bacd chore(deps): update pnpm to v10.30.2 2026-02-24 01:52:45 +00:00
renovate[bot]
e38b89996a chore(deps): update dependency eslint-plugin-playwright to v2.7.1 2026-02-24 01:52:34 +00:00
Christian Barcenas
8efbb8819b fix(server): use exec in launcher script 2026-02-24 00:22:49 +01:00
Christian Barcenas
e03bd3d716 fix(desktop): use exec in launcher scripts 2026-02-24 00:22:49 +01:00
Elian Doran
cfb56cb143 fix(deps): update dependency katex to v0.16.33 (#8802) 2026-02-23 21:38:38 +02:00
renovate[bot]
477e516473 fix(deps): update dependency katex to v0.16.33 2026-02-23 18:59:39 +00:00
Elian Doran
fd601eac5b add generic debugging/dap launch.json for client, server and vitest (#8668) 2026-02-23 20:56:34 +02:00
Elian Doran
df4fa42acd fix(desktop): protected session not refreshing while app is in background (closes #7761) 2026-02-23 19:56:03 +02:00
Elian Doran
b06d390df5 chore(server): use purple icon in dev mode 2026-02-23 18:59:07 +02:00
Elian Doran
7f013a58f5 fix(backend_log): not scrollable 2026-02-23 18:55:19 +02:00
Elian Doran
1fd0fb03fa chore(ckeditor): remove unnecessary priority
See https://github.com/ckeditor/ckeditor5/pull/19115#issuecomment-3944720307
2026-02-23 18:46:09 +02:00
Elian Doran
7ec718218e fix(hidden_subtree): attribute of wrong type (relation vs label) causing issues 2026-02-23 18:45:20 +02:00
misch334
b5300e5b86 make browser reference platform-agnostic and reliant on defaults 2026-02-23 12:29:29 +01:00
Adorian Doran
19d2f02694 ui/list & grid view: fix some issues 2026-02-23 12:29:28 +02:00
Adorian Doran
fec929dfee ui/list & grid view: refactor 2026-02-23 12:21:07 +02:00
Adorian Doran
d7339ff14d ui/collection properties: apply the correct inline padding 2026-02-23 11:40:02 +02:00
Adorian Doran
422bc00ade ui/list & grid view: add block margin for the bottom pager 2026-02-23 11:36:04 +02:00
Adorian Doran
8edf5483a6 ui/pager: add support for custom CSS class 2026-02-23 11:28:35 +02:00
Adorian Doran
00046d4145 ui/grid view: do not show the item menu for the options root 2026-02-23 11:15:35 +02:00
Adorian Doran
25e67f62d5 ui/list & grid view: use background effects 2026-02-23 11:10:39 +02:00
Adorian Doran
8d233e66e1 Grid collections: enhance the grid view (#8770) 2026-02-23 10:39:08 +02:00
Adorian Doran
39e0d5b629 ui/grid view: fix some issues 2026-02-23 10:16:04 +02:00
Adorian Doran
90844d5bc2 ui/grid view card: tweak style 2026-02-23 10:11:52 +02:00
Adorian Doran
29df06626b ui/grid view card: tweak CSS 2026-02-23 10:06:11 +02:00
Adorian Doran
d28661fee3 ui/grid view card: use tinted background 2026-02-23 09:55:47 +02:00
Adorian Doran
dc8c10e531 ui/grid view card: refactor CSS 2026-02-23 09:34:08 +02:00
Adorian Doran
538e1f0fdd ui/grid view card: tweak layout 2026-02-23 09:21:52 +02:00
Adorian Doran
eaf0c690e1 ui/grid view card: tweak layout 2026-02-23 09:19:01 +02:00
Adorian Doran
a4a3d6a82c ui/grid view card: restyle the button bar 2026-02-23 09:08:54 +02:00
renovate[bot]
e5eab3952b chore(deps): update dependency wxt to v0.20.18 2026-02-23 00:55:07 +00:00
BeatLink
9b45639148 Update apps/client/src/widgets/collections/calendar/event_builder.ts
Replace end with duration if recurrence set. Make duration calculation clearer

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-22 18:43:18 -05:00
BeatLink
e53cd7443a Add Docs for Recurrence 2026-02-22 18:22:21 -05:00
BeatLink
60b74f5959 Add Recurrence Support for Calendar 2026-02-22 18:13:08 -05:00
Elian Doran
a3e4044fec Translations update from Hosted Weblate (#8796) 2026-02-22 21:46:05 +02:00
Elian Doran
f380df9c01 feat(i18n): add support for Hindi 2026-02-22 21:35:14 +02:00
Hosted Weblate
83fc82a615 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/
2026-02-22 18:13:20 +00:00
Hosted Weblate
eaf3632fc9 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-02-22 18:13:06 +00:00
Vedansh Bodkhe
1d947f26ff Translated using Weblate (Hindi)
Currently translated at 100.0% (1809 of 1809 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-02-22 18:13:02 +00:00
Elian Doran
34a08be4cd feat(dev): remove ai/llm features (#8678) 2026-02-22 20:12:49 +02:00
Adorian Doran
f84a4c4856 ui/grid view: do not animate the card when pressing the item menu button 2026-02-22 19:48:58 +02:00
Adorian Doran
47aa24d0f4 ui/list view: use card frame component instead of a custom styled element 2026-02-22 19:43:25 +02:00
Adorian Doran
e6e132e905 ui/card frame: add support for arbitrary HTML attributes and event handlers 2026-02-22 19:42:08 +02:00
Adorian Doran
ad2df957c7 ui/cards: create a card frame component 2026-02-22 19:25:30 +02:00
Elian Doran
818ca2c2ab e2e(server): fix flaky test 2026-02-22 18:54:51 +02:00
Elian Doran
72c34eb491 docs(user): mention changes to LLM 2026-02-22 16:03:27 +02:00
Elian Doran
22341bf0b1 fix(server): LLM launcher & option still not removed 2026-02-22 15:46:56 +02:00
Elian Doran
c735870b95 test(server): reduce num workers temporarily 2026-02-22 14:33:14 +02:00
Elian Doran
56f796bf80 Fix searching with orderby and parentheses / increased expression level (#8717) 2026-02-22 12:34:59 +02:00
Elian Doran
225693fa79 chore(server): update integration DB to latest migration 2026-02-22 12:01:20 +02:00
Elian Doran
7f760bff17 Merge remote-tracking branch 'origin/main' into feat/remove-ai-llm 2026-02-22 11:51:57 +02:00
Elian Doran
6b4da33729 chore(launch_bar): log warnings instead of crashing on a bad launcher 2026-02-22 11:35:46 +02:00
Elian Doran
82cae8ffbf fix(hidden_subtree): clean up LLM launch item 2026-02-22 11:13:37 +02:00
Elian Doran
31dbbb1881 Translations update from Hosted Weblate (#8793) 2026-02-22 10:49:33 +02:00
green
31de1e007a Translated using Weblate (Japanese)
Currently translated at 100.0% (1809 of 1809 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-22 09:49:13 +01:00
Elian Doran
a2d0655a75 fix(deps): update dependency eslint-linter-browserify to v10.0.1 (#8788) 2026-02-22 10:49:01 +02:00
Elian Doran
269209dd31 fix(deps): update dependency mind-elixir to v5.9.0 (#8789) 2026-02-22 10:48:22 +02:00
Elian Doran
32082927b2 Translations update from Hosted Weblate (#8792) 2026-02-22 10:47:32 +02:00
Elian Doran
c10d0b6349 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-22 10:46:31 +02:00
Vedansh Bodkhe
11ff1266e5 Translated using Weblate (Hindi)
Currently translated at 4.9% (89 of 1809 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-02-22 04:49:25 +01:00
Marcel
8b5717ff7f Translated using Weblate (German)
Currently translated at 100.0% (1809 of 1809 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-22 04:49:25 +01:00
Marcel
e69f6bef2c Translated using Weblate (German)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/de/
2026-02-22 04:49:24 +01:00
renovate[bot]
2a17a322c2 fix(deps): update dependency mind-elixir to v5.9.0 2026-02-22 00:59:30 +00:00
renovate[bot]
26ddb76338 fix(deps): update dependency eslint-linter-browserify to v10.0.1 2026-02-22 00:58:07 +00:00
Elian Doran
f62f156c93 fix(editor): resolve strange stuck indent when a Note has images (#8520) 2026-02-22 00:01:07 +02:00
Elian Doran
f4b6c65bad fix(ckeditor5): cursor jumping on Tab for other widgets such as separators or footnote 2026-02-21 21:30:57 +02:00
Elian Doran
0a08399d9e feat(share-theme): align mobile/desktop UI with unified header (#8484) 2026-02-21 20:36:26 +02:00
Elian Doran
df1adaad3a Merge branch 'main' into share-theme-improvements 2026-02-21 20:12:37 +02:00
Elian Doran
33b1490de0 Fix week note title error when cross year (#8431) 2026-02-21 20:09:14 +02:00
Elian Doran
286720bdbf Improve no results for search (#8786) 2026-02-21 19:55:40 +02:00
Elian Doran
1441ac67ab chore(search): use translation for button 2026-02-21 19:21:09 +02:00
Elian Doran
d245fdd1b8 chore(search): reuse search command 2026-02-21 19:20:06 +02:00
Elian Doran
e9c9b456e0 feat(search): add button to search now 2026-02-21 19:15:42 +02:00
Elian Doran
d9eafff7df style(client): improve no items size and alignment 2026-02-21 19:02:29 +02:00
Elian Doran
94eb19d879 feat(search): center no results vertically 2026-02-21 18:59:13 +02:00
Elian Doran
d7b585230a fix(search): double scrollbars 2026-02-21 18:56:07 +02:00
Elian Doran
16ed14f85a feat(search): use NoItems for not executed & no results 2026-02-21 18:51:25 +02:00
Elian Doran
93e59cc3b6 fix(search): "no results" item not shown 2026-02-21 18:49:55 +02:00
Elian Doran
c38220892b Merge branch 'main' into fix_parens_orderby 2026-02-21 17:31:33 +02:00
Elian Doran
4ef72fb35b fix(breadcrumb): note title update (#8784) 2026-02-21 17:23:32 +02:00
Elian Doran
3f23ce095c Various bugfixes for the release (#8785) 2026-02-21 16:41:19 +02:00
JYC333
0ead246ce7 Merge branch 'main' into week-note 2026-02-21 14:10:19 +00:00
Elian Doran
0f8259fdf7 docs(user): align collection creation, hiding subtree & collection properties 2026-02-21 13:11:05 +02:00
Elian Doran
ebb180b0ab chore(hidden_subtree): disable hiding subtree by default for now 2026-02-21 12:45:24 +02:00
Elian Doran
c03f50583e chore(server): address requested changes 2026-02-21 12:33:30 +02:00
Elian Doran
b8a6cf0099 fix(calendar): wrong parent when creating day note from unhoisted workspace calendar root 2026-02-21 12:16:55 +02:00
Elian Doran
ad3cdc3364 chore(status_bar): fix inherited attribute key and useEffect deps 2026-02-21 12:00:02 +02:00
Elian Doran
d924d3fa4f fix(promoted_attributes): auto-complete interfering with value 2026-02-21 11:41:59 +02:00
Elian Doran
dd4a8c08c8 fix(promoted_attributes): multiple autocomplete instances 2026-02-21 11:34:04 +02:00
Vedansh Bodkhe
52f2e800e7 Update apps/client/src/widgets/react/hooks.tsx
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-21 15:01:29 +05:30
Vedansh Bodkhe
8d94b62db0 fix(breadcrumb): note title update 2026-02-21 14:53:06 +05:30
Elian Doran
c3eb483224 chore(promoted_attributes): unnecessary use of .entries() 2026-02-21 11:02:42 +02:00
Elian Doran
e7b1e0a81f fix(promoted_attributes): typing sometimes affects the value 2026-02-21 11:01:57 +02:00
Elian Doran
ca672c68a3 fix(export/zip): hidden note appears in tree (closes #8714) 2026-02-21 10:02:42 +02:00
Elian Doran
416e11a32b chore(share): write meta file for analysis 2026-02-21 09:57:05 +02:00
Elian Doran
bbaa2db377 docs: update SECURITY.MD (closes #8767) 2026-02-21 09:43:55 +02:00
Elian Doran
cf4e412ab8 fix(geomap): read-only map pin click broken (closes #8755) 2026-02-21 09:37:35 +02:00
Elian Doran
48ac88dbfe fix(geomap): collection properties overlapping with mobile backdrop 2026-02-21 09:16:29 +02:00
Elian Doran
4980e9a15b fix(desktop): window not draggable on legacy theme in vertical layout (closes #8402) 2026-02-21 09:10:00 +02:00
Elian Doran
290a2ab902 fix(deps): update dependency i18next to v25.8.13 (#8781) 2026-02-21 09:02:02 +02:00
Elian Doran
6a04d8fce1 chore(deps): update pnpm to v10.30.1 (#8779) 2026-02-21 09:01:47 +02:00
Elian Doran
7286ac743f chore(deps): update dependency eslint to v10.0.1 (#8778) 2026-02-21 08:57:32 +02:00
Elian Doran
41b6b5f615 fix(deps): update dependency @codemirror/view to v6.39.15 (#8780) 2026-02-21 08:57:17 +02:00
Elian Doran
d0572f7691 fix(deps): update dependency preact-render-to-string to v6.6.6 (#8782) 2026-02-21 08:57:01 +02:00
Elian Doran
a8210844cb chore(deps): update dependency happy-dom to v20.7.0 (#8783) 2026-02-21 08:56:43 +02:00
renovate[bot]
c5c39cedc4 chore(deps): update dependency happy-dom to v20.7.0 2026-02-21 01:40:07 +00:00
renovate[bot]
8d6ae2da82 fix(deps): update dependency preact-render-to-string to v6.6.6 2026-02-21 01:39:11 +00:00
renovate[bot]
9eea6b3645 fix(deps): update dependency i18next to v25.8.13 2026-02-21 01:38:19 +00:00
renovate[bot]
6d9638970d fix(deps): update dependency @codemirror/view to v6.39.15 2026-02-21 01:37:27 +00:00
renovate[bot]
ecd3638298 chore(deps): update pnpm to v10.30.1 2026-02-21 01:36:12 +00:00
renovate[bot]
bbde0e4d24 chore(deps): update dependency eslint to v10.0.1 2026-02-21 01:35:51 +00:00
Elian Doran
299f694aa9 chore: disable support notice for i18next
Signed-off-by: Elian Doran <contact@eliandoran.me>
2026-02-20 23:39:55 +02:00
Elian Doran
2675698d3a feat: enhance build-docs utility with pnpm and Nix support (#8574) 2026-02-20 23:13:05 +02:00
Elian Doran
3056f3cfcf fix(deps): update dependency preact to v10.28.4 (#8772) 2026-02-20 18:32:24 +02:00
Elian Doran
7310d8af30 Merge remote-tracking branch 'origin/main' into feat/remove-ai-llm 2026-02-20 15:31:34 +02:00
Elian Doran
b62f40f8c9 chore(server): format translations to avoid merge issues 2026-02-20 15:28:57 +02:00
Elian Doran
ae1aac02ae chore(client): format translations to avoid merge issues 2026-02-20 15:26:13 +02:00
Elian Doran
e19f344dc1 Translations update from Hosted Weblate (#8777) 2026-02-20 15:17:24 +02:00
손상모
1525ecdb05 Translated using Weblate (Korean)
Currently translated at 5.8% (105 of 1808 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ko/
2026-02-20 13:12:07 +00:00
손상모
4f6a13b2c4 Translated using Weblate (Korean)
Currently translated at 31.8% (124 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ko/
2026-02-20 13:12:06 +00:00
Ulices
4747297adc Translated using Weblate (Spanish)
Currently translated at 100.0% (1808 of 1808 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-20 13:12:05 +00:00
Hosted Weblate
f753974800 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-02-20 13:12:05 +00:00
Elian Doran
bd4002529c chore(deps): update dependency @anthropic-ai/sdk to v0.78.0 (#8773) 2026-02-20 15:11:38 +02:00
Elian Doran
00588eb099 chore(deps): update dependency electron to v40.6.0 (#8774) 2026-02-20 15:11:28 +02:00
Elian Doran
81420b6168 chore(deps): update dependency jsonc-eslint-parser to v3 (#8775) 2026-02-20 15:09:08 +02:00
renovate[bot]
fe74d2aec9 chore(deps): update dependency jsonc-eslint-parser to v3 2026-02-20 09:52:34 +00:00
renovate[bot]
6514701c6a chore(deps): update dependency electron to v40.6.0 2026-02-20 00:02:14 +00:00
renovate[bot]
9e206ce7ea chore(deps): update dependency @anthropic-ai/sdk to v0.78.0 2026-02-20 00:01:39 +00:00
renovate[bot]
b32ad68e4f fix(deps): update dependency preact to v10.28.4 2026-02-20 00:01:06 +00:00
Adorian Doran
dfb5322b1a ui/list view: fix a regression 2026-02-19 23:49:59 +02:00
Adorian Doran
47859d401e ui/grid view: refactor CSS 2026-02-19 22:46:01 +02:00
Elian Doran
5da90f9e16 chore(deps): update dependency pdfjs-dist to v5.4.624 (#8582) 2026-02-19 22:20:38 +02:00
Adorian Doran
4b098e2008 ui/list & grid view: correct attribute name 2026-02-19 22:20:18 +02:00
Adorian Doran
b4cd66db50 ui/grid view: fix some issues 2026-02-19 22:18:55 +02:00
Elian Doran
4847f4094a doc: enhanced icon pack creation (#8659) 2026-02-19 22:14:10 +02:00
Elian Doran
3fb34e6ae1 Merge branch 'main' into main 2026-02-19 22:13:57 +02:00
Elian Doran
135ed32374 feat(pdfjs): add WASM 2026-02-19 21:40:41 +02:00
Elian Doran
9b1fc77d56 fix(pdfjs): sidebar hiding no longer working 2026-02-19 21:30:48 +02:00
Elian Doran
5b751fd6e2 fix(pdfjs): custom scripts no longer embedded 2026-02-19 21:24:08 +02:00
Elian Doran
39ece711d9 chore(pdfjs): update to v5.4.624 2026-02-19 21:23:52 +02:00
Elian Doran
a77b5601be feat(pdfjs): add script to update viewer 2026-02-19 21:09:13 +02:00
Adorian Doran
8b5d88bf2b ui/grid view: fix CSS selector 2026-02-19 20:05:42 +02:00
Elian Doran
1f602f9cad always show Note Paths in Status Bar (#8623) 2026-02-19 20:03:22 +02:00
Adorian Doran
5588042ab6 ui/grid view: refactor CSS 2026-02-19 19:52:40 +02:00
Elian Doran
bc89bc8270 Further mobile improvements (#8769) 2026-02-19 19:42:24 +02:00
Adorian Doran
60ae094d3b ui/grid view: reveal the content using a fade in transition 2026-02-19 19:26:51 +02:00
Elian Doran
a9facc7ce2 chore(deps): update pnpm to v10.30.0 (#8754) 2026-02-19 19:14:51 +02:00
Adorian Doran
f200ac898a ui/grid view: add support for search view 2026-02-19 19:14:01 +02:00
Elian Doran
56ce25b8e8 fix(mobile): missing note icon for full-height notes 2026-02-19 19:13:31 +02:00
Elian Doran
4d5d9b4b70 fix(mobile): some buttons display a + from keyboard shortcut 2026-02-19 18:42:28 +02:00
Elian Doran
b97ab913bf fix(mobile/jump_to_note): full text search button clipped 2026-02-19 18:40:15 +02:00
Elian Doran
9fe7bdf79b fix(mobile/global_menu): tooltips not visible due to position 2026-02-19 18:35:32 +02:00
Elian Doran
4ebc4ece34 fix(mobile/tree): "toggle sidebar" tooltip shown after opening sidebar 2026-02-19 18:31:38 +02:00
Elian Doran
0110b3c4a2 fix(mobile/tree): too easy to accidentally close via drag gesture 2026-02-19 18:22:47 +02:00
Elian Doran
fb20d4998a fix(mobile/tree): partly hidden due to mobile viewport (closes #8740) 2026-02-19 18:19:41 +02:00
Elian Doran
bd06c530e4 feat(mobile/global_menu): add quick entry to recent changes 2026-02-19 18:14:21 +02:00
Elian Doran
cd4505bcf0 Translations update from Hosted Weblate (#8768) 2026-02-19 18:08:26 +02:00
renovate[bot]
173f4a23a5 chore(deps): update pnpm to v10.30.0 2026-02-19 16:08:03 +00:00
손상모
b33d4e64b9 Translated using Weblate (Korean)
Currently translated at 4.5% (83 of 1808 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ko/
2026-02-19 16:04:47 +00:00
Aindriú Mac Giolla Eoin
a4c95132bb Translated using Weblate (Irish)
Currently translated at 100.0% (1808 of 1808 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-19 16:04:47 +00:00
손상모
095e797a02 Translated using Weblate (Korean)
Currently translated at 26.7% (104 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ko/
2026-02-19 16:04:46 +00:00
손상모
aaec171d94 Translated using Weblate (Korean)
Currently translated at 96.8% (153 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2026-02-19 16:04:45 +00:00
Hosted Weblate
42b28993b2 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-02-19 16:04:44 +00:00
Elian Doran
09a39379ef fix(deps): update dependency marked to v17.0.3 (#8751) 2026-02-19 18:03:58 +02:00
Elian Doran
29f7370c72 chore(deps): update dependency @prefresh/vite to v2.4.12 (#8758) 2026-02-19 18:03:48 +02:00
Elian Doran
a07dcef2c4 chore(deps): update dependency happy-dom to v20.6.3 (#8759) 2026-02-19 18:03:38 +02:00
Elian Doran
6e696e1123 chore(deps): update dependency sanitize-html to v2.17.1 (#8760) 2026-02-19 18:03:27 +02:00
Elian Doran
f0b1aa914d fix(deps): update dependency i18next to v25.8.11 (#8761) 2026-02-19 18:03:11 +02:00
Elian Doran
ce1ae2c9bd fix(deps): update dependency mind-elixir to v5.8.3 (#8762) 2026-02-19 18:02:59 +02:00
Elian Doran
29597ddb48 chore(deps): update dependency @anthropic-ai/sdk to v0.77.0 (#8763) 2026-02-19 18:02:49 +02:00
Elian Doran
1c1421fe3a chore(deps): update dependency @redocly/cli to v2.19.1 (#8764) 2026-02-19 18:02:37 +02:00
Elian Doran
918a52005e chore(deps): update dependency eslint-plugin-playwright to v2.7.0 (#8765) 2026-02-19 18:02:23 +02:00
renovate[bot]
a3564b879f chore(deps): update dependency @redocly/cli to v2.19.1 2026-02-19 13:47:07 +00:00
renovate[bot]
b78f3bbaa7 chore(deps): update dependency eslint-plugin-playwright to v2.7.0 2026-02-19 01:14:05 +00:00
renovate[bot]
4cbdb5a073 chore(deps): update dependency @anthropic-ai/sdk to v0.77.0 2026-02-19 01:12:04 +00:00
renovate[bot]
758021e10c fix(deps): update dependency mind-elixir to v5.8.3 2026-02-19 01:11:03 +00:00
renovate[bot]
374e3560c3 fix(deps): update dependency i18next to v25.8.11 2026-02-19 01:10:07 +00:00
renovate[bot]
57a3524cc3 chore(deps): update dependency sanitize-html to v2.17.1 2026-02-19 01:09:10 +00:00
renovate[bot]
9af4773b45 chore(deps): update dependency happy-dom to v20.6.3 2026-02-19 01:08:16 +00:00
renovate[bot]
6942773965 chore(deps): update dependency @prefresh/vite to v2.4.12 2026-02-19 01:07:27 +00:00
Elian Doran
734b3ee519 Translations update from Hosted Weblate (#8757) 2026-02-18 22:37:17 +02:00
green
1b80fa8df2 Translated using Weblate (Japanese)
Currently translated at 100.0% (1808 of 1808 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-18 15:09:46 +01:00
renovate[bot]
afd6a05e0f fix(deps): update dependency marked to v17.0.3 2026-02-18 10:55:09 +00:00
Elian Doran
5a4bd1933d chore(deps): update dependency @smithy/middleware-retry to v4.4.33 (#8749) 2026-02-18 12:54:32 +02:00
Elian Doran
a25dfce410 chore(deps): update dependency happy-dom to v20.6.2 (#8750) 2026-02-18 12:54:03 +02:00
Elian Doran
fc0490bc6a chore(deps): update dependency @anthropic-ai/sdk to v0.76.0 (#8753) 2026-02-18 12:53:23 +02:00
Elian Doran
29cdd1d028 fix(deps): update dependency mermaid to v11.12.3 (#8752) 2026-02-18 12:52:33 +02:00
Adorian Doran
419e87b0de ui/grid view: add support for colored notes 2026-02-18 10:29:41 +02:00
Adorian Doran
75c7d55e01 ui/grid view: tweak style 2026-02-18 10:08:50 +02:00
Adorian Doran
e006550b9f ui/grid view: add a menu button for grid items 2026-02-18 09:51:18 +02:00
Adorian Doran
2a424f8dc4 ui/grid view: refactor 2026-02-18 09:31:03 +02:00
Adorian Doran
5036518458 ui/grid view: hide attributes 2026-02-18 09:20:00 +02:00
Adorian Doran
297cbbd8b3 ui/grid view: reuse some styles from list view 2026-02-18 09:15:38 +02:00
renovate[bot]
87a89f709a chore(deps): update dependency @anthropic-ai/sdk to v0.76.0 2026-02-18 05:27:47 +00:00
renovate[bot]
6f1991e20e fix(deps): update dependency mermaid to v11.12.3 2026-02-18 02:48:10 +00:00
renovate[bot]
700de58686 chore(deps): update dependency happy-dom to v20.6.2 2026-02-18 02:46:41 +00:00
renovate[bot]
57e1208c27 chore(deps): update dependency @smithy/middleware-retry to v4.4.33 2026-02-18 02:45:47 +00:00
contributor
b33fe2ca3a always show Note paths in Status Bar
useful for a opened from search tree (no other way to find out a note path)
2026-02-17 23:41:33 +02:00
Elian Doran
3c3e73edae Various bugfixes for v0.102.0 (#8748) 2026-02-17 23:40:02 +02:00
Elian Doran
ec4fd371b5 fix(webview): doesn't activate note context on click in desktop 2026-02-17 23:01:35 +02:00
Elian Doran
555b138a90 fix(webview): doesn't activate note context on click in browser 2026-02-17 22:53:48 +02:00
Elian Doran
c139ff776c chore(render): address requested changes 2026-02-17 22:24:42 +02:00
Elian Doran
0c6b5e3b8e chore(deps): update typescript-eslint monorepo to v8.56.0 (#8744) 2026-02-17 21:18:31 +02:00
renovate[bot]
1138f9a0e9 chore(deps): update typescript-eslint monorepo to v8.56.0 2026-02-17 18:55:01 +00:00
renovate[bot]
4955bd782b chore(deps): update dependency pdfjs-dist to v5.4.624 2026-02-17 18:52:59 +00:00
Adorian Doran
516f0052ef ui/grid view: use flex gap instead of margins for cards 2026-02-17 20:36:01 +02:00
Elian Doran
f4e82acc67 feat(render): add error boundary 2026-02-17 19:46:46 +02:00
Elian Doran
a5806c0d1d feat(render): intercept bundle errors 2026-02-17 19:22:12 +02:00
Elian Doran
bf302a84a9 feat(content_renderer): display render errors 2026-02-17 19:15:43 +02:00
Elian Doran
fcc740d592 fix(render): toast when execution fails 2026-02-17 19:11:33 +02:00
Elian Doran
cee16dc3dc feat(render): display syntax errors inside render note 2026-02-17 19:04:14 +02:00
Elian Doran
47601cd1da fix(active_content_badges): not refreshing on type or mime update 2026-02-17 18:55:28 +02:00
Elian Doran
092a60fdd9 fix(codemirror): missing Node globals from linter 2026-02-17 18:50:52 +02:00
Elian Doran
e8e7568bdc fix(breadcrumb): text overflow when width is constrained 2026-02-17 18:48:49 +02:00
Elian Doran
4caca56e3b fix(code): scrolling to end changes the viewport 2026-02-17 18:33:10 +02:00
Elian Doran
e4432e6feb fix(code): scrolling to end triggers on all splits 2026-02-17 18:32:00 +02:00
Elian Doran
d8275e7ea8 fix(server): in-app help not working in dev mode 2026-02-17 18:24:25 +02:00
Elian Doran
952dc634b4 fix(launch_bar): wrong tooltip in sync status (closes #8266) 2026-02-17 18:24:10 +02:00
Elian Doran
7dceca475d Translations update from Hosted Weblate (#8746) 2026-02-17 15:57:15 +02:00
Hosted Weblate
62a78bc272 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-02-17 14:02:25 +01:00
Adorian Doran
6aaf277b45 UI: improved pager (#8730) 2026-02-17 15:02:11 +02:00
Adorian Doran
a1c61768c4 Merge branch 'main' into feat/ui/improved-pager 2026-02-17 15:01:52 +02:00
Adorian Doran
8e4c88c10c ui/pager: add support for custom alignment 2026-02-17 14:59:21 +02:00
Adorian Doran
b57780519c ui/pager: fix attribute names 2026-02-17 14:55:43 +02:00
Adorian Doran
c07c4013be ui/pager: update colors 2026-02-17 14:55:27 +02:00
Elian Doran
768211cc26 fix(desktop): web view note in second window does not load content (#8739) 2026-02-17 13:22:33 +02:00
Elian Doran
b4a31503ee chore(deps): update dependency @redocly/cli to v2.18.2 (#8741) 2026-02-17 13:20:41 +02:00
Elian Doran
6c2b3e238c fix(deps): update dependency @preact/signals to v2.8.1 (#8742) 2026-02-17 13:20:16 +02:00
Elian Doran
f2ff834b9c fix(deps): update dependency mind-elixir to v5.8.2 (#8743) 2026-02-17 13:19:55 +02:00
Elian Doran
86e1688358 Translations update from Hosted Weblate (#8745) 2026-02-17 13:19:10 +02:00
Adorian Doran
d259eb1f9d ui/icon buttons: decrease opacity for disabled buttons 2026-02-17 12:46:57 +02:00
Adorian Doran
568a668e9f ui/pager: improve layout 2026-02-17 12:42:55 +02:00
Adorian Doran
0621dfbac7 ui/pager: extract colors as CSS variables 2026-02-17 12:12:34 +02:00
Adorian Doran
827bb9a32f ui/pager: tweak appearance 2026-02-17 12:07:51 +02:00
Stas Kies
fd08654dd5 Translated using Weblate (German)
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-17 10:28:43 +01:00
Stas Kies
e5d750722e Translated using Weblate (German)
Currently translated at 99.5% (1798 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-17 10:28:43 +01:00
renovate[bot]
b43c4ad666 fix(deps): update dependency mind-elixir to v5.8.2 2026-02-17 01:43:23 +00:00
renovate[bot]
c9f93a4706 fix(deps): update dependency @preact/signals to v2.8.1 2026-02-17 01:42:30 +00:00
renovate[bot]
a38834f7e2 chore(deps): update dependency @redocly/cli to v2.18.2 2026-02-17 01:41:41 +00:00
Adorian Doran
fcfc6f6476 ui/pager: improve code 2026-02-17 01:14:14 +02:00
contributor
b23a5348b5 fix(desktop): web view note in second window does not load content 2026-02-17 00:47:33 +02:00
Adorian Doran
782cd59407 ui/pager: improve layout 2026-02-16 21:46:16 +02:00
Adorian Doran
7964bd3be4 ui/pager: make responsive 2026-02-16 21:31:03 +02:00
Adorian Doran
3d41ce13b1 ui/pager: restyle the note counter 2026-02-16 20:48:51 +02:00
Adorian Doran
6f0881ab8a ui/pager: improve layout 2026-02-16 20:39:33 +02:00
Adorian Doran
a6309715f3 ui/pager: refactor 2026-02-16 20:24:17 +02:00
Adorian Doran
5a06193e65 ui/pager: restyle 2026-02-16 20:11:11 +02:00
Adorian Doran
4912834537 ui/buttons: refactor 2026-02-16 18:23:28 +02:00
Adorian Doran
d64f2c72b7 ui/buttons: add support for low profile style 2026-02-16 18:15:29 +02:00
Elian Doran
358d06a402 Translations update from Hosted Weblate (#8731) 2026-02-16 18:06:57 +02:00
Elian Doran
5deabe4260 fix(deps): update dependency mind-elixir to v5.8.1 (#8733) 2026-02-16 18:06:16 +02:00
Giovi
1b1162e26e Translated using Weblate (Italian)
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2026-02-16 17:04:44 +01:00
Francis C.
30ad5d531c Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-02-16 17:04:43 +01:00
Toto Yullian
99efc73e93 Translated using Weblate (Indonesian)
Currently translated at 3.9% (72 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/id/
2026-02-16 17:04:42 +01:00
Toto Yullian
17c0071dd1 Translated using Weblate (Indonesian)
Currently translated at 44.9% (71 of 158 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/id/
2026-02-16 17:04:42 +01:00
Francis C.
a26bec047f Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-02-16 17:04:41 +01:00
Stas Kies
748e7cc1df Translated using Weblate (German)
Currently translated at 99.1% (1792 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-02-16 17:04:40 +01:00
green
f653c86965 Translated using Weblate (Japanese)
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-16 17:04:39 +01:00
Toto Yullian
58a41e58ee Translated using Weblate (Indonesian)
Currently translated at 71.5% (83 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/id/
2026-02-16 17:04:38 +01:00
Aindriú Mac Giolla Eoin
309a55f276 Translated using Weblate (Irish)
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-16 17:04:38 +01:00
green
df96c6b9fa Translated using Weblate (Japanese)
Currently translated at 99.3% (1796 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-02-16 17:04:37 +01:00
Elian Doran
d779e078c7 fix(deps): update dependency i18next to v25.8.10 (#8732) 2026-02-16 18:04:12 +02:00
renovate[bot]
ae224151a0 fix(deps): update dependency i18next to v25.8.10 2026-02-16 12:47:46 +00:00
renovate[bot]
9f11717b69 fix(deps): update dependency mind-elixir to v5.8.1 2026-02-16 02:36:40 +00:00
Adorian Doran
ea25477e3d ui/pager: fix some issues 2026-02-16 02:06:39 +02:00
Adorian Doran
82576c9703 ui/pager: rewrite the pagination button distribution algorithm to better control the length 2026-02-16 01:51:32 +02:00
Elian Doran
80f1fc4c7c Translations update from Hosted Weblate (#8726) 2026-02-15 22:10:14 +02:00
Aindriú Mac Giolla Eoin
41cd4bcef6 Translated using Weblate (Irish)
Currently translated at 100.0% (1807 of 1807 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ga/
2026-02-15 20:49:02 +01:00
Ulices
46a414e155 Translated using Weblate (Spanish)
Currently translated at 100.0% (1806 of 1806 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/es/
2026-02-15 20:49:01 +01:00
Hosted Weblate
70a903f811 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2026-02-15 20:49:00 +01:00
Elian Doran
52db410c82 fix(collections/map): shifting into collection properties when switching styles 2026-02-15 21:48:46 +02:00
Elian Doran
08da8d73e0 fix(collections): collection properties still clipped in list/grid view 2026-02-15 21:48:39 +02:00
Elian Doran
81a572af25 feat(collections): improve display of no children 2026-02-15 21:48:33 +02:00
Elian Doran
23c50f34fe fix(collections): list/grid collection properties clipped when little results 2026-02-15 21:48:29 +02:00
Elian Doran
70aa115933 fix(collections): collection properties not shown if there are no children 2026-02-15 21:48:21 +02:00
Adorian Doran
0b8cba78d5 ui/pager: remove unused styles 2026-02-15 21:36:53 +02:00
Adorian Doran
d8806eaa04 ui/pager: replace the page number links with buttons 2026-02-15 21:31:58 +02:00
Adorian Doran
b8bc85856b ui/pager: add prev/next page navigation buttons 2026-02-15 21:12:42 +02:00
Adorian Doran
cc097c5414 UI: improved cards (#8728) 2026-02-15 20:32:29 +02:00
Adorian Doran
7551d0e044 ui/cards: fix attribute name 2026-02-15 20:32:01 +02:00
Adorian Doran
489a88b8da ui/cards: refactor 2026-02-15 20:23:19 +02:00
Adorian Doran
48013dc264 ui/cards: use a better HTML structure 2026-02-15 20:14:14 +02:00
Adorian Doran
4ee9d45dfc ui/cards: implement card titles (heading) 2026-02-15 20:06:01 +02:00
Adorian Doran
e00150a876 ui/cards: do not include nesting level CSS variables if not necessary 2026-02-15 19:46:08 +02:00
Adorian Doran
71668f8f8d List collections & search results: overhaul the list UI (#8705) 2026-02-15 19:32:21 +02:00
Adorian Doran
b71424d239 Merge branch 'main' into feat/list-view-overhaul 2026-02-15 19:20:41 +02:00
Adorian Doran
d1a3bceaa6 client/list view: refactor 2026-02-15 19:15:58 +02:00
Adorian Doran
483c57029a client/list view: extract event handler 2026-02-15 19:10:31 +02:00
Adorian Doran
7e6daf5b36 style/list view: refactor 2026-02-15 18:55:23 +02:00
Adorian Doran
b658253687 style/card: refactor 2026-02-15 18:55:04 +02:00
Adorian Doran
80b488deec style/list view: tweak indentation size 2026-02-15 18:34:54 +02:00
Adorian Doran
10cf1a371e style/card: improve 2026-02-15 18:34:00 +02:00
Adorian Doran
f645d9d721 style/list view: tweak 2026-02-15 18:20:45 +02:00
Adorian Doran
900bfdff9d style/list view: refactor 2026-02-15 18:18:50 +02:00
Adorian Doran
108ca5afb5 client/list view: add support for archived items 2026-02-15 18:16:46 +02:00
Adorian Doran
3df03a551c client/list view: add support for archived items 2026-02-15 18:15:07 +02:00
Adorian Doran
6ffbe19667 client/list view: add support for tinted notes 2026-02-15 18:07:35 +02:00
Adorian Doran
fea8de89c6 style/list view: handle the title and attributes when overflowing the container 2026-02-15 14:08:50 +02:00
Adorian Doran
4afbabb977 style/list view: tweak 2026-02-15 13:51:13 +02:00
Adorian Doran
b2ebaf111f style/card: add legacy theme fallback 2026-02-15 13:43:06 +02:00
Adorian Doran
9c2b01e3c9 style/list view: tweak 2026-02-15 13:34:45 +02:00
Adorian Doran
dca201ce42 style/list view: tweak 2026-02-15 13:31:45 +02:00
Adorian Doran
3774ea3768 style/list view: tweak 2026-02-15 02:28:52 +02:00
Adorian Doran
0a9c6a3119 client/list view: add a menu button for list items 2026-02-15 02:26:23 +02:00
Adorian Doran
a6cbde88bb client/list view: add an alternate style for search results 2026-02-15 01:57:10 +02:00
Adorian Doran
9c13f36ca0 client/list view: show the contents of a note only after its rendering completes 2026-02-14 16:39:20 +02:00
Adorian Doran
fe4a11c5ad client/list view: improve appearance 2026-02-14 16:22:13 +02:00
misch334
e29b238161 fix orderby in combination with parentheses / increased expression level 2026-02-14 14:50:10 +01:00
Adorian Doran
218343ca14 client/list view: use a sectioned card to display the list items 2026-02-14 01:14:54 +02:00
Adorian Doran
61953fd713 client/ui/cards: make a property optional 2026-02-14 00:10:54 +02:00
Adorian Doran
62ddf3a11b client/ui/cards: automatically determine the opacity of the background color of nested card sections 2026-02-14 00:08:59 +02:00
Adorian Doran
be12658864 client/ui/cards: add support for custom class names and action callbacks 2026-02-14 00:08:00 +02:00
Adorian Doran
b618e5a00f client: define the foundation of sectioned cards with multi-level nesting 2026-02-13 22:50:03 +02:00
misch334
4c36a9cca9 add client and compound launch entry 2026-02-12 16:11:21 +01:00
perfectra1n
f3d415a3a7 merge main into branch 2026-02-11 10:01:49 -08:00
misch334
fd8ab990bd add autoAttachChildProcesses to launch server config 2026-02-11 07:25:58 +01:00
misch334
5c007cdebc adjust vi launch.json to be based on vitest.dev/guide/debugging 2026-02-10 18:45:56 +01:00
misch334
ef6c733f93 add options recommended by tsx.is docs 2026-02-10 18:15:51 +01:00
misch334
e7d6658c7a add generic launch.json for debugging/dap 2026-02-10 17:34:00 +01:00
Elian Doran
9350c43e5b chore(core): port bulk actions route 2026-02-09 19:49:07 +02:00
Elian Doran
0fae11d54c chore(core): port bulk actions service 2026-02-09 19:46:34 +02:00
Elian Doran
1ed3999639 chore(core): port recent changes route 2026-02-09 19:43:53 +02:00
Elian Doran
7d30771f05 chore(core): port relation map route 2026-02-09 19:41:31 +02:00
Elian Doran
08f1d44d90 chore(core): port revisions route 2026-02-09 19:38:24 +02:00
Elian Doran
969860c344 chore(core): port attribute route 2026-02-09 19:32:46 +02:00
Elian Doran
ed905c9d64 chore(core): integrate builtin_attributes 2026-02-09 19:29:59 +02:00
Elian Doran
c5518b64b7 chore(core): integrate attribute_formatter 2026-02-09 19:24:06 +02:00
Elian Doran
a7b2b631c5 feat(standalone): add warning about stability 2026-02-09 18:59:44 +02:00
Elian Doran
dcfc1119eb chore(core): port sql route 2026-02-09 18:38:51 +02:00
Elian Doran
88add55ebc chore(standalone): wrap routes in a transaction 2026-02-09 18:35:29 +02:00
Elian Doran
ad41a58904 chore(standalone): use CLS with per-request context isolation 2026-02-09 18:20:14 +02:00
Elian Doran
49ce312ab2 chore(standalone): use a simpler CLS mechanism considering lack of multi-threading 2026-02-09 18:16:15 +02:00
Elian Doran
223d69206c fix(standalone): missing context menu cover 2026-02-09 18:00:11 +02:00
Elian Doran
d68ada1026 fix(standalone): translations not working in prod 2026-02-08 22:38:28 +02:00
Elian Doran
e0a23f6b63 fix(bootstrap): background effects are enabled 2026-02-08 21:30:19 +02:00
Elian Doran
bd147ea72e Merge remote-tracking branch 'origin/main' into standalone 2026-02-08 21:14:12 +02:00
hulmgulm
639b81b228 Update docs/User Guide/User Guide/Theme development/Creating an icon pack.md
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-08 10:46:02 +01:00
hulmgulm
0c552eb0c0 Update apps/server/src/assets/doc_notes/en/User Guide/User Guide/Theme development/Creating an icon pack.html
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-02-08 10:45:48 +01:00
hulmgulm
ae723f9557 Merge branch 'main' of https://github.com/hulmgulm/Trilium 2026-02-08 10:42:00 +01:00
hulmgulm
34d691341b Fixed export 2026-02-08 10:41:57 +01:00
hulmgulm
8574a11cf7 Merge branch 'TriliumNext:main' into main 2026-02-08 10:29:59 +01:00
hulmgulm
3742cd4a51 doc: enhanced icon pack creation 2026-02-08 10:29:43 +01:00
perfectra1n
d7d411caf9 feat(dev): remove ai/llm features 2026-02-07 13:04:37 -08:00
Jin
19781bb14c Merge branch 'main' into week-note 2026-02-01 14:29:45 +00:00
Jin
7fd2bc30cc test: add test for week number with different first day of week setting 2026-02-01 14:26:11 +00:00
Wael Nasreddine
07f273fda4 feat: enhance build-docs utility with pnpm and Nix support
This enhances the `build-docs` utility to allow running it via pnpm and
packaging it as a Nix application. Changes include:

- Added `build` and `cli` scripts to `apps/build-docs/package.json`.
- Implemented a standalone CLI wrapper for documentation generation.
- Added a `trilium-build-docs` package to the Nix flake for use in other projects.
- Integrated `apps/build-docs` into the Nix workspace and build pipeline.

These changes make the documentation build process more portable and easier
to integrate into CI/CD pipelines outside of the main repository.
2026-02-01 00:32:51 -08:00
Elian Doran
4494aed1cf chore(standalone): use async for init 2026-01-30 15:55:20 +02:00
Elian Doran
788eaad61c fix(standalone): wrong server translation path in production 2026-01-30 15:49:32 +02:00
Elian Doran
0cfd6bae0e refactor(standalone): use different mechanism for importing local server worker 2026-01-30 15:24:53 +02:00
Wael Nasreddine
b08126ec2b feat(share-theme): add borders to tables matching the dark/light theme 2026-01-30 02:13:38 -08:00
Wael Nasreddine
e0824a5426 feat(share-theme): Increase the max-body to 1600px 2026-01-30 02:13:12 -08:00
Wael Nasreddine
c4f5471e13 feat(share-theme): align mobile/desktop UI with unified header 2026-01-30 02:13:02 -08:00
Wael Nasreddine
99215c272c feat(share-theme): set minimum width for table cells to improve readability 2026-01-30 01:15:14 -08:00
Elian Doran
82c435b916 chore(ci): deploy app on workflow change 2026-01-30 07:55:21 +02:00
Elian Doran
bc5b9708c7 Merge remote-tracking branch 'origin/main' into standalone 2026-01-30 07:51:36 +02:00
Elian Doran
7e87e6f832 chore(ci): deploy app on standalone branch 2026-01-30 07:48:11 +02:00
Elian Doran
e5a7a32439 chore(core): port cloning route 2026-01-29 22:20:54 +02:00
Elian Doran
e9214d84b7 chore(core): port stats route 2026-01-29 21:51:47 +02:00
Elian Doran
da7a61a8b6 Merge remote-tracking branch 'origin/main' into HEAD
; Conflicts:
;	apps/client/src/index.ts
;	apps/client/src/widgets/sql_table_schemas.tsx
;	apps/server/package.json
;	apps/server/src/app.ts
;	apps/server/src/becca/entities/bnote.ts
;	apps/server/src/services/import/single.ts
;	apps/server/src/services/import/zip.ts
;	apps/server/src/services/note-interface.ts
;	apps/server/src/services/notes.ts
;	apps/server/src/services/tree.ts
;	apps/server/src/services/utils.ts
;	apps/server/src/share/shaca/entities/snote.ts
;	pnpm-lock.yaml
;	scripts/update-nightly-version.ts
;	scripts/update-version.ts
2026-01-29 21:47:06 +02:00
perfectra1n
51325e2f4a fix(editor): resolve strange stuck indent when a Note has images 2026-01-26 17:03:42 -08:00
Jin
bd7e47d8f0 refactor: clear calendar code 2026-01-19 21:15:18 +00:00
Jin
457a7a03fb test: add test for week notes utils 2026-01-19 21:01:07 +00:00
Jin
6e486c64f1 fix: move calendar to use common week note utils 2026-01-19 20:59:15 +00:00
Jin
4f3575d765 fix: move date_notes to use common week note utils 2026-01-19 20:56:52 +00:00
Jin
f5f1b27754 fix: add common week note utils 2026-01-19 20:56:12 +00:00
Elian Doran
458e858b24 fix(standalone): error due to SQL returning bigint 2026-01-17 20:01:46 +02:00
Elian Doran
ec84e72b4c Lightweight/browser api (#8287) 2026-01-14 18:30:05 +02:00
Elian Doran
64a8c3b005 chore(client-standalone): address requested changes 2026-01-14 18:27:53 +02:00
Elian Doran
0b5cf2e6c8 Merge remote-tracking branch 'origin/standalone' into lightweight/browser_api 2026-01-14 18:04:54 +02:00
Elian Doran
7ed4e1c284 Lightweight/decouple server api (#8284) 2026-01-14 18:01:54 +02:00
Elian Doran
9dd7616f7d chore(client-standalone): address requested changes 2026-01-14 18:00:10 +02:00
Elian Doran
ab29caff7b fix(client-standalone): CK premium features not working 2026-01-14 17:48:29 +02:00
Elian Doran
7633e3d48e chore(client-standalone): address requested changes 2026-01-14 17:41:24 +02:00
Elian Doran
411fdf3114 chore(client-standalone): disable WS error notification 2026-01-14 17:33:57 +02:00
Elian Doran
5c52917459 fix(client-standalone): webmanifest icon path not correct 2026-01-14 17:31:06 +02:00
Elian Doran
51753ad82a chore(ci): run tests on standalone branch as well 2026-01-12 21:51:26 +02:00
Elian Doran
7e00634f3d chore(deps): align package lock 2026-01-12 21:44:25 +02:00
Elian Doran
daf41804d4 chore(core): address requested changes 2026-01-12 21:43:57 +02:00
Elian Doran
43d087f886 chore(deps): update lock file 2026-01-12 21:32:06 +02:00
Elian Doran
503a6e520d Merge remote-tracking branch 'origin/main' into lightweight/decouple_server_api 2026-01-12 21:31:32 +02:00
Elian Doran
52610a7410 fix(client-standalone): missing manifest 2026-01-12 21:06:00 +02:00
Elian Doran
c7edb71fed fix(client-standalone): missing favicon 2026-01-12 21:05:21 +02:00
Elian Doran
83db37ed31 fix(server): app-info not showing data dir 2026-01-12 21:03:55 +02:00
Elian Doran
0d1c8ae01e fix(server): login not working due to bad import to i18n 2026-01-12 20:55:32 +02:00
Elian Doran
92f71e100f chore(core): integrate app_info route 2026-01-12 20:54:18 +02:00
Elian Doran
659573b864 fix(client-standalone): update version to match 2026-01-12 20:50:12 +02:00
Elian Doran
e1c798561b fix(client-standalone): user guide not working 2026-01-12 20:46:08 +02:00
Elian Doran
0c52b56e02 chore(core): integrate branches service and route 2026-01-12 19:25:45 +02:00
Elian Doran
f9731d9cfc chore(text): re-enable emojis 2026-01-12 19:00:35 +02:00
Elian Doran
7547371ba0 feat(client-standalone): proper integration of server-side locale 2026-01-12 18:44:48 +02:00
Elian Doran
84e1d45d2a fix(client-standalone): print not working 2026-01-11 23:05:27 +02:00
Elian Doran
364c9cda27 chore(client-standalone): reduce verbosity in logs for requests 2026-01-11 23:05:26 +02:00
Elian Doran
af944c29a8 feat(client-standalone): support more globals 2026-01-11 23:04:53 +02:00
Elian Doran
45577f1585 feat(client-standalone): better device detection 2026-01-11 23:04:53 +02:00
Elian Doran
1648c67467 feat(client-standalone): initialize server-side translations 2026-01-11 22:46:47 +02:00
Elian Doran
882793e794 chore(client-standalone): basic support for mobile 2026-01-11 18:29:47 +02:00
Elian Doran
4a4a7d79c2 chore(client-standalone): integrate faster preact 2026-01-11 17:52:56 +02:00
Elian Doran
a955eb80da chore(client-standalone): integrate main client script 2026-01-11 17:34:25 +02:00
Elian Doran
cd64a1ee18 chore(client-standalone): fix noscript 2026-01-11 17:31:15 +02:00
Elian Doran
9894d4256c chore(deps): update lock 2026-01-11 17:31:07 +02:00
Elian Doran
3b5f1dabd6 Merge remote-tracking branch 'origin/lightweight/decouple_server_api' into lightweight/browser_api 2026-01-11 17:21:37 +02:00
Elian Doran
750fa2e647 Merge remote-tracking branch 'origin/main' into lightweight/decouple_server_api 2026-01-11 17:15:35 +02:00
Elian Doran
4f0021e44e Merge remote-tracking branch 'origin/main' into lightweight/browser_api
; Conflicts:
;	apps/client/src/widgets/layout/StatusBar.tsx
2026-01-07 19:41:51 +02:00
Elian Doran
2546e4c0dc fix(client): server worker in client 2026-01-07 18:29:00 +02:00
Elian Doran
eac5dbb210 chore(client-standalone): async-proxy missing in prod 2026-01-07 17:58:12 +02:00
Elian Doran
8b6da981f7 chore(client-standalone): try to use plain header file 2026-01-07 17:49:25 +02:00
Elian Doran
7433ca069f chore(client-standalone): wrong file name to CORS 2026-01-07 17:35:37 +02:00
Elian Doran
128049b672 chore(core): integrate icon usage API 2026-01-07 17:33:44 +02:00
Elian Doran
0eb3cb1118 feat(client-standalone): proper startup without requiring refresh 2026-01-07 17:19:52 +02:00
Elian Doran
8fc28716a7 feat(client-standalone): set up CORS for Cloudflare Pages 2026-01-07 17:14:31 +02:00
Elian Doran
af346f455a fix(client-standalone): version check was broken 2026-01-07 16:53:37 +02:00
Elian Doran
3e5a6c1e51 chore(client-standalone): fake two more routes 2026-01-07 16:43:17 +02:00
Elian Doran
9e3b4435cd fix(client): request to recent changes for undefined note 2026-01-07 16:43:11 +02:00
Elian Doran
3a793a3549 chore(client-standalone): fake two more routes 2026-01-07 16:41:19 +02:00
Elian Doran
4f139552f4 chore(core): integrate recent-notes 2026-01-07 16:41:08 +02:00
Elian Doran
13f25e9fed chore(client-standalone): integrate note map backlink count 2026-01-07 16:36:49 +02:00
Elian Doran
91db73703b chore(client-standalone): add two dummy routes 2026-01-07 16:32:29 +02:00
Elian Doran
d690985b58 fix(client): SQL schemas loaded even when not needed 2026-01-07 16:27:48 +02:00
Elian Doran
b5bcf73531 chore(client-standalone): bring back window.global 2026-01-07 16:21:35 +02:00
Elian Doran
2e905c8292 fix(deps): lock file out of sync 2026-01-07 16:06:15 +02:00
Elian Doran
4374c92032 feat(ci): add deployment script for standalone client 2026-01-07 16:04:04 +02:00
Elian Doran
edde0d0f90 fix(client-standalone): get it to start in prod 2026-01-07 15:50:34 +02:00
Elian Doran
32c39384ff fix(client-standalone): missing entry point for sw, local-bridge, local-server-worker 2026-01-07 15:20:59 +02:00
Elian Doran
807ab4be8c fix(client-standalone): build missing .wasm 2026-01-07 15:16:38 +02:00
Elian Doran
4da20f4829 fix(client-standalone): some assets could not be loaded 2026-01-07 15:11:01 +02:00
Elian Doran
cb5b491633 fix(client-standalone): get client scripts to run 2026-01-07 14:42:02 +02:00
Elian Doran
e76c33c37a chore(client-standalone): relocate index file to root 2026-01-07 14:34:41 +02:00
Elian Doran
89fc89603e chore(client-standalone): set up live reload for assets 2026-01-07 14:30:29 +02:00
Elian Doran
c0bf294457 chore(client-standalone): basic integration for assets 2026-01-07 14:29:23 +02:00
Elian Doran
24e076cacf chore(client-standalone): integrate new files from client 2026-01-07 14:22:04 +02:00
Elian Doran
1e381b13ca chore(client-standalone): create empty project 2026-01-07 14:14:52 +02:00
Elian Doran
f83121ce1d chore(core): integrate attachments route 2026-01-07 13:48:59 +02:00
Elian Doran
b32480f1d3 feat(client/lightweight): basic WS support 2026-01-07 13:42:42 +02:00
Elian Doran
d4468bd97b feat(client/lightweight): basic OPFS support for persistence 2026-01-07 13:27:17 +02:00
Elian Doran
e8711d7cd5 fix(client/lightweight): not handling returning backend entities 2026-01-07 13:04:24 +02:00
Elian Doran
35f4d2aaad chore(client/lightweight): improve route error handling 2026-01-07 12:55:58 +02:00
Elian Doran
b1f3fe5345 fix(client/lightweight): saving not working 2026-01-07 12:53:07 +02:00
Elian Doran
9f1b0ac449 fix(client/lightweight): saved statements causing issues 2026-01-07 12:41:08 +02:00
Elian Doran
a84e804fc3 fix(client/lightweight): CLS not available in routes 2026-01-07 12:37:29 +02:00
Elian Doran
3371a31c70 fix(client/lightweight): crypto hash not working 2026-01-07 12:32:45 +02:00
Elian Doran
724af8e103 fix(client/lightweight): statements with parameters not working 2026-01-07 12:21:27 +02:00
Elian Doran
c5803a2650 fix(client/lightweight): missing pluck implementation 2026-01-07 12:16:09 +02:00
Elian Doran
baf18835be fix(client/lightweight): SQL nested transactions not supported 2026-01-07 12:14:30 +02:00
Elian Doran
3d1c93e58c fix(client/lightweight): note content not rendering 2026-01-07 12:07:49 +02:00
Elian Doran
ab0800a9f3 chore(core): integrate notes route 2026-01-07 12:00:38 +02:00
Elian Doran
dd58eac4b0 fix(client/lightweight): boxicons not loading 2026-01-07 11:50:25 +02:00
Elian Doran
c6d1457ad7 refactor(client/lightweight): bootstrap route as part of the new router 2026-01-07 11:48:22 +02:00
Elian Doran
f05fda871c chore(core): integrate icon_packs service 2026-01-07 11:45:40 +02:00
Elian Doran
22590596da feat(core): shared router between lightweight and server 2026-01-07 11:37:50 +02:00
Elian Doran
8274f9a220 feat(client): lightweight router implementation 2026-01-07 11:30:52 +02:00
Elian Doran
b19bf62d7e chore(client): bypass autocomplete count for now 2026-01-07 11:26:31 +02:00
Elian Doran
7b436bdf70 chore(client): vite not reloading core module 2026-01-07 11:24:04 +02:00
Elian Doran
a1c4a17d64 chore(core): integrate keyboard actions route 2026-01-07 11:23:46 +02:00
Elian Doran
7966cfd09c chore(client/lightweight): wait for becca to load before processing requests 2026-01-07 11:13:25 +02:00
Elian Doran
0fe299250e chore(client/lightweight): tree route import not seen 2026-01-07 11:07:23 +02:00
Elian Doran
adfe490480 chore(core): fix import 2026-01-07 10:43:02 +02:00
Elian Doran
872ab0864b chore(client/lightweight): handle routes properly 2026-01-06 23:19:41 +02:00
Elian Doran
6633b4233d chore(client/lightweight): initialize database earlier 2026-01-06 23:05:40 +02:00
Elian Doran
a2d873d16f chore(client/lightweight): port tree integration 2026-01-06 22:59:18 +02:00
Elian Doran
a6f52fff3e fix(client/lightweight): raw SQL queries not working 2026-01-06 22:20:53 +02:00
Elian Doran
7832f20c89 feat(client/lightweight): import demo database 2026-01-06 21:45:02 +02:00
Elian Doran
405db7cedb chore(client/lightweight): fix errors in SQL provider & implement crypto provider 2026-01-06 21:05:53 +02:00
Elian Doran
ccf4df8e86 chore(client/lightweight): basic SQL implementation 2026-01-06 20:59:52 +02:00
Elian Doran
1beda05e6c chore(client/lightweight): basic CLS implementation 2026-01-06 20:59:49 +02:00
Elian Doran
18a3d9d71a fix(client/lightweight): TypeScript not processed 2026-01-06 20:39:11 +02:00
Elian Doran
25dc9201bf feat(client/lightweight): improve error handling 2026-01-06 20:27:35 +02:00
Elian Doran
b60501dd3f chore(core) integrate options route 2026-01-06 20:12:03 +02:00
Elian Doran
cbd2fc3966 chore(client/lightweight): fix asset and API base path 2026-01-06 19:41:31 +02:00
Elian Doran
9bce12a85b Merge remote-tracking branch 'origin/lightweight/decouple_server_api' into lightweight/browser_api 2026-01-06 19:33:35 +02:00
Elian Doran
8523c369e1 fix(server): imports preventing start-up 2026-01-06 19:15:53 +02:00
Elian Doran
7c16aeca4a chore(core): crash due to dbReady before CLS init 2026-01-06 16:30:45 +02:00
Elian Doran
8399600e79 chore(core): address some missing methods in utils 2026-01-06 16:29:30 +02:00
Elian Doran
edac58f3fa chore(core): integrate revisions 2026-01-06 16:24:14 +02:00
Elian Doran
51d0d848c5 chore(core): no-op request service 2026-01-06 16:22:47 +02:00
Elian Doran
1edab8e8da chore(core): no-op image service 2026-01-06 16:21:42 +02:00
Elian Doran
e1e294914a chore(core): no-op search 2026-01-06 16:20:10 +02:00
Elian Doran
4668fdc15c chore(core): no-op sqlInit 2026-01-06 16:18:06 +02:00
Elian Doran
f1e0d5558c chore(core): integrate erase 2026-01-06 16:16:54 +02:00
Elian Doran
c94c54c641 chore(core): integrate task_context with ws no-op 2026-01-06 16:09:21 +02:00
Elian Doran
18416eb89a chore(core): no op script 2026-01-06 16:07:30 +02:00
Elian Doran
263c9028e2 chore(core): integrate hidden_subtree 2026-01-06 16:05:16 +02:00
Elian Doran
0b528e9937 chore(core): integrate handlers 2026-01-06 15:57:36 +02:00
Elian Doran
e905c1ec11 chore(core): integrate cloning service 2026-01-06 15:52:37 +02:00
Elian Doran
ecb27fe9f7 chore(core): integrate tree service 2026-01-06 15:48:48 +02:00
Elian Doran
a8f6db4b20 chore(core): fix some imports 2026-01-06 15:45:07 +02:00
Elian Doran
78262e55ec chore(core): integrate escape/unescape & toMap 2026-01-06 15:43:36 +02:00
Elian Doran
c6197e520d chore(core): integrate some more utils 2026-01-06 15:41:34 +02:00
Elian Doran
299c06c1a6 chore(core): fix inaccessible NoteParams 2026-01-06 15:33:48 +02:00
Elian Doran
674593b38c chore(core): integrate html_sanitizer 2026-01-06 15:31:22 +02:00
Elian Doran
f5535657ad chore(core): port note_types 2026-01-06 15:17:10 +02:00
Elian Doran
de4d07e904 chore(core): get rid of note_interface 2026-01-06 15:15:12 +02:00
Elian Doran
5508b505c8 chore(core): port notes service partially 2026-01-06 15:14:08 +02:00
Elian Doran
8cdfc108ba fix(core): wrong imports to src 2026-01-06 13:52:57 +02:00
Elian Doran
6a0f6fab83 fix(core): server not starting due to crypto not initialized 2026-01-06 13:50:22 +02:00
Elian Doran
ad3be73e1b chore(core): integrate note_set 2026-01-06 13:45:53 +02:00
Elian Doran
64b212b93e chore(core): integrate entity_changes 2026-01-06 13:42:29 +02:00
Elian Doran
60cb8d950e chore(core): integrate promoted_attribute_definition_parser 2026-01-06 13:30:21 +02:00
Elian Doran
61f6f94295 chore(core): integrate sanitize_attribute_name 2026-01-06 13:26:19 +02:00
Elian Doran
ebe7276f40 chore(core): fix some use of logs 2026-01-06 13:21:39 +02:00
Elian Doran
26d299aa44 chore(core): integrate options, options_init & keyboard_actions 2026-01-06 13:20:42 +02:00
Elian Doran
bd45c32251 chore(core): integrate utils partially 2026-01-06 13:06:14 +02:00
Elian Doran
321558a01f chore(core): fix minor type issue 2026-01-06 12:43:45 +02:00
Elian Doran
f5a77477aa chore(core): fix missing CLS method 2026-01-06 12:42:35 +02:00
Elian Doran
20c90d1296 chore(server): fix incompatibility with Uint8Array 2026-01-06 12:40:43 +02:00
Elian Doran
bbfef0315f chore(core): fix incompatibility with Uint8Array 2026-01-06 12:34:16 +02:00
Elian Doran
321fcf34f2 chore(core): fix references to getHoistedNoteId 2026-01-06 12:29:13 +02:00
Elian Doran
b9a59fe0c4 chore(core): fixs some imports to protected_session 2026-01-06 12:27:00 +02:00
Elian Doran
01f3c32d92 refactor(server): remove Blob interface in favor of BlobRow 2026-01-06 12:24:09 +02:00
Elian Doran
05b9e2ec2a chore(core): fix references to core 2026-01-06 12:20:01 +02:00
Elian Doran
c8d3b091fd chore(commons): fix Node reference 2026-01-06 12:19:42 +02:00
Elian Doran
d717a89163 chore(core): fix references to Buffer 2026-01-06 12:16:38 +02:00
Elian Doran
8149460547 chore(commons): fix issues with Buffer 2026-01-06 12:07:16 +02:00
Elian Doran
b7ad76827a chore(server): various references to core 2026-01-06 12:05:52 +02:00
Elian Doran
e19e9b3830 chore(core): fix references to blob-service 2026-01-06 12:01:18 +02:00
Elian Doran
40b07c3e8a chore(core): fix references to becca-interface 2026-01-06 11:59:45 +02:00
Elian Doran
a15b84b4e5 chore(core): fix type error in getFlatText 2026-01-06 11:56:52 +02:00
Elian Doran
544c52931c chore(server): fix references to abstract becca entity 2026-01-06 11:55:10 +02:00
Elian Doran
9391159413 chore(server): fix references to becca service 2026-01-06 11:52:25 +02:00
Elian Doran
f9e22a9ba9 chore(server): fix references to becca loader 2026-01-06 11:49:22 +02:00
Elian Doran
f88ac5dfae chore(server): fix imports to becca entities 2026-01-06 11:46:15 +02:00
Elian Doran
3459d2906e chore(server): fix imports to validation error 2026-01-06 11:41:06 +02:00
Elian Doran
4506b717d5 chore(server): fix imports to becca 2026-01-06 11:38:25 +02:00
Elian Doran
af8744ef2a chore(core): integrate errors 2026-01-06 11:31:13 +02:00
Elian Doran
320d8e3b45 chore(core): partially integrate becca 2026-01-06 11:23:52 +02:00
Elian Doran
c20da77f83 chore(core): integrate events service 2026-01-06 11:10:09 +02:00
Elian Doran
14e2e85da7 chore(core): integrate date_utils 2026-01-06 11:03:33 +02:00
Elian Doran
c7f0d541c2 fix(server): blob errors out 2026-01-06 10:41:50 +02:00
Elian Doran
5d474150da feat(client/lightweight): integrate SQLite 2026-01-05 20:00:00 +02:00
Elian Doran
d3941752f1 chore(client/lightweight): disable caching for now 2026-01-05 19:10:25 +02:00
Elian Doran
56b305b1de fix(client/lightweight): html aggressively cached 2026-01-05 18:50:09 +02:00
Elian Doran
bde472d649 feat(client/standalone): basic service worker attempt 2026-01-05 18:35:14 +02:00
Elian Doran
c1548b0f54 chore(server): integrate data_encryption, and protected_session 2026-01-05 17:47:25 +02:00
Elian Doran
6f04738629 chore(core): add documentation for SQL 2026-01-05 16:07:17 +02:00
Elian Doran
f79af7b045 fix(server): request content empty due to CLS 2026-01-05 16:01:27 +02:00
Elian Doran
527f502083 fix(server): requests failing due to cls namespacing issue 2026-01-05 15:58:24 +02:00
Elian Doran
d61e2c6f2c chore(server): get DB to be loaded 2026-01-05 15:52:31 +02:00
Elian Doran
ea31d2f446 chore(core): basic integration of SQL + CLS + log 2026-01-05 15:45:45 +02:00
Elian Doran
62803a1817 chore(server): set up dependency to trilium-core 2026-01-05 14:42:32 +02:00
Elian Doran
a67464b4a0 refactor(server): decouple bettersqlite3 from sql service 2026-01-05 14:03:03 +02:00
Elian Doran
00e7482968 chore(core): create empty package 2026-01-05 12:26:13 +02:00
Elian Doran
a3a3b3cb5c Merge remote-tracking branch 'origin/main' into renovate/csrf-csrf-4.x 2025-07-26 15:49:50 +03:00
Jon Fuller
d4aaf4ca9b Merge branch 'develop' into renovate/csrf-csrf-4.x 2025-06-11 12:44:51 -07:00
Elian Doran
e7450b5143 Merge branch 'develop' into renovate/csrf-csrf-4.x 2025-06-08 14:31:55 +03:00
Elian Doran
fd90454eb6 Merge branch 'develop' into renovate/csrf-csrf-4.x 2025-05-17 09:51:02 +03:00
Elian Doran
f327b54c0e feat(csrf): use different token to avoid issues with old token 2025-05-16 19:45:32 +03:00
Elian Doran
f38105ef05 Merge remote-tracking branch 'origin/develop' into renovate/csrf-csrf-4.x 2025-05-16 19:34:19 +03:00
Elian Doran
6f6041ee7b fix(server): migrate csrf to v4 2025-05-15 20:39:31 +03:00
renovate[bot]
2c1517d259 chore(deps): update dependency csrf-csrf to v4 2025-05-15 16:12:11 +00:00
991 changed files with 48878 additions and 60707 deletions

View File

@@ -1,6 +1,6 @@
root = true
[*.{js,ts,tsx,css}]
[*.{js,cjs,ts,tsx,css}]
charset = utf-8
end_of_line = lf
indent_size = 4

View File

@@ -85,6 +85,7 @@ runs:
APPLE_ID: ${{ env.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ env.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ env.WINDOWS_SIGN_EXECUTABLE }}
WINDOWS_SIGN_ERROR_LOG: ${{ env.WINDOWS_SIGN_ERROR_LOG }}
TRILIUM_ARTIFACT_NAME_HINT: TriliumNotes-${{ github.ref_name }}-${{ inputs.os }}-${{ inputs.arch }}
TARGET_ARCH: ${{ inputs.arch }}
run: pnpm run --filter desktop electron-forge:make --arch=${{ inputs.arch }} --platform=${{ inputs.forge_platform }}

View File

@@ -8,7 +8,7 @@ inputs:
runs:
using: composite
steps:
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:

View File

@@ -69,7 +69,7 @@ runs:
# Post github action comment
- name: Post comment
uses: marocchino/sticky-pull-request-comment@v2
uses: marocchino/sticky-pull-request-comment@v3
if: ${{ steps.bundleSize.outputs.hasDifferences == 'true' }} # post only in case of changes
with:
number: ${{ github.event.pull_request.number }}

67
.github/workflows/deploy-app.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Deploy Standalone App
on:
# Trigger on push to main branch
push:
branches:
- standalone
# Only run when app files change
paths:
- 'apps/client/**'
- 'apps/client-standalone/**'
- 'packages/trilium-core/**'
- '.github/workflows/deploy-app.yml'
# Allow manual triggering from Actions tab
workflow_dispatch:
# Run on pull requests for preview deployments
pull_request:
paths:
- 'apps/client/**'
- 'apps/client-standalone/**'
- 'packages/trilium-core/**'
- '.github/workflows/deploy-app.yml'
jobs:
build-and-deploy:
name: Build and Deploy App
runs-on: ubuntu-latest
timeout-minutes: 10
# Required permissions for deployment
permissions:
contents: read
deployments: write
pull-requests: write # For PR preview comments
id-token: write # For OIDC authentication (if needed)
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Trigger build of app
run: pnpm --filter=client-standalone build
- name: Deploy
uses: ./.github/actions/deploy-to-cloudflare-pages
if: github.repository == vars.REPO_MAIN
with:
project_name: "trilium-app"
comment_body: "🖥️ App preview is ready"
production_url: "https://app.triliumnotes.org"
deploy_dir: "apps/client-standalone/dist"
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -45,7 +45,7 @@ jobs:
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v5
- name: Setup Node.js
uses: actions/setup-node@v6

View File

@@ -1,9 +1,9 @@
name: Dev
on:
push:
branches: [ main ]
branches: [ main, standalone ]
pull_request:
branches: [ main ]
branches: [ main, standalone ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -26,7 +26,7 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -37,8 +37,35 @@ jobs:
- name: Typecheck
run: pnpm typecheck
- name: Run the unit tests
run: pnpm run test:all
- name: Run the client-side tests
run: pnpm run --filter=client test
- name: Upload client test report
uses: actions/upload-artifact@v7
if: always()
with:
name: client-test-report
path: apps/client/test-output/vitest/html/
retention-days: 30
- name: Run the server-side tests
run: pnpm run --filter=server test
- name: Upload server test report
uses: actions/upload-artifact@v7
if: always()
with:
name: server-test-report
path: apps/server/test-output/vitest/html/
retention-days: 30
- name: Run CKEditor e2e tests
run: |
pnpm run --filter=ckeditor5-mermaid test
pnpm run --filter=ckeditor5-math test
- name: Run the rest of the tests
run: pnpm run --filter=\!client --filter=\!server --filter=\!ckeditor5-mermaid --filter=\!ckeditor5-math test
build_docker:
name: Build Docker image
@@ -47,7 +74,7 @@ jobs:
- test_dev
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update build info
@@ -62,8 +89,8 @@ jobs:
key: ${{ secrets.RELATIVE_CI_CLIENT_KEY }}
- name: Trigger server build
run: pnpm run server:build
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
context: apps/server
cache-from: type=gha
@@ -82,7 +109,7 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Install dependencies
run: pnpm install --frozen-lockfile
@@ -97,10 +124,10 @@ jobs:
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Build and export to Docker
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: apps/server
file: apps/server/${{ matrix.dockerfile }}

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:

View File

@@ -40,9 +40,9 @@ jobs:
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -59,7 +59,7 @@ jobs:
run: pnpm run server:build
- name: Build and export to Docker
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: apps/server
file: apps/server/${{ matrix.dockerfile }}
@@ -86,12 +86,12 @@ jobs:
- name: Upload Playwright trace
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: Playwright trace (${{ matrix.dockerfile }})
path: test-output/playwright/output
- uses: actions/upload-artifact@v6
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: Playwright report (${{ matrix.dockerfile }})
@@ -142,7 +142,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -164,11 +164,9 @@ jobs:
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
@@ -177,28 +175,21 @@ jobs:
latest=false
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: apps/server
file: apps/server/${{ matrix.dockerfile }}
@@ -213,7 +204,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
path: /tmp/digests/*
@@ -227,7 +218,7 @@ jobs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: digests-*
@@ -237,75 +228,86 @@ jobs:
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=false
- name: Set up crane
uses: imjasonh/setup-crane@v0.5
- name: Login to GHCR
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
flavor: |
latest=false
- name: Verify digests exist on GHCR
working-directory: /tmp/digests
run: |
# Extract the branch or tag name from the ref
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
echo "Verifying all digests are available on GHCR..."
for DIGEST_FILE in *; do
DIGEST="sha256:${DIGEST_FILE}"
echo -n " ${DIGEST}: "
crane manifest "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" > /dev/null
echo "OK"
done
# Create and push the manifest list with both the branch/tag name and the commit SHA
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Create and push multi-arch manifest
working-directory: /tmp/digests
run: |
GHCR_IMAGE="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}"
DOCKERHUB_IMAGE="${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}"
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Build -m flags for crane index append from digest files
MANIFEST_ARGS=""
for d in *; do
MANIFEST_ARGS="${MANIFEST_ARGS} -m ${GHCR_IMAGE}@sha256:${d}"
done
# If the ref is a tag, also tag the image as stable as this is part of a 'release'
# and only go in the `if` if there is NOT a `-` in the tag's name, due to tagging of `-alpha`, `-beta`, etc...
# Create multi-arch manifest for each tag from metadata, plus copy to DockerHub
while IFS= read -r TAG; do
echo "Creating manifest: ${TAG}"
crane index append ${MANIFEST_ARGS} -t "${TAG}"
SUFFIX="${TAG#*:}"
echo "Copying to DockerHub: ${DOCKERHUB_IMAGE}:${SUFFIX}"
crane copy "${TAG}" "${DOCKERHUB_IMAGE}:${SUFFIX}"
done <<< "${{ steps.meta.outputs.tags }}"
# For stable releases (tags without hyphens), also create stable + latest
REF_NAME="${GITHUB_REF#refs/tags/}"
if [[ "${GITHUB_REF}" == refs/tags/* && ! "${REF_NAME}" =~ - ]]; then
# First create stable tags
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
# Small delay to ensure stable tag is fully propagated
sleep 5
# Now update latest tags
docker buildx imagetools create \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
docker buildx imagetools create \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:stable
echo "Creating stable tags..."
crane index append ${MANIFEST_ARGS} -t "${GHCR_IMAGE}:stable"
crane copy "${GHCR_IMAGE}:stable" "${DOCKERHUB_IMAGE}:stable"
echo "Creating latest tags..."
crane copy "${GHCR_IMAGE}:stable" "${GHCR_IMAGE}:latest"
crane copy "${GHCR_IMAGE}:latest" "${DOCKERHUB_IMAGE}:latest"
fi
- name: Inspect image
- name: Inspect manifests
run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
REF_NAME="${GITHUB_REF#refs/heads/}"
REF_NAME="${REF_NAME#refs/tags/}"
echo "=== GHCR ==="
crane manifest "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME}"
echo ""
echo "=== DockerHub ==="
crane manifest "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME}"

View File

@@ -61,7 +61,7 @@ jobs:
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -87,10 +87,11 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
WINDOWS_SIGN_ERROR_LOG: ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Publish release
uses: softprops/action-gh-release@v2.5.0
uses: softprops/action-gh-release@v2.6.1
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false
@@ -102,7 +103,7 @@ jobs:
name: Nightly Build
- name: Publish artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ github.event_name == 'pull_request' }}
with:
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
@@ -131,7 +132,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2.5.0
uses: softprops/action-gh-release@v2.6.1
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false

View File

@@ -38,7 +38,7 @@ jobs:
filter: tree:0
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- uses: actions/setup-node@v6
with:
node-version: 24
@@ -77,7 +77,7 @@ jobs:
- name: Upload test report
if: failure()
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: e2e report ${{ matrix.arch }}
path: apps/server-e2e/test-output

View File

@@ -17,10 +17,22 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
node-version: 24
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --filter source --frozen-lockfile --ignore-scripts
- name: Check version consistency
run: pnpm tsx ${{ github.workspace }}/scripts/check-version-consistency.ts ${{ github.ref_name }}
make-electron:
name: Make Electron
needs:
- sanity-check
strategy:
fail-fast: false
matrix:
@@ -54,7 +66,7 @@ jobs:
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -78,16 +90,19 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
WINDOWS_SIGN_EXECUTABLE: ${{ vars.WINDOWS_SIGN_EXECUTABLE }}
WINDOWS_SIGN_ERROR_LOG: ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Upload the artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
path: apps/desktop/upload/*.*
build_server:
name: Build Linux Server
needs:
- sanity-check
strategy:
fail-fast: false
matrix:
@@ -108,7 +123,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Upload the artifact
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: release-server-linux-${{ matrix.arch }}
path: upload/*.*
@@ -128,14 +143,14 @@ jobs:
docs/Release Notes
- name: Download all artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v8
with:
merge-multiple: true
pattern: release-*
path: upload
- name: Publish stable release
uses: softprops/action-gh-release@v2.5.0
uses: softprops/action-gh-release@v2.6.1
with:
draft: false
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:
@@ -48,7 +48,7 @@ jobs:
pnpm --filter web-clipper zip:firefox
- name: Upload build artifacts
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
if: ${{ !startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
name: web-clipper-extension
@@ -58,7 +58,7 @@ jobs:
compression-level: 0
- name: Release web clipper extension
uses: softprops/action-gh-release@v2.5.0
uses: softprops/action-gh-release@v2.6.1
if: ${{ startsWith(github.ref, 'refs/tags/web-clipper-v') }}
with:
draft: false

View File

@@ -26,7 +26,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v5
- name: Set up node & dependencies
uses: actions/setup-node@v6
with:

1
.gitignore vendored
View File

@@ -46,7 +46,6 @@ upload
/.direnv
/result
.svelte-kit
# docs
site/

2
.nvmrc
View File

@@ -1 +1 @@
24.13.1
24.14.0

57
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,57 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch client (Chrome)",
"request": "launch",
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/apps/client"
},
{
"name": "Launch server",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/apps/server/src/main.ts",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx",
"env": {
"NODE_ENV": "development",
"TRILIUM_ENV": "dev",
"TRILIUM_DATA_DIR": "${input:trilium_data_dir}",
"TRILIUM_RESOURCE_DIR": "${workspaceFolder}/apps/server/src"
},
"autoAttachChildProcesses": true,
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"skipFiles": ["<node_internals>/**", "${workspaceFolder}/node_modules/**"]
},
{
"name": "Launch Vitest with current test file",
"type": "node",
"request": "launch",
"autoAttachChildProcesses": true,
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"smartStep": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"cwd": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "Launch client (Chrome) and server",
"configurations": ["Launch server","Launch client (Chrome)"],
"stopAll": true
}
],
"inputs": [
{
"id": "trilium_data_dir",
"type": "promptString",
"description": "Select Trilum Notes data directory",
"default": "${workspaceFolder}/apps/server/data"
}
]
}

View File

@@ -10,4 +10,5 @@ Description above is a general rule and may be altered on case by case basis.
## Reporting a Vulnerability
You can report low severity vulnerabilities as GitHub issues, more severe vulnerabilities should be reported to the email [contact@eliandoran.me](mailto:contact@eliandoran.me)
* For low severity vulnerabilities, they can be reported as GitHub issues.
* For severe vulnerabilities, please report it using [GitHub Security Advisories](https://github.com/TriliumNext/Trilium/security/advisories).

View File

@@ -1,19 +1,25 @@
{
"name": "build-docs",
"version": "1.0.0",
"description": "",
"description": "Build documentation from Trilium notes",
"main": "src/main.ts",
"bin": {
"trilium-build-docs": "dist/cli.js"
},
"scripts": {
"start": "tsx ."
"start": "tsx .",
"cli": "tsx src/cli.ts",
"build": "tsx scripts/build.ts"
},
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.29.3",
"packageManager": "pnpm@10.32.1",
"devDependencies": {
"@redocly/cli": "2.18.1",
"@redocly/cli": "2.24.1",
"archiver": "7.0.1",
"fs-extra": "11.3.3",
"fs-extra": "11.3.4",
"js-yaml": "4.1.1",
"react": "19.2.4",
"react-dom": "19.2.4",
"typedoc": "0.28.17",

View File

@@ -0,0 +1,23 @@
import BuildHelper from "../../../scripts/build-utils";
const build = new BuildHelper("apps/build-docs");
async function main() {
// Build the CLI and other TypeScript files
await build.buildBackend([
"src/cli.ts",
"src/main.ts",
"src/build-docs.ts",
"src/swagger.ts",
"src/script-api.ts",
"src/context.ts"
]);
// Copy HTML template
build.copy("src/index.html", "index.html");
// Copy node modules dependencies if needed
build.copyNodeModules([ "better-sqlite3", "bindings", "file-uri-to-path" ]);
}
main();

View File

@@ -13,8 +13,12 @@
* Make sure to keep in line with backend's `script_context.ts`.
*/
export type { default as AbstractBeccaEntity } from "../../server/src/becca/entities/abstract_becca_entity.js";
export type { default as BAttachment } from "../../server/src/becca/entities/battachment.js";
export type {
default as AbstractBeccaEntity
} from "../../server/src/becca/entities/abstract_becca_entity.js";
export type {
default as BAttachment
} from "../../server/src/becca/entities/battachment.js";
export type { default as BAttribute } from "../../server/src/becca/entities/battribute.js";
export type { default as BBranch } from "../../server/src/becca/entities/bbranch.js";
export type { default as BEtapiToken } from "../../server/src/becca/entities/betapi_token.js";
@@ -31,6 +35,7 @@ export type { Api };
const fakeNote = new BNote();
/**
* The `api` global variable allows access to the backend script API, which is documented in {@link Api}.
* The `api` global variable allows access to the backend script API,
* which is documented in {@link Api}.
*/
export const api: Api = new BackendScriptApi(fakeNote, {});

View File

@@ -1,19 +1,90 @@
process.env.TRILIUM_INTEGRATION_TEST = "memory-no-store";
process.env.TRILIUM_RESOURCE_DIR = "../server/src";
// Only set TRILIUM_RESOURCE_DIR if not already set (e.g., by Nix wrapper)
if (!process.env.TRILIUM_RESOURCE_DIR) {
process.env.TRILIUM_RESOURCE_DIR = "../server/src";
}
process.env.NODE_ENV = "development";
import cls from "@triliumnext/server/src/services/cls.js";
import { dirname, join, resolve } from "path";
import archiver from "archiver";
import { execSync } from "child_process";
import { WriteStream } from "fs";
import * as fs from "fs/promises";
import * as fsExtra from "fs-extra";
import archiver from "archiver";
import { WriteStream } from "fs";
import { execSync } from "child_process";
import yaml from "js-yaml";
import { dirname, join, resolve } from "path";
import BuildContext from "./context.js";
interface NoteMapping {
rootNoteId: string;
path: string;
format: "markdown" | "html" | "share";
ignoredFiles?: string[];
exportOnly?: boolean;
}
interface Config {
baseUrl: string;
noteMappings: NoteMapping[];
}
const DOCS_ROOT = "../../../docs";
const OUTPUT_DIR = "../../site";
// Load configuration from edit-docs-config.yaml
async function loadConfig(configPath?: string): Promise<Config | null> {
const pathsToTry = configPath
? [resolve(configPath)]
: [
join(process.cwd(), "edit-docs-config.yaml"),
join(__dirname, "../../../edit-docs-config.yaml")
];
for (const path of pathsToTry) {
try {
const configContent = await fs.readFile(path, "utf-8");
const config = yaml.load(configContent) as Config;
// Resolve all paths relative to the config file's directory
const CONFIG_DIR = dirname(path);
config.noteMappings = config.noteMappings.map((mapping) => ({
...mapping,
path: resolve(CONFIG_DIR, mapping.path)
}));
return config;
} catch (error) {
if (error.code !== "ENOENT") {
throw error; // rethrow unexpected errors
}
}
}
return null; // No config file found
}
async function exportDocs(
noteId: string,
format: "markdown" | "html" | "share",
outputPath: string,
ignoredFiles?: string[]
) {
const zipFilePath = `output-${noteId}.zip`;
try {
const { exportToZipFile } = (await import("@triliumnext/server/src/services/export/zip.js"))
.default;
await exportToZipFile(noteId, format, zipFilePath, {});
const ignoredSet = ignoredFiles ? new Set(ignoredFiles) : undefined;
await extractZip(zipFilePath, outputPath, ignoredSet);
} finally {
if (await fsExtra.exists(zipFilePath)) {
await fsExtra.rm(zipFilePath);
}
}
}
async function importAndExportDocs(sourcePath: string, outputSubDir: string) {
const note = await importData(sourcePath);
@@ -21,15 +92,18 @@ async function importAndExportDocs(sourcePath: string, outputSubDir: string) {
const zipName = outputSubDir || "user-guide";
const zipFilePath = `output-${zipName}.zip`;
try {
const { exportToZip } = (await import("@triliumnext/server/src/services/export/zip.js")).default;
const { exportToZip } = (await import("@triliumnext/server/src/services/export/zip.js"))
.default;
const branch = note.getParentBranches()[0];
const taskContext = new (await import("@triliumnext/server/src/services/task_context.js")).default(
"no-progress-reporting",
"export",
null
);
const taskContext = new (await import("@triliumnext/server/src/services/task_context.js"))
.default(
"no-progress-reporting",
"export",
null
);
const fileOutputStream = fsExtra.createWriteStream(zipFilePath);
await exportToZip(taskContext, branch, "share", fileOutputStream);
const { waitForStreamToFinish } = await import("@triliumnext/server/src/services/utils.js");
await waitForStreamToFinish(fileOutputStream);
// Output to root directory if outputSubDir is empty, otherwise to subdirectory
@@ -42,7 +116,7 @@ async function importAndExportDocs(sourcePath: string, outputSubDir: string) {
}
}
async function buildDocsInner() {
async function buildDocsInner(config?: Config) {
const i18n = await import("@triliumnext/server/src/services/i18n.js");
await i18n.initializeTranslations();
@@ -53,18 +127,49 @@ async function buildDocsInner() {
const beccaLoader = await import("../../server/src/becca/becca_loader.js");
await beccaLoader.beccaLoaded;
// Build User Guide
console.log("Building User Guide...");
await importAndExportDocs(join(__dirname, DOCS_ROOT, "User Guide"), "user-guide");
if (config) {
// Config-based build (reads from edit-docs-config.yaml)
console.log("Building documentation from config file...");
// Build Developer Guide
console.log("Building Developer Guide...");
await importAndExportDocs(join(__dirname, DOCS_ROOT, "Developer Guide"), "developer-guide");
// Import all non-export-only mappings
for (const mapping of config.noteMappings) {
if (!mapping.exportOnly) {
console.log(`Importing from ${mapping.path}...`);
await importData(mapping.path);
}
}
// Copy favicon.
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "user-guide", "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico", join(OUTPUT_DIR, "developer-guide", "favicon.ico"));
// Export all mappings
for (const mapping of config.noteMappings) {
if (mapping.exportOnly) {
console.log(`Exporting ${mapping.format} to ${mapping.path}...`);
await exportDocs(
mapping.rootNoteId,
mapping.format,
mapping.path,
mapping.ignoredFiles
);
}
}
} else {
// Legacy hardcoded build (for backward compatibility)
console.log("Building User Guide...");
await importAndExportDocs(join(__dirname, DOCS_ROOT, "User Guide"), "user-guide");
console.log("Building Developer Guide...");
await importAndExportDocs(
join(__dirname, DOCS_ROOT, "Developer Guide"),
"developer-guide"
);
// Copy favicon.
await fs.copyFile("../../apps/website/src/assets/favicon.ico",
join(OUTPUT_DIR, "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico",
join(OUTPUT_DIR, "user-guide", "favicon.ico"));
await fs.copyFile("../../apps/website/src/assets/favicon.ico",
join(OUTPUT_DIR, "developer-guide", "favicon.ico"));
}
console.log("Documentation built successfully!");
}
@@ -91,12 +196,13 @@ async function createImportZip(path: string) {
zlib: { level: 0 }
});
console.log("Archive path is ", resolve(path))
console.log("Archive path is ", resolve(path));
archive.directory(path, "/");
const outputStream = fsExtra.createWriteStream(inputFile);
archive.pipe(outputStream);
archive.finalize();
const { waitForStreamToFinish } = await import("@triliumnext/server/src/services/utils.js");
await waitForStreamToFinish(outputStream);
try {
@@ -106,15 +212,15 @@ async function createImportZip(path: string) {
}
}
function waitForStreamToFinish(stream: WriteStream) {
return new Promise<void>((res, rej) => {
stream.on("finish", () => res());
stream.on("error", (err) => rej(err));
});
}
export async function extractZip(zipFilePath: string, outputPath: string, ignoredFiles?: Set<string>) {
const { readZipFile, readContent } = (await import("@triliumnext/server/src/services/import/zip.js"));
export async function extractZip(
zipFilePath: string,
outputPath: string,
ignoredFiles?: Set<string>
) {
const { readZipFile, readContent } = (await import(
"@triliumnext/server/src/services/import/zip.js"
));
await readZipFile(await fs.readFile(zipFilePath), async (zip, entry) => {
// We ignore directories since they can appear out of order anyway.
if (!entry.fileName.endsWith("/") && !ignoredFiles?.has(entry.fileName)) {
@@ -129,6 +235,27 @@ export async function extractZip(zipFilePath: string, outputPath: string, ignore
});
}
export async function buildDocsFromConfig(configPath?: string, gitRootDir?: string) {
const config = await loadConfig(configPath);
if (gitRootDir) {
// Build the share theme if we have a gitRootDir (for Trilium project)
execSync(`pnpm run --filter share-theme build`, {
stdio: "inherit",
cwd: gitRootDir
});
}
// Trigger the actual build.
await new Promise((res, rej) => {
cls.init(() => {
buildDocsInner(config ?? undefined)
.catch(rej)
.then(res);
});
});
}
export default async function buildDocs({ gitRootDir }: BuildContext) {
// Build the share theme.
execSync(`pnpm run --filter share-theme build`, {

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env node
import packageJson from "../package.json" with { type: "json" };
import { buildDocsFromConfig } from "./build-docs.js";
// Parse command-line arguments
function parseArgs() {
const args = process.argv.slice(2);
let configPath: string | undefined;
let showHelp = false;
let showVersion = false;
for (let i = 0; i < args.length; i++) {
if (args[i] === "--config" || args[i] === "-c") {
configPath = args[i + 1];
if (!configPath) {
console.error("Error: --config/-c requires a path argument");
process.exit(1);
}
i++; // Skip the next argument as it's the value
} else if (args[i] === "--help" || args[i] === "-h") {
showHelp = true;
} else if (args[i] === "--version" || args[i] === "-v") {
showVersion = true;
}
}
return { configPath, showHelp, showVersion };
}
function getVersion(): string {
return packageJson.version;
}
function printHelp() {
const version = getVersion();
console.log(`
Usage: trilium-build-docs [options]
Options:
-c, --config <path> Path to the configuration file
(default: edit-docs-config.yaml in current directory)
-h, --help Display this help message
-v, --version Display version information
Description:
Builds documentation from Trilium note structure and exports to various formats.
Configuration file should be in YAML format with the following structure:
baseUrl: "https://example.com"
noteMappings:
- rootNoteId: "noteId123"
path: "docs"
format: "markdown"
- rootNoteId: "noteId456"
path: "public/docs"
format: "share"
exportOnly: true
Version: ${version}
`);
}
function printVersion() {
const version = getVersion();
console.log(version);
}
async function main() {
const { configPath, showHelp, showVersion } = parseArgs();
if (showHelp) {
printHelp();
process.exit(0);
} else if (showVersion) {
printVersion();
process.exit(0);
}
try {
await buildDocsFromConfig(configPath);
process.exit(0);
} catch (error) {
console.error("Error building documentation:", error);
process.exit(1);
}
}
main();

View File

@@ -13,16 +13,19 @@
* Make sure to keep in line with frontend's `script_context.ts`.
*/
export type { default as BasicWidget } from "../../client/src/widgets/basic_widget.js";
export type { default as FAttachment } from "../../client/src/entities/fattachment.js";
export type { default as FAttribute } from "../../client/src/entities/fattribute.js";
export type { default as FBranch } from "../../client/src/entities/fbranch.js";
export type { default as FNote } from "../../client/src/entities/fnote.js";
export type { Api } from "../../client/src/services/frontend_script_api.js";
export type { default as NoteContextAwareWidget } from "../../client/src/widgets/note_context_aware_widget.js";
export type { default as BasicWidget } from "../../client/src/widgets/basic_widget.js";
export type {
default as NoteContextAwareWidget
} from "../../client/src/widgets/note_context_aware_widget.js";
export type { default as RightPanelWidget } from "../../client/src/widgets/right_panel_widget.js";
import FrontendScriptApi, { type Api } from "../../client/src/services/frontend_script_api.js";
//@ts-expect-error
// @ts-expect-error - FrontendScriptApi is not directly exportable as Api without this simulation.
export const api: Api = new FrontendScriptApi();

View File

@@ -1,9 +1,10 @@
import { join } from "path";
import BuildContext from "./context";
import buildSwagger from "./swagger";
import { cpSync, existsSync, mkdirSync, rmSync } from "fs";
import { join } from "path";
import buildDocs from "./build-docs";
import BuildContext from "./context";
import buildScriptApi from "./script-api";
import buildSwagger from "./swagger";
const context: BuildContext = {
gitRootDir: join(__dirname, "../../../"),

View File

@@ -1,7 +1,8 @@
import { execSync } from "child_process";
import BuildContext from "./context";
import { join } from "path";
import BuildContext from "./context";
export default function buildScriptApi({ baseDir, gitRootDir }: BuildContext) {
// Generate types
execSync(`pnpm typecheck`, { stdio: "inherit", cwd: gitRootDir });

View File

@@ -1,7 +1,8 @@
import BuildContext from "./context";
import { join } from "path";
import { execSync } from "child_process";
import { mkdirSync } from "fs";
import { join } from "path";
import BuildContext from "./context";
interface BuildInfo {
specPath: string;
@@ -27,6 +28,9 @@ export default function buildSwagger({ baseDir, gitRootDir }: BuildContext) {
const absSpecPath = join(gitRootDir, specPath);
const targetDir = join(baseDir, outDir);
mkdirSync(targetDir, { recursive: true });
execSync(`pnpm redocly build-docs ${absSpecPath} -o ${targetDir}/index.html`, { stdio: "inherit" });
execSync(
`pnpm redocly build-docs ${absSpecPath} -o ${targetDir}/index.html`,
{ stdio: "inherit" }
);
}
}

View File

@@ -1,6 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"include": [],
"include": [
"scripts/**/*.ts"
],
"references": [
{
"path": "../server"

View File

@@ -4,6 +4,7 @@
"entryPoints": [
"src/backend_script_entrypoint.ts"
],
"tsconfig": "tsconfig.app.json",
"plugin": [
"typedoc-plugin-missing-exports"
]

View File

@@ -4,6 +4,7 @@
"entryPoints": [
"src/frontend_script_entrypoint.ts"
],
"tsconfig": "tsconfig.app.json",
"plugin": [
"typedoc-plugin-missing-exports"
]

View File

@@ -0,0 +1,4 @@
# The development license key for premium CKEditor features.
# Note: This key must only be used for the Trilium Notes project.
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3ODcyNzA0MDAsImp0aSI6IjkyMWE1MWNlLTliNDMtNGRlMC1iOTQwLTc5ZjM2MDBkYjg1NyIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOiJ0cmlsaXVtIiwiZmVhdHVyZXMiOlsiVFJJTElVTSJdLCJ2YyI6ImU4YzRhMjBkIn0.hny77p-U4-jTkoqbwPytrEar5ylGCWBN7Ez3SlB8i6_mJCBIeCSTOlVQk_JMiOEq3AGykUMHzWXzjdMFwgniOw
VITE_CKEDITOR_ENABLE_INSPECTOR=false

View File

@@ -0,0 +1 @@
VITE_CKEDITOR_ENABLE_INSPECTOR=false

View File

@@ -0,0 +1,86 @@
{
"name": "@triliumnext/client-standalone",
"version": "0.102.1",
"description": "Standalone client for TriliumNext with SQLite WASM backend",
"private": true,
"license": "AGPL-3.0-only",
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"dev": "vite dev",
"test": "vitest",
"start-prod": "pnpm build && pnpm http-server dist -p 8888",
"coverage": "vitest --coverage"
},
"dependencies": {
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.20",
"@fullcalendar/daygrid": "6.1.20",
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/multimonth": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.0",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.5.1",
"@sqlite.org/sqlite-wasm": "3.51.1-build2",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
"@triliumnext/core": "workspace:*",
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"@zumer/snapdom": "2.0.1",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
"clsx": "2.1.1",
"color": "5.0.3",
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "17.0.0",
"i18next": "25.7.3",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery.fancytree": "2.38.5",
"js-sha1": "0.7.0",
"js-sha512": "0.9.0",
"jsplumb": "2.15.6",
"katex": "0.16.27",
"knockout": "3.5.1",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "17.0.1",
"mermaid": "11.12.2",
"mind-elixir": "5.4.0",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.2",
"react-i18next": "16.5.1",
"react-window": "2.2.3",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
"vanilla-js-wheel-zoom": "9.0.4"
},
"devDependencies": {
"@ckeditor/ckeditor5-inspector": "5.0.0",
"@preact/preset-vite": "2.10.2",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33",
"@types/leaflet": "1.9.21",
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"cross-env": "7.0.3",
"happy-dom": "20.0.11",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.4"
}
}

View File

@@ -0,0 +1,3 @@
/*
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,20 @@
{
"name": "Trilium Notes",
"short_name": "Trilium",
"description": "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
"theme_color": "#333333",
"background_color": "#1F1F1F",
"display": "standalone",
"scope": "/",
"start_url": "/",
"display_override": [
"window-controls-overlay"
],
"icons": [
{
"src": "assets/icon.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@@ -0,0 +1,2 @@
// Re-export desktop from client
export * from "../../client/src/desktop";

View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, viewport-fit=cover" />
<link rel="manifest" crossorigin="use-credentials" href="manifest.webmanifest">
<title>Trilium Notes</title>
</head>
<body id="trilium-app">
<noscript>Trilium requires JavaScript to be enabled.</noscript>
<div id="context-menu-cover"></div>
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
<!-- Required for match the PWA's top bar color with the theme -->
<!-- This works even when the user directly changes --root-background in CSS -->
<div id="background-color-tracker" style="position: absolute; visibility: hidden; color: var(--root-background); transition: color 1ms;"></div>
<!-- Bootstrap (request server for required information) -->
<script src="./main.ts" type="module"></script>
<!-- Required for correct loading of scripts in Electron -->
<script>
if (typeof module === 'object') {window.module = module; module = undefined;}
</script>
</body>
</html>

View File

@@ -0,0 +1,254 @@
/**
* Browser-compatible router that mimics Express routing patterns.
* Supports path parameters (e.g., /api/notes/:noteId) and query strings.
*/
import { getContext, routes } from "@triliumnext/core";
export interface BrowserRequest {
method: string;
url: string;
path: string;
params: Record<string, string>;
query: Record<string, string | undefined>;
body?: unknown;
}
export interface BrowserResponse {
status: number;
headers: Record<string, string>;
body: ArrayBuffer | null;
}
export type RouteHandler = (req: BrowserRequest) => unknown | Promise<unknown>;
interface Route {
method: string;
pattern: RegExp;
paramNames: string[];
handler: RouteHandler;
}
const encoder = new TextEncoder();
/**
* Convert an Express-style path pattern to a RegExp.
* Supports :param syntax for path parameters.
*
* Examples:
* /api/notes/:noteId -> /^\/api\/notes\/([^\/]+)$/
* /api/notes/:noteId/revisions -> /^\/api\/notes\/([^\/]+)\/revisions$/
*/
function pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
const paramNames: string[] = [];
// Escape special regex characters except for :param patterns
const regexPattern = path
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
paramNames.push(paramName);
return '([^/]+)';
});
return {
pattern: new RegExp(`^${regexPattern}$`),
paramNames
};
}
/**
* Parse query string into an object.
*/
function parseQuery(search: string): Record<string, string | undefined> {
const query: Record<string, string | undefined> = {};
if (!search || search === '?') return query;
const params = new URLSearchParams(search);
for (const [key, value] of params) {
query[key] = value;
}
return query;
}
/**
* Convert a result to a JSON response.
*/
function jsonResponse(obj: unknown, status = 200, extraHeaders: Record<string, string> = {}): BrowserResponse {
const parsedObj = routes.convertEntitiesToPojo(obj);
const body = encoder.encode(JSON.stringify(parsedObj)).buffer as ArrayBuffer;
return {
status,
headers: { "content-type": "application/json; charset=utf-8", ...extraHeaders },
body
};
}
/**
* Convert a string to a text response.
*/
function textResponse(text: string, status = 200, extraHeaders: Record<string, string> = {}): BrowserResponse {
const body = encoder.encode(text).buffer as ArrayBuffer;
return {
status,
headers: { "content-type": "text/plain; charset=utf-8", ...extraHeaders },
body
};
}
/**
* Browser router class that handles route registration and dispatching.
*/
export class BrowserRouter {
private routes: Route[] = [];
/**
* Register a route handler.
*/
register(method: string, path: string, handler: RouteHandler): void {
const { pattern, paramNames } = pathToRegex(path);
this.routes.push({
method: method.toUpperCase(),
pattern,
paramNames,
handler
});
}
/**
* Convenience methods for common HTTP methods.
*/
get(path: string, handler: RouteHandler): void {
this.register('GET', path, handler);
}
post(path: string, handler: RouteHandler): void {
this.register('POST', path, handler);
}
put(path: string, handler: RouteHandler): void {
this.register('PUT', path, handler);
}
patch(path: string, handler: RouteHandler): void {
this.register('PATCH', path, handler);
}
delete(path: string, handler: RouteHandler): void {
this.register('DELETE', path, handler);
}
/**
* Dispatch a request to the appropriate handler.
*/
async dispatch(method: string, urlString: string, body?: unknown, headers?: Record<string, string>): Promise<BrowserResponse> {
const url = new URL(urlString);
const path = url.pathname;
const query = parseQuery(url.search);
const upperMethod = method.toUpperCase();
// Parse JSON body if it's an ArrayBuffer and content-type suggests JSON
let parsedBody = body;
if (body instanceof ArrayBuffer && headers) {
const contentType = headers['content-type'] || headers['Content-Type'] || '';
if (contentType.includes('application/json')) {
try {
const text = new TextDecoder().decode(body);
if (text.trim()) {
parsedBody = JSON.parse(text);
}
} catch (e) {
console.warn('[Router] Failed to parse JSON body:', e);
// Keep original body if JSON parsing fails
parsedBody = body;
}
}
}
// Find matching route
for (const route of this.routes) {
if (route.method !== upperMethod) continue;
const match = path.match(route.pattern);
if (!match) continue;
// Extract path parameters
const params: Record<string, string> = {};
for (let i = 0; i < route.paramNames.length; i++) {
params[route.paramNames[i]] = decodeURIComponent(match[i + 1]);
}
const request: BrowserRequest = {
method: upperMethod,
url: urlString,
path,
params,
query,
body: parsedBody
};
try {
const result = await getContext().init(async () => await route.handler(request));
return this.formatResult(result);
} catch (error) {
return this.formatError(error, `Error handling ${method} ${path}`);
}
}
// No route matched
return textResponse(`Not found: ${method} ${path}`, 404);
}
/**
* Format a handler result into a response.
* Follows the same patterns as the server's apiResultHandler.
*/
private formatResult(result: unknown): BrowserResponse {
// Handle [statusCode, response] format
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
const [statusCode, response] = result;
return jsonResponse(response, statusCode);
}
// Handle undefined (no content) - 204 should have no body
if (result === undefined) {
return {
status: 204,
headers: {},
body: null
};
}
// Default: JSON response with 200
return jsonResponse(result, 200);
}
/**
* Format an error into a response.
*/
private formatError(error: unknown, context: string): BrowserResponse {
console.error('[Router] Handler error:', context, error);
// Check for known error types
if (error && typeof error === 'object') {
const err = error as { constructor?: { name?: string }; message?: string };
if (err.constructor?.name === 'NotFoundError') {
return jsonResponse({ message: err.message || 'Not found' }, 404);
}
if (err.constructor?.name === 'ValidationError') {
return jsonResponse({ message: err.message || 'Validation error' }, 400);
}
}
// Generic error
const message = error instanceof Error ? error.message : String(error);
return jsonResponse({ message }, 500);
}
}
/**
* Create a new router instance.
*/
export function createRouter(): BrowserRouter {
return new BrowserRouter();
}

View File

@@ -0,0 +1,101 @@
/**
* Browser route definitions.
* This integrates with the shared route builder from @triliumnext/core.
*/
import { BootstrapDefinition } from '@triliumnext/commons';
import { getSharedBootstrapItems, getSql, routes } from '@triliumnext/core';
import packageJson from '../../package.json' with { type: 'json' };
import { type BrowserRequest,BrowserRouter } from './browser_router';
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
/**
* Wraps a core route handler to work with the BrowserRouter.
* Core handlers expect an Express-like request object with params, query, and body.
*/
function wrapHandler(handler: (req: any) => unknown, transactional: boolean) {
return (req: BrowserRequest) => {
// Create an Express-like request object
const expressLikeReq = {
params: req.params,
query: req.query,
body: req.body
};
if (transactional) {
return getSql().transactional(() => handler(expressLikeReq));
}
return handler(expressLikeReq);
};
}
/**
* Creates an apiRoute function compatible with buildSharedApiRoutes.
* This bridges the core's route registration to the BrowserRouter.
*/
function createApiRoute(router: BrowserRouter, transactional: boolean) {
return (method: HttpMethod, path: string, handler: (req: any) => unknown) => {
router.register(method, path, wrapHandler(handler, transactional));
};
}
/**
* Register all API routes on the browser router using the shared builder.
*
* @param router - The browser router instance
*/
export function registerRoutes(router: BrowserRouter): void {
const apiRoute = createApiRoute(router, true);
routes.buildSharedApiRoutes({
apiRoute,
asyncApiRoute: createApiRoute(router, false)
});
apiRoute('get', '/bootstrap', bootstrapRoute);
// Dummy routes for compatibility.
apiRoute("get", "/api/script/widgets", () => []);
apiRoute("get", "/api/script/startup", () => []);
apiRoute("get", "/api/system-checks", () => ({ isCpuArchMismatch: false }));
apiRoute("get", "/api/autocomplete", () => []);
}
function bootstrapRoute() {
const assetPath = ".";
return {
...getSharedBootstrapItems(assetPath),
appPath: assetPath,
device: false, // Let the client detect device type.
csrfToken: "dummy-csrf-token",
themeCssUrl: false,
themeUseNextAsBase: "next",
triliumVersion: packageJson.version,
baseApiUrl: "../api/",
headingStyle: "plain",
layoutOrientation: "vertical",
platform: "web",
isDev: import.meta.env.DEV,
isMainWindow: true,
isElectron: false,
isStandalone: true,
hasNativeTitleBar: false,
hasBackgroundEffects: false,
// TODO: Fill properly
currentLocale: { id: "en", name: "English", rtl: false },
isRtl: false,
instanceName: null,
appCssNoteIds: [],
TRILIUM_SAFE_MODE: false
} satisfies BootstrapDefinition;
}
/**
* Create and configure a router with all routes registered.
*/
export function createConfiguredRouter(): BrowserRouter {
const router = new BrowserRouter();
registerRoutes(router);
return router;
}

View File

@@ -0,0 +1,77 @@
import { ExecutionContext } from "@triliumnext/core";
/**
* Browser execution context implementation.
*
* Handles per-request context isolation with support for fire-and-forget async operations
* using a context stack and grace-period cleanup to allow unawaited promises to complete.
*/
export default class BrowserExecutionContext implements ExecutionContext {
private contextStack: Map<string, any>[] = [];
private cleanupTimers = new WeakMap<Map<string, any>, ReturnType<typeof setTimeout>>();
private readonly CLEANUP_GRACE_PERIOD = 1000; // 1 second for fire-and-forget operations
private getCurrentContext(): Map<string, any> {
if (this.contextStack.length === 0) {
throw new Error("ExecutionContext not initialized");
}
return this.contextStack[this.contextStack.length - 1];
}
get<T = any>(key: string): T {
return this.getCurrentContext().get(key);
}
set(key: string, value: any): void {
this.getCurrentContext().set(key, value);
}
reset(): void {
this.contextStack = [];
}
init<T>(callback: () => T): T {
const context = new Map<string, any>();
this.contextStack.push(context);
// Cancel any pending cleanup timer for this context
const existingTimer = this.cleanupTimers.get(context);
if (existingTimer) {
clearTimeout(existingTimer);
this.cleanupTimers.delete(context);
}
try {
const result = callback();
// If the result is a Promise
if (result && typeof result === 'object' && 'then' in result && 'catch' in result) {
const promise = result as unknown as Promise<any>;
return promise.finally(() => {
this.scheduleContextCleanup(context);
}) as T;
} else {
// For synchronous results, schedule delayed cleanup to allow fire-and-forget operations
this.scheduleContextCleanup(context);
return result;
}
} catch (error) {
// Always clean up on error with grace period
this.scheduleContextCleanup(context);
throw error;
}
}
private scheduleContextCleanup(context: Map<string, any>): void {
const timer = setTimeout(() => {
// Remove from stack if still present
const index = this.contextStack.indexOf(context);
if (index !== -1) {
this.contextStack.splice(index, 1);
}
this.cleanupTimers.delete(context);
}, this.CLEANUP_GRACE_PERIOD);
this.cleanupTimers.set(context, timer);
}
}

View File

@@ -0,0 +1,145 @@
import type { CryptoProvider } from "@triliumnext/core";
import { sha1 } from "js-sha1";
import { sha512 } from "js-sha512";
interface Cipher {
update(data: Uint8Array): Uint8Array;
final(): Uint8Array;
}
const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/**
* Crypto provider for browser environments using the Web Crypto API.
*/
export default class BrowserCryptoProvider implements CryptoProvider {
createHash(algorithm: "sha1" | "sha512", content: string | Uint8Array): Uint8Array {
const data = typeof content === "string" ? content :
new TextDecoder().decode(content);
const hexHash = algorithm === "sha1" ? sha1(data) : sha512(data);
// Convert hex string to Uint8Array
const bytes = new Uint8Array(hexHash.length / 2);
for (let i = 0; i < hexHash.length; i += 2) {
bytes[i / 2] = parseInt(hexHash.substr(i, 2), 16);
}
return bytes;
}
createCipheriv(algorithm: "aes-128-cbc", key: Uint8Array, iv: Uint8Array): Cipher {
// Web Crypto API doesn't support streaming cipher like Node.js
// We need to implement a wrapper that collects data and encrypts on final()
return new WebCryptoCipher(algorithm, key, iv, "encrypt");
}
createDecipheriv(algorithm: "aes-128-cbc", key: Uint8Array, iv: Uint8Array): Cipher {
return new WebCryptoCipher(algorithm, key, iv, "decrypt");
}
randomBytes(size: number): Uint8Array {
const bytes = new Uint8Array(size);
crypto.getRandomValues(bytes);
return bytes;
}
randomString(length: number): string {
const bytes = this.randomBytes(length);
let result = "";
for (let i = 0; i < length; i++) {
result += CHARS[bytes[i] % CHARS.length];
}
return result;
}
}
/**
* A cipher implementation that wraps Web Crypto API.
* Note: This buffers all data until final() is called, which differs from
* Node.js's streaming cipher behavior.
*/
class WebCryptoCipher implements Cipher {
private chunks: Uint8Array[] = [];
private algorithm: string;
private key: Uint8Array;
private iv: Uint8Array;
private mode: "encrypt" | "decrypt";
private finalized = false;
constructor(
algorithm: "aes-128-cbc",
key: Uint8Array,
iv: Uint8Array,
mode: "encrypt" | "decrypt"
) {
this.algorithm = algorithm;
this.key = key;
this.iv = iv;
this.mode = mode;
}
update(data: Uint8Array): Uint8Array {
if (this.finalized) {
throw new Error("Cipher has already been finalized");
}
// Buffer the data - Web Crypto doesn't support streaming
this.chunks.push(data);
// Return empty array since we process everything in final()
return new Uint8Array(0);
}
final(): Uint8Array {
if (this.finalized) {
throw new Error("Cipher has already been finalized");
}
this.finalized = true;
// Web Crypto API is async, but we need sync behavior
// This is a fundamental limitation that requires architectural changes
// For now, throw an error directing users to use async methods
throw new Error(
"Synchronous cipher finalization not available in browser. " +
"The Web Crypto API is async-only. Use finalizeAsync() instead."
);
}
/**
* Async version that actually performs the encryption/decryption.
*/
async finalizeAsync(): Promise<Uint8Array> {
if (this.finalized) {
throw new Error("Cipher has already been finalized");
}
this.finalized = true;
// Concatenate all chunks
const totalLength = this.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const data = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of this.chunks) {
data.set(chunk, offset);
offset += chunk.length;
}
// Copy key and iv to ensure they're plain ArrayBuffer-backed
const keyBuffer = new Uint8Array(this.key);
const ivBuffer = new Uint8Array(this.iv);
// Import the key
const cryptoKey = await crypto.subtle.importKey(
"raw",
keyBuffer,
{ name: "AES-CBC" },
false,
[this.mode]
);
// Perform encryption/decryption
const result = this.mode === "encrypt"
? await crypto.subtle.encrypt({ name: "AES-CBC", iv: ivBuffer }, cryptoKey, data)
: await crypto.subtle.decrypt({ name: "AES-CBC", iv: ivBuffer }, cryptoKey, data);
return new Uint8Array(result);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,90 @@
import type { WebSocketMessage } from "@triliumnext/commons";
import type { MessagingProvider, MessageHandler } from "@triliumnext/core";
/**
* Messaging provider for browser Worker environments.
*
* This provider uses the Worker's postMessage API to communicate
* with the main thread. It's designed to be used inside a Web Worker
* that runs the core services.
*
* Message flow:
* - Outbound (worker → main): Uses self.postMessage() with type: "WS_MESSAGE"
* - Inbound (main → worker): Listens to onmessage for type: "WS_MESSAGE"
*/
export default class WorkerMessagingProvider implements MessagingProvider {
private messageHandlers: MessageHandler[] = [];
private isDisposed = false;
constructor() {
// Listen for incoming messages from the main thread
self.addEventListener("message", this.handleIncomingMessage);
}
private handleIncomingMessage = (event: MessageEvent) => {
if (this.isDisposed) return;
const { type, message } = event.data || {};
if (type === "WS_MESSAGE" && message) {
// Dispatch to all registered handlers
for (const handler of this.messageHandlers) {
try {
handler(message as WebSocketMessage);
} catch (e) {
console.error("[WorkerMessagingProvider] Error in message handler:", e);
}
}
}
};
/**
* Send a message to all clients (in this case, the main thread).
* The main thread is responsible for further distribution if needed.
*/
sendMessageToAllClients(message: WebSocketMessage): void {
if (this.isDisposed) {
console.warn("[WorkerMessagingProvider] Cannot send message - provider is disposed");
return;
}
try {
self.postMessage({
type: "WS_MESSAGE",
message
});
} catch (e) {
console.error("[WorkerMessagingProvider] Error sending message:", e);
}
}
/**
* Subscribe to incoming messages from the main thread.
*/
onMessage(handler: MessageHandler): () => void {
this.messageHandlers.push(handler);
return () => {
this.messageHandlers = this.messageHandlers.filter(h => h !== handler);
};
}
/**
* Get the number of connected "clients".
* In worker context, there's always exactly 1 client (the main thread).
*/
getClientCount(): number {
return this.isDisposed ? 0 : 1;
}
/**
* Clean up resources.
*/
dispose(): void {
if (this.isDisposed) return;
this.isDisposed = true;
self.removeEventListener("message", this.handleIncomingMessage);
this.messageHandlers = [];
}
}

View File

@@ -0,0 +1,637 @@
import { type BindableValue, default as sqlite3InitModule } from "@sqlite.org/sqlite-wasm";
import type { DatabaseProvider, RunResult, Statement, Transaction } from "@triliumnext/core";
import demoDbSql from "./db.sql?raw";
// Type definitions for SQLite WASM (the library doesn't export these directly)
type Sqlite3Module = Awaited<ReturnType<typeof sqlite3InitModule>>;
type Sqlite3Database = InstanceType<Sqlite3Module["oo1"]["DB"]>;
type Sqlite3PreparedStatement = ReturnType<Sqlite3Database["prepare"]>;
/**
* Wraps an SQLite WASM PreparedStatement to match the Statement interface
* expected by trilium-core.
*/
class WasmStatement implements Statement {
private isRawMode = false;
private isPluckMode = false;
private isFinalized = false;
constructor(
private stmt: Sqlite3PreparedStatement,
private db: Sqlite3Database,
private sqlite3: Sqlite3Module,
private sql: string
) {}
run(...params: unknown[]): RunResult {
if (this.isFinalized) {
throw new Error("Cannot call run() on finalized statement");
}
this.bindParams(params);
try {
// Use step() and then reset instead of stepFinalize()
// This allows the statement to be reused
this.stmt.step();
const changes = this.db.changes();
// Get the last insert row ID using the C API
const lastInsertRowid = this.db.pointer ? this.sqlite3.capi.sqlite3_last_insert_rowid(this.db.pointer) : 0;
this.stmt.reset();
return {
changes,
lastInsertRowid: typeof lastInsertRowid === "bigint" ? Number(lastInsertRowid) : lastInsertRowid
};
} catch (e) {
// Reset on error to allow reuse
this.stmt.reset();
throw e;
}
}
get(params: unknown): unknown {
if (this.isFinalized) {
throw new Error("Cannot call get() on finalized statement");
}
this.bindParams(Array.isArray(params) ? params : params !== undefined ? [params] : []);
try {
if (this.stmt.step()) {
if (this.isPluckMode) {
// In pluck mode, return only the first column value
const row = this.stmt.get([]);
return Array.isArray(row) && row.length > 0 ? row[0] : undefined;
}
return this.isRawMode ? this.stmt.get([]) : this.stmt.get({});
}
return undefined;
} finally {
this.stmt.reset();
}
}
all(...params: unknown[]): unknown[] {
if (this.isFinalized) {
throw new Error("Cannot call all() on finalized statement");
}
this.bindParams(params);
const results: unknown[] = [];
try {
while (this.stmt.step()) {
if (this.isPluckMode) {
// In pluck mode, return only the first column value for each row
const row = this.stmt.get([]);
if (Array.isArray(row) && row.length > 0) {
results.push(row[0]);
}
} else {
results.push(this.isRawMode ? this.stmt.get([]) : this.stmt.get({}));
}
}
return results;
} finally {
this.stmt.reset();
}
}
iterate(...params: unknown[]): IterableIterator<unknown> {
if (this.isFinalized) {
throw new Error("Cannot call iterate() on finalized statement");
}
this.bindParams(params);
const stmt = this.stmt;
const isRaw = this.isRawMode;
const isPluck = this.isPluckMode;
return {
[Symbol.iterator]() {
return this;
},
next(): IteratorResult<unknown> {
if (stmt.step()) {
if (isPluck) {
const row = stmt.get([]);
const value = Array.isArray(row) && row.length > 0 ? row[0] : undefined;
return { value, done: false };
}
return { value: isRaw ? stmt.get([]) : stmt.get({}), done: false };
}
stmt.reset();
return { value: undefined, done: true };
}
};
}
raw(toggleState?: boolean): this {
// In raw mode, rows are returned as arrays instead of objects
// If toggleState is undefined, enable raw mode (better-sqlite3 behavior)
this.isRawMode = toggleState !== undefined ? toggleState : true;
return this;
}
pluck(toggleState?: boolean): this {
// In pluck mode, only the first column of each row is returned
// If toggleState is undefined, enable pluck mode (better-sqlite3 behavior)
this.isPluckMode = toggleState !== undefined ? toggleState : true;
return this;
}
/**
* Detect the prefix used for a parameter name in the SQL query.
* SQLite supports @name, :name, and $name parameter styles.
* Returns the prefix character, or ':' as default if not found.
*/
private detectParamPrefix(paramName: string): string {
// Search for the parameter with each possible prefix
for (const prefix of [':', '@', '$']) {
// Use word boundary to avoid partial matches
const pattern = new RegExp(`\\${prefix}${paramName}(?![a-zA-Z0-9_])`);
if (pattern.test(this.sql)) {
return prefix;
}
}
// Default to ':' if not found (most common in Trilium)
return ':';
}
private bindParams(params: unknown[]): void {
this.stmt.clearBindings();
if (params.length === 0) {
return;
}
// Handle single object with named parameters
if (params.length === 1 && typeof params[0] === "object" && params[0] !== null && !Array.isArray(params[0])) {
const inputBindings = params[0] as { [paramName: string]: BindableValue };
// SQLite WASM expects parameter names to include the prefix (@ : or $)
// We detect the prefix used in the SQL for each parameter
const bindings: { [paramName: string]: BindableValue } = {};
for (const [key, value] of Object.entries(inputBindings)) {
// If the key already has a prefix, use it as-is
if (key.startsWith('@') || key.startsWith(':') || key.startsWith('$')) {
bindings[key] = value;
} else {
// Detect the prefix used in the SQL and apply it
const prefix = this.detectParamPrefix(key);
bindings[`${prefix}${key}`] = value;
}
}
this.stmt.bind(bindings);
} else {
// Handle positional parameters - flatten and cast to BindableValue[]
const flatParams = params.flat() as BindableValue[];
if (flatParams.length > 0) {
this.stmt.bind(flatParams);
}
}
}
finalize(): void {
if (!this.isFinalized) {
try {
this.stmt.finalize();
} catch (e) {
console.warn("Error finalizing SQLite statement:", e);
} finally {
this.isFinalized = true;
}
}
}
}
/**
* SQLite database provider for browser environments using SQLite WASM.
*
* This provider wraps the official @sqlite.org/sqlite-wasm package to provide
* a DatabaseProvider implementation compatible with trilium-core.
*
* @example
* ```typescript
* const provider = new BrowserSqlProvider();
* await provider.initWasm(); // Initialize SQLite WASM module
* provider.loadFromMemory(); // Open an in-memory database
* // or
* provider.loadFromBuffer(existingDbBuffer); // Load from existing data
* ```
*/
export default class BrowserSqlProvider implements DatabaseProvider {
private db?: Sqlite3Database;
private sqlite3?: Sqlite3Module;
private _inTransaction = false;
private initPromise?: Promise<void>;
private initError?: Error;
private statementCache: Map<string, WasmStatement> = new Map();
// OPFS state tracking
private opfsDbPath?: string;
/**
* Get the SQLite WASM module version info.
* Returns undefined if the module hasn't been initialized yet.
*/
get version(): { libVersion: string; sourceId: string } | undefined {
return this.sqlite3?.version;
}
/**
* Initialize the SQLite WASM module.
* This must be called before using any database operations.
* Safe to call multiple times - subsequent calls return the same promise.
*
* @returns A promise that resolves when the module is initialized
* @throws Error if initialization fails
*/
async initWasm(): Promise<void> {
// Return existing promise if already initializing/initialized
if (this.initPromise) {
return this.initPromise;
}
// Fail fast if we already tried and failed
if (this.initError) {
throw this.initError;
}
this.initPromise = this.doInitWasm();
return this.initPromise;
}
private async doInitWasm(): Promise<void> {
try {
console.log("[BrowserSqlProvider] Initializing SQLite WASM...");
const startTime = performance.now();
this.sqlite3 = await sqlite3InitModule({
print: console.log,
printErr: console.error,
});
const initTime = performance.now() - startTime;
console.log(
`[BrowserSqlProvider] SQLite WASM initialized in ${initTime.toFixed(2)}ms:`,
this.sqlite3.version.libVersion
);
} catch (e) {
this.initError = e instanceof Error ? e : new Error(String(e));
console.error("[BrowserSqlProvider] SQLite WASM initialization failed:", this.initError);
throw this.initError;
}
}
/**
* Check if the SQLite WASM module has been initialized.
*/
get isInitialized(): boolean {
return this.sqlite3 !== undefined;
}
// ==================== OPFS Support ====================
/**
* Check if the OPFS VFS is available.
* This requires:
* - Running in a Worker context
* - Browser support for OPFS APIs
* - COOP/COEP headers sent by the server (for SharedArrayBuffer)
*
* @returns true if OPFS VFS is available for use
*/
isOpfsAvailable(): boolean {
this.ensureSqlite3();
// SQLite WASM automatically installs the OPFS VFS if the environment supports it
// We can check for its presence via sqlite3_vfs_find or the OpfsDb class
return this.sqlite3!.oo1.OpfsDb !== undefined;
}
/**
* Load or create a database stored in OPFS for persistent storage.
* The database will persist across browser sessions.
*
* Requires COOP/COEP headers to be set by the server:
* - Cross-Origin-Opener-Policy: same-origin
* - Cross-Origin-Embedder-Policy: require-corp
*
* @param path - The path for the database file in OPFS (e.g., "/trilium.db")
* Paths without a leading slash are treated as relative to OPFS root.
* Leading directories are created automatically.
* @param options - Additional options
* @throws Error if OPFS VFS is not available
*
* @example
* ```typescript
* const provider = new BrowserSqlProvider();
* await provider.initWasm();
* if (provider.isOpfsAvailable()) {
* provider.loadFromOpfs("/my-database.db");
* } else {
* console.warn("OPFS not available, using in-memory database");
* provider.loadFromMemory();
* }
* ```
*/
loadFromOpfs(path: string, options: { createIfNotExists?: boolean } = {}): void {
this.ensureSqlite3();
if (!this.isOpfsAvailable()) {
throw new Error(
"OPFS VFS is not available. This requires:\n" +
"1. Running in a Worker context\n" +
"2. Browser support for OPFS (Chrome 102+, Firefox 111+, Safari 17+)\n" +
"3. COOP/COEP headers from the server:\n" +
" Cross-Origin-Opener-Policy: same-origin\n" +
" Cross-Origin-Embedder-Policy: require-corp"
);
}
console.log(`[BrowserSqlProvider] Loading database from OPFS: ${path}`);
const startTime = performance.now();
try {
// OpfsDb automatically creates directories in the path
// Mode 'c' = create if not exists
const mode = options.createIfNotExists !== false ? 'c' : '';
this.db = new this.sqlite3!.oo1.OpfsDb(path, mode);
this.opfsDbPath = path;
// Configure the database for OPFS
// Note: WAL mode requires exclusive locking in OPFS environment
this.db.exec("PRAGMA journal_mode = DELETE");
this.db.exec("PRAGMA synchronous = NORMAL");
const loadTime = performance.now() - startTime;
console.log(`[BrowserSqlProvider] OPFS database loaded in ${loadTime.toFixed(2)}ms`);
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e));
console.error(`[BrowserSqlProvider] Failed to load OPFS database: ${error.message}`);
throw error;
}
}
/**
* Check if the currently open database is stored in OPFS.
*/
get isUsingOpfs(): boolean {
return this.opfsDbPath !== undefined;
}
/**
* Get the OPFS path of the currently open database.
* Returns undefined if not using OPFS.
*/
get currentOpfsPath(): string | undefined {
return this.opfsDbPath;
}
/**
* Check if the database has been initialized with a schema.
* This is a simple sanity check that looks for the existence of core tables.
*
* @returns true if the database appears to be initialized
*/
isDbInitialized(): boolean {
this.ensureDb();
// Check if the 'notes' table exists (a core table that must exist in an initialized DB)
const tableExists = this.db!.selectValue(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'notes'"
);
return tableExists !== undefined;
}
// ==================== End OPFS Support ====================
loadFromFile(_path: string, _isReadOnly: boolean): void {
// Browser environment doesn't have direct file system access.
// Use OPFS for persistent storage.
throw new Error(
"loadFromFile is not supported in browser environment. " +
"Use loadFromMemory() for temporary databases, loadFromBuffer() to load from data, " +
"or loadFromOpfs() for persistent storage."
);
}
/**
* Create an empty in-memory database.
* Data will be lost when the page is closed.
*
* For persistent storage, use loadFromOpfs() instead.
* To load demo data, call initializeDemoDatabase() after this.
*/
loadFromMemory(): void {
this.ensureSqlite3();
console.log("[BrowserSqlProvider] Creating in-memory database...");
const startTime = performance.now();
this.db = new this.sqlite3!.oo1.DB(":memory:", "c");
this.opfsDbPath = undefined; // Not using OPFS
this.db.exec("PRAGMA journal_mode = WAL");
// Initialize with demo data for in-memory databases
// (since they won't persist anyway)
this.initializeDemoDatabase();
const loadTime = performance.now() - startTime;
console.log(`[BrowserSqlProvider] In-memory database created in ${loadTime.toFixed(2)}ms`);
}
/**
* Initialize the database with demo/starter data.
* This should only be called once when creating a new database.
*
* For OPFS databases, this is called automatically only if the database
* doesn't already exist.
*/
initializeDemoDatabase(): void {
this.ensureDb();
console.log("[BrowserSqlProvider] Initializing database with demo data...");
const startTime = performance.now();
this.db!.exec(demoDbSql);
const loadTime = performance.now() - startTime;
console.log(`[BrowserSqlProvider] Demo data loaded in ${loadTime.toFixed(2)}ms`);
}
loadFromBuffer(buffer: Uint8Array): void {
this.ensureSqlite3();
// SQLite WASM can deserialize a database from a byte array
const p = this.sqlite3!.wasm.allocFromTypedArray(buffer);
try {
this.db = new this.sqlite3!.oo1.DB({ filename: ":memory:", flags: "c" });
this.opfsDbPath = undefined; // Not using OPFS
const rc = this.sqlite3!.capi.sqlite3_deserialize(
this.db.pointer!,
"main",
p,
buffer.byteLength,
buffer.byteLength,
this.sqlite3!.capi.SQLITE_DESERIALIZE_FREEONCLOSE |
this.sqlite3!.capi.SQLITE_DESERIALIZE_RESIZEABLE
);
if (rc !== 0) {
throw new Error(`Failed to deserialize database: ${rc}`);
}
} catch (e) {
this.sqlite3!.wasm.dealloc(p);
throw e;
}
}
backup(_destinationFile: string): void {
// In browser, we can serialize the database to a byte array
// For actual file backup, we'd need to use File System Access API or download
throw new Error(
"backup to file is not supported in browser environment. " +
"Use serialize() to get the database as a Uint8Array instead."
);
}
/**
* Serialize the database to a byte array.
* This can be used to save the database to IndexedDB, download it, etc.
*/
serialize(): Uint8Array {
this.ensureDb();
// Use the convenience wrapper which handles all the memory management
return this.sqlite3!.capi.sqlite3_js_db_export(this.db!);
}
prepare(query: string): Statement {
this.ensureDb();
// Check if we already have this statement cached
if (this.statementCache.has(query)) {
return this.statementCache.get(query)!;
}
// Create new statement and cache it
const stmt = this.db!.prepare(query);
const wasmStatement = new WasmStatement(stmt, this.db!, this.sqlite3!, query);
this.statementCache.set(query, wasmStatement);
return wasmStatement;
}
transaction<T>(func: (statement: Statement) => T): Transaction {
this.ensureDb();
const self = this;
let savepointCounter = 0;
// Helper function to execute within a transaction
const executeTransaction = (beginStatement: string, ...args: unknown[]): T => {
// If we're already in a transaction, use SAVEPOINTs for nesting
// This mimics better-sqlite3's behavior
if (self._inTransaction) {
const savepointName = `sp_${++savepointCounter}_${Date.now()}`;
self.db!.exec(`SAVEPOINT ${savepointName}`);
try {
const result = func.apply(null, args as [Statement]);
self.db!.exec(`RELEASE SAVEPOINT ${savepointName}`);
return result;
} catch (e) {
self.db!.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`);
throw e;
}
}
// Not in a transaction, start a new one
self._inTransaction = true;
self.db!.exec(beginStatement);
try {
const result = func.apply(null, args as [Statement]);
self.db!.exec("COMMIT");
return result;
} catch (e) {
self.db!.exec("ROLLBACK");
throw e;
} finally {
self._inTransaction = false;
}
};
// Create the transaction function that acts like better-sqlite3's Transaction interface
// In better-sqlite3, the transaction function is callable and has .deferred(), .immediate(), etc.
const transactionWrapper = Object.assign(
// Default call executes with BEGIN (same as immediate)
(...args: unknown[]): T => executeTransaction("BEGIN", ...args),
{
// Deferred transaction - locks acquired on first data access
deferred: (...args: unknown[]): T => executeTransaction("BEGIN DEFERRED", ...args),
// Immediate transaction - acquires write lock immediately
immediate: (...args: unknown[]): T => executeTransaction("BEGIN IMMEDIATE", ...args),
// Exclusive transaction - exclusive lock
exclusive: (...args: unknown[]): T => executeTransaction("BEGIN EXCLUSIVE", ...args),
// Default is same as calling directly
default: (...args: unknown[]): T => executeTransaction("BEGIN", ...args)
}
);
return transactionWrapper as unknown as Transaction;
}
get inTransaction(): boolean {
return this._inTransaction;
}
exec(query: string): void {
this.ensureDb();
this.db!.exec(query);
}
close(): void {
// Clean up all cached statements first
for (const statement of this.statementCache.values()) {
try {
statement.finalize();
} catch (e) {
// Ignore errors during cleanup
console.warn("Error finalizing statement during cleanup:", e);
}
}
this.statementCache.clear();
if (this.db) {
this.db.close();
this.db = undefined;
}
// Reset OPFS state
this.opfsDbPath = undefined;
}
/**
* Get the number of rows changed by the last INSERT, UPDATE, or DELETE statement.
*/
changes(): number {
this.ensureDb();
return this.db!.changes();
}
/**
* Check if the database is currently open.
*/
isOpen(): boolean {
return this.db !== undefined && this.db.isOpen();
}
private ensureSqlite3(): void {
if (!this.sqlite3) {
throw new Error(
"SQLite WASM module not initialized. Call initialize() first with the sqlite3 module."
);
}
}
private ensureDb(): void {
this.ensureSqlite3();
if (!this.db) {
throw new Error("Database not opened. Call loadFromMemory(), loadFromBuffer(), or loadFromOpfs() first.");
}
}
}

View File

@@ -0,0 +1,16 @@
import { LOCALE_IDS } from "@triliumnext/commons";
import type i18next from "i18next";
import I18NextHttpBackend from "i18next-http-backend";
export default async function translationProvider(i18nextInstance: typeof i18next, locale: LOCALE_IDS) {
await i18nextInstance.use(I18NextHttpBackend).init({
lng: locale,
fallbackLng: "en",
ns: "server",
backend: {
loadPath: `${import.meta.resolve("../server-assets/translations")}/{{lng}}/{{ns}}.json`
},
returnEmptyString: false,
debug: true
});
}

View File

@@ -0,0 +1,88 @@
import LocalServerWorker from "./local-server-worker?worker";
let localWorker: Worker | null = null;
const pending = new Map();
export function startLocalServerWorker() {
if (localWorker) return localWorker;
localWorker = new LocalServerWorker();
// Handle worker errors during initialization
localWorker.onerror = (event) => {
console.error("[LocalBridge] Worker error:", event);
// Reject all pending requests
for (const [, resolver] of pending) {
resolver.reject(new Error(`Worker error: ${event.message}`));
}
pending.clear();
};
localWorker.onmessage = (event) => {
const msg = event.data;
// Handle worker error reports
if (msg?.type === "WORKER_ERROR") {
console.error("[LocalBridge] Worker reported error:", msg.error);
// Reject all pending requests with the error
for (const [, resolver] of pending) {
resolver.reject(new Error(msg.error?.message || "Unknown worker error"));
}
pending.clear();
return;
}
if (!msg || msg.type !== "LOCAL_RESPONSE") return;
const { id, response, error } = msg;
const resolver = pending.get(id);
if (!resolver) return;
pending.delete(id);
if (error) resolver.reject(new Error(error));
else resolver.resolve(response);
};
return localWorker;
}
export function attachServiceWorkerBridge() {
navigator.serviceWorker.addEventListener("message", async (event) => {
const msg = event.data;
if (!msg || msg.type !== "LOCAL_FETCH") return;
const port = event.ports && event.ports[0];
if (!port) return;
try {
startLocalServerWorker();
const id = msg.id;
const req = msg.request;
const response = await new Promise((resolve, reject) => {
pending.set(id, { resolve, reject });
// Transfer body to worker for efficiency (if present)
localWorker.postMessage({
type: "LOCAL_REQUEST",
id,
request: req
}, req.body ? [req.body] : []);
});
port.postMessage({
type: "LOCAL_FETCH_RESPONSE",
id,
response
}, response.body ? [response.body] : []);
} catch (e) {
port.postMessage({
type: "LOCAL_FETCH_RESPONSE",
id: msg.id,
response: {
status: 500,
headers: { "content-type": "text/plain; charset=utf-8" },
body: new TextEncoder().encode(String(e?.message || e)).buffer
}
});
}
});
}

View File

@@ -0,0 +1,257 @@
// =============================================================================
// ERROR HANDLERS FIRST - No static imports above this!
// ES modules hoist static imports, so they execute BEFORE any code runs.
// We use dynamic imports below to ensure error handlers are registered first.
// =============================================================================
self.onerror = (message, source, lineno, colno, error) => {
const errorMsg = `[Worker] Uncaught error: ${message}\n at ${source}:${lineno}:${colno}`;
console.error(errorMsg, error);
try {
self.postMessage({
type: "WORKER_ERROR",
error: {
message: String(message),
source,
lineno,
colno,
stack: error?.stack || new Error().stack
}
});
} catch (e) {
console.error("[Worker] Failed to report error:", e);
}
return false;
};
self.onunhandledrejection = (event) => {
const reason = event.reason;
const errorMsg = `[Worker] Unhandled rejection: ${reason?.message || reason}`;
console.error(errorMsg, reason);
try {
self.postMessage({
type: "WORKER_ERROR",
error: {
message: String(reason?.message || reason),
stack: reason?.stack || new Error().stack
}
});
} catch (e) {
console.error("[Worker] Failed to report rejection:", e);
}
};
console.log("[Worker] Error handlers installed, loading modules...");
// =============================================================================
// TYPE-ONLY IMPORTS (erased at runtime, safe as static imports)
// =============================================================================
import type { BrowserRouter } from './lightweight/browser_router';
// =============================================================================
// MODULE STATE (populated by dynamic imports)
// =============================================================================
let BrowserSqlProvider: typeof import('./lightweight/sql_provider').default;
let WorkerMessagingProvider: typeof import('./lightweight/messaging_provider').default;
let BrowserExecutionContext: typeof import('./lightweight/cls_provider').default;
let BrowserCryptoProvider: typeof import('./lightweight/crypto_provider').default;
let translationProvider: typeof import('./lightweight/translation_provider').default;
let createConfiguredRouter: typeof import('./lightweight/browser_routes').createConfiguredRouter;
// Instance state
let sqlProvider: InstanceType<typeof BrowserSqlProvider> | null = null;
let messagingProvider: InstanceType<typeof WorkerMessagingProvider> | null = null;
// Core module, router, and initialization state
let coreModule: typeof import("@triliumnext/core") | null = null;
let router: BrowserRouter | null = null;
let initPromise: Promise<void> | null = null;
let initError: Error | null = null;
/**
* Load all required modules using dynamic imports.
* This allows errors to be caught by our error handlers.
*/
async function loadModules(): Promise<void> {
console.log("[Worker] Loading lightweight modules...");
const [
sqlModule,
messagingModule,
clsModule,
cryptoModule,
translationModule,
routesModule
] = await Promise.all([
import('./lightweight/sql_provider.js'),
import('./lightweight/messaging_provider.js'),
import('./lightweight/cls_provider.js'),
import('./lightweight/crypto_provider.js'),
import('./lightweight/translation_provider.js'),
import('./lightweight/browser_routes.js')
]);
BrowserSqlProvider = sqlModule.default;
WorkerMessagingProvider = messagingModule.default;
BrowserExecutionContext = clsModule.default;
BrowserCryptoProvider = cryptoModule.default;
translationProvider = translationModule.default;
createConfiguredRouter = routesModule.createConfiguredRouter;
// Create instances
sqlProvider = new BrowserSqlProvider();
messagingProvider = new WorkerMessagingProvider();
console.log("[Worker] Lightweight modules loaded successfully");
}
/**
* Initialize SQLite WASM and load the core module.
* This happens once at worker startup.
*/
async function initialize(): Promise<void> {
if (initPromise) {
return initPromise; // Already initializing
}
if (initError) {
throw initError; // Failed before, don't retry
}
initPromise = (async () => {
try {
// First, load all modules dynamically
await loadModules();
console.log("[Worker] Initializing SQLite WASM...");
await sqlProvider!.initWasm();
// Try to use OPFS for persistent storage
if (sqlProvider!.isOpfsAvailable()) {
console.log("[Worker] OPFS available, loading persistent database...");
sqlProvider!.loadFromOpfs("/trilium.db");
// Check if database is initialized (schema exists)
if (!sqlProvider!.isDbInitialized()) {
console.log("[Worker] Database not initialized, loading demo data...");
sqlProvider!.initializeDemoDatabase();
console.log("[Worker] Demo data loaded");
} else {
console.log("[Worker] Existing initialized database loaded");
}
} else {
// Fall back to in-memory database (non-persistent)
console.warn("[Worker] OPFS not available, using in-memory database (data will not persist)");
console.warn("[Worker] To enable persistence, ensure COOP/COEP headers are set by the server");
sqlProvider!.loadFromMemory();
}
console.log("[Worker] Database loaded");
console.log("[Worker] Loading @triliumnext/core...");
coreModule = await import("@triliumnext/core");
await coreModule.initializeCore({
executionContext: new BrowserExecutionContext(),
crypto: new BrowserCryptoProvider(),
messaging: messagingProvider!,
translations: translationProvider,
dbConfig: {
provider: sqlProvider!,
isReadOnly: false,
onTransactionCommit: () => {
// No-op for now
},
onTransactionRollback: () => {
// No-op for now
}
}
});
console.log("[Worker] Supported routes", Object.keys(coreModule.routes));
// Create and configure the router
router = createConfiguredRouter();
console.log("[Worker] Router configured");
console.log("[Worker] Initializing becca...");
await coreModule.becca_loader.beccaLoaded;
console.log("[Worker] Initialization complete");
} catch (error) {
initError = error instanceof Error ? error : new Error(String(error));
console.error("[Worker] Initialization failed:", initError);
throw initError;
}
})();
return initPromise;
}
/**
* Ensure the worker is initialized before processing requests.
* Returns the router if initialization was successful.
*/
async function ensureInitialized() {
await initialize();
if (!router) {
throw new Error("Router not initialized");
}
return router;
}
interface LocalRequest {
method: string;
url: string;
body?: unknown;
headers?: Record<string, string>;
}
// Main dispatch
async function dispatch(request: LocalRequest) {
const url = new URL(request.url);
console.log("[Worker] Dispatch:", url.pathname);
// Ensure initialization is complete and get the router
const appRouter = await ensureInitialized();
// Dispatch to the router
return appRouter.dispatch(request.method, request.url, request.body, request.headers);
}
// Start initialization immediately when the worker loads
console.log("[Worker] Starting initialization...");
initialize().catch(err => {
console.error("[Worker] Initialization failed:", err);
// Post error to main thread
self.postMessage({
type: "WORKER_ERROR",
error: {
message: String(err?.message || err),
stack: err?.stack
}
});
});
self.onmessage = async (event) => {
const msg = event.data;
if (!msg || msg.type !== "LOCAL_REQUEST") return;
const { id, request } = msg;
try {
const response = await dispatch(request);
// Transfer body back (if any) - use options object for proper typing
(self as unknown as Worker).postMessage({
type: "LOCAL_RESPONSE",
id,
response
}, { transfer: response.body ? [response.body] : [] });
} catch (e) {
console.error("[Worker] Dispatch error:", e);
(self as unknown as Worker).postMessage({
type: "LOCAL_RESPONSE",
id,
error: String((e as Error)?.message || e)
});
}
};

View File

@@ -0,0 +1,84 @@
import { attachServiceWorkerBridge, startLocalServerWorker } from "./local-bridge.js";
async function waitForServiceWorkerControl(): Promise<void> {
if (!("serviceWorker" in navigator)) {
throw new Error("Service Worker not supported in this browser");
}
// If already controlling, we're good
if (navigator.serviceWorker.controller) {
console.log("[Bootstrap] Service worker already controlling");
return;
}
console.log("[Bootstrap] Waiting for service worker to take control...");
// Register service worker
await navigator.serviceWorker.register("./sw.js", { scope: "/" });
// Wait for it to be ready (installed + activated)
await navigator.serviceWorker.ready;
// Check if we're now controlling
if (navigator.serviceWorker.controller) {
console.log("[Bootstrap] Service worker now controlling");
return;
}
// If not controlling yet, we need to reload the page for SW to take control
// This is standard PWA behavior on first install
console.log("[Bootstrap] Service worker installed but not controlling yet - reloading page");
// Wait a tiny bit for SW to fully activate
await new Promise(resolve => setTimeout(resolve, 100));
// Reload to let SW take control
window.location.reload();
// Throw to stop execution (page will reload)
throw new Error("Reloading for service worker activation");
}
async function bootstrap() {
/* fixes https://github.com/webpack/webpack/issues/10035 */
window.global = globalThis;
try {
// 1) Start local worker ASAP (so /bootstrap is fast)
startLocalServerWorker();
// 2) Bridge SW -> local worker
attachServiceWorkerBridge();
// 3) Wait for service worker to control the page (may reload on first install)
await waitForServiceWorkerControl();
await loadScripts();
} catch (err) {
// If error is from reload, it will stop here (page reloads)
// Otherwise, show error to user
if (err instanceof Error && err.message.includes("Reloading")) {
// Page is reloading, do nothing
return;
}
console.error("[Bootstrap] Fatal error:", err);
document.body.innerHTML = `
<div style="padding: 40px; max-width: 600px; margin: 0 auto; font-family: system-ui, sans-serif;">
<h1 style="color: #d32f2f;">Failed to Initialize</h1>
<p>The application failed to start. Please check the browser console for details.</p>
<pre style="background: #f5f5f5; padding: 16px; border-radius: 4px; overflow: auto;">${err instanceof Error ? err.message : String(err)}</pre>
<button onclick="location.reload()" style="padding: 12px 24px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px;">
Reload Page
</button>
</div>
`;
document.body.style.display = "block";
}
}
async function loadScripts() {
await import("../../client/src/index.js");
}
bootstrap();

View File

@@ -0,0 +1,185 @@
// public/sw.js
const VERSION = "localserver-v1.4";
const STATIC_CACHE = `static-${VERSION}`;
// Check if running in dev mode (passed via URL parameter)
const isDev = true;
if (isDev) {
console.log('[Service Worker] Running in DEV mode - caching disabled');
}
// Adjust these to your routes:
const LOCAL_FIRST_PREFIXES = [
"/bootstrap",
"/api/",
"/sync/",
"/search/"
];
// Optional: basic precache list (keep small; you can expand later)
const PRECACHE_URLS = [
// "/",
// "/index.html",
// "/manifest.webmanifest",
// "/favicon.ico",
];
self.addEventListener("install", (event) => {
event.waitUntil((async () => {
// Skip precaching in dev mode
if (!isDev) {
const cache = await caches.open(STATIC_CACHE);
await cache.addAll(PRECACHE_URLS);
}
self.skipWaiting();
})());
});
self.addEventListener("activate", (event) => {
event.waitUntil((async () => {
// Cleanup old caches
const keys = await caches.keys();
await Promise.all(keys.map((k) => (k === STATIC_CACHE ? Promise.resolve() : caches.delete(k))));
await self.clients.claim();
})());
});
function isLocalFirst(url) {
return LOCAL_FIRST_PREFIXES.some((p) => url.pathname.startsWith(p));
}
async function cacheFirst(request) {
// In dev mode, always bypass cache
if (isDev) {
return fetch(request);
}
const cache = await caches.open(STATIC_CACHE);
const cached = await cache.match(request);
if (cached) return cached;
const fresh = await fetch(request);
// Cache only successful GETs
if (request.method === "GET" && fresh.ok) cache.put(request, fresh.clone());
return fresh;
}
async function networkFirst(request) {
// In dev mode, always bypass cache
if (isDev) {
return fetch(request);
}
const cache = await caches.open(STATIC_CACHE);
try {
const fresh = await fetch(request);
// Cache only successful GETs
if (request.method === "GET" && fresh.ok) cache.put(request, fresh.clone());
return fresh;
} catch (error) {
// Fallback to cache if network fails
const cached = await cache.match(request);
if (cached) return cached;
throw error;
}
}
async function forwardToClientLocalServer(request, clientId) {
// Find a client to handle the request (prefer the initiating client if available)
let client = clientId ? await self.clients.get(clientId) : null;
if (!client) {
const all = await self.clients.matchAll({ type: "window", includeUncontrolled: true });
client = all[0] || null;
}
// If no page is available, fall back to network
if (!client) return fetch(request);
const reqUrl = request.url;
const headersObj = {};
for (const [k, v] of request.headers.entries()) headersObj[k] = v;
const body = (request.method === "GET" || request.method === "HEAD")
? null
: await request.arrayBuffer();
const id = crypto.randomUUID();
const channel = new MessageChannel();
const responsePromise = new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Local server timeout"));
}, 30_000);
channel.port1.onmessage = (event) => {
clearTimeout(timeout);
resolve(event.data);
};
channel.port1.onmessageerror = () => {
clearTimeout(timeout);
reject(new Error("Local server message error"));
};
});
// Send to the client with a reply port
client.postMessage({
type: "LOCAL_FETCH",
id,
request: {
url: reqUrl,
method: request.method,
headers: headersObj,
body // ArrayBuffer or null
}
}, [channel.port2]);
const localResp = await responsePromise;
if (!localResp || localResp.type !== "LOCAL_FETCH_RESPONSE" || localResp.id !== id) {
// Protocol mismatch; fall back
return fetch(request);
}
// localResp.response: { status, headers, body }
const { status, headers, body: respBody } = localResp.response;
const respHeaders = new Headers();
if (headers) {
for (const [k, v] of Object.entries(headers)) respHeaders.set(k, String(v));
}
return new Response(respBody ? respBody : null, {
status: status || 200,
headers: respHeaders
});
}
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// Only handle same-origin
if (url.origin !== self.location.origin) return;
// HTML files: network-first to ensure updates are reflected immediately
if (event.request.mode === "navigate" || url.pathname.endsWith(".html")) {
event.respondWith(networkFirst(event.request));
return;
}
// Static assets: cache-first for performance
if (event.request.method === "GET" && !isLocalFirst(url)) {
event.respondWith(cacheFirst(event.request));
return;
}
// API-ish: local-first via bridge
if (isLocalFirst(url)) {
event.respondWith(forwardToClientLocalServer(event.request, event.clientId));
return;
}
// Default
event.respondWith(fetch(event.request));
});

View File

@@ -0,0 +1,31 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
interface Window {
glob: {
assetPath: string;
themeCssUrl?: string;
themeUseNextAsBase?: string;
iconPackCss: string;
device: string;
headingStyle: string;
layoutOrientation: string;
platform: string;
isElectron: boolean;
hasNativeTitleBar: boolean;
hasBackgroundEffects: boolean;
currentLocale: {
id: string;
rtl: boolean;
};
activeDialog: any;
};
global: typeof globalThis;
}

View File

@@ -0,0 +1,26 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": [
"ES2022",
"dom",
"dom.iterable"
],
"skipLibCheck": true,
"types": [
"vite/client"
],
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"include": [
"src/**/*",
"../client/src/**/*"
],
"exclude": [
"src/**/*.spec.ts",
"src/**/*.test.ts",
"../client/src/**/*.spec.ts",
"../client/src/**/*.test.ts"
]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.spec.json" }
]
}

View File

@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"lib": [
"ES2022",
"dom",
"dom.iterable"
],
"types": [
"vitest/globals",
"happy-dom"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.test.ts"
]
}

View File

@@ -0,0 +1,188 @@
import prefresh from '@prefresh/vite';
import { join } from 'path';
import { defineConfig } from 'vite';
import { viteStaticCopy } from 'vite-plugin-static-copy';
const clientAssets = ["assets", "stylesheets", "fonts", "translations"];
const isDev = process.env.NODE_ENV === "development";
// Watch client files and trigger reload in development
const clientWatchPlugin = () => ({
name: 'client-watch',
configureServer(server: any) {
if (isDev) {
// Watch client source files (adjusted for new root)
server.watcher.add('../../client/src/**/*');
server.watcher.on('change', (file: string) => {
if (file.includes('../../client/src/')) {
server.ws.send({
type: 'full-reload'
});
}
});
}
}
});
// Always copy SQLite WASM files so they're available to the module
const sqliteWasmPlugin = viteStaticCopy({
targets: [
{
src: "../../../node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3.wasm",
dest: "assets"
},
{
src: "../../../node_modules/@sqlite.org/sqlite-wasm/sqlite-wasm/jswasm/sqlite3-opfs-async-proxy.js",
dest: "assets"
}
]
});
let plugins: any = [
sqliteWasmPlugin, // Always include SQLite WASM files
viteStaticCopy({
targets: clientAssets.map((asset) => ({
src: `../../client/src/${asset}/*`,
dest: asset
})),
// Enable watching in development
...(isDev && {
watch: {
reloadPageOnChange: true
}
})
}),
viteStaticCopy({
targets: [
{
src: "../../server/src/assets/*",
dest: "server-assets"
}
]
}),
// Watch client files for changes in development
...(isDev ? [
prefresh(),
clientWatchPlugin()
] : [])
];
if (!isDev) {
plugins = [
...plugins,
viteStaticCopy({
structured: true,
targets: [
{
src: "../../../node_modules/@excalidraw/excalidraw/dist/prod/fonts/*",
dest: "",
}
]
})
]
}
export default defineConfig(() => ({
root: join(__dirname, 'src'), // Set src as root so index.html is served from /
envDir: __dirname, // Load .env files from client-standalone directory, not src/
cacheDir: '../../../node_modules/.vite/apps/client-standalone',
base: "",
plugins,
esbuild: {
jsx: 'automatic',
jsxImportSource: 'preact',
jsxDev: isDev
},
css: {
transformer: 'lightningcss',
devSourcemap: isDev
},
publicDir: join(__dirname, 'public'),
resolve: {
alias: [
{
find: "react",
replacement: "preact/compat"
},
{
find: "react-dom",
replacement: "preact/compat"
},
{
find: "@client",
replacement: join(__dirname, "../client/src")
}
],
dedupe: [
"react",
"react-dom",
"preact",
"preact/compat",
"preact/hooks"
]
},
server: {
watch: {
// Watch workspace packages
ignored: ['!**/node_modules/@triliumnext/**'],
// Also watch client assets for live reload
usePolling: false,
interval: 100,
binaryInterval: 300
},
// Watch additional directories for changes
fs: {
allow: [
// Allow access to workspace root
'../../../',
// Explicitly allow client directory
'../../client/src/'
]
},
headers: {
// Required for SharedArrayBuffer which is needed by SQLite WASM OPFS VFS
// See: https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp"
}
},
optimizeDeps: {
exclude: ['@sqlite.org/sqlite-wasm', '@triliumnext/core']
},
worker: {
format: "es" as const
},
commonjsOptions: {
transformMixedEsModules: true,
},
build: {
target: "esnext",
outDir: join(__dirname, 'dist'),
emptyOutDir: true,
rollupOptions: {
input: {
main: join(__dirname, 'src', 'index.html'),
sw: join(__dirname, 'src', 'sw.ts'),
'local-bridge': join(__dirname, 'src', 'local-bridge.ts'),
},
output: {
entryFileNames: (chunkInfo) => {
// Service worker and other workers should be at root level
if (chunkInfo.name === 'sw') {
return '[name].js';
}
return 'src/[name].js';
},
chunkFileNames: "src/[name].js",
assetFileNames: "src/[name].[ext]"
}
}
},
test: {
environment: "happy-dom"
},
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
}
}));

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.101.3",
"version": "0.102.1",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -22,18 +22,28 @@
"@fullcalendar/interaction": "6.1.20",
"@fullcalendar/list": "6.1.20",
"@fullcalendar/multimonth": "6.1.20",
"@fullcalendar/rrule": "6.1.20",
"@fullcalendar/timegrid": "6.1.20",
"@mermaid-js/layout-elk": "0.2.0",
"@maplibre/maplibre-gl-leaflet": "0.1.3",
"@mermaid-js/layout-elk": "0.2.1",
"@mind-elixir/node-menu": "5.0.1",
"@popperjs/core": "2.11.8",
"@preact/signals": "2.8.0",
"@preact/signals": "2.8.2",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"@zumer/snapdom": "2.0.2",
"@univerjs/preset-sheets-conditional-formatting": "0.18.0",
"@univerjs/preset-sheets-core": "0.18.0",
"@univerjs/preset-sheets-data-validation": "0.18.0",
"@univerjs/preset-sheets-filter": "0.18.0",
"@univerjs/preset-sheets-find-replace": "0.18.0",
"@univerjs/preset-sheets-note": "0.18.0",
"@univerjs/preset-sheets-sort": "0.18.0",
"@univerjs/presets": "0.18.0",
"@zumer/snapdom": "2.5.0",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
@@ -41,42 +51,44 @@
"color": "5.0.3",
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.1",
"globals": "17.3.0",
"i18next": "25.8.7",
"force-graph": "1.51.2",
"globals": "17.4.0",
"i18next": "25.10.3",
"i18next-http-backend": "3.0.2",
"jquery": "4.0.0",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.28",
"knockout": "3.5.1",
"katex": "0.16.40",
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"maplibre-gl": "5.18.0",
"marked": "17.0.2",
"mermaid": "11.12.2",
"mind-elixir": "5.8.0",
"marked": "17.0.5",
"mermaid": "11.13.0",
"mind-elixir": "5.9.3",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.28.3",
"react-i18next": "16.5.4",
"preact": "10.29.0",
"react-i18next": "16.6.0",
"react-window": "2.2.7",
"reveal.js": "5.2.1",
"reveal.js": "6.0.0",
"rrule": "2.8.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
"tabulator-tables": "6.4.0",
"vanilla-js-wheel-zoom": "9.0.4"
},
"devDependencies": {
"@ckeditor/ckeditor5-inspector": "5.0.0",
"@prefresh/vite": "2.4.11",
"@prefresh/vite": "2.4.12",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.33",
"@types/jquery": "4.0.0",
"@types/leaflet": "1.9.21",
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.6.1",
"lightningcss": "1.31.1",
"copy-webpack-plugin": "14.0.0",
"happy-dom": "20.8.4",
"lightningcss": "1.32.0",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.2.0"
"vite-plugin-static-copy": "3.3.0"
}
}

View File

@@ -101,8 +101,6 @@ export type CommandMappings = {
showRevisions: CommandData & {
noteId?: string | null;
};
showLlmChat: CommandData;
createAiChat: CommandData;
showOptions: CommandData & {
section: string;
};

View File

@@ -381,6 +381,10 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
// Collections must always display a note list, even if no children.
if (note.type === "book") {
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
return false;
}
const viewType = note.getLabelValue("viewType") ?? "grid";
if (!["list", "grid"].includes(viewType)) {
return true;

View File

@@ -1,10 +1,8 @@
import dateNoteService from "../services/date_notes.js";
import froca from "../services/froca.js";
import noteCreateService from "../services/note_create.js";
import openService from "../services/open.js";
import options from "../services/options.js";
import protectedSessionService from "../services/protected_session.js";
import toastService from "../services/toast.js";
import treeService from "../services/tree.js";
import utils, { openInReusableSplit } from "../services/utils.js";
import appContext, { type CommandListenerData } from "./app_context.js";
@@ -248,34 +246,4 @@ export default class RootCommandExecutor extends Component {
}
}
async createAiChatCommand() {
try {
// Create a new AI Chat note at the root level
const rootNoteId = "root";
const result = await noteCreateService.createNote(rootNoteId, {
title: "New AI Chat",
type: "aiChat",
content: JSON.stringify({
messages: [],
title: "New AI Chat"
})
});
if (!result.note) {
toastService.showError("Failed to create AI Chat note");
return;
}
await appContext.tabManager.openTabWithNoteWithHoisting(result.note.noteId, {
activate: true
});
toastService.showMessage("Created new AI Chat note");
}
catch (e) {
console.error("Error creating AI Chat note:", e);
toastService.showError(`Failed to create AI Chat note: ${(e as Error).message}`);
}
}
}

View File

@@ -18,7 +18,7 @@ const RELATION = "relation";
* end user. Those types should be used only for checking against, they are
* not for direct use.
*/
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "aiChat";
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "spreadsheet";
export interface NotePathRecord {
isArchived: boolean;

View File

@@ -36,10 +36,37 @@ async function setupGlob() {
window.global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.glob = {
...json,
activeDialog: null
activeDialog: null,
device: json.device || getDevice()
};
}
function getDevice() {
// Respect user's manual override via URL.
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("print")) {
return "print";
} else if (urlParams.has("desktop")) {
return "desktop";
} else if (urlParams.has("mobile")) {
return "mobile";
}
const deviceCookie = document.cookie.split("; ").find(row => row.startsWith("trilium-device="))?.split("=")[1];
if (deviceCookie === "desktop" || deviceCookie === "mobile") return deviceCookie;
return isMobile() ? "mobile" : "desktop";
}
// https://stackoverflow.com/a/73731646/944162
function isMobile() {
const mQ = matchMedia?.("(pointer:coarse)");
if (mQ?.media === "(pointer:coarse)") return !!mQ.matches;
if ("orientation" in window) return true;
const userAgentsRegEx = /\b(Android|iPhone|iPad|iPod|Windows Phone|BlackBerry|webOS|IEMobile)\b/i;
return userAgentsRegEx.test(navigator.userAgent);
}
async function loadBootstrapCss() {
// We have to selectively import Bootstrap CSS based on text direction.
if (glob.isRtl) {

View File

@@ -30,6 +30,7 @@ import SpacerWidget from "../widgets/launch_bar/SpacerWidget.jsx";
import InlineTitle from "../widgets/layout/InlineTitle.jsx";
import NoteBadges from "../widgets/layout/NoteBadges.jsx";
import NoteTitleActions from "../widgets/layout/NoteTitleActions.jsx";
import StandaloneWarningBar from "../widgets/layout/StandaloneWarningBar.jsx";
import StatusBar from "../widgets/layout/StatusBar.jsx";
import NoteIconWidget from "../widgets/note_icon.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
@@ -186,6 +187,7 @@ export default class DesktopLayout {
)
)
.optChild(launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
.optChild(glob.isStandalone, <StandaloneWarningBar />)
.child(<CloseZenModeButton />)
// Desktop-specific dialogs.

View File

@@ -13,6 +13,7 @@ import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
import InlineTitle from "../widgets/layout/InlineTitle.jsx";
import NoteBadges from "../widgets/layout/NoteBadges.jsx";
import NoteTitleActions from "../widgets/layout/NoteTitleActions.jsx";
import StandaloneWarningBar from "../widgets/layout/StandaloneWarningBar";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
@@ -23,8 +24,6 @@ import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import QuickSearchWidget from "../widgets/quick_search.js";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import ScrollPadding from "../widgets/scroll_padding";
import SearchResult from "../widgets/search_result.jsx";
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
@@ -57,6 +56,7 @@ export default class MobileLayout {
.child(
new SplitNoteContainer(() =>
new NoteWrapperWidget()
.optChild(glob.isStandalone, <StandaloneWarningBar />)
.child(
new FlexContainer("row")
.class("title-row note-split-title")

View File

@@ -3,6 +3,8 @@ import options from "../services/options.js";
import zoomService from "../components/zoom.js";
import contextMenu, { type MenuItem } from "./context_menu.js";
import { t } from "../services/i18n.js";
import server from "../services/server.js";
import * as clipboardExt from "../services/clipboard_ext.js";
import type { BrowserWindow } from "electron";
import type { CommandNames, AppContext } from "../components/app_context.js";
@@ -60,6 +62,33 @@ function setupContextMenu() {
uiIcon: "bx bx-copy",
handler: () => webContents.copy()
});
items.push({
enabled: hasText,
title: t("electron_context_menu.copy-as-markdown"),
uiIcon: "bx bx-copy-alt",
handler: async () => {
const selection = window.getSelection();
if (!selection || !selection.rangeCount) return '';
const range = selection.getRangeAt(0);
const div = document.createElement('div');
div.appendChild(range.cloneContents());
const htmlContent = div.innerHTML;
if (htmlContent) {
try {
const { markdownContent } = await server.post<{ markdownContent: string }>(
"other/to-markdown",
{ htmlContent }
);
await clipboardExt.copyTextWithToast(markdownContent);
} catch (error) {
console.error("Failed to copy as markdown:", error);
}
}
}
});
}
if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === "none") {

View File

@@ -1,4 +1,4 @@
import type { GeoMouseEvent } from "../widgets/collections/geomap/map.js";
import type { LeafletMouseEvent } from "leaflet";
import appContext, { type CommandNames } from "../components/app_context.js";
import { t } from "../services/i18n.js";
@@ -16,7 +16,7 @@ function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewS
});
}
function getItems(e: ContextMenuEvent | GeoMouseEvent): MenuItem<CommandNames>[] {
function getItems(e: ContextMenuEvent | LeafletMouseEvent): MenuItem<CommandNames>[] {
const ntxId = getNtxId(e);
const isMobileSplitOpen = isMobile() && appContext.tabManager.getNoteContextById(ntxId).getMainContext().getSubContexts().length > 1;
@@ -28,7 +28,7 @@ function getItems(e: ContextMenuEvent | GeoMouseEvent): MenuItem<CommandNames>[]
];
}
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | GeoMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEvent | LeafletMouseEvent, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
if (!hoistedNoteId) {
hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId ?? null;
}
@@ -52,7 +52,7 @@ function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEv
return false;
}
function getNtxId(e: ContextMenuEvent | GeoMouseEvent) {
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
if (utils.isDesktop()) {
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
if (!subContexts) return null;

View File

@@ -2,7 +2,6 @@ import { h, VNode } from "preact";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import froca from "./froca.js";
import type { Entity } from "./frontend_script_api.js";
import { WidgetDefinitionWithType } from "./frontend_script_api_preact.js";
import { t } from "./i18n.js";
@@ -38,15 +37,18 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
export type ParentName = "left-pane" | "center-pane" | "note-detail-pane" | "right-pane";
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
export async function executeBundleWithoutErrorHandling(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
return await function () {
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
}.call(apiContext);
}
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
try {
return await function () {
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
}.call(apiContext);
} catch (e: any) {
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: e.message }));
return await executeBundleWithoutErrorHandling(bundle, originEntity, $container);
} catch (e: unknown) {
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: getErrorMessage(e) }));
logError("Widget initialization failed: ", e);
}
}

View File

@@ -1,6 +1,7 @@
import "./content_renderer.css";
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
import { h, render } from "preact";
import WheelZoom from 'vanilla-js-wheel-zoom';
import FAttachment from "../entities/fattachment.js";
@@ -15,7 +16,7 @@ import protectedSessionService from "./protected_session.js";
import protectedSessionHolder from "./protected_session_holder.js";
import renderService from "./render.js";
import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js";
import utils from "./utils.js";
import utils, { getErrorMessage } from "./utils.js";
let idCounter = 1;
@@ -53,16 +54,19 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
await renderText(entity, $renderedContent, options);
} else if (type === "code") {
await renderCode(entity, $renderedContent);
} else if (["image", "canvas", "mindMap"].includes(type)) {
} else if (["image", "canvas", "mindMap", "spreadsheet"].includes(type)) {
renderImage(entity, $renderedContent, options);
} else if (!options.tooltip && ["file", "pdf", "audio", "video"].includes(type)) {
renderFile(entity, type, $renderedContent);
await renderFile(entity, type, $renderedContent);
} else if (type === "mermaid") {
await renderMermaid(entity, $renderedContent);
} else if (type === "render" && entity instanceof FNote) {
const $content = $("<div>");
await renderService.render(entity, $content);
await renderService.render(entity, $content, (e) => {
const $error = $("<div>").addClass("admonition caution").text(typeof e === "string" ? e : getErrorMessage(e));
$content.empty().append($error);
});
$renderedContent.append($content);
} else if (type === "doc" && "noteId" in entity) {
@@ -176,7 +180,7 @@ function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery<HTMLE
imageContextMenuService.setupContextMenu($img);
}
function renderFile(entity: FNote | FAttachment, type: string, $renderedContent: JQuery<HTMLElement>) {
async function renderFile(entity: FNote | FAttachment, type: string, $renderedContent: JQuery<HTMLElement>) {
let entityType, entityId;
if (entity instanceof FNote) {
@@ -189,13 +193,17 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
throw new Error(`Can't recognize entity type of '${entity}'`);
}
const $content = $('<div style="display: flex; flex-direction: column; height: 100%;">');
const $content = $('<div style="display: flex; flex-direction: column; height: 100%; justify-content: end;">');
if (type === "pdf") {
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
$pdfPreview.attr("src", openService.getUrlForDownload(`pdfjs/web/viewer.html?file=../../api/${entityType}/${entityId}/open`));
const url = `../../api/${entityType}/${entityId}/open`;
const $viewer = $(`<div style="height: 100%">`);
const PdfViewer = (await import("../widgets/type_widgets/file/PdfViewer")).default;
render(h(PdfViewer, {pdfUrl: url, editable: false}), $viewer.get(0)!);
$content.append($viewer);
$content.append($pdfPreview);
} else if (type === "audio") {
const $audioPreview = $("<audio controls></audio>")
.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open-partial`))

View File

@@ -120,7 +120,6 @@ async function renderChildrenList($renderedContent: JQuery<HTMLElement>, note: F
return;
}
$renderedContent.css("padding", "10px");
$renderedContent.addClass("text-with-ellipsis");
// just load the first 10 child notes

View File

@@ -1,4 +1,5 @@
import { dayjs } from "@triliumnext/commons";
import type { FNoteRow } from "../entities/fnote.js";
import froca from "./froca.js";
import server from "./server.js";
@@ -14,8 +15,13 @@ async function getTodayNote() {
return await getDayNote(dayjs().format("YYYY-MM-DD"));
}
async function getDayNote(date: string) {
const note = await server.get<FNoteRow>(`special-notes/days/${date}`, "date-note");
async function getDayNote(date: string, calendarRootId?: string) {
let url = `special-notes/days/${date}`;
if (calendarRootId) {
url += `?calendarRootId=${calendarRootId}`;
}
const note = await server.get<FNoteRow>(url, "date-note");
await ws.waitForMaxKnownEntityChangeId();

View File

@@ -5,7 +5,7 @@ import { formatCodeBlocks } from "./syntax_highlight.js";
export default function renderDoc(note: FNote) {
return new Promise<JQuery<HTMLElement>>((resolve) => {
let docName = note.getLabelValue("docName");
const docName = note.getLabelValue("docName");
const $content = $("<div>");
if (docName) {
@@ -16,7 +16,7 @@ export default function renderDoc(note: FNote) {
if (status === "error") {
const fallbackUrl = getUrl(docName, "en");
$content.load(fallbackUrl, async () => {
await processContent(fallbackUrl, $content)
await processContent(fallbackUrl, $content);
resolve($content);
});
return;
@@ -37,9 +37,9 @@ async function processContent(url: string, $content: JQuery<HTMLElement>) {
const dir = url.substring(0, url.lastIndexOf("/"));
// Images are relative to the docnote but that will not work when rendered in the application since the path breaks.
$content.find("img").each((i, el) => {
$content.find("img").each((_i, el) => {
const $img = $(el);
$img.attr("src", dir + "/" + $img.attr("src"));
$img.attr("src", `${dir}/${$img.attr("src")}`);
});
formatCodeBlocks($content);
@@ -51,7 +51,17 @@ async function processContent(url: string, $content: JQuery<HTMLElement>) {
function getUrl(docNameValue: string, language: string) {
// Cannot have spaces in the URL due to how JQuery.load works.
docNameValue = docNameValue.replaceAll(" ", "%20");
const basePath = window.glob.isDev ? window.glob.assetPath + "/.." : window.glob.assetPath;
return `${basePath}/doc_notes/${language}/${docNameValue}.html`;
// The user guide is available only in English, so make sure we are requesting correctly since 404s in standalone client are treated differently.
if (docNameValue.includes("User%20Guide")) language = "en";
return `${getBasePath()}/doc_notes/${language}/${docNameValue}.html`;
}
function getBasePath() {
if (window.glob.isStandalone) {
return `server-assets`;
}
if (window.glob.isDev) {
return `${window.glob.assetPath }/..`;
}
return window.glob.assetPath;
}

View File

@@ -110,7 +110,12 @@ function processNoteChange(loadResults: LoadResults, ec: EntityChange) {
}
}
if (ec.componentId) {
// Only register as a content change if the protection status didn't change.
// When isProtected changes, the blobId change is a side effect of re-encryption,
// not a content edit. Registering it as content would cause the tree's content-only
// filter to incorrectly skip the note update (since both changes share the same
// componentId).
if (ec.componentId && note.isProtected === (ec.entity as FNoteRow).isProtected) {
loadResults.addNoteContent(note.noteId, ec.componentId);
}
}

View File

@@ -206,7 +206,7 @@ export interface Api {
* Instance name identifies particular Trilium instance. It can be useful for scripts
* if some action needs to happen on only one specific instance.
*/
getInstanceName(): string;
getInstanceName(): string | null;
/**
* @returns date in YYYY-MM-DD format

View File

@@ -24,7 +24,8 @@ export async function initLocale() {
backend: {
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
},
returnEmptyString: false
returnEmptyString: false,
showSupportNotice: false
});
await setDayjsLocale(locale);

View File

@@ -1,4 +1,5 @@
import { NoteType } from "@triliumnext/commons";
import FNote from "../entities/fnote";
import { ViewTypeOptions } from "../widgets/collections/interface";
@@ -18,7 +19,7 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
search: null,
text: null,
webView: null,
aiChat: null
spreadsheet: null
};
export const byBookType: Record<ViewTypeOptions, string | null> = {
@@ -39,6 +40,6 @@ export function getHelpUrlForNote(note: FNote | null | undefined) {
} else if (note?.hasLabel("textSnippet")) {
return "pwc194wlRzcH";
} else if (note && note.type === "book") {
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""];
}
}

View File

@@ -12,7 +12,7 @@ const SELECTED_NOTE_PATH_KEY = "data-note-path";
const SELECTED_EXTERNAL_LINK_KEY = "data-external-link";
// To prevent search lag when there are a large number of notes, set a delay based on the number of notes to avoid jitter.
const notesCount = await server.get<number>(`autocomplete/notesCount`);
const notesCount = 10000; // TODO: Replace with dynamic count from becca once available.
let debounceTimeoutId: ReturnType<typeof setTimeout>;
function getSearchDelay(notesCount: number): number {

View File

@@ -1,15 +1,17 @@
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import { AttributeRow } from "@triliumnext/commons";
import appContext from "../components/app_context.js";
import type FBranch from "../entities/fbranch.js";
import type FNote from "../entities/fnote.js";
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import protectedSessionHolder from "./protected_session_holder.js";
import server from "./server.js";
import ws from "./ws.js";
import froca from "./froca.js";
import treeService from "./tree.js";
import toastService from "./toast.js";
import { t } from "./i18n.js";
import type FNote from "../entities/fnote.js";
import type FBranch from "../entities/fbranch.js";
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import treeService from "./tree.js";
import ws from "./ws.js";
export interface CreateNoteOpts {
isProtected?: boolean;
@@ -24,6 +26,8 @@ export interface CreateNoteOpts {
target?: string;
targetBranchId?: string;
textEditor?: CKTextEditor;
/** Attributes to be set on the note. These are set atomically on note creation, so entity changes are not sent for attributes defined here. */
attributes?: Omit<AttributeRow, "noteId" | "attributeId">[];
}
interface Response {
@@ -37,7 +41,7 @@ interface DuplicateResponse {
note: FNote;
}
async function createNote(parentNotePath: string | undefined, options: CreateNoteOpts = {}) {
async function createNote(parentNotePath: string | undefined, options: CreateNoteOpts = {}, componentId?: string) {
options = Object.assign(
{
activate: true,
@@ -63,22 +67,15 @@ async function createNote(parentNotePath: string | undefined, options: CreateNot
const parentNoteId = treeService.getNoteIdFromUrl(parentNotePath);
if (options.type === "mermaid" && !options.content && !options.templateNoteId) {
options.content = `graph TD;
A-->B;
A-->C;
B-->D;
C-->D;`;
}
const { note, branch } = await server.post<Response>(`notes/${parentNoteId}/children?target=${options.target}&targetBranchId=${options.targetBranchId || ""}`, {
title: options.title,
content: options.content || "",
isProtected: options.isProtected,
type: options.type,
mime: options.mime,
templateNoteId: options.templateNoteId
});
templateNoteId: options.templateNoteId,
attributes: options.attributes
}, componentId);
if (options.saveSelection) {
// we remove the selection only after it was saved to server to make sure we don't lose anything
@@ -140,9 +137,8 @@ function parseSelectedHtml(selectedHtml: string) {
const content = selectedHtml.replace(dom[0].outerHTML, "");
return [title, content];
} else {
return [null, selectedHtml];
}
return [null, selectedHtml];
}
async function duplicateSubtree(noteId: string, parentNotePath: string) {

View File

@@ -1,20 +1,20 @@
import treeService from "./tree.js";
import linkService from "./link.js";
import froca from "./froca.js";
import utils from "./utils.js";
import attributeRenderer from "./attribute_renderer.js";
import contentRenderer from "./content_renderer.js";
import appContext from "../components/app_context.js";
import type FNote from "../entities/fnote.js";
import attributeRenderer from "./attribute_renderer.js";
import contentRenderer from "./content_renderer.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import linkService from "./link.js";
import treeService from "./tree.js";
import utils from "./utils.js";
// Track all elements that open tooltips
let openTooltipElements: JQuery<HTMLElement>[] = [];
let dismissTimer: ReturnType<typeof setTimeout>;
function setupGlobalTooltip() {
$(document).on("mouseenter", "a:not(.no-tooltip-preview)", mouseEnterHandler);
$(document).on("mouseenter", "[data-href]:not(.no-tooltip-preview)", mouseEnterHandler);
$(document).on("pointerenter", "a:not(.no-tooltip-preview)", mouseEnterHandler);
$(document).on("pointerenter", "[data-href]:not(.no-tooltip-preview)", mouseEnterHandler);
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
$(document).on("click", (e) => {
@@ -37,10 +37,12 @@ function dismissAllTooltips() {
}
function setupElementTooltip($el: JQuery<HTMLElement>) {
$el.on("mouseenter", mouseEnterHandler);
$el.on("pointerenter", mouseEnterHandler);
}
async function mouseEnterHandler(this: HTMLElement) {
async function mouseEnterHandler<T>(this: HTMLElement, e: JQuery.TriggeredEvent<T, undefined, T, T>) {
if (e.pointerType !== "mouse") return;
const $link = $(this);
if ($link.hasClass("no-tooltip-preview") || $link.hasClass("disabled")) {
@@ -91,7 +93,7 @@ async function mouseEnterHandler(this: HTMLElement) {
}
const html = `<div class="note-tooltip-content">${content}</div>`;
const tooltipClass = "tooltip-" + Math.floor(Math.random() * 999_999_999);
const tooltipClass = `tooltip-${ Math.floor(Math.random() * 999_999_999)}`;
// we need to check if we're still hovering over the element
// since the operation to get tooltip content was async, it is possible that
@@ -224,7 +226,7 @@ function renderFootnoteOrAnchor($link: JQuery<HTMLElement>, url: string) {
}
let footnoteContent = $targetContent.html();
footnoteContent = `<div class="ck-content">${footnoteContent}</div>`
footnoteContent = `<div class="ck-content">${footnoteContent}</div>`;
return footnoteContent || "";
}

View File

@@ -1,9 +1,9 @@
import { t } from "./i18n.js";
import froca from "./froca.js";
import server from "./server.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge, MenuSeparatorItem } from "../menus/context_menu.js";
import type { NoteType } from "../entities/fnote.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge, MenuSeparatorItem } from "../menus/context_menu.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import server from "./server.js";
export interface NoteTypeMapping {
type: NoteType;
@@ -26,6 +26,7 @@ export const NOTE_TYPES: NoteTypeMapping[] = [
// The default note type (always the first item)
{ type: "text", mime: "text/html", title: t("note_types.text"), icon: "bx-note" },
{ type: "spreadsheet", mime: "application/json", title: t("note_types.spreadsheet"), icon: "bx-table", isBeta: true },
// Text notes group
{ type: "book", mime: "", title: t("note_types.book"), icon: "bx-book" },
@@ -53,7 +54,6 @@ export const NOTE_TYPES: NoteTypeMapping[] = [
{ type: "file", title: t("note_types.file"), reserved: true },
{ type: "image", title: t("note_types.image"), reserved: true },
{ type: "launcher", mime: "", title: t("note_types.launcher"), reserved: true },
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), reserved: true }
];
/** The maximum age in days for a template to be marked with the "New" badge */
@@ -97,9 +97,9 @@ function getBlankNoteTypes(command?: TreeCommandNames): MenuItem<TreeCommandName
title: nt.title,
command,
type: nt.type,
uiIcon: "bx " + nt.icon,
uiIcon: `bx ${nt.icon}`,
badges: []
}
};
if (nt.isNew) {
menuItem.badges?.push(NEW_BADGE);
@@ -131,7 +131,7 @@ async function getUserTemplates(command?: TreeCommandNames) {
const item: MenuItem<TreeCommandNames> = {
title: templateNote.title,
uiIcon: templateNote.getIcon(),
command: command,
command,
type: templateNote.type,
templateNoteId: templateNote.noteId
};
@@ -160,7 +160,7 @@ async function getBuiltInTemplates(title: string | null, command: TreeCommandNam
const items: MenuItem<TreeCommandNames>[] = [];
if (title) {
items.push({
title: title,
title,
kind: "header"
});
} else {
@@ -176,7 +176,7 @@ async function getBuiltInTemplates(title: string | null, command: TreeCommandNam
const item: MenuItem<TreeCommandNames> = {
title: templateNote.title,
uiIcon: templateNote.getIcon(),
command: command,
command,
type: templateNote.type,
templateNoteId: templateNote.noteId
};
@@ -194,7 +194,7 @@ async function isNewTemplate(templateNoteId) {
if (rootCreationDate === undefined) {
// Retrieve the root note creation date
try {
let rootNoteInfo: any = await server.get("notes/root");
const rootNoteInfo: any = await server.get("notes/root");
if ("dateCreated" in rootNoteInfo) {
rootCreationDate = new Date(rootNoteInfo.dateCreated);
}
@@ -209,7 +209,7 @@ async function isNewTemplate(templateNoteId) {
if (creationDate === undefined) {
// The creation date isn't available in the cache, try to retrieve it from the server
try {
const noteInfo: any = await server.get("notes/" + templateNoteId);
const noteInfo: any = await server.get(`notes/${templateNoteId}`);
if ("dateCreated" in noteInfo) {
creationDate = new Date(noteInfo.dateCreated);
creationDateCache.set(templateNoteId, creationDate);
@@ -231,9 +231,8 @@ async function isNewTemplate(templateNoteId) {
const age = (new Date().getTime() - creationDate.getTime()) / DAY_LENGTH;
// Return true if the template is at most NEW_TEMPLATE_MAX_AGE days old
return (age <= NEW_TEMPLATE_MAX_AGE);
} else {
return false;
}
return false;
}
export default {

View File

@@ -1,14 +1,4 @@
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url" | "color";
type Multiplicity = "single" | "multi";
export interface DefinitionObject {
isPromoted?: boolean;
labelType?: LabelType;
multiplicity?: Multiplicity;
numberPrecision?: number;
promotedAlias?: string;
inverseRelation?: string;
}
import { DefinitionObject, LabelType, Multiplicity } from "@triliumnext/commons";
function parse(value: string) {
const tokens = value.split(",").map((t) => t.trim());
@@ -17,7 +7,7 @@ function parse(value: string) {
for (const token of tokens) {
if (token === "promoted") {
defObj.isPromoted = true;
} else if (["text", "number", "boolean", "date", "datetime", "time", "url", "color"].includes(token)) {
} else if (["text", "textarea", "number", "boolean", "date", "datetime", "time", "url", "color"].includes(token)) {
defObj.labelType = token as LabelType;
} else if (["single", "multi"].includes(token)) {
defObj.multiplicity = token as Multiplicity;

View File

@@ -1,56 +0,0 @@
import { h, VNode } from "preact";
import type FNote from "../entities/fnote.js";
import { renderReactWidgetAtElement } from "../widgets/react/react_utils.jsx";
import bundleService, { type Bundle } from "./bundle.js";
import froca from "./froca.js";
import server from "./server.js";
async function render(note: FNote, $el: JQuery<HTMLElement>, onError?: (e: unknown) => void) {
const relations = note.getRelations("renderNote");
const renderNoteIds = relations.map((rel) => rel.value).filter((noteId) => noteId);
$el.empty().toggle(renderNoteIds.length > 0);
for (const renderNoteId of renderNoteIds) {
const bundle = await server.post<Bundle>(`script/bundle/${renderNoteId}`);
const $scriptContainer = $("<div>");
$el.append($scriptContainer);
$scriptContainer.append(bundle.html);
// async so that scripts cannot block trilium execution
bundleService.executeBundle(bundle, note, $scriptContainer)
.catch(onError)
.then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el).catch(onError);
}
});
}
return renderNoteIds.length > 0;
}
async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery<HTMLElement>) {
// Ensure the root script note is actually a JSX.
const rootScriptNoteId = await froca.getNote(bundle.noteId);
if (rootScriptNoteId?.mime !== "text/jsx") return;
// Ensure the output is a valid el.
if (typeof result !== "function") return;
// Obtain the parent component.
const closestComponent = glob.getComponentByEl($el.closest(".component")[0]);
if (!closestComponent) return;
// Render the element.
const el = h(result as () => VNode, {});
renderReactWidgetAtElement(closestComponent, el, $el[0]);
}
export default {
render
};

View File

@@ -0,0 +1,86 @@
import { Component, h, VNode } from "preact";
import type FNote from "../entities/fnote.js";
import { renderReactWidgetAtElement } from "../widgets/react/react_utils.jsx";
import { type Bundle, executeBundleWithoutErrorHandling } from "./bundle.js";
import froca from "./froca.js";
import server from "./server.js";
type ErrorHandler = (e: unknown) => void;
async function render(note: FNote, $el: JQuery<HTMLElement>, onError?: ErrorHandler) {
const relations = note.getRelations("renderNote");
const renderNoteIds = relations.map((rel) => rel.value).filter((noteId) => noteId);
$el.empty().toggle(renderNoteIds.length > 0);
try {
for (const renderNoteId of renderNoteIds) {
const bundle = await server.postWithSilentInternalServerError<Bundle>(`script/bundle/${renderNoteId}`);
const $scriptContainer = $("<div>");
$el.append($scriptContainer);
$scriptContainer.append(bundle.html);
// async so that scripts cannot block trilium execution
executeBundleWithoutErrorHandling(bundle, note, $scriptContainer)
.catch(onError)
.then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el, onError).catch(onError);
}
});
}
return renderNoteIds.length > 0;
} catch (e) {
if (typeof e === "string" && e.startsWith("{") && e.endsWith("}")) {
try {
onError?.(JSON.parse(e));
} catch (e) {
onError?.(e);
}
} else {
onError?.(e);
}
}
}
async function renderIfJsx(bundle: Bundle, result: unknown, $el: JQuery<HTMLElement>, onError?: ErrorHandler) {
// Ensure the root script note is actually a JSX.
const rootScriptNoteId = await froca.getNote(bundle.noteId);
if (rootScriptNoteId?.mime !== "text/jsx") return;
// Ensure the output is a valid el.
if (typeof result !== "function") return;
// Obtain the parent component.
const closestComponent = glob.getComponentByEl($el.closest(".component")[0]);
if (!closestComponent) return;
// Render the element.
const UserErrorBoundary = class UserErrorBoundary extends Component {
constructor(props: object) {
super(props);
this.state = { error: null };
}
componentDidCatch(error: unknown) {
onError?.(error);
this.setState({ error });
}
render() {
if ("error" in this.state && this.state?.error) return null;
return this.props.children;
}
};
const el = h(UserErrorBoundary, {}, h(result as () => VNode, {}));
renderReactWidgetAtElement(closestComponent, el, $el[0]);
}
export default {
render
};

View File

@@ -73,6 +73,10 @@ async function post<T>(url: string, data?: unknown, componentId?: string) {
return await call<T>("POST", url, componentId, { data });
}
async function postWithSilentInternalServerError<T>(url: string, data?: unknown, componentId?: string) {
return await call<T>("POST", url, componentId, { data, silentInternalServerError: true });
}
async function put<T>(url: string, data?: unknown, componentId?: string) {
return await call<T>("PUT", url, componentId, { data });
}
@@ -85,21 +89,33 @@ async function remove<T>(url: string, componentId?: string) {
return await call<T>("DELETE", url, componentId);
}
async function upload(url: string, fileToUpload: File, componentId?: string) {
async function upload(url: string, fileToUpload: File, componentId?: string, method = "PUT") {
const formData = new FormData();
formData.append("upload", fileToUpload);
return await $.ajax({
const doUpload = async () => $.ajax({
url: window.glob.baseApiUrl + url,
headers: await getHeaders(componentId ? {
"trilium-component-id": componentId
} : undefined),
data: formData,
type: "PUT",
type: method,
timeout: 60 * 60 * 1000,
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false // NEEDED, DON'T REMOVE THIS
});
try {
return await doUpload();
} catch (e: unknown) {
// jQuery rejects with the jqXHR object
const jqXhr = e as JQuery.jqXHR;
if (jqXhr?.status && isCsrfError(jqXhr.status, jqXhr.responseText)) {
await refreshCsrfToken();
return await doUpload();
}
throw e;
}
}
let idCounter = 1;
@@ -108,11 +124,55 @@ const idToRequestMap: Record<string, RequestData> = {};
let maxKnownEntityChangeId = 0;
let csrfRefreshInProgress: Promise<void> | null = null;
/**
* Re-fetches /bootstrap to obtain a fresh CSRF token. This is needed when the
* server session expires (e.g. mobile tab backgrounded for a long time) and the
* existing CSRF token is no longer valid.
*
* Coalesces concurrent calls so only one bootstrap request is in-flight at a time.
*/
async function refreshCsrfToken(): Promise<void> {
if (csrfRefreshInProgress) {
return csrfRefreshInProgress;
}
csrfRefreshInProgress = (async () => {
try {
const response = await fetch(`./bootstrap${window.location.search}`, { cache: "no-store" });
if (response.ok) {
const json = await response.json();
glob.csrfToken = json.csrfToken;
}
} finally {
csrfRefreshInProgress = null;
}
})();
return csrfRefreshInProgress;
}
function isCsrfError(status: number, responseText: string): boolean {
if (status !== 403) {
return false;
}
try {
const body = JSON.parse(responseText);
return body.message === "Invalid CSRF token";
} catch {
return false;
}
}
interface CallOptions {
data?: unknown;
silentNotFound?: boolean;
silentInternalServerError?: boolean;
// If `true`, the value will be returned as a string instead of a JavaScript object if JSON, XMLDocument if XML, etc.
raw?: boolean;
/** Used internally to prevent infinite retry loops on CSRF refresh. */
csrfRetried?: boolean;
}
async function call<T>(method: string, url: string, componentId?: string, options: CallOptions = {}) {
@@ -143,7 +203,7 @@ async function call<T>(method: string, url: string, componentId?: string, option
});
})) as any;
} else {
resp = await ajax(url, method, data, headers, !!options.silentNotFound, options.raw);
resp = await ajax(url, method, data, headers, options);
}
const maxEntityChangeIdStr = resp.headers["trilium-max-entity-change-id"];
@@ -155,17 +215,14 @@ async function call<T>(method: string, url: string, componentId?: string, option
return resp.body as T;
}
/**
* @param raw if `true`, the value will be returned as a string instead of a JavaScript object if JSON, XMLDocument if XML, etc.
*/
function ajax(url: string, method: string, data: unknown, headers: Headers, silentNotFound: boolean, raw?: boolean): Promise<Response> {
function ajax(url: string, method: string, data: unknown, headers: Headers, opts: CallOptions): Promise<Response> {
return new Promise((res, rej) => {
const options: JQueryAjaxSettings = {
url: window.glob.baseApiUrl + url,
type: method,
headers,
timeout: 60000,
success: (body, textStatus, jqXhr) => {
success: (body, _textStatus, jqXhr) => {
const respHeaders: Headers = {};
jqXhr
@@ -190,7 +247,27 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
// don't report requests that are rejected by the browser, usually when the user is refreshing or going to a different page.
rej("rejected by browser");
return;
} else if (silentNotFound && jqXhr.status === 404) {
}
// If the CSRF token is stale (e.g. session expired while tab was backgrounded),
// refresh it and retry the request once.
if (!opts.csrfRetried && isCsrfError(jqXhr.status, jqXhr.responseText)) {
try {
await refreshCsrfToken();
// Rebuild headers so the fresh glob.csrfToken is picked up
const retryHeaders = await getHeaders({ "trilium-component-id": headers["trilium-component-id"] });
const retryResult = await ajax(url, method, data, retryHeaders, { ...opts, csrfRetried: true });
res(retryResult);
return;
} catch (retryErr) {
rej(retryErr);
return;
}
}
if (opts.silentNotFound && jqXhr.status === 404) {
// report nothing
} else if (opts.silentInternalServerError && jqXhr.status === 500) {
// report nothing
} else {
await reportError(method, url, jqXhr.status, jqXhr.responseText);
@@ -200,7 +277,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
}
};
if (raw) {
if (opts.raw) {
options.dataType = "text";
}
@@ -299,6 +376,7 @@ export default {
get,
getWithSilentNotFound,
post,
postWithSilentInternalServerError,
put,
patch,
remove,

View File

@@ -12,6 +12,7 @@ export default class SpacedUpdate {
private updateInterval: number;
private changeForbidden?: boolean;
private stateCallback?: StateCallback;
private lastState: SaveState = "saved";
constructor(updater: Callback, updateInterval = 1000, stateCallback?: StateCallback) {
this.updater = updater;
@@ -24,7 +25,7 @@ export default class SpacedUpdate {
scheduleUpdate() {
if (!this.changeForbidden) {
this.changed = true;
this.stateCallback?.("unsaved");
this.onStateChanged("unsaved");
setTimeout(() => this.triggerUpdate());
}
}
@@ -34,12 +35,12 @@ export default class SpacedUpdate {
this.changed = false; // optimistic...
try {
this.stateCallback?.("saving");
this.onStateChanged("saving");
await this.updater();
this.stateCallback?.("saved");
this.onStateChanged("saved");
} catch (e) {
this.changed = true;
this.stateCallback?.("error");
this.onStateChanged("error");
logError(getErrorMessage(e));
throw e;
}
@@ -76,13 +77,13 @@ export default class SpacedUpdate {
}
if (Date.now() - this.lastUpdated > this.updateInterval) {
this.stateCallback?.("saving");
this.onStateChanged("saving");
try {
await this.updater();
this.stateCallback?.("saved");
this.onStateChanged("saved");
this.changed = false;
} catch (e) {
this.stateCallback?.("error");
this.onStateChanged("error");
logError(getErrorMessage(e));
}
this.lastUpdated = Date.now();
@@ -92,6 +93,13 @@ export default class SpacedUpdate {
}
}
onStateChanged(state: SaveState) {
if (state === this.lastState) return;
this.stateCallback?.(state);
this.lastState = state;
}
async allowUpdateWithoutChange(callback: Callback) {
this.changeForbidden = true;

View File

@@ -14,7 +14,9 @@ export function reloadFrontendApp(reason?: string) {
}
if (isElectron()) {
dynamicRequire("@electron/remote").BrowserWindow.getFocusedWindow()?.reload();
for (const window of dynamicRequire("@electron/remote").BrowserWindow.getAllWindows()) {
window.reload();
}
} else {
window.location.reload();
}
@@ -133,6 +135,8 @@ export function isElectron() {
return !!(window && window.process && window.process.type);
}
export const isStandalone = window.glob.isStandalone;
/**
* Returns `true` if the client is running as a PWA, otherwise `false`.
*/
@@ -814,7 +818,7 @@ function compareVersions(v1: string, v2: string): number {
/**
* Compares two semantic version strings and returns `true` if the latest version is greater than the current version.
*/
function isUpdateAvailable(latestVersion: string | null | undefined, currentVersion: string): boolean {
export function isUpdateAvailable(latestVersion: string | null | undefined, currentVersion: string): boolean {
if (!latestVersion) {
return false;
}

View File

@@ -133,49 +133,6 @@ async function handleMessage(event: MessageEvent<any>) {
appContext.triggerEvent("apiLogMessages", { noteId: message.noteId, messages: message.messages });
} else if (message.type === "toast") {
toastService.showMessage(message.message);
} else if (message.type === "llm-stream") {
// ENHANCED LOGGING FOR DEBUGGING
console.log(`[WS-CLIENT] >>> RECEIVED LLM STREAM MESSAGE <<<`);
console.log(`[WS-CLIENT] Message details: sessionId=${message.sessionId}, hasContent=${!!message.content}, contentLength=${message.content ? message.content.length : 0}, hasThinking=${!!message.thinking}, hasToolExecution=${!!message.toolExecution}, isDone=${!!message.done}`);
if (message.content) {
console.log(`[WS-CLIENT] CONTENT PREVIEW: "${message.content.substring(0, 50)}..."`);
}
// Create the event with detailed logging
console.log(`[WS-CLIENT] Creating CustomEvent 'llm-stream-message'`);
const llmStreamEvent = new CustomEvent('llm-stream-message', { detail: message });
// Dispatch to multiple targets to ensure delivery
try {
console.log(`[WS-CLIENT] Dispatching event to window`);
window.dispatchEvent(llmStreamEvent);
console.log(`[WS-CLIENT] Event dispatched to window`);
// Also try document for completeness
console.log(`[WS-CLIENT] Dispatching event to document`);
document.dispatchEvent(new CustomEvent('llm-stream-message', { detail: message }));
console.log(`[WS-CLIENT] Event dispatched to document`);
} catch (err) {
console.error(`[WS-CLIENT] Error dispatching event:`, err);
}
// Debug current listeners (though we can't directly check for specific event listeners)
console.log(`[WS-CLIENT] Active event listeners should receive this message now`);
// Detailed logging based on message type
if (message.content) {
console.log(`[WS-CLIENT] Content message: ${message.content.length} chars`);
} else if (message.thinking) {
console.log(`[WS-CLIENT] Thinking update: "${message.thinking}"`);
} else if (message.toolExecution) {
console.log(`[WS-CLIENT] Tool execution: action=${message.toolExecution.action}, tool=${message.toolExecution.tool || 'unknown'}`);
if (message.toolExecution.result) {
console.log(`[WS-CLIENT] Tool result preview: "${String(message.toolExecution.result).substring(0, 50)}..."`);
}
} else if (message.done) {
console.log(`[WS-CLIENT] Completion signal received`);
}
} else if (message.type === "execute-script") {
// TODO: Remove after porting the file
// @ts-ignore
@@ -304,7 +261,7 @@ async function sendPing() {
}
setTimeout(() => {
if (glob.device === "print") return;
if (glob.device === "print" || glob.isStandalone) return;
ws = connectWebSocket();

View File

@@ -1,66 +1,107 @@
import "jquery";
import utils from "./services/utils.js";
import ko from "knockout";
// TriliumNextTODO: properly make use of below types
// type SetupModelSetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | "";
// type SetupModelStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop";
type SetupStep = "sync-in-progress" | "setup-type" | "new-document-in-progress" | "sync-from-desktop" | "sync-from-server";
type SetupType = "new-document" | "sync-from-desktop" | "sync-from-server" | "";
class SetupModel {
syncInProgress: boolean;
step: ko.Observable<string>;
setupType: ko.Observable<string>;
setupNewDocument: ko.Observable<boolean>;
setupSyncFromDesktop: ko.Observable<boolean>;
setupSyncFromServer: ko.Observable<boolean>;
syncServerHost: ko.Observable<string | undefined>;
syncProxy: ko.Observable<string | undefined>;
password: ko.Observable<string | undefined>;
class SetupController {
private step: SetupStep;
private setupType: SetupType = "";
private syncPollIntervalId: number | null = null;
private rootNode: HTMLElement;
private setupTypeForm: HTMLFormElement;
private syncFromServerForm: HTMLFormElement;
private setupTypeNextButton: HTMLButtonElement;
private setupTypeInputs: HTMLInputElement[];
private syncServerHostInput: HTMLInputElement;
private syncProxyInput: HTMLInputElement;
private passwordInput: HTMLInputElement;
private sections: Record<SetupStep, HTMLElement>;
constructor(syncInProgress: boolean) {
this.syncInProgress = syncInProgress;
this.step = ko.observable(syncInProgress ? "sync-in-progress" : "setup-type");
this.setupType = ko.observable("");
this.setupNewDocument = ko.observable(false);
this.setupSyncFromDesktop = ko.observable(false);
this.setupSyncFromServer = ko.observable(false);
this.syncServerHost = ko.observable();
this.syncProxy = ko.observable();
this.password = ko.observable();
constructor(rootNode: HTMLElement, syncInProgress: boolean) {
this.rootNode = rootNode;
this.step = syncInProgress ? "sync-in-progress" : "setup-type";
this.setupTypeForm = mustGetElement("setup-type-form", HTMLFormElement);
this.syncFromServerForm = mustGetElement("sync-from-server-form", HTMLFormElement);
this.setupTypeNextButton = mustGetElement("setup-type-next", HTMLButtonElement);
this.setupTypeInputs = Array.from(document.querySelectorAll<HTMLInputElement>("input[name='setup-type']"));
this.syncServerHostInput = mustGetElement("sync-server-host", HTMLInputElement);
this.syncProxyInput = mustGetElement("sync-proxy", HTMLInputElement);
this.passwordInput = mustGetElement("password", HTMLInputElement);
this.sections = {
"setup-type": mustGetElement("setup-type-section", HTMLElement),
"new-document-in-progress": mustGetElement("new-document-in-progress-section", HTMLElement),
"sync-from-desktop": mustGetElement("sync-from-desktop-section", HTMLElement),
"sync-from-server": mustGetElement("sync-from-server-section", HTMLElement),
"sync-in-progress": mustGetElement("sync-in-progress-section", HTMLElement)
};
}
if (this.syncInProgress) {
setInterval(checkOutstandingSyncs, 1000);
init() {
this.setupTypeForm.addEventListener("submit", (event) => {
event.preventDefault();
void this.selectSetupType();
});
this.syncFromServerForm.addEventListener("submit", (event) => {
event.preventDefault();
void this.finish();
});
for (const input of this.setupTypeInputs) {
input.addEventListener("change", () => {
this.setupType = input.value as SetupType;
this.render();
});
}
for (const backButton of document.querySelectorAll<HTMLElement>("[data-action='back']")) {
backButton.addEventListener("click", () => {
this.back();
});
}
const serverAddress = `${location.protocol}//${location.host}`;
$("#current-host").html(serverAddress);
if (this.step === "sync-in-progress") {
this.startSyncPolling();
}
this.render();
this.rootNode.style.display = "";
}
// this is called in setup.ejs
setupTypeSelected() {
return !!this.setupType();
}
private async selectSetupType() {
if (this.setupType === "new-document") {
this.setStep("new-document-in-progress");
selectSetupType() {
if (this.setupType() === "new-document") {
this.step("new-document-in-progress");
await $.post("api/setup/new-document");
window.location.replace("./setup");
return;
}
$.post("api/setup/new-document").then(() => {
window.location.replace("./setup");
});
} else {
this.step(this.setupType());
if (this.setupType) {
this.setStep(this.setupType);
}
}
back() {
this.step("setup-type");
this.setupType("");
private back() {
this.setStep("setup-type");
this.setupType = "";
for (const input of this.setupTypeInputs) {
input.checked = false;
}
this.render();
}
async finish() {
const syncServerHost = this.syncServerHost();
const syncProxy = this.syncProxy();
const password = this.password();
private async finish() {
const syncServerHost = this.syncServerHostInput.value.trim();
const syncProxy = this.syncProxyInput.value.trim();
const password = this.passwordInput.value;
if (!syncServerHost) {
showAlert("Trilium server address can't be empty");
@@ -74,21 +115,44 @@ class SetupModel {
// not using server.js because it loads too many dependencies
const resp = await $.post("api/setup/sync-from-server", {
syncServerHost: syncServerHost,
syncProxy: syncProxy,
password: password
syncServerHost,
syncProxy,
password
});
if (resp.result === "success") {
this.step("sync-in-progress");
setInterval(checkOutstandingSyncs, 1000);
hideAlert();
this.setStep("sync-in-progress");
this.startSyncPolling();
} else {
showAlert(`Sync setup failed: ${resp.error}`);
}
}
private setStep(step: SetupStep) {
this.step = step;
this.render();
}
private render() {
for (const [step, section] of Object.entries(this.sections) as [SetupStep, HTMLElement][]) {
section.style.display = step === this.step ? "" : "none";
}
this.setupTypeNextButton.disabled = !this.setupType;
}
private getSelectedSetupType(): SetupType {
return (this.setupTypeInputs.find((input) => input.checked)?.value ?? "") as SetupType;
}
private startSyncPolling() {
if (this.syncPollIntervalId !== null) {
return;
}
this.syncPollIntervalId = window.setInterval(checkOutstandingSyncs, 1000);
}
}
async function checkOutstandingSyncs() {
@@ -122,7 +186,19 @@ function getSyncInProgress() {
return !!parseInt(el.content);
}
function mustGetElement<T extends typeof HTMLElement>(id: string, ctor: T): InstanceType<T> {
const element = document.getElementById(id);
if (!element || !(element instanceof ctor)) {
throw new Error(`Expected element #${id}`);
}
return element as InstanceType<T>;
}
addEventListener("DOMContentLoaded", (event) => {
ko.applyBindings(new SetupModel(getSyncInProgress()), document.getElementById("setup-dialog"));
$("#setup-dialog").show();
const rootNode = document.getElementById("setup-dialog");
if (!rootNode || !(rootNode instanceof HTMLElement)) return;
new SetupController(rootNode, getSyncInProgress()).init();
});

View File

@@ -1,450 +0,0 @@
/* LLM Chat Panel Styles */
.note-context-chat {
background-color: var(--main-background-color);
}
/* Message Styling */
.chat-message {
margin-bottom: 1rem;
}
.message-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
font-size: 1.25rem;
flex-shrink: 0;
}
.user-avatar {
background-color: var(--input-background-color);
color: var(--cmd-button-icon-color);
}
.assistant-avatar {
background-color: var(--subtle-border-color, var(--main-border-color));
color: var(--hover-item-text-color);
}
.message-content {
max-width: calc(100% - 50px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
color: var(--main-text-color);
}
.user-content {
border-radius: 0.5rem 0.5rem 0 0.5rem !important;
background-color: var(--input-background-color) !important;
}
.assistant-content {
border-radius: 0.5rem 0.5rem 0.5rem 0 !important;
background-color: var(--main-background-color);
border: 1px solid var(--subtle-border-color, var(--main-border-color));
}
/* Tool Execution Styling */
.tool-execution-info {
margin-top: 0.75rem;
margin-bottom: 1.5rem;
border: 1px solid var(--subtle-border-color);
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
background-color: var(--main-background-color);
/* Add a subtle transition effect */
transition: all 0.2s ease-in-out;
}
.tool-execution-status {
background-color: var(--accented-background-color, rgba(0, 0, 0, 0.03)) !important;
border-radius: 0 !important;
padding: 0.5rem !important;
max-height: 250px !important;
overflow-y: auto;
}
.tool-execution-status .d-flex {
border-bottom: 1px solid var(--subtle-border-color);
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
}
.tool-step {
padding: 0.5rem;
margin-bottom: 0.75rem;
border-radius: 0.375rem;
background-color: var(--main-background-color);
border: 1px solid var(--subtle-border-color);
transition: background-color 0.2s ease;
}
.tool-step:hover {
background-color: rgba(0, 0, 0, 0.01);
}
.tool-step:last-child {
margin-bottom: 0;
}
/* Tool step specific styling */
.tool-step.executing {
background-color: rgba(0, 123, 255, 0.05);
border-color: rgba(0, 123, 255, 0.2);
}
.tool-step.result {
background-color: rgba(40, 167, 69, 0.05);
border-color: rgba(40, 167, 69, 0.2);
}
.tool-step.error {
background-color: rgba(220, 53, 69, 0.05);
border-color: rgba(220, 53, 69, 0.2);
}
/* Tool result formatting */
.tool-result pre {
margin: 0.5rem 0;
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 0.25rem;
overflow: auto;
max-height: 300px;
}
.tool-result code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
font-size: 0.9em;
}
.tool-args code {
display: block;
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.03);
border-radius: 0.25rem;
margin-top: 0.25rem;
font-size: 0.85em;
color: var(--muted-text-color);
white-space: pre-wrap;
overflow: auto;
max-height: 100px;
}
/* Tool Execution in Chat Styling */
.chat-tool-execution {
padding: 0 0 0 36px; /* Aligned with message content, accounting for avatar width */
width: 100%;
margin-bottom: 1rem;
}
.tool-execution-container {
background-color: var(--accented-background-color, rgba(245, 247, 250, 0.7));
border: 1px solid var(--subtle-border-color);
border-radius: 0.375rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
overflow: hidden;
max-width: calc(100% - 20px);
transition: all 0.3s ease;
}
.tool-execution-container.collapsed {
display: none;
}
.tool-execution-header {
background-color: var(--main-background-color);
border-bottom: 1px solid var(--subtle-border-color);
margin-bottom: 0.5rem;
color: var(--muted-text-color);
font-weight: 500;
padding: 0.6rem 0.8rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
.tool-execution-header:hover {
background-color: var(--hover-item-background-color, rgba(0, 0, 0, 0.03));
}
.tool-execution-toggle {
color: var(--muted-text-color) !important;
background: transparent !important;
padding: 0.2rem 0.4rem !important;
transition: transform 0.2s ease;
}
.tool-execution-toggle:hover {
color: var(--main-text-color) !important;
}
.tool-execution-toggle i.bx-chevron-down {
transform: rotate(0deg);
transition: transform 0.3s ease;
}
.tool-execution-toggle i.bx-chevron-right {
transform: rotate(-90deg);
transition: transform 0.3s ease;
}
.tool-execution-chat-steps {
padding: 0.5rem;
max-height: 300px;
overflow-y: auto;
}
/* Make error text more visible */
.text-danger {
color: #dc3545 !important;
}
/* Sources Styling */
.sources-container {
background-color: var(--accented-background-color, var(--main-background-color));
border-top: 1px solid var(--main-border-color);
color: var(--main-text-color);
}
.source-item {
transition: all 0.2s ease;
background-color: var(--main-background-color);
border-color: var(--subtle-border-color, var(--main-border-color)) !important;
}
.source-item:hover {
background-color: var(--link-hover-background, var(--hover-item-background-color));
}
.source-link {
color: var(--link-color, var(--hover-item-text-color));
text-decoration: none;
display: block;
width: 100%;
}
.source-link:hover {
color: var(--link-hover-color, var(--hover-item-text-color));
}
/* Input Area Styling */
.note-context-chat-form {
background-color: var(--main-background-color);
border-top: 1px solid var(--main-border-color);
}
.context-option-container {
padding: 0.5rem 0;
border-bottom: 1px solid var(--subtle-border-color, var(--main-border-color));
color: var(--main-text-color);
}
.chat-input-container {
padding-top: 0.5rem;
}
.note-context-chat-input {
border-color: var(--subtle-border-color, var(--main-border-color));
background-color: var(--input-background-color) !important;
color: var(--input-text-color) !important;
resize: none;
transition: all 0.2s ease;
min-height: 50px;
max-height: 150px;
}
.note-context-chat-input:focus {
border-color: var(--input-focus-outline-color, var(--main-border-color));
box-shadow: 0 0 0 0.25rem var(--input-focus-outline-color, rgba(13, 110, 253, 0.25));
}
.note-context-chat-send-button {
width: 40px;
height: 40px;
align-self: flex-end;
background-color: var(--cmd-button-background-color) !important;
color: var(--cmd-button-text-color) !important;
}
/* Loading Indicator */
.loading-indicator {
align-items: center;
justify-content: center;
padding: 1rem;
color: var(--muted-text-color);
}
/* Thinking display styles */
.llm-thinking-container {
margin: 1rem 0;
animation: fadeInUp 0.3s ease-out;
}
.thinking-bubble {
background-color: var(--accented-background-color, var(--main-background-color));
border: 1px solid var(--subtle-border-color, var(--main-border-color));
border-radius: 0.75rem;
padding: 0.75rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
position: relative;
overflow: hidden;
transition: all 0.2s ease;
}
.thinking-bubble:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.thinking-bubble::before {
content: '';
position: absolute;
top: 0;
inset-inline-start: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, var(--hover-item-background-color, rgba(0, 0, 0, 0.03)), transparent);
animation: shimmer 2s infinite;
opacity: 0.5;
}
.thinking-header {
cursor: pointer;
transition: all 0.2s ease;
border-radius: 0.375rem;
}
.thinking-header:hover {
background-color: var(--hover-item-background-color, rgba(0, 0, 0, 0.03));
padding: 0.25rem;
margin: -0.25rem;
}
.thinking-dots {
display: flex;
gap: 3px;
align-items: center;
}
.thinking-dots span {
width: 6px;
height: 6px;
background-color: var(--link-color, var(--hover-item-text-color));
border-radius: 50%;
animation: thinkingPulse 1.4s infinite ease-in-out;
}
.thinking-dots span:nth-child(1) {
animation-delay: -0.32s;
}
.thinking-dots span:nth-child(2) {
animation-delay: -0.16s;
}
.thinking-dots span:nth-child(3) {
animation-delay: 0s;
}
.thinking-label {
font-weight: 500;
color: var(--link-color, var(--hover-item-text-color)) !important;
}
.thinking-toggle {
color: var(--muted-text-color) !important;
transition: transform 0.2s ease;
background: transparent !important;
border: none !important;
}
.thinking-toggle:hover {
color: var(--main-text-color) !important;
}
.thinking-toggle.expanded {
transform: rotate(180deg);
}
.thinking-content {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--subtle-border-color, var(--main-border-color));
animation: expandDown 0.3s ease-out;
}
.thinking-text {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
font-size: 0.875rem;
line-height: 1.5;
color: var(--main-text-color);
white-space: pre-wrap;
word-wrap: break-word;
background-color: var(--input-background-color);
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid var(--subtle-border-color, var(--main-border-color));
max-height: 300px;
overflow-y: auto;
transition: border-color 0.2s ease;
}
.thinking-text:hover {
border-color: var(--main-border-color);
}
/* Animations */
@keyframes thinkingPulse {
0%, 80%, 100% {
transform: scale(0.8);
opacity: 0.6;
}
40% {
transform: scale(1);
opacity: 1;
}
}
@keyframes shimmer {
0% {
inset-inline-start: -100%;
}
100% {
inset-inline-start: 100%;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes expandDown {
from {
opacity: 0;
max-height: 0;
}
to {
opacity: 1;
max-height: 300px;
}
}
/* Responsive adjustments */
@media (max-width: 768px) {
.thinking-bubble {
margin: 0.5rem 0;
padding: 0.5rem;
}
.thinking-text {
font-size: 0.8rem;
padding: 0.5rem;
max-height: 200px;
}
}

View File

@@ -1587,7 +1587,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
position: absolute;
top: 0;
inset-inline-start: 0;
bottom: 0;
height: 100dvh;
width: 85vw;
padding-top: env(safe-area-inset-top);
transition: transform 250ms ease-in-out;
@@ -1612,11 +1612,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
}
body.mobile #launcher-container {
justify-content: center;
}
body.mobile #launcher-container button {
margin: 0 16px;
justify-content: space-evenly;
}
body.mobile .modal.show {
@@ -1651,13 +1647,27 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
word-break: break-all;
}
body.mobile .jump-to-note-dialog .modal-content {
overflow-y: auto;
}
body.mobile .jump-to-note-dialog {
.modal-header {
padding-bottom: 0.75rem !important;
}
body.mobile .jump-to-note-dialog .modal-dialog .aa-dropdown-menu {
max-height: unset;
overflow: auto;
.modal-content {
padding-bottom: 0 !important;
}
.modal-body {
overflow-y: auto;
}
.aa-dropdown-menu {
max-height: unset;
overflow: auto;
}
.aa-suggestion {
padding-inline: 0;
}
}
body.mobile .modal-dialog .dropdown-menu {
@@ -2616,7 +2626,7 @@ iframe.print-iframe {
}
}
#root-widget.virtual-keyboard-opened .note-split:not(.active) {
body:not(.ios) #root-widget.virtual-keyboard-opened .note-split:not(.active) {
max-height: 80px;
opacity: 0.4;
}

View File

@@ -291,6 +291,15 @@
--ck-editor-toolbar-button-on-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
--ck-editor-toolbar-dropdown-button-open-background: #ffffff14;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: rgba(0, 0, 0, .2);
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: black;
--calendar-coll-event-background-saturation: 25%;
--calendar-coll-event-background-lightness: 20%;
--calendar-coll-event-background-color: #3c3c3c;
@@ -304,7 +313,9 @@
* Dark color scheme tweaks
*/
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color,
.note-book-card .note-book-header.use-note-color {
--custom-color: var(--dark-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -354,7 +365,9 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue,
.note-book-card.with-hue .note-book-header {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);
@@ -364,3 +377,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.quick-edit-dialog-wrapper.with-hue *::selection {
--selection-background-color: hsl(var(--custom-color-hue), 49.2%, 35%);
}
.note-book-card.with-hue {
--card-background-color: hsl(var(--custom-color-hue), 6%, 21%);
--card-background-hover-color: hsl(var(--custom-color-hue), 8%, 25%);
}

View File

@@ -289,6 +289,15 @@
--ck-editor-toolbar-button-on-shadow: none;
--ck-editor-toolbar-dropdown-button-open-background: #0000000f;
--note-list-view-icon-color: var(--left-pane-icon-color);
--note-list-view-large-icon-background: var(--note-icon-background-color);
--note-list-view-large-icon-color: var(--note-icon-color);
--note-list-view-search-result-highlight-background: transparent;
--note-list-view-search-result-highlight-color: var(--quick-search-result-highlight-color);
--note-list-view-content-background: #b1b1b133;
--note-list-view-content-search-result-highlight-background: var(--quick-search-result-highlight-color);
--note-list-view-content-search-result-highlight-color: white;
--calendar-coll-event-background-lightness: 95%;
--calendar-coll-event-background-saturation: 80%;
--calendar-coll-event-background-color: #eaeaea;
@@ -298,7 +307,9 @@
--calendar-coll-today-background-color: #00000006;
}
#left-pane .fancytree-node.tinted {
#left-pane .fancytree-node.tinted,
.nested-note-list-item.use-note-color,
.note-book-card .note-book-header.use-note-color {
--custom-color: var(--light-theme-custom-color);
/* The background color of the active item in the note tree.
@@ -324,7 +335,9 @@
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
.quick-edit-dialog-wrapper.with-hue,
.nested-note-list-item.with-hue,
.note-book-card.with-hue .note-book-header {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 44.5%, 43.1%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 91.3%, 91%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 55.1%, 50.2%);
@@ -334,3 +347,8 @@
.quick-edit-dialog-wrapper.with-hue *::selection {
--selection-background-color: hsl(var(--custom-color-hue), 60%, 90%);
}
.note-book-card.with-hue {
--card-background-color: hsl(var(--custom-color-hue), 21%, 94%);
--card-background-hover-color: hsl(var(--custom-color-hue), 21%, 87%);
}

View File

@@ -643,139 +643,6 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
transform: translateY(4%);
}
/*
* NOTE LIST
*/
.note-list .note-book-card {
--note-list-horizontal-padding: 22px;
--note-list-vertical-padding: 15px;
background-color: var(--card-background-color);
border: 1px solid var(--card-border-color) !important;
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.grid-view .note-book-card:active {
transform: scale(.98);
}
.note-list.list-view .note-book-card {
box-shadow: 0 0 3px var(--card-shadow-color);
}
.note-list.list-view .note-book-card .note-book-header .note-icon {
vertical-align: middle;
}
.note-list-wrapper .note-book-card a {
color: inherit !important;
}
.note-list-wrapper .note-book-card .note-book-header {
font-size: 1em;
font-weight: bold;
padding: 0.5em 1rem;
border-bottom-color: var(--card-border-color);
}
.note-list-wrapper .note-book-card .note-book-header .note-icon {
font-size: 17px;
vertical-align: text-bottom;
}
.note-list-wrapper .note-book-card .note-book-header .note-book-title {
font-size: 1em;
color: var(--active-item-text-color);
vertical-align: middle;
}
.note-list-wrapper .note-book-card .note-book-header .rendered-note-attributes {
font-size: 0.7em;
font-weight: normal;
margin-bottom: 0;
}
.note-list-wrapper .note-book-card .note-book-header:last-child {
border-bottom: 0;
}
.note-list-wrapper .note-book-card .note-book-content {
padding: 0 !important;
font-size: 0.8rem;
}
.note-list-wrapper .note-book-card .note-book-content .rendered-content {
padding: 1rem;
}
.note-list-wrapper .note-book-card .note-book-content.type-image .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-pdf .rendered-content {
padding: 0;
}
.note-list-wrapper .note-book-card .note-book-content .rendered-content.text-with-ellipsis {
padding: 1rem !important;
}
.note-list-wrapper .note-book-card .note-book-content h1,
.note-list-wrapper .note-book-card .note-book-content h2,
.note-list-wrapper .note-book-card .note-book-content h3,
.note-list-wrapper .note-book-card .note-book-content h4,
.note-list-wrapper .note-book-card .note-book-content h5,
.note-list-wrapper .note-book-card .note-book-content h6 {
font-size: 1rem;
color: var(--active-item-text-color);
}
.note-list-wrapper .note-book-card .note-book-content p:last-child {
margin-bottom: 0;
}
.note-list-wrapper .note-book-card .note-book-content.type-canvas .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-mindMap .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-code .rendered-content,
.note-list-wrapper .note-book-card .note-book-content.type-video .rendered-content {
padding: 0;
}
.note-list-wrapper .note-book-card .note-book-content.type-code {
height: 100%;
}
.note-list-wrapper .note-book-card .note-book-content.type-code pre {
height: 100%;
padding: 1em;
margin-bottom: 0;
}
.note-list-wrapper .note-book-card .tn-icon {
color: var(--left-pane-icon-color) !important;
}
.note-list.grid-view .note-book-card:hover {
filter: contrast(105%);
}
.note-list.grid-view .ck-content {
line-height: 1.3;
}
.note-list.grid-view .ck-content p {
margin-bottom: 0.5em;
}
.note-list.grid-view .ck-content figure.image {
width: 25%;
}
/*
* NOTE SEARCH SUGGESTIONS
*/
@@ -808,10 +675,11 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
div.alert {
margin-bottom: 8px;
background: var(--alert-bar-background) !important;
color: var(--main-text-color);
border-radius: 8px;
font-size: .85em;
}
div.alert p + p {
margin-block: 1em 0;
}
}

View File

@@ -145,6 +145,10 @@ button.tn-low-profile:hover {
font-size: calc(var(--icon-button-size) * var(--icon-button-icon-ratio));
}
:root .icon-action.disabled::before {
opacity: .5;
}
:root .icon-action:not(.global-menu-button):hover,
:root .icon-action:not(.global-menu-button).show,
:root .tn-tool-button:hover,

View File

@@ -1,122 +0,0 @@
/* LLM Chat Launcher Widget Styles */
.note-context-chat {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
.note-context-chat-container {
flex-grow: 1;
overflow-y: auto;
padding: 15px;
}
.chat-message {
display: flex;
margin-bottom: 15px;
max-width: 85%;
}
.chat-message.user-message {
margin-inline-start: auto;
}
.chat-message.assistant-message {
margin-inline-end: auto;
}
.message-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-inline-end: 8px;
}
.user-message .message-avatar {
background-color: var(--primary-color);
color: white;
}
.assistant-message .message-avatar {
background-color: var(--secondary-color);
color: white;
}
.message-content {
background-color: var(--more-accented-background-color);
border-radius: 12px;
padding: 10px 15px;
max-width: calc(100% - 40px);
}
.user-message .message-content {
background-color: var(--accented-background-color);
}
.message-content pre {
background-color: var(--code-background-color);
border-radius: 5px;
padding: 10px;
overflow-x: auto;
max-width: 100%;
}
.message-content code {
background-color: var(--code-background-color);
padding: 2px 4px;
border-radius: 3px;
}
.loading-indicator {
display: flex;
align-items: center;
margin: 10px 0;
color: var(--muted-text-color);
}
.sources-container {
background-color: var(--accented-background-color);
border-top: 1px solid var(--main-border-color);
padding: 8px;
}
.sources-list {
font-size: 0.9em;
}
.source-item {
padding: 4px 0;
}
.source-link {
color: var(--link-color);
text-decoration: none;
}
.source-link:hover {
text-decoration: underline;
}
.note-context-chat-form {
display: flex;
background-color: var(--main-background-color);
border-top: 1px solid var(--main-border-color);
padding: 10px;
}
.note-context-chat-input {
resize: vertical;
min-height: 44px;
max-height: 200px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.chat-message {
max-width: 95%;
}
}

View File

@@ -647,10 +647,10 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
font-weight: 600;
}
.ck-content hr {
margin: 5px 0;
height: 1px;
background-color: var(--main-border-color);
:root .ck-content hr {
margin-block: 5px;
height: 0;
border: thin solid var(--main-border-color);
opacity: 1;
}

View File

@@ -372,10 +372,6 @@ body[dir=ltr] #launcher-container {
.calendar-dropdown-widget .calendar-header [data-calendar-input="month"] {
--input-background-color: transparent;
--menu-background-color: transparent;
text-align: center;
font-size: 1.4em;
font-weight: 300;
}
.calendar-dropdown-widget .calendar-header input:not(:focus) {
@@ -425,8 +421,6 @@ body[dir=ltr] #launcher-container {
}
.calendar-dropdown-widget .calendar-week span {
font-size: 0.85em;
font-weight: 500;
color: var(--calendar-weekday-labels-color);
}
@@ -689,9 +683,10 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
padding-inline-start: 12px;
}
#left-pane span.fancytree-node.fancytree-active {
#left-pane span.fancytree-node.fancytree-active,
#left-pane span.fancytree-node.fancytree-active:hover {
position: relative;
background: transparent !important;
background: transparent;
color: var(--custom-color, var(--left-pane-item-selected-color));
}
@@ -704,6 +699,14 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
}
}
/*
* .fancytree-node pseudo-elements:
*
* - ::before: the active tree item decorator.
* - ::after: the selected tree item background. A pseudo-element is used instead of the
* element's background color, to allow alpha compositing for the hover state.
*/
#left-pane span.fancytree-node.fancytree-active::before {
position: absolute;
content: "";
@@ -718,6 +721,24 @@ body.layout-vertical.background-effects div.quick-search .dropdown-menu {
z-index: -1;
}
#left-pane span.fancytree-node.fancytree-selected {
--left-pane-item-selected-shadow-size: 4px;
position: relative;
background-color: transparent;
border-radius: 0;
}
#left-pane span.fancytree-node.fancytree-selected::after {
display: block;
position: absolute;
z-index: -2;
content: "";
inset: 0;
background: var(--selection-background-color);
animation: left-pane-item-select 100ms ease-out;
}
#left-pane span.fancytree-node.protected > span.fancytree-custom-icon {
position: relative;
filter: unset !important;
@@ -751,12 +772,14 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
}
}
#left-pane .fancytree-expander {
#left-pane .fancytree-expander,
.nested-note-list-item .note-expander {
opacity: 0.65;
transition: opacity 150ms ease-in;
}
#left-pane .fancytree-expander:hover {
#left-pane .fancytree-expander:hover,
.nested-note-list-item .note-expander:hover {
opacity: 1;
transition: opacity 300ms ease-out;
}
@@ -778,7 +801,8 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
opacity: 0.5;
}
#left-pane .tree-item-button {
#left-pane .tree-item-button,
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon {
margin-inline-end: 6px;
border: unset;
border-radius: 50%;
@@ -789,7 +813,8 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
box-shadow 200ms ease-out;
}
#left-pane .tree-item-button:hover {
#left-pane .tree-item-button:hover,
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon:hover {
background: var(--left-pane-item-action-button-hover-background);
box-shadow: var(--left-pane-item-action-button-hover-shadow);
transition:
@@ -797,10 +822,41 @@ body[dir=rtl] #left-pane span.fancytree-node.protected > span.fancytree-custom-i
box-shadow 100ms ease-in;
}
#left-pane span.fancytree-node.fancytree-active .tree-item-button:hover {
#left-pane span.fancytree-node.fancytree-active .tree-item-button:hover,
#left-pane span.fancytree-node.fancytree-active.fancytree-selected .fancytree-custom-icon:hover {
box-shadow: var(--left-pane-item-selected-action-button-hover-shadow);
}
/* Selected item bulk action button */
@keyframes bulk-action-button-blink {
from {
opacity: 1;
}
to {
opacity: .3;
}
}
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon {
margin: 0;
}
#left-pane span.fancytree-node.fancytree-selected .fancytree-custom-icon::before {
border: 0;
font-size: .65em;
}
#left-pane span.fancytree-node.fancytree-selected:hover .fancytree-custom-icon:not(:hover)::before {
animation: bulk-action-button-blink 500ms linear infinite alternate;
}
#left-pane span.fancytree-node.fancytree-selected.protected .fancytree-custom-icon::after {
/* Protected note indicator */
display: none;
}
#context-menu-container {
/* The context menu of the tree */
--menu-item-icon-vert-offset: -1px;
@@ -1031,7 +1087,7 @@ body.layout-vertical.electron.platform-darwin .tab-row-container {
height: var(--tab-height) !important;
}
.tab-row-widget > * {
body.layout-vertical .tab-row-widget > * {
margin-top: calc((var(--tab-bar-height) - var(--tab-height)) / 2);
}

View File

@@ -140,10 +140,22 @@ ul.fancytree-container {
background-color: inherit;
}
.fancytree-custom-icon {
display: flex;
justify-content: center;
align-items: center;
width: 1em;
height: 1em;
font-size: 1.2em;
}
/* Fallback icon */
:where(.fancytree-custom-icon)::before {
content: "?";
}
/* Protected note icon badge */
span.fancytree-node.protected > span.fancytree-custom-icon {
filter: drop-shadow(2px 2px 2px var(--main-text-color));
}
@@ -185,7 +197,7 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
span.fancytree-active {
color: var(--active-item-text-color);
background-color: var(--active-item-background-color) !important;
background-color: var(--active-item-background-color);
border-color: transparent; /* invisible border */
border-radius: 5px;
}
@@ -195,20 +207,15 @@ span.fancytree-active .fancytree-title {
border: 0;
}
span.fancytree-selected {
border-color: var(--main-border-color) !important;
border-radius: 5px;
}
span.fancytree-selected .fancytree-title {
text-decoration: underline;
font-style: italic;
span.fancytree-node.fancytree-selected {
background-color: var(--selection-background-color);
border-radius: 0;
}
span.fancytree-selected .fancytree-custom-icon::before {
font-family: "boxicons";
content: "\eb43";
border: 1px solid var(--main-border-color);
content: "\ef05";
border: 1px solid var(--main-text-color);
border-radius: 3px;
}

View File

@@ -566,113 +566,6 @@
"enable-smooth-scroll": "تمكين التمرير السلس",
"enable-motion": "تمكين الانتقالات والرسوم المتحركة"
},
"ai_llm": {
"progress": "تقدم",
"openai_tab": "OpenAI",
"actions": "أجراءات",
"retry": "أعد المحاولة",
"reprocessing_index": "جار اعادة البناء...",
"never": "ابدٱ",
"agent": {
"processing": "جار المعالجة...",
"thinking": "جار التفكير...",
"loading": "جار التحميل...",
"generating": "جار الانشاء..."
},
"name": "الذكاء الأصطناعي",
"openai": "OpenAI",
"sources": "مصادر",
"temperature": "درجة الحرارة",
"model": "نموذج",
"refreshing_models": "جار التحديث...",
"error": "خطأ",
"refreshing": "جار التحديث...",
"ollama_tab": "Ollama",
"anthropic_tab": "انتروبيك",
"not_started": "لم يبدأ بعد",
"title": "اعدادات AI",
"processed_notes": "الملاحظات المعالجة",
"total_notes": "الملاحظات الكلية",
"queued_notes": "الملاحظات في قائمة الانتظار",
"failed_notes": "الملاحظات الفاشلة",
"last_processed": "اخر معالجة",
"refresh_stats": "تحديث الاحصائيات",
"voyage_tab": "استكشاف AI",
"provider_precedence": "اولوية المزود",
"system_prompt": "موجه النظام",
"openai_configuration": "اعدادات OpenAI",
"openai_settings": "اعدادات OpenAI",
"api_key": "مفتاح واجهة برمجة التطبيقات",
"url": "عنوان URL الاساسي",
"default_model": "النموذج الافتراضي",
"base_url": "عنوان URL الأساسي",
"openai_url_description": "افتراضيا: https://api.openai.com/v1",
"anthropic_settings": "اعدادات انتروبيك",
"ollama_settings": "اعدادات Ollama",
"anthropic_configuration": "تهيئة انتروبيك",
"voyage_url_description": "افتراضيا: https://api.voyageai.com/v1",
"ollama_configuration": "تهيئة Ollama",
"enable_ollama": "تمكين Ollama",
"last_attempt": "اخر محاولة",
"active_providers": "المزودون النشطون",
"disabled_providers": "المزودون المعطلون",
"similarity_threshold": "عتبة التشابه",
"complete": "اكتمل (100%)",
"ai_settings": "اعدادات AI",
"show_thinking": "عرض التفكير",
"index_status": "حالة الفهرس",
"indexed_notes": "الملاحظات المفهرسة",
"indexing_stopped": "تم ايقاف الفهرسة",
"last_indexed": "اخر فهرسة",
"note_chat": "دردشة الملاحظة",
"start_indexing": "بدء الفهرسة",
"chat": {
"root_note_title": "دردشات AI",
"new_chat_title": "دردشة جديدة",
"create_new_ai_chat": "انشاء دردشة AI جديدة"
},
"selected_provider": "المزود المحدد",
"select_model": "اختر النموذج...",
"select_provider": "اختر المزود...",
"ollama_model": "نموذج Ollama",
"refresh_models": "تحديث النماذج",
"rebuild_index": "اعادة بناء الفهرس",
"note_title": "عنوان الملاحظة",
"processing": "جاري المعالجة ({{percentage}}%)",
"incomplete": "غير مكتمل ({{percentage}}%)",
"ollama_url": "عنوان URL الخاص ب Ollama",
"provider_configuration": "تكوين موفر AI",
"voyage_settings": "استكشاف اعدادات AI",
"enable_automatic_indexing": "تمكين الفهرسة التلقائية",
"index_rebuild_progress": "تقدم اعادة انشاء الفهرس",
"index_rebuild_complete": "اكتملت عملية تحسين الفهرس",
"use_enhanced_context": "استخدام السياق المحسن",
"enter_message": "ادخل رسالتك...",
"index_all_notes": "فهرسة جميع الملاحظات",
"indexing_in_progress": "جار فهرسة الملاحظات...",
"use_advanced_context": "استخدم السياق المتقدم",
"ai_enabled": "تمكين مميزات AI",
"ai_disabled": "الغاء تمكين مميزات AI",
"enable_ai_features": "تمكين خصائص AI/LLM",
"enable_ai": "تمكين خصائص AI/LLM",
"reprocess_index": "اعادة بناء فهرس البحث",
"index_rebuilding": "جار تحسين الفهرس {{percentage}}",
"voyage_configuration": "اعدادت Voyage AI",
"openai_model_description": "الامثلة: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
"partial": "{{ percentage }} % مكتمل",
"retry_queued": "تم جدولة الملاحظة لاعادة المحاولة",
"max_notes_per_llm_query": "اكبر عدد للملاحظات لكل استعلام",
"remove_provider": "احذف المزود من البحث",
"restore_provider": "استعادة المزود الى البحث",
"reprocess_index_error": "حدث خطأ اثناء اعادة بناء فهرس البحث",
"auto_refresh_notice": "تحديث تلقائي كل {{seconds}} ثانية",
"note_queued_for_retry": "الملاحظة جاهزة لاعادة المحاولة لاحقا",
"failed_to_retry_note": "‎فشل في اعادة محاولة معالجة المحاولة",
"failed_to_retry_all": "فشل في اعادة محاولة معالجة الملاحظة",
"error_generating_response": "‌فشل في توليد استجابة من ال AI",
"create_new_ai_chat": "انشاء دردشة AI جديدة",
"error_fetching": "فشل في استرجاع النماذج: {{error}}"
},
"code_auto_read_only_size": {
"unit": "حروف",
"title": "الحجم التلقائي للقراءه فقط"
@@ -910,13 +803,13 @@
"web-view": "عرض الويب",
"mind-map": "خريطة ذهنية",
"geo-map": "خريطة جغرافية",
"ai-chat": "دردشة AI",
"task-list": "قائمة المهام"
"task-list": "قائمة المهام",
"spreadsheet": "جدول البيانات"
},
"shared_switch": {
"shared": "مشترك",
"toggle-on-title": "مشاركة الملاحظة",
"toggle-off-title": "الغاء مشاركة الملاحظة"
"toggle-off-title": "إلغاء مشاركة الملاحظة"
},
"template_switch": {
"template": "قالب"
@@ -990,6 +883,7 @@
"electron_context_menu": {
"cut": "قص",
"copy": "نسخ",
"copy-as-markdown": "نسخ كـ Markdown",
"paste": "لصق",
"copy-link": "نسخ الرابط",
"add-term-to-dictionary": "اضافة \"{{term}}\" الى القاموس",
@@ -1175,7 +1069,6 @@
"rename_note": "اعادة تسمية الملاحظة",
"remove_relation": "حذف العلاقة",
"default_new_note_title": "ملاحظة جديدة",
"open_in_new_tab": "فتح في تبويب جديد",
"enter_new_title": "ادخل عنوان ملاحظة جديدة:",
"note_not_found": "الملاحظة {{noteId}} غير موجودة!",
"cannot_match_transform": "تعذر مطابقة التحويل: {{transform}}"
@@ -1393,8 +1286,10 @@
"search-for": "بحث ل \"{{term}}\""
},
"protect_note": {
"toggle-off": "ازالة الحماية عن الملاحظة",
"toggle-on": "حماية الملاحظة"
"toggle-off": "إزالة الحماية عن الملاحظة",
"toggle-on": "حماية الملاحظة",
"toggle-on-hint": "الملاحظة غير محمة، انقر لحمايتها",
"toggle-off-hint": "الملاحظة محمية، انقر لإزالة الحماية منها"
},
"open-help-page": "فتح صفحة المساعدة",
"empty": {

View File

@@ -1008,7 +1008,7 @@
"no_attachments": "此笔记没有附件。"
},
"book": {
"no_children_help": "此类型为书籍的笔记没有任何子笔记,因此没有内容显示。请参阅 <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> 了解详情。",
"no_children_help": "此集合没有任何子笔记,因此没有内容显示。",
"drag_locked_title": "锁定编辑",
"drag_locked_message": "无法拖拽,因为集合已被锁定编辑。"
},
@@ -1047,7 +1047,6 @@
"unprotecting-title": "解除保护状态"
},
"relation_map": {
"open_in_new_tab": "在新标签页中打开",
"remove_note": "删除笔记",
"edit_title": "编辑标题",
"rename_note": "重命名笔记",
@@ -1064,10 +1063,6 @@
"default_new_note_title": "新笔记",
"click_on_canvas_to_place_new_note": "点击画布以放置新笔记"
},
"render": {
"note_detail_render_help_1": "之所以显示此帮助说明,是因为这个类型为渲染 HTML 的笔记没有正常工作所需的关系。",
"note_detail_render_help_2": "渲染 HTML 笔记类型用于<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">编写脚本</a>。简而言之,您有一份 HTML 代码笔记(可包含一些 JavaScript然后这个笔记会把页面渲染出来。要使其正常工作您需要定义一个名为 \"renderNote\" 的<a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">关系</a>指向要渲染的 HTML 笔记。"
},
"backend_log": {
"refresh": "刷新"
},
@@ -1536,10 +1531,11 @@
"geo-map": "地理地图",
"beta-feature": "测试版",
"task-list": "任务列表",
"ai-chat": "AI聊天",
"new-feature": "新建",
"collections": "集合",
"book": "集合"
"book": "集合",
"ai-chat": "AI聊天",
"spreadsheet": "电子表格"
},
"protect_note": {
"toggle-on": "保护笔记",
@@ -1635,7 +1631,8 @@
},
"search_result": {
"no_notes_found": "没有找到符合搜索条件的笔记。",
"search_not_executed": "尚未执行搜索。请点击上方的\"搜索\"按钮查看结果。"
"search_not_executed": "尚未执行搜索。",
"search_now": "立即搜索"
},
"spacer": {
"configure_launchbar": "配置启动栏"
@@ -1763,6 +1760,7 @@
"add-term-to-dictionary": "将 \"{{term}}\" 添加到字典",
"cut": "剪切",
"copy": "复制",
"copy-as-markdown": "复制为 Markdown",
"copy-link": "复制链接",
"paste": "粘贴",
"paste-as-plain-text": "以纯文本粘贴",
@@ -1843,149 +1841,6 @@
"yesterday": "昨天"
}
},
"ai_llm": {
"not_started": "未开始",
"title": "AI设置",
"processed_notes": "已处理笔记",
"total_notes": "笔记总数",
"progress": "进度",
"queued_notes": "排队中笔记",
"failed_notes": "失败笔记",
"last_processed": "最后处理时间",
"refresh_stats": "刷新统计数据",
"enable_ai_features": "启用AI/LLM功能",
"enable_ai_description": "启用笔记摘要、内容生成等AI功能及其他LLM能力",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
"ollama_tab": "Ollama",
"enable_ai": "启用AI/LLM功能",
"enable_ai_desc": "启用笔记摘要、内容生成等AI功能及其他LLM能力",
"provider_configuration": "AI提供商配置",
"provider_precedence": "提供商优先级",
"provider_precedence_description": "按优先级排序的提供商列表(用逗号分隔,例如:'openai,anthropic,ollama'",
"temperature": "温度参数",
"temperature_description": "控制响应的随机性0 = 确定性输出2 = 最大随机性)",
"system_prompt": "系统提示词",
"system_prompt_description": "所有AI交互使用的默认系统提示词",
"openai_configuration": "OpenAI配置",
"openai_settings": "OpenAI设置",
"api_key": "API密钥",
"url": "基础URL",
"model": "模型",
"openai_api_key_description": "用于访问OpenAI服务的API密钥",
"anthropic_api_key_description": "用于访问Claude模型的Anthropic API密钥",
"default_model": "默认模型",
"openai_model_description": "示例gpt-4o、gpt-4-turbo、gpt-3.5-turbo",
"base_url": "基础URL",
"openai_url_description": "默认https://api.openai.com/v1",
"anthropic_settings": "Anthropic设置",
"anthropic_url_description": "Anthropic API的基础URL默认https://api.anthropic.com",
"anthropic_model_description": "用于聊天补全的Anthropic Claude模型",
"voyage_settings": "Voyage AI设置",
"ollama_settings": "Ollama设置",
"ollama_url_description": "Ollama API的URL默认http://localhost:11434",
"ollama_model_description": "用于聊天补全的 Ollama 模型",
"anthropic_configuration": "Anthropic配置",
"voyage_configuration": "Voyage AI配置",
"voyage_url_description": "默认https://api.voyageai.com/v1",
"ollama_configuration": "Ollama配置",
"enable_ollama": "启用Ollama",
"enable_ollama_description": "启用Ollama以使用本地AI模型",
"ollama_url": "Ollama URL",
"ollama_model": "Ollama模型",
"refresh_models": "刷新模型",
"refreshing_models": "刷新中...",
"enable_automatic_indexing": "启用自动索引",
"rebuild_index": "重建索引",
"rebuild_index_error": "启动索引重建失败。请查看日志了解详情。",
"note_title": "笔记标题",
"error": "错误",
"last_attempt": "最后尝试时间",
"actions": "操作",
"retry": "重试",
"partial": "{{ percentage }}% 已完成",
"retry_queued": "笔记已加入重试队列",
"retry_failed": "笔记加入重试队列失败",
"max_notes_per_llm_query": "每次查询的最大笔记数",
"max_notes_per_llm_query_description": "AI上下文包含的最大相似笔记数量",
"active_providers": "活跃提供商",
"disabled_providers": "已禁用提供商",
"remove_provider": "从搜索中移除提供商",
"restore_provider": "将提供商恢复到搜索中",
"similarity_threshold": "相似度阈值",
"similarity_threshold_description": "纳入LLM查询上下文的笔记最低相似度分数0-1",
"reprocess_index": "重建搜索索引",
"reprocessing_index": "重建中...",
"reprocess_index_started": "搜索索引优化已在后台启动",
"reprocess_index_error": "重建搜索索引失败",
"index_rebuild_progress": "索引重建进度",
"index_rebuilding": "正在优化索引({{percentage}}%",
"index_rebuild_complete": "索引优化完成",
"index_rebuild_status_error": "检查索引重建状态失败",
"never": "从未",
"processing": "处理中({{percentage}}%",
"incomplete": "未完成({{percentage}}%",
"complete": "已完成100%",
"refreshing": "刷新中...",
"auto_refresh_notice": "每 {{seconds}} 秒自动刷新",
"note_queued_for_retry": "笔记已加入重试队列",
"failed_to_retry_note": "重试笔记失败",
"all_notes_queued_for_retry": "所有失败笔记已加入重试队列",
"failed_to_retry_all": "重试笔记失败",
"ai_settings": "AI设置",
"api_key_tooltip": "用于访问服务的API密钥",
"empty_key_warning": {
"anthropic": "Anthropic API密钥为空。请输入有效的API密钥。",
"openai": "OpenAI API密钥为空。请输入有效的API密钥。",
"voyage": "Voyage API密钥为空。请输入有效的API密钥。",
"ollama": "Ollama API密钥为空。请输入有效的API密钥。"
},
"agent": {
"processing": "处理中...",
"thinking": "思考中...",
"loading": "加载中...",
"generating": "生成中..."
},
"name": "AI",
"openai": "OpenAI",
"use_enhanced_context": "使用增强上下文",
"enhanced_context_description": "为AI提供来自笔记及其相关笔记的更多上下文以获得更好的响应",
"show_thinking": "显示思考过程",
"show_thinking_description": "显示AI的思维链过程",
"enter_message": "输入你的消息...",
"error_contacting_provider": "联系AI提供商失败。请检查你的设置和网络连接。",
"error_generating_response": "生成AI响应失败",
"index_all_notes": "为所有笔记建立索引",
"index_status": "索引状态",
"indexed_notes": "已索引笔记",
"indexing_stopped": "索引已停止",
"indexing_in_progress": "索引进行中...",
"last_indexed": "最后索引时间",
"note_chat": "笔记聊天",
"sources": "来源",
"start_indexing": "开始索引",
"use_advanced_context": "使用高级上下文",
"ollama_no_url": "Ollama 未配置。请输入有效的URL。",
"chat": {
"root_note_title": "AI聊天记录",
"root_note_content": "此笔记包含你保存的AI聊天对话。",
"new_chat_title": "新聊天",
"create_new_ai_chat": "创建新的AI聊天"
},
"create_new_ai_chat": "创建新的AI聊天",
"configuration_warnings": "你的AI配置存在一些问题。请检查你的设置。",
"experimental_warning": "LLM功能目前处于实验阶段 - 特此提醒。",
"selected_provider": "已选提供商",
"selected_provider_description": "选择用于聊天和补全功能的AI提供商",
"select_model": "选择模型...",
"select_provider": "选择提供商...",
"ai_enabled": "已启用 AI 功能",
"ai_disabled": "已禁用 AI 功能",
"no_models_found_online": "找不到模型。请检查您的 API 密钥及设置。",
"no_models_found_ollama": "找不到 Ollama 模型。请确认 Ollama 是否正在运行。",
"error_fetching": "获取模型失败:{{error}}"
},
"code-editor-options": {
"title": "编辑器"
},
@@ -2073,7 +1928,8 @@
"raster": "栅格",
"vector_light": "矢量(浅色)",
"vector_dark": "矢量(深色)",
"show-scale": "显示比例尺"
"show-scale": "显示比例尺",
"show-labels": "显示标记名称"
},
"table_context_menu": {
"delete_row": "删除行"
@@ -2152,8 +2008,9 @@
"app-restart-required": "(需重启程序以应用更改)"
},
"pagination": {
"page_title": "第 {{startIndex}} 页 - 第 {{endIndex}} 页",
"total_notes": "{{count}} 篇笔记"
"total_notes": "{{count}} 篇笔记",
"prev_page": "上一页",
"next_page": "下一页"
},
"collections": {
"rendering_error": "出现错误无法显示内容。"
@@ -2272,6 +2129,43 @@
"url_placeholder": "输入或粘贴网站地址,例如 https://triliumnotes.org",
"create_button": "创建网页视图",
"invalid_url_title": "无效的地址",
"invalid_url_message": "请输入有效的网址,例如 https://triliumnotes.org。"
"invalid_url_message": "请输入有效的网址,例如 https://triliumnotes.org。",
"disabled_description": "此网页视图来自外部来源。为保护您免受网络钓鱼或恶意内容侵害,该视图不会自动加载。若您信任该来源,可手动启用加载功能。",
"disabled_button_enable": "启用网页视图"
},
"render": {
"setup_title": "在此笔记中显示自定义 HTML 或 Preact JSX",
"setup_create_sample_preact": "使用 Preact 建立范例笔记",
"setup_create_sample_html": "使用 HTML 建立范例笔记",
"setup_sample_created": "已建立一个范例笔记作为子笔记。",
"disabled_description": "此渲染笔记来自外部来源。为保护您免受恶意内容侵害,该功能默认处于禁用状态。启用前请确保您信任该来源。",
"disabled_button_enable": "启用渲染笔记"
},
"active_content_badges": {
"type_icon_pack": "图标包",
"type_backend_script": "后端脚本",
"type_frontend_script": "前端脚本",
"type_widget": "小部件",
"type_app_css": "自定义 CSS",
"type_render_note": "渲染笔记",
"type_web_view": "网页视图",
"type_app_theme": "自定义主题",
"toggle_tooltip_enable_tooltip": "点击以启用此 {{type}}。",
"toggle_tooltip_disable_tooltip": "点击以禁用此 {{type}}。",
"menu_docs": "打开文档",
"menu_execute_now": "立即执行脚本",
"menu_run": "自动执行",
"menu_run_disabled": "手动",
"menu_run_backend_startup": "当后端启动时",
"menu_run_hourly": "每小时",
"menu_run_daily": "每日",
"menu_run_frontend_startup": "当桌面前端启动时",
"menu_run_mobile_startup": "当移动前端启动时",
"menu_change_to_widget": "更改为小部件",
"menu_change_to_frontend_script": "更改为前端脚本",
"menu_theme_base": "主题基底"
},
"setup_form": {
"more_info": "了解更多"
}
}

View File

@@ -428,9 +428,9 @@
"run_on_note_content_change": "Wird ausgeführt, wenn der Inhalt einer Notiz geändert wird (einschließlich der Erstellung von Notizen).",
"run_on_note_change": "Wird ausgeführt, wenn eine Notiz geändert wird (einschließlich der Erstellung von Notizen). Enthält keine Inhaltsänderungen",
"run_on_note_deletion": "Wird ausgeführt, wenn eine Notiz gelöscht wird",
"run_on_branch_creation": "wird ausgeführt, wenn ein Zweig erstellt wird. Der Zweig ist eine Verbindung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. erstellt. beim Klonen oder Verschieben von Notizen.",
"run_on_branch_creation": "wird ausgeführt, wenn ein Zweig erstellt wird. Der Zweig ist eine Verbindung zwischen der übergeordneten und der untergeordneten Notiz und wird z. B. beim Klonen oder Verschieben von Notizen erstellt.",
"run_on_branch_change": "wird ausgeführt, wenn ein Zweig aktualisiert wird.",
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten Notiz und der untergeordneten Notiz und wird z. B. gelöscht. beim Verschieben der Notiz (alter Zweig/Link wird gelöscht).",
"run_on_branch_deletion": "wird ausgeführt, wenn ein Zweig gelöscht wird. Der Zweig ist eine Verknüpfung zwischen der übergeordneten und der untergeordneten Notiz und wird z. B. beim Verschieben der Notiz gelöscht (alter Zweig/Link wird gelöscht).",
"run_on_attribute_creation": "wird ausgeführt, wenn für die Notiz ein neues Attribut erstellt wird, das diese Beziehung definiert",
"run_on_attribute_change": " wird ausgeführt, wenn das Attribut einer Notiz geändert wird, die diese Beziehung definiert. Dies wird auch ausgelöst, wenn das Attribut gelöscht wird",
"relation_template": "Die Attribute der Notiz werden auch ohne eine Hierarchische-Beziehung vererbt. Der Inhalt und der Zweig werden den Instanznotizen hinzugefügt, wenn sie leer sind. Einzelheiten findest du in der Dokumentation.",
@@ -801,7 +801,7 @@
"expand_tooltip": "Erweitert die direkten Unterelemente dieser Sammlung (eine Ebene tiefer). Für weitere Optionen auf den Pfeil rechts klicken.",
"expand_first_level": "Direkte Unterelemente erweitern",
"expand_nth_level": "{{depth}} Ebenen erweitern",
"hide_child_notes": "Unternotizen im Baum ausblenden"
"hide_child_notes": "Untergeordnete Notizen im Baum ausblenden"
},
"edited_notes": {
"no_edited_notes_found": "An diesem Tag wurden noch keine Notizen bearbeitet...",
@@ -1007,7 +1007,7 @@
"no_attachments": "Diese Notiz enthält keine Anhänge."
},
"book": {
"no_children_help": "Diese Notiz mit dem Notiztyp Buch besitzt keine Unternotizen, deshalb ist nichts zum Anzeigen vorhanden. Siehe <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">Wiki</a> für mehr Details.",
"no_children_help": "Diese Sammlung enthält keine untergeordnete Notizen, daher gibt es nichts anzuzeigen.",
"drag_locked_title": "Für Bearbeitung gesperrt",
"drag_locked_message": "Das Ziehen ist nicht möglich, da die Sammlung für die Bearbeitung gesperrt ist."
},
@@ -1046,7 +1046,6 @@
"unprotecting-title": "Ungeschützt-Status"
},
"relation_map": {
"open_in_new_tab": "In neuem Tab öffnen",
"remove_note": "Notiz entfernen",
"edit_title": "Titel bearbeiten",
"rename_note": "Notiz umbenennen",
@@ -1063,10 +1062,6 @@
"default_new_note_title": "neue Notiz",
"click_on_canvas_to_place_new_note": "Klicke auf den Canvas, um eine neue Notiz zu platzieren"
},
"render": {
"note_detail_render_help_1": "Diese Hilfesnotiz wird angezeigt, da diese Notiz vom Typ „HTML rendern“ nicht über die erforderliche Beziehung verfügt, um ordnungsgemäß zu funktionieren.",
"note_detail_render_help_2": "Render-HTML-Notiztyp wird benutzt für <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/scripts.html\">scripting</a>. Kurzgesagt, du hast ein HTML-Code-Notiz (optional mit JavaScript) und diese Notiz rendert es. Damit es funktioniert, musst du eine a <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/attributes.html\">Beziehung</a> namens \"renderNote\" zeigend auf die HTML-Notiz zum rendern definieren."
},
"backend_log": {
"refresh": "Aktualisieren"
},
@@ -1191,7 +1186,7 @@
"tooltip_code_note_syntax": "Code-Notizen"
},
"vim_key_bindings": {
"use_vim_keybindings_in_code_notes": "Verwende VIM-Tastenkombinationen in Codenotizen (kein Ex-Modus)",
"use_vim_keybindings_in_code_notes": "Vim Tastenbelegung",
"enable_vim_keybindings": "Aktiviere Vim-Tastenkombinationen"
},
"wrap_lines": {
@@ -1443,7 +1438,7 @@
"open-in-a-new-tab": "In neuem Tab öffnen",
"open-in-a-new-split": "In neuem Split öffnen",
"insert-note-after": "Notiz dahinter einfügen",
"insert-child-note": "Unternotiz einfügen",
"insert-child-note": "Untergeordnete Notiz einfügen",
"delete": "Löschen",
"search-in-subtree": "Im Zweig suchen",
"hoist-note": "Notiz-Fokus setzen",
@@ -1492,20 +1487,21 @@
"mermaid-diagram": "Mermaid Diagramm",
"canvas": "Leinwand",
"web-view": "Webansicht",
"mind-map": "Mind Map",
"mind-map": "Mindmap",
"file": "Datei",
"image": "Bild",
"launcher": "Starter",
"doc": "Dokument",
"widget": "Widget",
"confirm-change": "Es is nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
"confirm-change": "Es ist nicht empfehlenswert den Notiz-Typ zu ändern, wenn der Inhalt der Notiz nicht leer ist. Möchtest du dennoch fortfahren?",
"geo-map": "Geo-Karte",
"beta-feature": "Beta",
"book": "Sammlung",
"ai-chat": "KI Chat",
"ai-chat": "KI-Chat",
"task-list": "Aufgabenliste",
"new-feature": "Neu",
"collections": "Sammlungen"
"collections": "Sammlungen",
"spreadsheet": "Tabelle"
},
"protect_note": {
"toggle-on": "Notiz schützen",
@@ -1562,7 +1558,7 @@
"saved-search-note-refreshed": "Gespeicherte Such-Notiz wurde aktualisiert.",
"hoist-this-note-workspace": "Diese Notiz fokussieren (Arbeitsbereich)",
"refresh-saved-search-results": "Gespeicherte Suchergebnisse aktualisieren",
"create-child-note": "Unternotiz anlegen",
"create-child-note": "Untergeordnete Notiz anlegen",
"unhoist": "Fokus verlassen",
"toggle-sidebar": "Seitenleiste ein-/ausblenden",
"dropping-not-allowed": "Ablegen von Notizen an dieser Stelle ist nicht zulässig.",
@@ -1573,7 +1569,7 @@
"subtree-hidden-tooltip_one": "{{count}} untergeordnete Notiz, die im Baum ausgeblendet ist",
"subtree-hidden-tooltip_other": "{{count}} untergeordnete Notizen, die im Baum ausgeblendet sind",
"subtree-hidden-moved-title": "Zu {{title}} hinzugefügt",
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre Unternotizen im Baum aus.",
"subtree-hidden-moved-description-collection": "Diese Sammlung blendet ihre untergeordneten Notizen im Baum aus.",
"subtree-hidden-moved-description-other": "Untergeordnete Notizen sind im Baum für diese Notiz ausgeblendet."
},
"title_bar_buttons": {
@@ -1604,7 +1600,8 @@
},
"search_result": {
"no_notes_found": "Es wurden keine Notizen mit den angegebenen Suchparametern gefunden.",
"search_not_executed": "Die Suche wurde noch nicht ausgeführt. Klicke oben auf „Suchen“, um die Ergebnisse anzuzeigen."
"search_not_executed": "Die Suche wurde noch nicht ausgeführt.",
"search_now": "Jetzt suchen"
},
"spacer": {
"configure_launchbar": "Starterleiste konfigurieren"
@@ -1732,6 +1729,7 @@
"add-term-to-dictionary": "Begriff \"{{term}}\" zum Wörterbuch hinzufügen",
"cut": "Ausschneiden",
"copy": "Kopieren",
"copy-as-markdown": "Als Markdown kopieren",
"copy-link": "Link kopieren",
"paste": "Einfügen",
"paste-as-plain-text": "Als unformatierten Text einfügen",
@@ -1760,7 +1758,7 @@
},
"note_autocomplete": {
"search-for": "Suche nach \"{{term}}\"",
"create-note": "Erstelle und verlinke Unternotiz \"{{term}}\"",
"create-note": "Erstelle und verlinke untergeordnete Notiz \"{{term}}\"",
"insert-external-link": "Einfügen von Externen Link zu \"{{term}}\"",
"clear-text-field": "Textfeldinhalt löschen",
"show-recent-notes": "Aktuelle Notizen anzeigen",
@@ -1771,7 +1769,7 @@
"quick-edit": "Schnellbearbeitung"
},
"geo-map": {
"create-child-note-title": "Neue Unternotiz anlegen und zur Karte hinzufügen",
"create-child-note-title": "Neue untergeordnete Notiz anlegen und zur Karte hinzufügen",
"create-child-note-instruction": "Auf die Karte klicken, um eine neue Notiz an der Stelle zu erstellen oder Escape drücken um abzubrechen.",
"unable-to-load-map": "Karte konnte nicht geladen werden.",
"create-child-note-text": "Marker hinzufügen"
@@ -1798,149 +1796,6 @@
"close": "Schließen",
"help_title": "Zeige mehr Informationen zu diesem Fenster"
},
"ai_llm": {
"not_started": "Nicht gestartet",
"title": "KI Einstellungen",
"processed_notes": "Verarbeitete Notizen",
"total_notes": "Gesamt Notizen",
"progress": "Fortschritt",
"queued_notes": "Eingereihte Notizen",
"failed_notes": "Fehlgeschlagenen Notizen",
"last_processed": "Zuletzt verarbeitet",
"refresh_stats": "Statistiken neu laden",
"enable_ai_features": "Aktiviere KI/LLM Funktionen",
"enable_ai_description": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
"openai_tab": "OpenAI",
"anthropic_tab": "Anthropic",
"voyage_tab": "Voyage AI",
"ollama_tab": "Ollama",
"enable_ai": "Aktiviere KI/LLM Funktionen",
"enable_ai_desc": "Aktiviere KI-Funktionen wie Notizzusammenfassungen, Inhaltserzeugung und andere LLM-Funktionen",
"provider_configuration": "KI-Anbieterkonfiguration",
"provider_precedence": "Anbieter Priorität",
"provider_precedence_description": "Komma-getrennte Liste von Anbieter in der Reihenfolge ihrer Priorität (z.B. 'openai, anthropic,ollama')",
"temperature": "Temperatur",
"temperature_description": "Regelt die Zufälligkeit in Antworten (0 = deterministisch, 2 = maximale Zufälligkeit)",
"system_prompt": "Systemaufforderung",
"system_prompt_description": "Standard Systemaufforderung für alle KI-Interaktionen",
"openai_configuration": "OpenAI Konfiguration",
"openai_settings": "OpenAI Einstellungen",
"api_key": "API Schlüssel",
"url": "Basis-URL",
"model": "Modell",
"anthropic_settings": "Anthropic Einstellungen",
"partial": "{{ percentage }}% verarbeitet",
"anthropic_api_key_description": "Dein Anthropic API-Key für den Zugriff auf Claude Modelle",
"anthropic_model_description": "Anthropic Claude Modell für Chat-Vervollständigung",
"voyage_settings": "Einstellungen für Voyage AI",
"ollama_url_description": "URL für die Ollama API (Standard: http://localhost:11434)",
"ollama_model_description": "Ollama Modell für Chat-Vervollständigung",
"anthropic_configuration": "Anthropic Konfiguration",
"voyage_configuration": "Voyage AI Konfiguration",
"voyage_url_description": "Standard: https://api.voyageai.com/v1",
"ollama_configuration": "Ollama Konfiguration",
"enable_ollama": "Aktiviere Ollama",
"enable_ollama_description": "Aktiviere Ollama für lokale KI Modell Nutzung",
"ollama_url": "Ollama URL",
"ollama_model": "Ollama Modell",
"refresh_models": "Aktualisiere Modelle",
"refreshing_models": "Aktualisiere...",
"enable_automatic_indexing": "Aktiviere automatische Indizierung",
"rebuild_index": "Index neu aufbauen",
"rebuild_index_error": "Fehler beim Neuaufbau des Index. Prüfe Log für mehr Informationen.",
"retry_failed": "Fehler: Notiz konnte nicht erneut eingereiht werden",
"max_notes_per_llm_query": "Max. Notizen je Abfrage",
"max_notes_per_llm_query_description": "Maximale Anzahl ähnlicher Notizen zum Einbinden als KI Kontext",
"active_providers": "Aktive Anbieter",
"disabled_providers": "Inaktive Anbieter",
"remove_provider": "Entferne Anbieter von Suche",
"restore_provider": "Anbieter zur Suche wiederherstellen",
"similarity_threshold": "Ähnlichkeitsschwelle",
"similarity_threshold_description": "Mindestähnlichkeitswert (0-1) für Notizen, die im Kontext für LLM-Abfragen berücksichtigt werden sollen",
"reprocess_index": "Suchindex neu erstellen",
"reprocessing_index": "Neuerstellung...",
"reprocess_index_started": "Suchindex-Optimierung wurde im Hintergrund gestartet",
"reprocess_index_error": "Fehler beim Wiederaufbau des Suchindex",
"index_rebuild_progress": "Fortschritt der Index-Neuerstellung",
"index_rebuilding": "Optimierung Index ({{percentage}}%)",
"index_rebuild_complete": "Index Optimierung abgeschlossen",
"index_rebuild_status_error": "Fehler bei Überprüfung Status Index Neuerstellung",
"never": "Niemals",
"processing": "Verarbeitung ({{percentage}}%)",
"refreshing": "Aktualisiere...",
"incomplete": "Unvollständig ({{percentage}}%)",
"complete": "Abgeschlossen (100%)",
"auto_refresh_notice": "Auto-Aktualisierung alle {{seconds}} Sekunden",
"note_queued_for_retry": "Notiz in Warteschlange für erneuten Versuch hinzugefügt",
"failed_to_retry_note": "Wiederholungsversuch fehlgeschlagen für Notiz",
"ai_settings": "KI Einstellungen",
"agent": {
"processing": "Verarbeite...",
"thinking": "Nachdenken...",
"loading": "Lade...",
"generating": "Generiere..."
},
"name": "KI",
"openai": "OpenAI",
"use_enhanced_context": "Benutze verbesserten Kontext",
"openai_api_key_description": "Dein OpenAPI-Key für den Zugriff auf den KI-Dienst",
"default_model": "Standardmodell",
"openai_model_description": "Beispiele: gpt-4o, gpt-4-turbo, gpt-3.5-turbo",
"base_url": "Basis URL",
"openai_url_description": "Standard: https://api.openai.com/v1",
"anthropic_url_description": "Basis URL für Anthropic API (Standard: https://api.anthropic.com)",
"ollama_settings": "Ollama Einstellungen",
"note_title": "Notiz Titel",
"error": "Fehler",
"last_attempt": "Letzter Versuch",
"actions": "Aktionen",
"retry": "Erneut versuchen",
"retry_queued": "Notiz für weiteren Versuch eingereiht",
"empty_key_warning": {
"anthropic": "Anthropic API-Key ist leer. Bitte gültigen API-Key eingeben.",
"openai": "OpenAI API-Key ist leer. Bitte gültigen API-Key eingeben.",
"voyage": "Voyage API-Key ist leer. Bitte gültigen API-Key eingeben.",
"ollama": "Ollama API-Key ist leer. Bitte gültigen API-Key eingeben."
},
"api_key_tooltip": "API-Key für den Zugriff auf den Dienst",
"failed_to_retry_all": "Wiederholungsversuch für Notizen fehlgeschlagen",
"all_notes_queued_for_retry": "Alle fehlgeschlagenen Notizen wurden zur Wiederholung in die Warteschlange gestellt",
"enhanced_context_description": "Versorgt die KI mit mehr Kontext aus der Notiz und den zugehörigen Notizen, um bessere Antworten zu ermöglichen",
"show_thinking": "Zeige Denkprozess",
"show_thinking_description": "Zeige den Denkprozess der KI",
"enter_message": "Geben Sie Ihre Nachricht ein...",
"error_contacting_provider": "Fehler beim Kontaktieren des KI-Anbieters. Bitte überprüfe die Einstellungen und die Internetverbindung.",
"error_generating_response": "Fehler beim Generieren der KI Antwort",
"index_all_notes": "Indiziere alle Notizen",
"index_status": "Indizierungsstatus",
"indexed_notes": "Indizierte Notizen",
"indexing_stopped": "Indizierung gestoppt",
"indexing_in_progress": "Indizierung in Bearbeitung...",
"last_indexed": "Zuletzt Indiziert",
"note_chat": "Notizen-Chat",
"sources": "Quellen",
"start_indexing": "Starte Indizierung",
"use_advanced_context": "Benutze erweiterten Kontext",
"ollama_no_url": "Ollama ist nicht konfiguriert. Bitte trage eine gültige URL ein.",
"chat": {
"root_note_title": "KI Chats",
"root_note_content": "Diese Notiz enthält gespeicherte KI-Chat-Unterhaltungen.",
"new_chat_title": "Neuer Chat",
"create_new_ai_chat": "Erstelle neuen KI Chat"
},
"create_new_ai_chat": "Erstelle neuen KI Chat",
"configuration_warnings": "Es wurden Probleme mit der KI Konfiguration festgestellt. Bitte überprüfe die Einstellungen.",
"experimental_warning": "Die LLM-Funktionen sind aktuell experimentell - sei an dieser Stelle gewarnt.",
"selected_provider": "Ausgewählter Anbieter",
"selected_provider_description": "Wähle einen KI-Anbieter für Chat- und Vervollständigungsfunktionen",
"select_model": "Wähle Modell...",
"select_provider": "Wähle Anbieter...",
"ai_enabled": "KI Funktionen aktiviert",
"ai_disabled": "KI Funktionen deaktiviert",
"no_models_found_online": "Keine Modelle gefunden. Bitte überprüfe den API-Key und die Einstellungen.",
"no_models_found_ollama": "Kein Ollama Modell gefunden. Bitte prüfe, ob Ollama gerade läuft.",
"error_fetching": "Fehler beim Abrufen der Modelle: {{error}}"
},
"zen_mode": {
"button_exit": "Verlasse Zen Modus"
},
@@ -1975,7 +1830,7 @@
"no_totp_secret_warning": "Um TOTP zu aktivieren, muss zunächst ein TOTP Geheimnis generiert werden.",
"totp_secret_description_warning": "Nach der Generierung des TOTP Geheimnisses ist eine Neuanmeldung mit dem TOTP Geheimnis erforderlich.",
"totp_secret_generated": "TOTP Geheimnis generiert",
"totp_secret_warning": "Bitte speichere das TOTP Geheimnis an einem sicheren Ort. Es wird nicht noch einmal angezeigt.",
"totp_secret_warning": "Bitte speichere das generierte Geheimnis an einem sicheren Ort. Es wird nicht noch einmal angezeigt.",
"totp_secret_regenerate_confirm": "Möchten Sie das TOTP-Geheimnis wirklich neu generieren? Dadurch werden das bisherige TOTP-Geheimnis und alle vorhandenen Wiederherstellungscodes ungültig.",
"recovery_keys_title": "Einmalige Wiederherstellungsschlüssel",
"recovery_keys_description": "Einmalige Wiederherstellungsschlüssel werden verwendet, um sich anzumelden, falls Sie keinen Zugriff auf Ihre Authentifizierungscodes haben.",
@@ -2073,7 +1928,7 @@
"show-hide-columns": "Zeige/verberge Spalten",
"row-insert-above": "Zeile oberhalb einfügen",
"row-insert-below": "Zeile unterhalb einfügen",
"row-insert-child": "Unternotiz einfügen",
"row-insert-child": "Untergeordnete Notiz einfügen",
"add-column-to-the-left": "Spalte links einfügen",
"add-column-to-the-right": "Spalte rechts einfügen",
"edit-column": "Spalte editieren",
@@ -2090,7 +1945,8 @@
"raster": "Raster",
"vector_light": "Vektor (Hell)",
"vector_dark": "Vektor (Dunkel)",
"show-scale": "Zeige Skalierung"
"show-scale": "Zeige Skalierung",
"show-labels": "Zeige Markierungsnamen"
},
"table_context_menu": {
"delete_row": "Zeile entfernen"
@@ -2157,8 +2013,9 @@
"percentage": "%"
},
"pagination": {
"page_title": "Seite {{startIndex}} von {{endIndex}}",
"total_notes": "{{count}} Notizen"
"total_notes": "{{count}} Notizen",
"prev_page": "Vorherige Seite",
"next_page": "Nächste Seite"
},
"collections": {
"rendering_error": "Aufgrund eines Fehlers können keine Inhalte angezeigt werden."
@@ -2204,7 +2061,7 @@
"hoisted_badge_title": "Abgesenkt",
"workspace_badge": "Arbeitsfläche",
"scroll_to_top_title": "Zum Anfang der Notiz springen",
"create_new_note": "Neue Unternotiz erstellen",
"create_new_note": "Neue untergeordnete Notiz erstellen",
"empty_hide_archived_notes": "Archivierte Notizen ausblenden"
},
"breadcrumb_badges": {
@@ -2287,6 +2144,90 @@
"url_placeholder": "Gib oder füge die Adresse der Webseite ein, zum Beispiel https://triliumnotes.org",
"create_button": "Erstelle Web Ansicht",
"invalid_url_title": "Ungültige Adresse",
"invalid_url_message": "Füge eine valide Webadresse ein, zum Beispiel https://triliumnotes.org."
"invalid_url_message": "Füge eine valide Webadresse ein, zum Beispiel https://triliumnotes.org.",
"disabled_description": "Diese Webansicht wurde von einer externen Quelle importiert. Um Sie vor Phishing oder schädlichen Inhalten zu schützen, wird sie nicht automatisch geladen. Sie können sie aktivieren, wenn Sie der Quelle vertrauen.",
"disabled_button_enable": "Webansicht aktivieren"
},
"render": {
"setup_create_sample_html": "Eine Beispielnotiz mit HTML erstellen",
"setup_create_sample_preact": "Eine Beispielnotiz mit Preact erstellen",
"setup_title": "Benutzerdefiniertes HTML oder Preact JSX in dieser Notiz anzeigen",
"setup_sample_created": "Eine Beispielnotiz wurde als untergeordnete Notiz erstellt.",
"disabled_description": "Diese Rendering-Notizen stammen aus einer externen Quelle. Um Sie vor schädlichen Inhalten zu schützen, ist diese Funktion standardmäßig deaktiviert. Stellen Sie sicher, dass Sie der Quelle vertrauen, bevor Sie sie aktivieren.",
"disabled_button_enable": "Rendering-Notiz aktivieren"
},
"active_content_badges": {
"type_icon_pack": "Icon-Paket",
"type_backend_script": "Backend-Skript",
"type_frontend_script": "Frontend-Skript",
"type_widget": "Widget",
"type_app_css": "Benutzerdefiniertes CSS",
"type_render_note": "Rendering-Notiz",
"type_web_view": "Webansicht",
"type_app_theme": "Benutzerdefiniertes Thema",
"toggle_tooltip_enable_tooltip": "Klicken, um diesen {{type}} zu aktivieren.",
"toggle_tooltip_disable_tooltip": "Klicken, um diesen {{type}} zu deaktivieren.",
"menu_docs": "Dokumentation öffnen",
"menu_execute_now": "Skript jetzt ausführen",
"menu_run": "Automatisch ausführen",
"menu_run_disabled": "Manuell",
"menu_run_backend_startup": "Wenn das Backend startet",
"menu_run_hourly": "Stündlich",
"menu_run_daily": "Täglich",
"menu_run_frontend_startup": "Wenn das Desktop-Frontend startet",
"menu_run_mobile_startup": "Wenn das mobile Frontend startet",
"menu_change_to_widget": "Zum Widget wechseln",
"menu_change_to_frontend_script": "Zum Frontend-Skript wechseln",
"menu_theme_base": "Themenbasis"
},
"setup_form": {
"more_info": "Mehr erfahren"
},
"media": {
"play": "Abspielen (Arbeitsbereich)",
"pause": "Pausieren (Arbeitsbereich)",
"back-10s": "10 s zurück (Linke Pfeiltaste)",
"forward-30s": "30 s vorwärts",
"mute": "Stumm (M)",
"unmute": "Stummschaltung aufheben (M)",
"playback-speed": "Wiedergabegeschwindigkeit",
"loop": "Schleife",
"disable-loop": "Schleife deaktivieren",
"rotate": "Rotieren",
"picture-in-picture": "Bild-in-Bild",
"exit-picture-in-picture": "Bild-in-Bild verlassen",
"fullscreen": "Vollbild (F)",
"exit-fullscreen": "Vollbild verlassen",
"unsupported-format": "Medienvorschau ist für dieses Format nicht verfügbar:\n{{mime}}",
"zoom-to-fit": "Zoomen um auszufüllen",
"zoom-reset": "Zoomen um auszufüllen zurücksetzen"
},
"mermaid": {
"placeholder": "Geben den Inhalt des Mermaid-Diagramms ein oder verwenden eine der folgenden Beispieldiagramme.",
"sample_diagrams": "Beispieldiagramme:",
"sample_flowchart": "Flussdiagramm",
"sample_class": "Klasse",
"sample_sequence": "Abfolge",
"sample_entity_relationship": "Entität Beziehung",
"sample_state": "Zustandsübergangsdiagramm",
"sample_mindmap": "Mindmap",
"sample_architecture": "Architektur",
"sample_block": "Block",
"sample_c4": "C4",
"sample_gantt": "Gantt",
"sample_git": "GitGraph",
"sample_kanban": "Kanban",
"sample_packet": "Paket",
"sample_pie": "Kuchen",
"sample_quadrant": "Quadrant",
"sample_radar": "Radar",
"sample_requirement": "Anforderung",
"sample_sankey": "Sankey",
"sample_timeline": "Zeitstrahl",
"sample_treemap": "Kachel",
"sample_user_journey": "Benutzererfahrung",
"sample_xy": "XY",
"sample_venn": "Mengen",
"sample_ishikawa": "Ursache-Wirkung"
}
}

View File

@@ -1,6 +1,6 @@
{
"about": {
"title": "Πληροφορίες για το Trilium Notes",
"title": "Σχετικά με το Trilium Notes",
"homepage": "Αρχική Σελίδα:",
"app_version": "Έκδοση εφαρμογής:",
"db_version": "Έκδοση βάσης δεδομένων:",

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