Compare commits

...

1915 Commits

Author SHA1 Message Date
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
aaca18003d Translations update from Hosted Weblate (#8279) 2026-01-06 13:54:24 +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
Kim Nøglegaard
5ec521b024 Translated using Weblate (Norwegian Bokmål)
Currently translated at 68.4% (104 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-06 04:01:53 +01:00
Yatrik Patel
b3c0be7559 Translated using Weblate (Hindi)
Currently translated at 3.0% (12 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-06 04:01:51 +01:00
Máté Zsólya
d52b735b99 Translated using Weblate (Hungarian)
Currently translated at 1.9% (34 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hu/
2026-01-06 04:01:49 +01:00
Yatrik Patel
639b1f2863 Translated using Weblate (Hindi)
Currently translated at 5.9% (9 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-06 04:01:47 +01:00
Elian Doran
7f2cc885fe Feat(math): Improve legacy math input with MathLive (#7842) 2026-01-06 00:12:38 +02:00
Elian Doran
19a365a370 fix(sql_console): cannot copy table data (#8268) 2026-01-06 00:10:11 +02:00
Elian Doran
9a50da328e chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.9 (#8265) 2026-01-05 23:53:05 +02:00
Elian Doran
181e36a7c1 Merge remote-tracking branch 'origin/main' into Meinzzzz/main
; Conflicts:
;	.gitignore
2026-01-05 23:46:12 +02:00
Elian Doran
178508d245 Merge branch 'main' into fix/sql_select_text 2026-01-05 23:43:29 +02:00
Elian Doran
d132d084cf Merge branch 'main' into renovate/rollup-plugin-webpack-stats-2.x 2026-01-05 23:43:06 +02:00
Elian Doran
494b55d685 fix(ckeditor): missing pl locale 2026-01-05 23:39:36 +02:00
Elian Doran
51513d3779 fix(status_bar): count not refreshing properly after change 2026-01-05 21:03:32 +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
688d197472 chore(client): set up body classes 2026-01-05 11:55:51 +02:00
Elian Doran
b745fb476e chore(client): get icons to load 2026-01-05 11:50:21 +02:00
Elian Doran
047b5a85d2 chore(client): load stylesheets 2026-01-05 11:48:19 +02:00
Elian Doran
370a0c6a05 feat(client): get desktop to start 2026-01-05 11:40:53 +02:00
Elian Doran
0d4558fee1 feat(client): get glob to be populated 2026-01-05 11:37:03 +02:00
Elian Doran
76526e0a96 feat(server): bootstrap route 2026-01-05 11:22:10 +02:00
Elian Doran
70093e0a7d feat(server): render static Vite HTML 2026-01-05 11:07:40 +02:00
SngAbc
458398f2ca Merge branch 'main' into fix/sql_select_text 2026-01-05 13:51:45 +08:00
SngAbc
7a6cc4f51e fix(sql_console): cannot copy table data
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-01-05 12:16:16 +08:00
SiriusXT
f4ccce7de5 fix(sql_console): cannot copy table data 2026-01-05 11:23:50 +08:00
renovate[bot]
f8b5417d6c chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.9 2026-01-05 01:03:52 +00:00
Elian Doran
13ce8cf498 fix(note_list): the note list cannot open the context menu. (#8254) 2026-01-04 23:49:25 +02:00
Elian Doran
6c2afc086c feat(i18n): add Polish 2026-01-04 23:38:51 +02:00
Elian Doran
93d50712a9 chore(scripts): fix typecheck issue 2026-01-04 23:38:51 +02:00
Elian Doran
ed91a44928 feat(scripts): check translation coverage 2026-01-04 23:38:50 +02:00
Elian Doran
cd10e66fbb chore(scripts): build scripts not working properly on Windows 2026-01-04 23:38:50 +02:00
Elian Doran
d6aa126fcc Translations update from Hosted Weblate (#8264) 2026-01-04 22:34:38 +02:00
noobhjy
3308c7bdf4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1751 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-01-04 20:01:53 +00:00
Francis C.
56341a1a73 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1751 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-01-04 20:01:52 +00:00
green
0857e1a536 Translated using Weblate (Japanese)
Currently translated at 100.0% (1751 of 1751 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-01-04 20:01:50 +00:00
Kim Nøglegaard
5d6b25a29e Translated using Weblate (Norwegian Bokmål)
Currently translated at 57.2% (87 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-04 20:01:49 +00:00
Elian Doran
5bc15a5448 PDFjs v1 final tweaks (#8256) 2026-01-04 21:21:51 +02:00
Elian Doran
51a19d0544 docs(user): add missing slug 2026-01-04 21:11:13 +02:00
Elian Doran
fb4e912ed0 chore(pdfjs): address requested changes 2026-01-04 21:08:39 +02:00
Elian Doran
20c2652013 chore(server): set up environment for starting Nginx proxy with subdir 2026-01-04 20:33:54 +02:00
Elian Doran
971d6ad9e3 chore(pdfjs): use version-based system for cache busting 2026-01-04 20:06:11 +02:00
Elian Doran
757fc7a7fe chore(pdfjs): embed sandbox file 2026-01-04 18:50:40 +02:00
Elian Doran
e4d0a4554a feat(client/note_list): use built-in PDF viewer 2026-01-04 18:40:17 +02:00
Elian Doran
dfab7dbc4b fix(note_list): missing margin in button 2026-01-04 18:39:23 +02:00
Elian Doran
0039f4c155 feat(pdfjs): replace blob instead of creating a new revision every time 2026-01-04 17:25:56 +02:00
Elian Doran
23f7dc63b8 feat(pdfjs): enable editing features only if in main editor 2026-01-04 17:11:16 +02:00
Elian Doran
e485b75a44 fix(pdfjs): saves as soon as document is opened 2026-01-04 17:06:41 +02:00
Elian Doran
dbef57d329 chore(deps): update dependency webdriverio to v9.23.0 (#8258) 2026-01-04 10:36:47 +02:00
SiriusXT
c650441655 Merge branch 'main' into fix/note_list 2026-01-04 10:49:46 +08:00
SiriusXT
e573a8af77 chore(note_grid): remove unused tree import 2026-01-04 10:48:45 +08:00
SiriusXT
b23252d046 fix(note_grid): the note grid cannot open the context menu 2026-01-04 10:25:43 +08:00
renovate[bot]
2f7448dbd4 chore(deps): update dependency webdriverio to v9.23.0 2026-01-04 02:13:34 +00:00
Adorian Doran
9bf4aa2968 readme: update screenshot 2026-01-04 00:28:00 +02:00
Elian Doran
d78a7bad3b feat(import/markdown): handle bash as sh 2026-01-03 23:30:38 +02:00
Elian Doran
b812177e78 docs(user): add spellcheck=false to inline code 2026-01-03 23:28:28 +02:00
Elian Doran
4710a6af41 feat(export/markdown): add spellcheck=false to inline code 2026-01-03 23:19:58 +02:00
Elian Doran
a613980ea4 docs(user): add missing jsx / HTML code blocks 2026-01-03 22:56:23 +02:00
Elian Doran
20ae1f844b feat(markdown): support html, jsx in code blocks 2026-01-03 22:44:48 +02:00
Elian Doran
69511134e5 refactor(client/pdf): handle blob request on client side 2026-01-03 20:54:28 +02:00
Elian Doran
75952563e4 Translations update from Hosted Weblate (#8255) 2026-01-03 20:31:35 +02:00
Elian Doran
21cf5e1df7 chore(client/pdf): use custom spaced update hook 2026-01-03 20:29:54 +02:00
Hosted Weblate
9df5505989 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-01-03 18:03:22 +00:00
Kim Nøglegaard
1809d59193 Translated using Weblate (Norwegian Bokmål)
Currently translated at 43.4% (66 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-03 18:03:20 +00:00
Francis C.
feaa54d660 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1745 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2026-01-03 18:03:20 +00:00
noobhjy
c94bd41162 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (1743 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-01-03 18:03:19 +00:00
Elian Doran
f1f3e66537 Save indicator (#8249) 2026-01-03 20:03:12 +02:00
Elian Doran
80363cdc73 chore(client/save_indicator): fix some spacing issues 2026-01-03 19:53:46 +02:00
Elian Doran
02e08fdf12 chore(client/save_indicator): address requested changes 2026-01-03 19:47:33 +02:00
Elian Doran
42283b2469 doc(user): mention the save status indicator 2026-01-03 19:40:39 +02:00
Elian Doran
d3b598a5b2 fix(client/save_indicator): not visible on light theme 2026-01-03 19:39:16 +02:00
Elian Doran
0dd3a03c6b chore(client): fix type issue 2026-01-03 19:30:52 +02:00
Elian Doran
2144888447 Merge remote-tracking branch 'origin/main' into feature/save_indicator 2026-01-03 19:24:51 +02:00
Elian Doran
b2549066dc PDF.js refinement (#8247) 2026-01-03 19:24:28 +02:00
Elian Doran
cd1f3aa9a7 chore(client): address self-review 2026-01-03 10:05:44 +02:00
Elian Doran
1674401342 Merge remote-tracking branch 'origin/main' into feature/pdfjs_refinement 2026-01-03 09:57:42 +02:00
Elian Doran
7ba8dbbf6e fix(deps): update dependency mind-elixir to v5.4.0 (#8253) 2026-01-03 09:53:36 +02:00
Elian Doran
ad27d9ed0e chore(deps): update dependency @redocly/cli to v2.14.3 (#8252) 2026-01-03 09:53:03 +02:00
renovate[bot]
482d2f9624 fix(deps): update dependency mind-elixir to v5.4.0 2026-01-03 01:46:19 +00:00
renovate[bot]
824ef704d4 chore(deps): update dependency @redocly/cli to v2.14.3 2026-01-03 01:45:28 +00:00
Adorian Doran
58b73cfc7d Merge branch 'main' of https://github.com/TriliumNext/Trilium 2026-01-03 02:25:07 +02:00
Adorian Doran
0465fea2db style/pdf viewer: improve appearance 2026-01-03 02:24:59 +02:00
Elian Doran
39b75e3561 Translations update from Hosted Weblate (#8248) 2026-01-03 00:10:42 +02:00
Elian Doran
2933db9b16 feat(save_indicator): fade out after a few seconds 2026-01-02 23:53:14 +02:00
Kim Nøglegaard
d94914046b Translated using Weblate (Norwegian Bokmål)
Currently translated at 23.0% (35 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-02 21:51:24 +00:00
Kim Nøglegaard
9cf384b14b Translated using Weblate (Norwegian Bokmål)
Currently translated at 20.3% (31 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-02 21:51:23 +00:00
Kim Nøglegaard
614a2f0ccb Translated using Weblate (Norwegian Bokmål)
Currently translated at 3.2% (5 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/
2026-01-02 21:51:23 +00:00
Yatrik Patel
5cecc72384 Translated using Weblate (Hindi)
Currently translated at 2.8% (11 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-02 21:51:22 +00:00
Yatrik Patel
3ad37fb602 Translated using Weblate (Hindi)
Currently translated at 5.2% (8 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-02 21:51:21 +00:00
noobhjy
42b048c2bf Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.8% (1742 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2026-01-02 21:51:21 +00:00
Kim Nøglegaard
a01bf3dfa1 Translated using Weblate (Norwegian Bokmål)
Currently translated at 2.0% (8 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/nb_NO/
2026-01-02 21:51:20 +00:00
Yatrik Patel
ad60988553 Translated using Weblate (Hindi)
Currently translated at 0.7% (13 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-01-02 21:51:19 +00:00
green
c9ae4e4cc6 Translated using Weblate (Japanese)
Currently translated at 100.0% (1745 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2026-01-02 21:51:18 +00:00
Kim Nøglegaard
d2639851d5 Translated using Weblate (Norwegian Bokmål)
Currently translated at 0.6% (11 of 1745 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/nb_NO/
2026-01-02 21:51:18 +00:00
Hosted Weblate
8dc5f9cfa4 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-02 21:51:17 +00:00
Adorian Doran
d99a408e04 style/pdf viewer: add support for background effects 2026-01-02 23:51:07 +02:00
Elian Doran
5f14861682 feat(save_indicator): indicate errors 2026-01-02 23:22:33 +02:00
Adorian Doran
8f8493f3ec client/note split: allow enabling background effects according to the MIME type 2026-01-02 23:06:08 +02:00
Elian Doran
62af66b5ae feat(save_indicator): report saving and saved states 2026-01-02 22:53:18 +02:00
Elian Doran
e8d1fa7447 chore(save_indicator): basic infrastructure to display state 2026-01-02 22:44:29 +02:00
Adorian Doran
ee03871405 style/pdf viewer: remove irrelevant elements 2026-01-02 22:28:33 +02:00
Elian Doran
345378d97f feat(save_indicator): add tooltip for each of the states 2026-01-02 22:14:58 +02:00
Elian Doran
07a463ee52 feat(save_indicator): improve display of some states 2026-01-02 22:10:41 +02:00
Elian Doran
3157047160 chore(save_indicator): add opacity 2026-01-02 22:00:25 +02:00
Elian Doran
a1dda3b578 chore(save_indicator): prepare icon and title 2026-01-02 21:58:13 +02:00
Elian Doran
e161ffce57 fix(client/pdf): not always focusing on click 2026-01-02 21:20:29 +02:00
Adorian Doran
0c1859dc43 style/note splits: highlight the active split only in a multi-split view 2026-01-02 20:59:51 +02:00
Elian Doran
e4dcc0f768 chore(client): fix typecheck issues 2026-01-02 20:45:28 +02:00
Elian Doran
74ab591214 chore(package): automatically build share theme & PDF viewer 2026-01-02 20:38:18 +02:00
Elian Doran
7bd7996893 feat(revisions): use customized PDF viewer 2026-01-02 20:17:27 +02:00
Elian Doran
505ae4eeb5 chore(revisions): remove "Preview" heading 2026-01-02 20:02:39 +02:00
Elian Doran
951d6d3ce3 feat(revisions): display PDF preview for revisions 2026-01-02 20:02:13 +02:00
Elian Doran
5ff7764699 style(revisions): prevent revision list from overflowing 2026-01-02 19:49:20 +02:00
Elian Doran
0d74998625 style(revisions): prevent buttons from overflowing 2026-01-02 19:47:07 +02:00
Elian Doran
29b70a12bd feat(revisions): display video preview for revisions 2026-01-02 19:44:23 +02:00
Elian Doran
d84150e97b feat(revisions): display audio preview for revisions 2026-01-02 19:21:51 +02:00
Elian Doran
2b2ef4251f style(revisions): minor spacing adjustments to file table 2026-01-02 18:44:06 +02:00
Elian Doran
2840ea0f38 chore(revisions): display a message when a preview is not available 2026-01-02 18:42:05 +02:00
Elian Doran
542d485267 fix(revisions): missing meta information about revisions 2026-01-02 18:34:45 +02:00
Elian Doran
cdd4fbc81d refactor(client): fix lint warnings in revisions modal 2026-01-02 18:23:04 +02:00
Elian Doran
bfdddab0a0 refactor(client): format revisions dialog 2026-01-02 18:20:55 +02:00
Elian Doran
44d1d01105 fix(pdfjs): preferences don't account for ntxId or noteId 2026-01-02 18:08:25 +02:00
Elian Doran
120bb09171 fix(pdfjs): saving doesn't account for ntxId or noteId 2026-01-02 17:57:43 +02:00
Elian Doran
b7af99c671 refactor(pdfjs): add type safety for messages 2026-01-02 17:57:28 +02:00
Elian Doran
869e0b3973 docs(user): mention updates to the new PDF functions 2026-01-02 12:49:30 +02:00
Elian Doran
b68613dee4 feat(share): integrate custom pdf.js viewer 2026-01-02 12:13:31 +02:00
Elian Doran
ce0f32e7d5 chore(client/pdfjs): remove open file 2026-01-02 11:44:33 +02:00
Elian Doran
78bc9b59c2 chore(client/pdfjs): remove download button from toolbar 2026-01-02 11:41:58 +02:00
Elian Doran
23cf3d2923 feat(client/pdfjs): rewrite download button 2026-01-02 11:40:32 +02:00
Elian Doran
335136f3a3 fix(deps): update dependency preact-render-to-string to v6.6.5 (#8240) 2026-01-02 11:13:42 +02:00
renovate[bot]
11dd7aef09 fix(deps): update dependency preact-render-to-string to v6.6.5 2026-01-02 09:09:55 +00:00
Elian Doran
2d1769e2f9 fix: toggling right pane visibility incorrectly affects all windows (#8226) 2026-01-02 11:08:27 +02:00
Elian Doran
21e26147b0 fix(deps): update dependency react-i18next to v16.5.1 (#8241) 2026-01-02 11:06:38 +02:00
Elian Doran
ba301f8c12 fix(deps): update dependency globals to v17 (#8242) 2026-01-02 11:06:13 +02:00
SiriusXT
3420374649 fix: toggling right pane visibility incorrectly affects all windows 2026-01-02 11:31:59 +08:00
SiriusXT
644d3a181f fix: toggling right pane visibility incorrectly affects all windows 2026-01-02 11:08:49 +08:00
SiriusXT
4be3011a8a fix: toggling right pane visibility incorrectly affects all windows 2026-01-02 10:30:15 +08:00
SiriusXT
5aa0a956dd fix: toggling right pane visibility incorrectly affects all windows 2026-01-02 10:25:34 +08:00
renovate[bot]
7fdb1bdce8 fix(deps): update dependency globals to v17 2026-01-02 01:53:43 +00:00
renovate[bot]
57c6cef2bd fix(deps): update dependency react-i18next to v16.5.1 2026-01-02 01:52:49 +00:00
Elian Doran
e5599adca1 feat(share): Add support for shareJs in static website export (#8173) 2026-01-02 00:39:36 +02:00
Elian Doran
ab392ffb7f Translations update from Hosted Weblate (#8239) 2026-01-02 00:38:23 +02:00
Hosted Weblate
7585d4b258 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2026-01-01 22:37:01 +00:00
Elian Doran
ff82d9c38c Clean up Vite (#8238) 2026-01-02 00:36:46 +02:00
Elian Doran
920fde69bb chore: add missing space from imports 2026-01-02 00:21:50 +02:00
Elian Doran
053812e5f0 e2e(server): wrong import 2026-01-02 00:14:07 +02:00
Elian Doran
c2f59c4b6c test(server): type error 2026-01-02 00:07:04 +02:00
Elian Doran
06980fe9b5 chore(tsconfig): fix empty type 2026-01-02 00:04:52 +02:00
Elian Doran
3f5616f1fc chore(vitest): fix node:test import 2026-01-02 00:03:45 +02:00
Elian Doran
b6af3b70b0 test(client): increase a timeout for local run 2026-01-01 23:57:43 +02:00
Elian Doran
d8e4547988 chore(vitest): get rid of warning about number of projects 2026-01-01 23:56:26 +02:00
Elian Doran
34f649155e chore(vite): remove vite/global for other projects 2026-01-01 23:44:17 +02:00
Elian Doran
11779fe3e3 chore(vite): remove vite/global for commons 2026-01-01 23:43:59 +02:00
Elian Doran
032cde67b0 chore(vite): remove vite/global for express-partial-content 2026-01-01 23:42:02 +02:00
Elian Doran
229636a796 chore(vite): remove vite/global for server 2026-01-01 23:39:53 +02:00
Elian Doran
da9c9ac346 chore(vite): remove vite/importMeta from spec types 2026-01-01 23:34:39 +02:00
Elian Doran
3fecc4c648 chore(vite): remove vite/client from spec types 2026-01-01 23:33:21 +02:00
Elian Doran
98cefcf77b fix(desktop/pdfjs): not working due to build script 2026-01-01 23:13:21 +02:00
Elian Doran
413ee81ffa Translations update from Hosted Weblate (#8234) 2026-01-01 22:51:03 +02:00
Yatrik Patel
578ca8785e Translated using Weblate (Hindi)
Currently translated at 0.5% (10 of 1740 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-01-01 20:49:31 +00:00
Yatrik Patel
da4112c078 Translated using Weblate (Hindi)
Currently translated at 2.0% (8 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-01 20:49:30 +00:00
Yatrik Patel
704c7c881d Translated using Weblate (Hindi)
Currently translated at 4.6% (7 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-01 20:49:29 +00:00
Elian Doran
63b6abdb9d PDF.js sidebar experiments for new layout (#8212) 2026-01-01 22:49:15 +02:00
Elian Doran
2e936a3d5c test(pdfjs): disable for now as there are no tests 2026-01-01 22:31:49 +02:00
Elian Doran
606574e18e chore(pdjs): address self-review 2026-01-01 22:15:40 +02:00
Elian Doran
1021879167 chore(client/pdfjs): add some missing translations 2026-01-01 22:15:31 +02:00
Elian Doran
dc4aa9c607 feat(ui): implement tooltips for share icons and clone icons (#8211) 2026-01-01 21:13:01 +02:00
Elian Doran
b2c3d78773 Fix excessive noteContext calls (#8233) 2026-01-01 21:09:39 +02:00
Elian Doran
8d3a0b5295 test(pdfjs): replace beforeAll with beforeEach 2026-01-01 21:06:03 +02:00
lzinga
9879d07bec fix(widget): remove redundant note context update in useLegacyWidget 2026-01-01 11:05:30 -08:00
Elian Doran
7bfce851e7 fix(mermaid) diagrams not saving content and SVG attachment (#8220) 2026-01-01 20:58:56 +02:00
Elian Doran
34e81881ec fix(popupEditor): fix closing of popupEditor when inserting note link (#8224) 2026-01-01 20:56:04 +02:00
Lucas
0143d6c60d Merge branch 'TriliumNext:main' into fix/layout-calls 2026-01-01 10:55:25 -08:00
Elian Doran
267c2bc907 Translations update from Hosted Weblate (#8231) 2026-01-01 19:25:36 +02:00
Yatrik Patel
316f27d88c Translated using Weblate (Hindi)
Currently translated at 0.1% (2 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/hi/
2026-01-01 17:39:40 +01:00
Yatrik Patel
452b56f470 Translated using Weblate (Hindi)
Currently translated at 2.6% (4 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/hi/
2026-01-01 17:39:40 +01:00
Jan Klass
43aeaa4455 Translated using Weblate (German)
Currently translated at 96.1% (1669 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2026-01-01 17:39:40 +01:00
Yatrik Patel
08b7a6985e Translated using Weblate (Hindi)
Currently translated at 1.0% (4 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/hi/
2026-01-01 17:39:40 +01:00
dirlligafu
4bbd8e28c1 Translated using Weblate (French)
Currently translated at 95.3% (1656 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/
2026-01-01 17:39:40 +01:00
Yatrik Patel
fcf4c09389 Translated using Weblate (Hindi)
Currently translated at 1.7% (2 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/hi/
2026-01-01 17:39:40 +01:00
Elian Doran
2c323cbe80 fix(client): color with uppercase causing exception (closes #8232) 2026-01-01 18:39:31 +02:00
Elian Doran
7ec7b6bd7b chore(pdfjs): manage requested changes 2026-01-01 12:36:33 +02:00
Elian Doran
b2378f2a53 test(server/pdf): move beforeAll 2026-01-01 12:19:51 +02:00
Elian Doran
8bf8d85bb7 test(server/pdf): switching to another note 2026-01-01 11:23:24 +02:00
Elian Doran
676173e895 test(server/pdf): layer listing works 2026-01-01 11:06:29 +02:00
Elian Doran
d8649c87e0 test(server/pdf): attachment listing works 2026-01-01 01:06:26 +02:00
Elian Doran
b9456ca466 test(server/pdf): basic page navigation test 2026-01-01 00:35:39 +02:00
Elian Doran
cfccbb8927 test(server/pdf): basic table of contents test 2025-12-31 23:40:32 +02:00
Elian Doran
a18578362a Merge remote-tracking branch 'origin/main' into feature/pdfjs_sidebar_experiments 2025-12-31 22:55:37 +02:00
Elian Doran
2f9f94dee0 fix(server): pdfjs not available in dist 2025-12-31 22:46:55 +02:00
Elian Doran
c84e45ddee test(pdfjs): set up basic vitest 2025-12-31 21:18:27 +02:00
Lucas
ea558d8c9d Merge branch 'TriliumNext:main' into fix/layout-calls 2025-12-31 07:33:11 -08:00
lzinga
b936a35b63 fix(widget): prevent unnecessary refresh by checking note context change 2025-12-31 07:31:22 -08:00
Elian Doran
b4ef4c2143 chore(pdfjs): fix code scanning issues 2025-12-31 17:27:58 +02:00
Elian Doran
0ff4756ef4 chore(pdfjs): fix typecheck issues 2025-12-31 17:00:56 +02:00
Elian Doran
94204b4739 style(pdf_pages): slight improvement to page layout 2025-12-31 16:45:11 +02:00
Elian Doran
bf3a2b768e chore(pdfjs): set proper target origin when posting messages 2025-12-31 16:37:51 +02:00
SiriusXT
5fb7badfb4 fix(rightPane): toggling right pane visibility incorrectly affects all windows 2025-12-31 19:54:31 +08:00
Elian Doran
239d56f9a3 fix(deps): update dependency @codemirror/view to v6.39.8 (#8222) 2025-12-31 10:38:22 +02:00
Elian Doran
9163fc23f4 chore(deps): update dependency @redocly/cli to v2.14.2 (#8221) 2025-12-31 10:37:30 +02:00
Elian Doran
d225c28fde chore(deps): update pnpm to v10.27.0 (#8223) 2025-12-31 10:29:28 +02:00
SiriusXT
8a3f02e845 fix(popupEditor): fix closing of popupEditor when inserting note link 2025-12-31 14:12:38 +08:00
renovate[bot]
d0dc92c891 chore(deps): update pnpm to v10.27.0 2025-12-31 02:34:03 +00:00
renovate[bot]
8d660f5a2f fix(deps): update dependency @codemirror/view to v6.39.8 2025-12-31 02:33:52 +00:00
renovate[bot]
b41b4e77b2 chore(deps): update dependency @redocly/cli to v2.14.2 2025-12-31 02:33:14 +00:00
lzinga
267a37d3bd feat(component): add removeChild method for cleanup of child components
feat(hooks): improve useLegacyWidget cleanup and memoization logic
2025-12-30 13:59:46 -08:00
Lucas
0cf23c7d7c Update apps/client/src/widgets/type_widgets/helpers/SvgSplitEditor.tsx
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-30 11:56:34 -08:00
lzinga
a632486229 fix(mermaid) diagrams not saving content and SVG attachment 2025-12-30 11:35:48 -08:00
Elian Doran
64a518a00b chore(deps): update typescript-eslint monorepo to v8.51.0 (#8214) 2025-12-30 12:07:14 +02:00
Elian Doran
2f3a914027 Translations update from Hosted Weblate (#8213) 2025-12-30 12:06:27 +02:00
Elian Doran
7182d32d9c Merge remote-tracking branch 'origin/main' into feature/pdfjs_sidebar_experiments 2025-12-30 11:47:00 +02:00
green
18381c5d32 Translated using Weblate (Japanese)
Currently translated at 100.0% (1736 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-30 09:00:07 +01:00
Kuzma Simonov
79327073b4 Translated using Weblate (Russian)
Currently translated at 100.0% (1736 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-30 09:00:06 +01:00
Giovi
018f2fd789 Translated using Weblate (Italian)
Currently translated at 100.0% (1736 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-30 09:00:06 +01:00
noobhjy
3889392aed Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1736 of 1736 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-30 09:00:05 +01:00
Elian Doran
bd976a25f1 style(next): use border for focus instead 2025-12-30 09:59:46 +02:00
Elian Doran
01f05ac6fd fix(pdf): active context not changed when clicking preview 2025-12-30 09:47:02 +02:00
Elian Doran
52292cb5a5 style(next): indicate active note context 2025-12-30 09:31:03 +02:00
perfectra1n
c6dd1ba0ca fix(types): resolve typecheck issue with note_tree 2025-12-29 18:25:55 -08:00
renovate[bot]
84f069087c chore(deps): update typescript-eslint monorepo to v8.51.0 2025-12-30 01:05:53 +00:00
Elian Doran
76cfced60f chore(pdfjs): fix partially removed method 2025-12-30 01:43:07 +02:00
Elian Doran
5c2aea0a6b chore: remove LLM generated doc 2025-12-30 01:40:34 +02:00
Elian Doran
7a883c62df Merge remote-tracking branch 'origin/main' into feature/pdfjs_sidebar_experiments 2025-12-30 01:40:03 +02:00
Elian Doran
ff97461ff8 PDF.js integration (part I) (#8206) 2025-12-30 01:39:09 +02:00
Elian Doran
51b0eb74a5 chore(pdfjs): address requested changes 2025-12-30 01:37:44 +02:00
Elian Doran
2304407986 chore(pdfjs): address origin concerns 2025-12-30 01:27:05 +02:00
Elian Doran
a1ebdc3004 chore(pdfjs): integrate into typecheck 2025-12-30 01:23:19 +02:00
Elian Doran
fef30f4bea chore(client): fix typecheck 2025-12-30 01:20:29 +02:00
Elian Doran
eee8d9ab7c feat(pdfjs): optionally hide left sidebar 2025-12-30 01:09:45 +02:00
perfectra1n
4f2678d321 feat(ui): implement tooltips for share icons and clone icons
asdf
2025-12-29 14:42:34 -08:00
Elian Doran
c473fba628 refactor(right_pane): move PDF-specific components in own dir 2025-12-30 00:17:29 +02:00
Elian Doran
9a9cd8e6a5 feat(right_pane): add count in title for PDF items 2025-12-30 00:16:45 +02:00
Elian Doran
f5a89aa81a feat(right_pane): hide PDF attachments/layers when not needed 2025-12-30 00:10:23 +02:00
Elian Doran
3c1beab725 fix(pdf_pages): fix a few type errors 2025-12-30 00:00:03 +02:00
Elian Doran
79f03ad3ac fix(pdf_layers): toggling layers and updating state not working 2025-12-29 23:57:52 +02:00
Elian Doran
574138a1fb refactor(pdf_layers): get layers to show 2025-12-29 23:08:35 +02:00
Elian Doran
6513e2cfca refactor(pdf_attachments): deduplicate font size 2025-12-29 23:04:02 +02:00
Elian Doran
43a749b6a7 feat(right_pane): display attachments 2025-12-29 22:56:06 +02:00
Elian Doran
c1d6b3121a fix(pdf_pages): pages not updating between notes 2025-12-29 22:50:48 +02:00
Elian Doran
0d9c8ae4df style(pdf_pages): page numbers within pages 2025-12-29 22:46:20 +02:00
Elian Doran
62d8c089ed chore(pdf_pages): remove logs 2025-12-29 22:43:17 +02:00
Elian Doran
971a76ce11 style(pdf_pages): render in multiple columns 2025-12-29 22:39:38 +02:00
Elian Doran
cb33404122 feat(client/right_pane): use intersection observer for performance 2025-12-29 22:36:03 +02:00
Elian Doran
bcf72f4624 feat(client/right_pane): display pages 2025-12-29 22:34:36 +02:00
Elian Doran
77ad6950e8 feat(client/right_pane): highlight current heading 2025-12-29 22:11:25 +02:00
Elian Doran
e2d29aadca refactor(pdfjs): extract toc logic to separate file 2025-12-29 21:58:11 +02:00
Elian Doran
64ca04ad07 feat(client/right_pane): jump to heading 2025-12-29 21:55:47 +02:00
Elian Doran
b6506a9331 chore(client/right_pane): get table of contents to show 2025-12-29 21:49:02 +02:00
Elian Doran
fd7222242a chore(pdf): process PDF outline 2025-12-29 21:44:49 +02:00
Elian Doran
e36049cd43 chore(client/right_pane): get raw ToC data to show up 2025-12-29 21:44:15 +02:00
Elian Doran
257f6c5994 chore(client/right_pane): inject title into PDF toc sidebar 2025-12-29 21:28:17 +02:00
Elian Doran
9098bfb63a chore(client): prototype implementation to communicate data through note context 2025-12-29 21:26:52 +02:00
Wael Nasreddine
118d22c4ec Merge branch 'main' into static-implement-sharejs 2025-12-29 11:02:07 -08:00
Elian Doran
758df0d85a fix(share): Prevent crashing if candidate note is null (#8164) 2025-12-29 20:43:12 +02:00
Elian Doran
59bbd902fc feat(share): Render JS Frontend files as-is with extension .js (#8172) 2025-12-29 20:41:46 +02:00
Elian Doran
d96528dae4 chore(server): fix type error 2025-12-29 20:38:48 +02:00
Elian Doran
f3cfa84d1d Merge remote-tracking branch 'origin/main' into static-correct-type 2025-12-29 20:37:05 +02:00
Elian Doran
dc2ffa516b Fix: Change /calendar/weeks/{date} to use ISO week format (YYYY-Www) … (#8204) 2025-12-29 20:18:46 +02:00
Elian Doran
fffab73061 feat(pdfjs): auto-watch dev 2025-12-29 19:23:56 +02:00
Elian Doran
0a9ce84cf2 feat(client/pdf): respect locale 2025-12-29 19:10:14 +02:00
Elian Doran
07a1734d4b chore(pdfjs): copy locales during build 2025-12-29 19:09:03 +02:00
Elian Doran
6e41d3591d chore(pdfjs): add locales 2025-12-29 19:06:08 +02:00
Elian Doran
4134e5054a fix(client/pdf): not refreshing when uploading new revision 2025-12-29 17:02:22 +02:00
Elian Doran
bb374a5ce2 fix(client/pdf): blob reloaded when saving 2025-12-29 16:46:30 +02:00
Elian Doran
359f398afa feat(pdfjs): debounce saving view config 2025-12-29 16:19:21 +02:00
Elian Doran
84425e86e9 feat(client/pdf): filter out view config by fingerprint 2025-12-29 16:15:38 +02:00
Elian Doran
ebf725c949 feat(client/pdf): store and restore page position 2025-12-29 15:55:47 +02:00
Elian Doran
fc0ea36cf3 chore(pdfjs): first attempt at intercepting store 2025-12-29 14:06:59 +02:00
Elian Doran
7836de3f08 fix(client/pdf): form elements not detected for save 2025-12-29 13:29:04 +02:00
Elian Doran
406232c478 chore(pdfjs): log event bus 2025-12-29 13:23:54 +02:00
Elian Doran
9e0c29496f refactor(pdfjs): use TypeScript for the custom script 2025-12-29 13:14:00 +02:00
Elian Doran
480954ee87 feat(pdfjs): react to dark mode 2025-12-29 12:51:43 +02:00
Elian Doran
94039bd9b1 chore(pdfjs): improve toolbar contrast 2025-12-29 12:40:43 +02:00
Elian Doran
667eaca9f2 feat(pdfjs): improve style to better match Trilium 2025-12-29 12:35:49 +02:00
Elian Doran
446822a7ae chore(client/pdf): inject some CSS variables 2025-12-29 12:21:44 +02:00
Elian Doran
f09a3e06f4 refactor(client/pdf): split into own component 2025-12-29 12:01:47 +02:00
Elian Doran
7c4a56f5f2 chore(deps): add missing pdfjs dependency 2025-12-29 11:10:12 +02:00
Elian Doran
08f6a32c34 fix(client/pdfjs): not reacting to all changes 2025-12-29 10:33:57 +02:00
Elian Doran
3e255fa647 feat(client/pdf): add debouncing 2025-12-29 10:15:15 +02:00
Elian Doran
c0a90402ef feat(client/pdf): save annotations by uploading new revision 2025-12-29 09:51:54 +02:00
openapphub
37c0f7ec75 Fix: Change /calendar/weeks/{date} to use ISO week format (YYYY-Www) instead of date 2025-12-29 15:44:37 +08:00
Elian Doran
5e42627bce chore(client/pdf): basic reaction to annotations 2025-12-29 02:00:59 +02:00
Elian Doran
41bcf9524a feat(client/pdf): integrate pdf.js 2025-12-29 01:16:56 +02:00
Elian Doran
914cf10911 chore(pdfjs): get icons to show up 2025-12-29 01:11:04 +02:00
Elian Doran
855d4d139d chore(pdfjs): get to actually render something 2025-12-29 01:03:16 +02:00
Elian Doran
abb7b0f8c8 feat(server): serve pdfjs over static route 2025-12-29 00:50:59 +02:00
Elian Doran
d78ad52662 chore(pdfjs): copy viewer to dist 2025-12-29 00:45:57 +02:00
Elian Doran
25b4bcd311 chore(pdfjs): create empty package 2025-12-29 00:26:37 +02:00
Elian Doran
a14eed81f6 Translations update from Hosted Weblate (#8197) 2025-12-28 23:25:34 +02:00
Hosted Weblate
54f51b365a 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/
2025-12-28 22:19:17 +01:00
Marcelo Nolasco
c0e0a712ad Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-28 22:19:16 +01:00
Marcelo Nolasco
3ab5bbae4d Translated using Weblate (Portuguese (Brazil))
Currently translated at 11.2% (13 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pt_BR/
2025-12-28 22:19:15 +01:00
Marcelo Nolasco
cafeb3920a Translated using Weblate (Portuguese (Brazil))
Currently translated at 12.5% (19 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-28 22:19:14 +01:00
MarcelWie
fb465b442c Translated using Weblate (German)
Currently translated at 94.3% (1632 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-12-28 22:19:13 +01:00
Francis C.
d3a559a700 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.5% (1722 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-28 22:19:13 +01:00
green
7768003735 Translated using Weblate (Japanese)
Currently translated at 100.0% (1730 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-28 22:19:12 +01:00
Elian Doran
f02b3b48e8 docs(user): add missing share aliases 2025-12-28 23:18:51 +02:00
Elian Doran
ba273bb9f4 Custom icon pack (#8190) 2025-12-28 23:09:43 +02:00
Elian Doran
490c539d63 Merge branch 'main' into feature/icon_packs 2025-12-28 23:09:34 +02:00
Elian Doran
ebd60519dd fix(note_icon): empty slots appearing when reducing list 2025-12-28 22:59:01 +02:00
Elian Doran
56304a4d71 chore(icon-pack-builder): improve output dir 2025-12-28 22:54:29 +02:00
Elian Doran
32f0f98522 feat(icon-pack-builder): integrate boxicons 3 with brands 2025-12-28 22:53:03 +02:00
Elian Doran
b18dd22341 fix(icon-pack-builder): add missing deps 2025-12-28 22:52:54 +02:00
Elian Doran
8eebae0955 chore(scripts): add script to compare the two boxicons 2025-12-28 22:29:51 +02:00
Elian Doran
ed229e0578 chore(scripts): update boxicons script to use packs instead of weights 2025-12-28 22:29:42 +02:00
Elian Doran
dbfaad6c06 test(server): fix broken test after changes to CSS generation 2025-12-28 21:46:19 +02:00
Elian Doran
6e5176b088 chore(deps): fix dependency 2025-12-28 21:31:10 +02:00
Elian Doran
becf4d7426 fix(note_icon): crash when reducing number of items 2025-12-28 21:10:22 +02:00
Elian Doran
082040c6e1 feat(share): display an icon for attachment download 2025-12-28 21:03:30 +02:00
Elian Doran
1ae11ce3a5 fix(export/share): .zip attachment marked as html 2025-12-28 20:52:47 +02:00
Elian Doran
cf968b3590 fix(export/share): attachment download links not working 2025-12-28 20:45:33 +02:00
Adorian Doran
a3db1ab156 UI fixes (#8200) 2025-12-28 20:36:19 +02:00
Adorian Doran
7440110a44 Merge branch 'feat/ui/fixes' of https://github.com/TriliumNext/Trilium into feat/ui/fixes 2025-12-28 20:31:43 +02:00
Adorian Doran
3638e6b12c style/note title actions: fix an issue identified by gemini-code-assist 2025-12-28 20:31:32 +02:00
Adorian Doran
621ed5b9de Update apps/client/src/widgets/type_widgets/text/EditableText.css
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-28 20:24:24 +02:00
Adorian Doran
1e3135dea0 style/note title actions: fix width for full-width content notes 2025-12-28 20:19:51 +02:00
Elian Doran
8f21c0b34a feat(note_icon): use grid virtualization for listing high performance 2025-12-28 19:46:57 +02:00
Adorian Doran
b3feb38369 style/options: properly align the title with the option cards when the content is centered 2025-12-28 19:45:48 +02:00
Adorian Doran
2bf862d5b9 style/note scrolling container: make the alignment of children more consistent 2025-12-28 19:20:46 +02:00
Elian Doran
cbb7b4ffea refactor(note_icon): split off into two hooks 2025-12-28 19:20:44 +02:00
Elian Doran
b2f496048f feat(icon-pack-builder): add icons to packs 2025-12-28 18:57:32 +02:00
Elian Doran
e084bc4c07 feat(icon-pack-builder): add phosphor fill 2025-12-28 18:52:08 +02:00
Elian Doran
d9b0660def fix(icon-pack-builder): some phosphor icons not working due to alias 2025-12-28 18:41:54 +02:00
Elian Doran
b997452733 feat(icon-pack-builder): integrate phosphor 2025-12-28 18:33:01 +02:00
Elian Doran
e699566e62 chore(note_icon): remove ellipsis in placeholder 2025-12-28 18:13:46 +02:00
Elian Doran
2bd83e6285 feat(note_icon): display count and filter in search placeholder 2025-12-28 17:41:11 +02:00
Elian Doran
46e5090445 fix(icon-pack): non-BMP icons not rendering 2025-12-28 17:33:12 +02:00
Elian Doran
035a311e4d feat(icon-pack-builder): save attachment 2025-12-28 17:17:08 +02:00
Elian Doran
850528750c feat(icon-pack-builder): add manifest to zip 2025-12-28 16:56:41 +02:00
Elian Doran
645720a725 feat(icon-pack-builder): build zip without content yet 2025-12-28 16:50:16 +02:00
Elian Doran
a6c74449aa feat(icon-pack-builder): generate CSS for mdi 2025-12-28 16:25:21 +02:00
Elian Doran
7f05d9cdff test(client): broken tests after change in icon definition 2025-12-28 15:51:48 +02:00
Elian Doran
02d42dc5ff chore(icon_packs): address requested changes 2025-12-28 15:50:01 +02:00
Elian Doran
e730378b27 fix(icon_packs): references to .bx for icon selection 2025-12-28 13:42:45 +02:00
Elian Doran
c14d95f561 docs(user): mention icon pack prefix constraints 2025-12-28 12:36:18 +02:00
Elian Doran
13b700e0e5 chore(icon_packs): address requested changes 2025-12-28 12:30:26 +02:00
Elian Doran
f849c4b315 chore: fix typecheck 2025-12-28 12:15:43 +02:00
Elian Doran
c2c19e8ecd chore(server): address self-review 2025-12-28 12:01:10 +02:00
Elian Doran
12875ec308 chore(deps): update package lock 2025-12-28 11:56:50 +02:00
Elian Doran
5d12d57a22 test(server): fix broken tests after changes 2025-12-28 11:52:49 +02:00
Elian Doran
5cc2296768 chore(server): fix typecheck 2025-12-28 11:46:25 +02:00
Elian Doran
7c1175995f chore(icon_packs): remove prefix from phosphor script 2025-12-28 11:43:37 +02:00
Elian Doran
d834cd78a7 docs(user): document icon packs 2025-12-28 11:43:25 +02:00
Adorian Doran
79d2010bfa style/note title actions: properly align when the content is centered 2025-12-28 10:18:50 +02:00
Adorian Doran
3f86c809ce style/note title actions: properly align when the content is centered 2025-12-28 10:12:01 +02:00
Elian Doran
1570ea77d8 chore(icon_packs): integrate prefix as part of the attribute instead of manifest 2025-12-28 09:56:08 +02:00
Elian Doran
99bdd2e433 feat(icon_packs): skip duplicate icon packs 2025-12-28 09:43:33 +02:00
Elian Doran
7646061215 refactor(client): move a bx style to dedicated CSS 2025-12-28 09:43:17 +02:00
Elian Doran
505a985755 fix(mobile): icons missing 2025-12-28 09:28:24 +02:00
Elian Doran
e895ea406a chore(client): reintegrate boxicons special class names (e.g. flip) 2025-12-28 09:25:10 +02:00
Elian Doran
8b8a78e949 chore(server): get rid of boxicons CSS import 2025-12-28 02:27:02 +02:00
Elian Doran
1c940ff8a2 fix(icon_packs): integrate boxicons back into share export 2025-12-28 02:24:18 +02:00
Elian Doran
841cb32835 fix(icon_packs): integrate boxicons back into share theme 2025-12-28 01:58:22 +02:00
Elian Doran
61e96f91d0 fix(icon_packs): use right name for boxicons for compatibility 2025-12-28 01:20:01 +02:00
Elian Doran
9f6c07f5cc chore(icon_packs): use builtin boxicons for client 2025-12-28 01:00:45 +02:00
Elian Doran
1efb21c627 feat(export/share): render custom icons 2025-12-27 23:39:27 +02:00
Elian Doran
d5b04864c8 chore(export/share): inject font 2025-12-27 23:31:56 +02:00
Elian Doran
da28f4505a chore(export/share): inject pack CSS 2025-12-27 23:21:59 +02:00
Elian Doran
5174deac07 fix(status_bar): attribute count displays system links 2025-12-27 23:02:35 +02:00
Elian Doran
e2a628fa2f feat(icon_packs): ignore protected notes 2025-12-27 22:50:31 +02:00
Elian Doran
290f488c78 feat(share): ignore unsupported icon packs 2025-12-27 22:09:16 +02:00
Elian Doran
b00cb52da5 feat(share): basic support for custom icon packs 2025-12-27 21:58:18 +02:00
Elian Doran
c7bb5ff119 feat(attachments): display MIME type 2025-12-27 20:54:14 +02:00
Elian Doran
faa069b8a1 feat(note_icon): add message if no results 2025-12-27 20:47:56 +02:00
Elian Doran
e57f1e6f23 feat(note_icon): add placeholder for search 2025-12-27 20:45:08 +02:00
Elian Doran
73975ab521 feat(note_icon): use bootstrap tooltip 2025-12-27 20:43:05 +02:00
Elian Doran
761a67f238 feat(note_icon): display icon pack in note title 2025-12-27 20:37:59 +02:00
Elian Doran
736c69816d feat(note_icon): change design for icon reset button 2025-12-27 20:21:10 +02:00
Elian Doran
270339da11 style(next): selector interfering with grouped buttons 2025-12-27 20:16:13 +02:00
Elian Doran
aa93bc5492 fix(note_icon): one column short 2025-12-27 19:52:11 +02:00
Elian Doran
0c9c36ea7e fix(note_icon): missing tooltip for filter 2025-12-27 18:14:53 +02:00
Elian Doran
af67967502 fix(note_icon): modal not dismissing 2025-12-27 18:11:07 +02:00
Elian Doran
78bec0c782 feat(icon_packs): integrate boxicons JSON 2025-12-27 18:04:16 +02:00
Elian Doran
0c77563672 feat(icon_packs): mark icon packs as unsafe 2025-12-27 18:02:59 +02:00
Elian Doran
241a9e2e7f chore(icon_packs): process boxicons v2 2025-12-27 17:56:12 +02:00
Elian Doran
59b691d670 chore(scripts): process boxicons v3 icons 2025-12-27 00:26:33 +02:00
Elian Doran
a6c515aea0 Translations update from Hosted Weblate (#8189) 2025-12-26 23:02:47 +02:00
green
850710926e Translated using Weblate (Japanese)
Currently translated at 100.0% (1730 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-26 21:50:48 +01:00
noobhjy
904da14895 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1730 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-26 21:50:47 +01:00
Kuzma Simonov
4c5bc3a3d3 Translated using Weblate (Russian)
Currently translated at 100.0% (1730 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-26 21:50:47 +01:00
Elian Doran
ecec661b72 chore(scripts): add icon to process phosphor meta 2025-12-26 22:43:03 +02:00
Elian Doran
fb629f7693 feat(note_icon): display note pack icon 2025-12-26 21:14:13 +02:00
Elian Doran
13fff33aa4 feat(icon_packs): use note title isntead of manifest 2025-12-26 21:08:37 +02:00
Elian Doran
8053221b12 chore(note_icon): hide filter if no custom icon packs 2025-12-26 21:01:48 +02:00
Elian Doran
ba699f9842 refactor(note_icon): split filter content into a component 2025-12-26 21:01:19 +02:00
Elian Doran
eb5ebb53cb fix(icon_list): border-right icon missing 2025-12-26 20:57:10 +02:00
Elian Doran
c26357be40 feat(note_icon): allow filtering default icons 2025-12-26 20:49:20 +02:00
Elian Doran
db4af96040 feat(note_icon): filter by icon pack 2025-12-26 20:42:19 +02:00
Elian Doran
5cb3983fe0 chore(note_icon): get rid of categories 2025-12-26 20:03:34 +02:00
Elian Doran
92292de0ff chore(client): basic integration of icon packs in icon selector 2025-12-26 19:52:54 +02:00
Elian Doran
a26923cc6d fix(icon_pack): listing definitions even if parsing fails 2025-12-26 19:42:23 +02:00
Elian Doran
2c4ac4ba30 fix(server): crashing due to bad icon pack 2025-12-26 19:37:10 +02:00
Elian Doran
254511bfbf chore(icon_pack): switch schema to support multiple terms per icon 2025-12-26 19:25:31 +02:00
Elian Doran
e2f6f8a4e4 feat(icon_pack): generate icon registry for client 2025-12-26 19:10:28 +02:00
Elian Doran
e346963e76 feat(icon_pack): inject the icon pack into the client 2025-12-26 18:36:36 +02:00
Elian Doran
5f1bdf7264 chore(icon_pack): generate icon declarations 2025-12-26 18:16:33 +02:00
Elian Doran
93a3b29677 chore(icon_pack): generate root declaration 2025-12-26 18:08:26 +02:00
Elian Doran
b157cd909c chore(icon_pack): generate src declaration 2025-12-26 18:04:39 +02:00
Elian Doran
2f24703690 chore(icon_pack): generate font face declaration without source 2025-12-26 17:42:44 +02:00
Elian Doran
27efa8844e refactor(server): mark ownerId in AttachmentRow as mandatory 2025-12-26 17:32:28 +02:00
Elian Doran
98de4b6dc3 chore(icon_pack): map ttf 2025-12-26 17:31:35 +02:00
Elian Doran
d121de5152 chore(icon_pack): map woff attachment 2025-12-26 17:30:19 +02:00
Elian Doran
5ad7323d03 chore(icon_pack): map woff2 attachment 2025-12-26 17:28:57 +02:00
Elian Doran
183020a4e3 chore(icon_pack): return icon mappings 2025-12-26 16:04:56 +02:00
Elian Doran
a56a5fe1f5 feat(icon_pack): check if JSON is parsable 2025-12-26 16:00:21 +02:00
Elian Doran
af4fc11a4e docs(release): fix link to new layout 2025-12-26 14:13:52 +02:00
Elian Doran
a63b8b2031 chore(release): prepare for v0.101.1 2025-12-26 14:01:04 +02:00
Elian Doran
7b02d87558 docs(release): prepare for v0.101.1 2025-12-26 13:59:49 +02:00
Elian Doran
16737b93dd fix(client): not working in HTTPs (closes #8165) 2025-12-26 13:51:01 +02:00
Elian Doran
c23fe7cf13 fix(table_of_contents): not showing on first render of read-only notes 2025-12-26 13:28:29 +02:00
Adorian Doran
6d80323a76 style/text selection: refactor, apply custom color tint over the note title selection 2025-12-26 11:45:42 +02:00
Adorian Doran
8b630c6e2e UI fixes (#8180) 2025-12-26 11:25:29 +02:00
Adorian Doran
047a8d9644 Fix #8177 2025-12-26 10:56:48 +02:00
Adorian Doran
f14ae01fab style/links: fix background color 2025-12-26 10:42:53 +02:00
Adorian Doran
785470b0ae style/global menu: fix icon alignment 2025-12-26 10:31:06 +02:00
Adorian Doran
73918c042b Fix #8170 2025-12-26 10:28:25 +02:00
Wael Nasreddine
1d3e971ed7 Merge branch 'static-correct-type' into static-implement-sharejs
* static-correct-type:
  improve the protected note handling
  be loosy and honor startsWith application/javascript
2025-12-25 23:01:30 -08:00
Wael Nasreddine
7e7f3ba78f improve the protected note handling 2025-12-25 23:01:01 -08:00
Wael Nasreddine
03eaebc71c be loosy and honor startsWith application/javascript 2025-12-25 22:54:58 -08:00
Wael Nasreddine
3d1f6c4f91 be loosy and honor startsWith application/javascript 2025-12-25 22:54:14 -08:00
Wael Nasreddine
8368969932 implement the second part of the sharejs 2025-12-25 22:06:30 -08:00
Wael Nasreddine
afcd23cb99 add a todo 2025-12-25 22:03:06 -08:00
Wael Nasreddine
94d1181fe8 render js notes as-is 2025-12-25 21:52:35 -08:00
Wael Nasreddine
7e45aaa1da for frontend js files add .js 2025-12-25 21:40:42 -08:00
Wael Nasreddine
cb016c4307 Address Gemini's comment 2025-12-25 16:26:58 -08:00
Wael Nasreddine
7c7797d35a fix(share/prev_next): Prevent crashing if candide page is null
When a note is not visible, attempting to export it ends up crashing the
server with this error:

```
TypeError: ejs:193
    191|
    192|                 <% if (hasTree) { %>
 >> 193|                     <%- include("prev_next", { note: note, subRoot: subRoot }) %>
    194|                 <% } %>
    195|             </footer>
    196|         </div>
ejs:1
 >> 1| <%
    2|     // TODO: code cleanup + putting this behind a toggle/attribute
    3|     const previousNote = (() => {
    4|         // If we are at the subRoot, there is no previous
Cannot read properties of undefined (reading 'hasVisibleChildren')
    at eval (eval at compile (/usr/src/app/main.cjs:553:203), <anonymous>:27:26)
    at eval (eval at compile (/usr/src/app/main.cjs:553:203), <anonymous>:34:7)
    at d (/usr/src/app/main.cjs:557:265)
    at g (/usr/src/app/main.cjs:557:251)
    at eval (eval at compile (/usr/src/app/main.cjs:553:203), <anonymous>:293:17)
    at d (/usr/src/app/main.cjs:557:265)
    at as.render (/usr/src/app/main.cjs:532:458)
    at Omr (/usr/src/app/main.cjs:581:109552)
    at Rmr (/usr/src/app/main.cjs:581:107637)
    at $W.prepareContent (/usr/src/app/main.cjs:653:28) {
  path: ''
```

fixes #8002
fixes #8162
2025-12-25 16:11:01 -08:00
Elian Doran
8d6eb6fa53 fix(right_pane): toggle shortcut not working on new layout 2025-12-25 23:42:45 +02:00
Elian Doran
f97fbf8325 Translations update from Hosted Weblate (#8161) 2025-12-25 23:24:14 +02:00
Mohammed Saati
e7d6e646be Translated using Weblate (Arabic)
Currently translated at 60.9% (1054 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ar/
2025-12-25 21:22:36 +00:00
Mohammed Saati
839444af47 Translated using Weblate (Arabic)
Currently translated at 57.8% (88 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ar/
2025-12-25 21:22:35 +00:00
Hosted Weblate
24a58da4b6 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-25 21:22:34 +00:00
Elian Doran
aa37196169 fix(layout): missing right pane toggle on macos vertical layout (closes #8157) 2025-12-25 23:22:17 +02:00
Elian Doran
f821d7fcd6 fix(desktop): right margin on icon 2025-12-25 23:16:07 +02:00
Elian Doran
b417831507 Merge branch 'main' of github.com:TriliumNext/Trilium 2025-12-25 22:52:04 +02:00
Elian Doran
a4dbefd7ef fix(launch_bar): note launcher no longer hoisting in same tab (closes #8160) 2025-12-25 22:52:01 +02:00
Elian Doran
2d486c9934 chore(release): prepare for v0.101.0 2025-12-25 19:14:38 +02:00
Elian Doran
47cfbd0740 docs(release): prepare for v0.101.0 2025-12-25 19:08:55 +02:00
Elian Doran
f09d918695 fix(promoted_attributes): editing with multiplicity modifies all displayed values (closes #7992) 2025-12-25 18:32:52 +02:00
Elian Doran
33098a1120 feat(login): implement better time comparison management across several features (#8129) 2025-12-25 17:58:36 +02:00
Elian Doran
73f7c42437 Fixes for main (#8151) 2025-12-25 15:40:37 +02:00
Elian Doran
4ff0e2a1d9 Merge branch 'main' into feature/fixes_for_main 2025-12-25 15:40:34 +02:00
Elian Doran
afeaf51831 style(attachment): fix margin for code blocks 2025-12-25 11:54:00 +02:00
Adorian Doran
98db2fcd7b fix note title color for legacy themes (#7997) 2025-12-25 11:40:00 +02:00
Elian Doran
6b4d069211 fix(attachment): deletion time displayed incorrectly (closes #7987) 2025-12-25 11:34:49 +02:00
Adorian Doran
cecf7e6afb style/selected tree item color: use the color adjusted for readability 2025-12-25 11:34:41 +02:00
Adorian Doran
920e53957c Merge branch 'main' of https://github.com/TriliumNext/Trilium into bugfix/title-color-in-note-tree 2025-12-25 11:32:08 +02:00
Elian Doran
27c3d4da3c style(attachment): improve paddings & margins, remove scroll for image 2025-12-25 11:28:51 +02:00
Elian Doran
00a1a63d7e chore(attachment): use admonition instead of alert 2025-12-25 11:25:48 +02:00
Elian Doran
b0e7b6ce67 chore: address requested changes 2025-12-25 11:22:18 +02:00
Elian Doran
e06c004e0d Merge remote-tracking branch 'origin/main' into feature/fixes_for_main
; Conflicts:
;	apps/client/src/stylesheets/theme-next/base.css
2025-12-25 11:01:49 +02:00
Adorian Doran
a47ea0d653 style/quick editor: fix title margin 2025-12-25 10:47:05 +02:00
Adorian Doran
86c04cae96 style/menus: fix broken margin 2025-12-25 10:31:00 +02:00
Elian Doran
9eedbb8cd5 chore(deps): update dependency webdriverio to v9.22.0 (#8155) 2025-12-25 09:50:07 +02:00
Elian Doran
85404b02fc fix(deps): update dependency @codemirror/view to v6.39.7 (#8154) 2025-12-25 09:49:37 +02:00
Elian Doran
ae08c47929 chore(deps): update dependency @redocly/cli to v2.14.1 (#8153) 2025-12-25 09:49:12 +02:00
Adorian Doran
c97a6d82c3 style/menus: rollback a change 2025-12-25 05:13:17 +02:00
Adorian Doran
656b261946 style/note attachments: fix the context menu getting clipped 2025-12-25 05:10:20 +02:00
Adorian Doran
13fb57b46d Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-25 05:05:13 +02:00
Adorian Doran
fe38689a26 style/note attachments: make the list scrollable again 2025-12-25 05:05:03 +02:00
Adorian Doran
62263e3eea style/menus: fix menu backdrop effects 2025-12-25 04:44:51 +02:00
Adorian Doran
22d258b6bc style/menus: fix transparent submenus 2025-12-25 04:21:36 +02:00
Adorian Doran
51242f9bcb style/menus: ignore the disabled destructive menu items from being highlighted with red 2025-12-25 04:10:16 +02:00
Adorian Doran
ab0a0d3807 style/menus: bring back the red trash bin icon 2025-12-25 04:03:27 +02:00
Adorian Doran
cc37da9f11 style/menus: improve the icon-caption alignment 2025-12-25 03:46:32 +02:00
renovate[bot]
404618ed79 chore(deps): update dependency webdriverio to v9.22.0 2025-12-25 00:44:26 +00:00
renovate[bot]
3deda84b38 fix(deps): update dependency @codemirror/view to v6.39.7 2025-12-25 00:43:46 +00:00
renovate[bot]
ccf8e07316 chore(deps): update dependency @redocly/cli to v2.14.1 2025-12-25 00:43:09 +00:00
Adorian Doran
9c73b71feb style: add some animations when opening the sidebar and the bottom panel 2025-12-25 02:40:42 +02:00
Adorian Doran
bbec2f6082 style/bottom panel: improve appearance 2025-12-25 02:26:17 +02:00
Adorian Doran
924bd4512b style/options: tweak the padding of the page 2025-12-25 02:00:18 +02:00
Adorian Doran
ecb8bc0158 style/note header: fix a background issue on code notes 2025-12-25 01:50:28 +02:00
Adorian Doran
67b01ec1d8 style/bottom bar: tweak light color scheme colors 2025-12-25 01:31:47 +02:00
Adorian Doran
7ca7dd8867 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-25 01:22:43 +02:00
Adorian Doran
ac086bc2a4 style/UI switcher illustration: improve 2025-12-25 01:22:35 +02:00
Elian Doran
a583369658 fix(zen): formatting toolbar disappearing due to specificity 2025-12-24 23:56:31 +02:00
Elian Doran
4bc2e9743c Translations update from Hosted Weblate (#8149) 2025-12-24 23:40:48 +02:00
Elian Doran
ba2ba4fd80 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-24 23:40:24 +02:00
Elian Doran
52aaa72935 fix(launch_bar): bookmarks not refreshing 2025-12-24 23:16:09 +02:00
Kuzma Simonov
4123c24a9f Translated using Weblate (Russian)
Currently translated at 100.0% (1726 of 1726 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-24 21:15:43 +00:00
noobhjy
4bba910501 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1726 of 1726 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-24 21:15:42 +00:00
green
3b31916b26 Translated using Weblate (Japanese)
Currently translated at 100.0% (1726 of 1726 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-24 21:15:41 +00:00
Francis C.
4234ee97fb Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1724 of 1724 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-24 21:15:41 +00:00
Marcelo Nolasco
a5c8287446 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1724 of 1724 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/
2025-12-24 21:15:40 +00:00
Luk On
77a90e524a Translated using Weblate (Polish)
Currently translated at 100.0% (1724 of 1724 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-24 21:15:39 +00:00
Francis C.
814f81adfc Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1724 of 1724 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-24 21:15:39 +00:00
Adorian Doran
45c8b24b9c UI Tweaks (#8152) 2025-12-24 23:15:17 +02:00
Adorian Doran
2a7023ea16 style/tab bar: fix tab button rounded corners on horizontal layout 2025-12-24 23:06:06 +02:00
Elian Doran
4a2ff25052 chore(client): provide workaround for bypassing widget-missing-parent 2025-12-24 22:55:54 +02:00
Elian Doran
93582c1489 test(client): fix broken test after change to reference links 2025-12-24 22:41:29 +02:00
Adorian Doran
37d647261a style/watched file change alert bar: tweak 2025-12-24 22:39:10 +02:00
Adorian Doran
8af549aba4 style/bottom panel: do not use Mica on horizontal layout 2025-12-24 22:21:42 +02:00
Elian Doran
662423221e docs(user): mention new printing limitations 2025-12-24 22:21:14 +02:00
Adorian Doran
d6cd252f23 style/note title widget: fix the input selected text color when the current note is tinted 2025-12-24 22:13:52 +02:00
Adorian Doran
5af87788c6 style/quick edit/promoted attributes: fix card ghost margin causing unnecessary scroll 2025-12-24 21:59:55 +02:00
Adorian Doran
c329ab7eee style/zen mode/formatting toolbar: fix the background 2025-12-24 21:45:51 +02:00
Adorian Doran
2340c307cf style/label & relation definition dialog: fix the deformed close button 2025-12-24 20:06:50 +02:00
Adorian Doran
219454c7d3 style/links: fix the color for links referencing colored notes 2025-12-24 19:41:05 +02:00
Adorian Doran
3c14750e90 style/links: improve the selection outline 2025-12-24 19:36:26 +02:00
Elian Doran
b49ca3efe3 fix(print): disable for protected notes 2025-12-24 19:27:29 +02:00
Elian Doran
d21127e3a7 chore(client): fix typecheck 2025-12-24 19:25:13 +02:00
Elian Doran
9adf24da01 chore(client): address requested changes 2025-12-24 19:23:17 +02:00
Elian Doran
571cdfdeea feat(print): report for electron as well 2025-12-24 19:16:31 +02:00
Elian Doran
c2214493dc feat(print): add links to print report 2025-12-24 18:59:50 +02:00
Elian Doran
293ef60350 feat(print): display list of ignored notes 2025-12-24 18:50:50 +02:00
Elian Doran
60866c959f feat(print): report ignored notes 2025-12-24 18:42:13 +02:00
Elian Doran
84c40eb233 fix(print): skip files from being printed 2025-12-24 17:56:15 +02:00
Elian Doran
2afc8d181d fix(print): don't skip child notes if parent note has to be skipped 2025-12-24 17:55:30 +02:00
Elian Doran
66cdee82a4 fix(print): skip printing protected notes if session not available 2025-12-24 17:54:27 +02:00
Elian Doran
502c896616 fix(breadcrumbs): bottom gap caused by scrollbar in context menu 2025-12-24 17:45:45 +02:00
Elian Doran
b5af513371 fix(breadcrumbs): not reacting to protected session being started 2025-12-24 17:41:44 +02:00
Elian Doran
30cc221eca fix(breadcrumbs): branch prefix not shown 2025-12-24 17:37:31 +02:00
Elian Doran
db3aedf39d fix(note_detail): spaced update sometimes overwrites when note type is changed 2025-12-24 16:36:34 +02:00
Elian Doran
fd760951cc fix(text): recreating editor rapidly causes error 2025-12-24 16:23:17 +02:00
Elian Doran
b84cdd648f fix(layout): broken layout if new-layout is enabled as experimental feature 2025-12-24 16:05:20 +02:00
Elian Doran
b94b30e864 fix(options): new layout shown in experimental options 2025-12-24 15:51:26 +02:00
Elian Doran
0027bca415 fix(collections/grid): not displaying some images properly (closes #7969) 2025-12-24 15:02:17 +02:00
Elian Doran
4ac3634b04 fix(collections/list): archived notes always shown in sub-children (closes #8127) 2025-12-24 13:28:26 +02:00
Elian Doran
3207f37ac3 chore(client): format ListOrGridView 2025-12-24 13:20:00 +02:00
Elian Doran
28d3e61199 fix(text): search crashes editor (closes #8114) 2025-12-24 13:16:35 +02:00
Elian Doran
e476a2d306 fix(status_bar): code popup not displaying tooltips 2025-12-24 12:59:13 +02:00
Elian Doran
1d6766d9f3 fix(call_to_action): new layout message displayed for new installations as well 2025-12-24 12:49:45 +02:00
Elian Doran
b429861e8f fix(note_list): reference links displayed incorrectly 2025-12-24 12:45:11 +02:00
Elian Doran
81c55aab72 fix(launch_bar): escaped HTML in tooltip 2025-12-24 12:28:31 +02:00
Elian Doran
f67e06c8b2 fix(geomap): vector maps not rendering labels
Obtained from https://www.npmjs.com/package/@versatiles/style
2025-12-24 12:25:21 +02:00
Elian Doran
b3d92f932f fix(breadcrumbs): menu dismisses on click 2025-12-24 12:19:00 +02:00
Elian Doran
478c0e93f0 fix(breadcrumbs): last arrow not displayed on overflow menu 2025-12-24 12:14:54 +02:00
Elian Doran
bd24cf68b6 fix(note_type_switcher): don't read blob 2025-12-24 11:40:01 +02:00
Elian Doran
faea35b363 Revert "chore(right_pane): revert note data store"
This reverts commit 02294206ec.
2025-12-24 11:35:23 +02:00
Elian Doran
6b66d2089f fix(inline_title): reading metadata on every keystroke 2025-12-24 11:33:44 +02:00
Elian Doran
df05101e93 fix(inline_title): prevent flicker when updating tooltip 2025-12-24 10:52:36 +02:00
Elian Doran
7a1ea8833f fix(inline_title): tooltip getting stuck if refresh 2025-12-24 10:49:36 +02:00
Elian Doran
3600192933 fix(status_bar): note info requesting update on each keystroke 2025-12-24 10:38:57 +02:00
Elian Doran
d6d75a0e48 Minor tweaks (#8145) 2025-12-24 09:42:29 +02:00
Elian Doran
102e10efe3 fix(deps): update dependency @codemirror/view to v6.39.6 (#8147) 2025-12-24 09:41:28 +02:00
Elian Doran
a516185a2e fix(deps): update dependency preact to v10.28.1 (#8148) 2025-12-24 09:37:35 +02:00
Elian Doran
ae81b20334 chore(deps): update pnpm to v10.26.2 (#8146) 2025-12-24 09:29:18 +02:00
renovate[bot]
8fffbdc748 fix(deps): update dependency preact to v10.28.1 2025-12-24 00:25:29 +00:00
renovate[bot]
14d7d1da03 fix(deps): update dependency @codemirror/view to v6.39.6 2025-12-24 00:24:44 +00:00
renovate[bot]
9cb565e821 chore(deps): update pnpm to v10.26.2 2025-12-24 00:24:06 +00:00
Elian Doran
e3595a43c2 docs(user): missing language tags for JSX code blocks 2025-12-24 00:42:20 +02:00
Elian Doran
963fcd615a docs(user): missing language tags for code blocks 2025-12-24 00:30:16 +02:00
Elian Doran
fc8605a14f docs(user): broken code blocks due to table 2025-12-24 00:26:29 +02:00
Elian Doran
04fffb7ee0 Merge remote-tracking branch 'origin/main' into feature/minor_tweaks 2025-12-24 00:20:48 +02:00
Elian Doran
86307b482f docs(user): change URL for demo notes 2025-12-24 00:01:13 +02:00
Elian Doran
3e50262665 fix(status_bar): attribute pane not shown when adding new attribute def 2025-12-23 23:40:16 +02:00
Elian Doran
4e5c97d548 fix(toast): unreadable buttons on light theme 2025-12-23 23:06:29 +02:00
Elian Doran
1185d4b10b chore(layout): reduce padding for promoted attributes 2025-12-23 23:02:26 +02:00
Elian Doran
19cd7a0cad feat(script): improve script error message 2025-12-23 23:02:15 +02:00
Elian Doran
8fda283977 fix(title_actions): dark background in code affecting readability 2025-12-23 22:14:07 +02:00
Elian Doran
6e3a020d0f chore(badges): increase threshold for hiding text 2025-12-23 22:02:06 +02:00
Elian Doran
2fef25e57b chore(badges): allow overflow with clipping 2025-12-23 22:01:49 +02:00
Elian Doran
89ef38ba97 docs(user): mention history navigation buttons in the tab bar 2025-12-23 21:16:24 +02:00
Elian Doran
e96ee87472 chore: prevent error in .envrc for non nix systems (#8144) 2025-12-23 21:10:56 +02:00
Elian Doran
ae83126903 chore(tab_navigation): enable on server as well 2025-12-23 21:09:03 +02:00
Elian Doran
a6c7610fcc fix(dropdown): clicking in the outer area of a menu dismisses it 2025-12-23 21:06:47 +02:00
contributor
d8ce0e5f16 chore: use direnv built-in has command
https://direnv.net/man/direnv-stdlib.1.html#stdlib
2025-12-23 20:52:07 +02:00
Elian Doran
1eebc8ff77 fix(note_badges): avoid "shared locally" on server build 2025-12-23 20:44:12 +02:00
Elian Doran
00592025c0 fix(breadcrumb): overflow hides more items than threshold 2025-12-23 20:40:56 +02:00
Elian Doran
1ac7db41d3 fix(note_title_actions): edited notes link looking strange 2025-12-23 20:33:46 +02:00
contributor
ce84e7a861 chore: prevent error in .envrc for non nix systems 2025-12-23 20:25:15 +02:00
Elian Doran
cf039916d3 chore(note_title_actions): rephrase edited notes 2025-12-23 20:08:21 +02:00
Elian Doran
bfb3ed3ddf chore(layout): relocate note type switcher right above content 2025-12-23 20:03:27 +02:00
Elian Doran
a4f34ce6c5 refactor(client): remove items array 2025-12-23 19:55:50 +02:00
Elian Doran
2ac3d3aaed style(layout): adjust paddings slightly 2025-12-23 19:49:21 +02:00
Elian Doran
494b99d073 chore(layout): integrate edited notes into note title actions 2025-12-23 19:34:19 +02:00
Elian Doran
8434549a9b feat(breadcrumbs): display separator even if no child notes 2025-12-23 19:15:48 +02:00
Elian Doran
c1e01467a5 fix(breadcrumbs): not showing on first render 2025-12-23 19:12:18 +02:00
meinzzzz
87ab41c80c Fix shift+tab behavior in MathInputView 2025-12-23 18:02:40 +01:00
Elian Doran
4bd8eeb52a Translations update from Hosted Weblate (#8143) 2025-12-23 15:41:11 +02:00
Marcelo Nolasco
f4a6edbc9f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pt_BR/
2025-12-23 13:38:17 +00:00
noobhjy
0d0a1866e4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-23 13:38:16 +00:00
green
cd47e79a1b Translated using Weblate (Japanese)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-23 13:38:16 +00:00
Kuzma Simonov
d0a83f7c05 Translated using Weblate (Russian)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-23 13:38:15 +00:00
Marcelo Nolasco
26160b44ea Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pt_BR/
2025-12-23 13:38:14 +00:00
Giovi
9df7b04d7d Translated using Weblate (Italian)
Currently translated at 100.0% (1720 of 1720 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-23 13:38:13 +00:00
Elian Doran
b8d933d308 Icons for code notes by mime type (#8142) 2025-12-23 15:37:51 +02:00
Elian Doran
9021b119b2 fix(client): some tooltips don't render correctly due to extra whitespace 2025-12-23 15:34:53 +02:00
Elian Doran
35034fe9df chore: address requested changes 2025-12-23 15:30:57 +02:00
Elian Doran
2eef655ec2 feat(client): display mapped icon as default for notes 2025-12-23 15:15:22 +02:00
Elian Doran
57ff2f4023 feat(status_bar): display icon for code note switcher 2025-12-23 15:00:31 +02:00
Elian Doran
df6331e3a0 chore(commons): add icon mappings to some common mime types 2025-12-23 15:00:19 +02:00
Elian Doran
b84da65a81 fix(code): not reacting to mime type changes 2025-12-23 14:38:17 +02:00
Elian Doran
58e04a6f72 Support for scripting with Preact and JSX (#8126) 2025-12-23 13:50:35 +02:00
Elian Doran
450bdeb39e fix(deps): update dependency @codemirror/view to v6.39.5 (#8137) 2025-12-23 13:39:58 +02:00
Elian Doran
79494e8cfe chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.2.3 (#8135) 2025-12-23 13:39:40 +02:00
Elian Doran
2afba34055 chore(deps): update typescript-eslint monorepo to v8.50.1 (#8136) 2025-12-23 13:39:26 +02:00
Elian Doran
c391234eeb fix(deps): update fullcalendar monorepo to v6.1.20 (#8138) 2025-12-23 13:39:13 +02:00
Elian Doran
a3fca323c7 test(server): fix test depending on note content 2025-12-23 13:38:38 +02:00
Elian Doran
9332b9ca8f docs(demo): add JSX widget showcase 2025-12-23 13:34:26 +02:00
Elian Doran
8740bf84cf chore(mime_types): set JSX as enabled by default 2025-12-23 13:12:29 +02:00
Elian Doran
1554085d7a chore(scripts/preact): address review 2025-12-23 13:06:33 +02:00
renovate[bot]
7dd4c09057 fix(deps): update fullcalendar monorepo to v6.1.20 2025-12-23 11:05:39 +00:00
renovate[bot]
eafd5140ea fix(deps): update dependency @codemirror/view to v6.39.5 2025-12-23 11:04:52 +00:00
renovate[bot]
cddde353cd chore(deps): update typescript-eslint monorepo to v8.50.1 2025-12-23 11:04:01 +00:00
renovate[bot]
e4ef8f2352 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54.2.3 2025-12-23 11:02:27 +00:00
Elian Doran
2561c7ca0d Merge remote-tracking branch 'origin/main' into feature/preact_scripts 2025-12-23 13:00:55 +02:00
Elian Doran
b4e4950d20 chore(client): fix typecheck 2025-12-23 12:59:22 +02:00
Adorian Doran
a4be86dbd8 style/text: prevent reference links inherit color from the current note 2025-12-23 05:26:54 +02:00
Adorian Doran
b6ca6476de demo notes: change the icon and color for the "Trilium Demo" branch 2025-12-23 05:00:23 +02:00
Adorian Doran
db1f632859 style/zen mode: make the read-only note badge visible 2025-12-23 04:24:39 +02:00
Adorian Doran
7af8acec0f style/zen mode: fix the title widget layout, make the icon and title editable 2025-12-23 04:20:17 +02:00
Adorian Doran
3f1b0fa71e style/breadcrumb: tweak 2025-12-23 04:02:53 +02:00
Adorian Doran
519323292c style/breadcrumb: tweak 2025-12-23 03:48:09 +02:00
Adorian Doran
2d6f17aeaa style/breadcrumb: tweak dropdown lists 2025-12-23 03:07:30 +02:00
Adorian Doran
7507d6b385 style/bottom panel: tweak colors 2025-12-23 02:52:03 +02:00
Adorian Doran
d4fa21e7c1 style/bottom panel: tweak colors 2025-12-22 23:57:40 +02:00
Adorian Doran
608f156b82 client: rename "status bar pane" to "bottom panel" 2025-12-22 23:40:11 +02:00
Adorian Doran
0c965bfdf4 client/status bar panes: extract colors as CSS variables 2025-12-22 23:36:35 +02:00
Adorian Doran
d407c72fae Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-22 23:27:55 +02:00
Adorian Doran
bdc0b062d5 client/status bar panes: improve 2025-12-22 23:27:45 +02:00
Elian Doran
0b912b9c7d Translations update from Hosted Weblate (#8134) 2025-12-22 22:35:26 +02:00
Anton Antonov
99ac6b4df1 Translated using Weblate (Bulgarian)
Currently translated at 16.4% (25 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/bg/
2025-12-22 20:23:36 +00:00
Hosted Weblate
b0a97208a2 Update translation files
Updated by "Remove blank strings" add-on in Weblate.

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/
2025-12-22 20:23:36 +00:00
Anton Antonov
6e044b19c8 Translated using Weblate (Bulgarian)
Currently translated at 12.5% (19 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/bg/
2025-12-22 20:23:35 +00:00
Anton Antonov
198dd15fb4 Added translation using Weblate (Bulgarian) 2025-12-22 20:23:34 +00:00
Anton Antonov
18f3b83827 Added translation using Weblate (Bulgarian) 2025-12-22 20:23:33 +00:00
Anton Antonov
8142b7489a Added translation using Weblate (Bulgarian) 2025-12-22 20:23:33 +00:00
Anton Antonov
7bf0a4134e Added translation using Weblate (Bulgarian) 2025-12-22 20:23:32 +00:00
Kuzma Simonov
29ed08d062 Translated using Weblate (Russian)
Currently translated at 100.0% (1718 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-22 20:23:31 +00:00
Francis C.
68dc7eedec Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.5% (1711 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-22 20:23:31 +00:00
Yunho Park
3fc195998c Translated using Weblate (Korean)
Currently translated at 67.7% (103 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-22 20:23:30 +00:00
Kuzma Simonov
b6d550f682 Translated using Weblate (Russian)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/
2025-12-22 20:23:29 +00:00
Adorian Doran
1f55ff536e client/status bar panes: tweak 2025-12-22 22:23:16 +02:00
Adorian Doran
67fb8d0354 client/status bar panes: tweak 2025-12-22 21:25:39 +02:00
Adorian Doran
1408b159d7 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-22 19:40:01 +02:00
Adorian Doran
74b00e60e3 client/status bar panes: refactor into own component, add title bar and close button 2025-12-22 19:39:52 +02:00
Elian Doran
1b18a964b9 chore(deps): update dependency @types/tabulator-tables to v6.3.1 (#8131) 2025-12-22 19:35:09 +02:00
renovate[bot]
931f0a694e chore(deps): update dependency @types/tabulator-tables to v6.3.1 2025-12-22 01:50:47 +00:00
Adorian Doran
0d32e1f0d8 style/classic toolbar: fix broken border radius 2025-12-22 02:43:39 +02:00
Adorian Doran
d0f91e7709 style/status bar: hide the focus outline for dropdown buttons 2025-12-22 02:37:00 +02:00
Adorian Doran
353d626d45 style/breadcrumb: tweak arrows 2025-12-22 02:29:05 +02:00
Adorian Doran
af67a3ba11 style/breadcrumb: use scrollable dropdowns for note listings 2025-12-22 02:24:34 +02:00
Adorian Doran
a867c646e4 style: refactor 2025-12-22 02:23:43 +02:00
Adorian Doran
150e2504b1 style: add (limited) support for scrollable menus 2025-12-22 02:20:56 +02:00
Adorian Doran
aa7ae150dc style/text editor/links: tweak 2025-12-22 02:04:35 +02:00
Adorian Doran
d99e08bfdd style/text editor: fix links 2025-12-22 01:39:33 +02:00
Elian Doran
29d038c76b Translations update from Hosted Weblate (#8130) 2025-12-22 00:25:39 +02:00
Barszczun
f1615bb4f6 Translated using Weblate (Polish)
Currently translated at 99.6% (1712 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-21 23:11:12 +01:00
Elian Doran
0688ea7de3 chore(client): address requested changes 2025-12-21 22:59:21 +02:00
Elian Doran
af37c175a3 chore(client): fix typecheck 2025-12-21 22:55:20 +02:00
Elian Doran
7567903da3 docs(user): improve documentation on custom widgets & Preact 2025-12-21 22:51:23 +02:00
Elian Doran
531698cafb fix(server/script): ignoring sub-component JSX 2025-12-21 20:01:59 +02:00
perfectra1n
f45920e506 fix(comparison): check all tokens, and do not short-circuit 2025-12-21 09:26:20 -08:00
perfectra1n
6fdd418edd fix(comparison): also fix string comparison hashes in recovery codes 2025-12-21 09:22:53 -08:00
perfectra1n
409ecb84a8 feat(login): implement better time comparison management across several features 2025-12-21 08:58:38 -08:00
Elian Doran
f68f99806b Merge remote-tracking branch 'origin/main' into feature/preact_scripts 2025-12-21 18:25:24 +02:00
Elian Doran
ea1efb785b Translations update from Hosted Weblate (#8128) 2025-12-21 17:13:12 +02:00
noobhjy
308943f754 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1718 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-21 15:00:25 +00:00
green
d43409f6e4 Translated using Weblate (Japanese)
Currently translated at 100.0% (1718 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-21 15:00:23 +00:00
noobhjy
4f51aa7a43 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 96.2% (1654 of 1718 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-21 15:00:22 +00:00
Elian Doran
c4f55395a9 feat(client/jsx): disable debug info 2025-12-21 13:31:44 +02:00
Elian Doran
444c0c6107 chore(client/jsx): fix errors in API 2025-12-21 13:19:42 +02:00
Elian Doran
4da5cb43fc fet(client/jsx): expose basic React widgets 2025-12-21 13:16:05 +02:00
Elian Doran
e6b79e83c4 fet(client/jsx): basic support for JSX render notes 2025-12-21 11:18:42 +02:00
Elian Doran
6e67da7b1f chore(deps): revert sucrase from client 2025-12-21 10:32:54 +02:00
Elian Doran
9071e54bfe chore(client/jsx): use different method for launcher widget defs 2025-12-21 10:26:20 +02:00
Elian Doran
783b5ac8e3 feat(client/jsx): support launcher widgets 2025-12-21 10:23:34 +02:00
Elian Doran
f3f491d141 feat(client/bundle): respect position for TSX widgets 2025-12-21 10:02:13 +02:00
Adorian Doran
a9f58262fc style/links: tweak 2025-12-21 00:29:25 +02:00
Adorian Doran
3ef39784ed Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-21 00:21:26 +02:00
Adorian Doran
24d0d562a9 style/links: tweak 2025-12-21 00:21:17 +02:00
Elian Doran
86c5a4d10c chore(etapi): Add missing share format (#8125) 2025-12-20 23:58:24 +02:00
Elian Doran
f8bf301d12 feat(client/bundle): use new toast for script errors with known note ID 2025-12-20 23:34:36 +02:00
Adorian Doran
32f0d81c07 style/links: tweak colors 2025-12-20 23:33:27 +02:00
Elian Doran
2c25786fa2 feat(client/bundle): expose Trilium hooks 2025-12-20 23:26:10 +02:00
Elian Doran
1093acfe45 feat(client/bundle): make Preact custom widgets content-sized by default 2025-12-20 23:17:30 +02:00
Elian Doran
76f054bbd5 feat(client/bundle): support rendering in other places 2025-12-20 23:16:19 +02:00
Adorian Doran
1ed4192c93 style/note badges: tweak 2025-12-20 23:12:39 +02:00
Elian Doran
c558255450 feat(client/bundle): add button to open script note 2025-12-20 22:51:04 +02:00
Elian Doran
1e94125133 feat(client/bundle): display toast when parent is missing 2025-12-20 22:45:58 +02:00
Elian Doran
64a770175f refactor(client/bundle): use type for parent name 2025-12-20 22:40:03 +02:00
Elian Doran
e0416097e1 feat(script/jsx): support import syntax for api 2025-12-20 22:23:25 +02:00
Elian Doran
6c1b327f5f feat(script/jsx): support import syntax for preact 2025-12-20 22:14:45 +02:00
Wael Nasreddine
17a9550803 chore(etapi): Add missing share format 2025-12-20 12:01:12 -08:00
Elian Doran
284b66acd2 feat(script/jsx): support export default syntax 2025-12-20 21:59:03 +02:00
Elian Doran
dcd73ff9f9 test(script/jsx): JSX fragment 2025-12-20 21:37:41 +02:00
Elian Doran
645557b505 test(script/jsx): basic JSX processing 2025-12-20 21:35:52 +02:00
Elian Doran
22a83d9f82 refactor(script/jsx): "react-widget" -> "preact-widget" 2025-12-20 21:26:01 +02:00
Elian Doran
f64de3acca chore(script/jsx): move defineWidget into Preact API 2025-12-20 21:25:36 +02:00
Elian Doran
34d5793888 chore(script/jsx): expose RightPanelWidget 2025-12-20 21:19:53 +02:00
Adorian Doran
0de9b219a5 style/note badges: extract colors as CSS variables 2025-12-20 20:41:23 +02:00
Elian Doran
44ca9f457c feat(script/jsx): add support for React hooks 2025-12-20 20:29:03 +02:00
Adorian Doran
93eead04a9 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-20 20:27:12 +02:00
Adorian Doran
8ff1f7ec84 style/classic toolbar: tweak background color 2025-12-20 20:27:04 +02:00
Elian Doran
4d7e5bc8f6 chore(script/jsx): move Preact API in dedicated object 2025-12-20 20:10:19 +02:00
Elian Doran
644ff07a50 feat(script/jsx): get right panel widgets to actually render 2025-12-20 19:49:24 +02:00
Elian Doran
41220a9d1d fix(script/jsx): cannot find preact hydration function 2025-12-20 19:45:44 +02:00
Elian Doran
88945788d6 fix(script/jsx): critical crash if widget fails to render 2025-12-20 19:41:48 +02:00
Elian Doran
fe8f033409 chore(script/jsx): get widgets to be interpreted 2025-12-20 19:36:02 +02:00
Elian Doran
eee7c49f6e fix(script/jsx): module not defined 2025-12-20 19:28:26 +02:00
Elian Doran
d036bf0870 fix(client): full crash if server fails to obtain list of widgets 2025-12-20 19:18:50 +02:00
Elian Doran
fa8ff4bfbf chore(script/jsx): basic client-side logic to render bundles 2025-12-20 19:01:29 +02:00
Elian Doran
3619c0c3e4 feat(script/jsx): compile JSX on server side 2025-12-20 18:46:15 +02:00
Elian Doran
883e32f5c9 chore(script): install sucrase 2025-12-20 18:03:45 +02:00
Elian Doran
8722ed405e docs(user): add missing share aliases 2025-12-20 17:56:18 +02:00
Adorian Doran
201c3a6eba style: tint the text selection according to the note's custom color 2025-12-20 16:13:38 +02:00
Adorian Doran
5a46f6ad04 style/classic toolbar: tweak 2025-12-20 15:51:55 +02:00
Adorian Doran
4f20ffe933 Tweak the classic formatting toolbar (#8123) 2025-12-20 15:33:17 +02:00
Adorian Doran
061b0966bb rollback: style/tab bar: visually merge the tab bar with the center panel 2025-12-20 15:28:46 +02:00
Elian Doran
bd799823b8 docs(user): add troubleshooting on content-sized 2025-12-20 13:54:50 +02:00
Elian Doran
c581ee7252 docs(user): improve examples for right pane widgets 2025-12-20 13:29:38 +02:00
Elian Doran
666c434c74 docs(user): mention changes to sidebar 2025-12-20 13:17:01 +02:00
Elian Doran
78ac59581e New layout: Right panel (sidebar) (#8095) 2025-12-20 13:09:59 +02:00
Elian Doran
d7b370253d chore(right_pane): fix regression in highlights_list 2025-12-20 13:00:13 +02:00
Elian Doran
1e885625f6 chore(right_pane): address requested changes 2025-12-20 12:58:06 +02:00
Adorian Doran
8cf6a6b9ae style: make the center panel border radius apply over the classic formatting toolbar if required 2025-12-20 12:51:43 +02:00
Elian Doran
f1ca8881a1 chore(right_pane): fix typecheck 2025-12-20 12:32:20 +02:00
Elian Doran
ea76fd797c chore(right_pane): address requested changes 2025-12-20 12:29:43 +02:00
Elian Doran
b248805905 feat(right_pane): add count to highlights list 2025-12-20 12:25:43 +02:00
Adorian Doran
7af5c77bcb style/tab bar: tweak margin 2025-12-20 12:18:11 +02:00
Elian Doran
35afd60d00 feat(right_pane): respect position 2025-12-20 12:17:14 +02:00
Elian Doran
2b827991ef feat(right_pane): only grow table of contents & highlights 2025-12-20 11:52:40 +02:00
Elian Doran
bc8c852a4d chore(right_pane): align collapse icon with menu item 2025-12-20 11:48:47 +02:00
Elian Doran
cd49c36529 chore(right_pane): decrease context menu size slightly 2025-12-20 11:45:08 +02:00
Elian Doran
a0577dc202 chore(right_pane): use menu instead of button for highlights list 2025-12-20 11:42:21 +02:00
Elian Doran
dced799976 feat(right_pane): add context menu with go to source for custom widgets 2025-12-20 11:39:46 +02:00
Elian Doran
eeea96b98c chore(right_pane): missing key for custom widgets 2025-12-20 11:17:29 +02:00
Elian Doran
e82e92c22c fix(right_pane): custom widgets not aware of note context 2025-12-20 11:16:41 +02:00
Elian Doran
e1df65adce fix(right_pane): custom widgets not rendering after being expanded 2025-12-20 11:09:59 +02:00
Adorian Doran
b7b7610f4d style/classic toolbar: allow customizing the background color via a CSS variable 2025-12-20 11:05:14 +02:00
Adorian Doran
37ea1584c9 style/tab bar: visually merge the tab bar with the center panel 2025-12-20 10:42:13 +02:00
Elian Doran
aac4316fb8 feat(right_pane): render title bar 2025-12-20 10:33:28 +02:00
Elian Doran
fb96b3f80a Translations update from Hosted Weblate (#8120) 2025-12-20 09:04:25 +02:00
Maxime
243d8158cf Translated using Weblate (French)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/fr/
2025-12-20 07:03:32 +00:00
Eugene
6861a61cac Translated using Weblate (Russian)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2025-12-20 07:03:31 +00:00
Kuzma Simonov
d3299d8aa4 Translated using Weblate (Russian)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2025-12-20 07:03:30 +00:00
Maxime
0be5581fe5 Translated using Weblate (French)
Currently translated at 99.7% (388 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/fr/
2025-12-20 07:03:30 +00:00
Kuzma Simonov
60572a28ff Translated using Weblate (Russian)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/
2025-12-20 07:03:29 +00:00
Maxime
422c391c82 Translated using Weblate (French)
Currently translated at 95.9% (1640 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/fr/
2025-12-20 07:03:28 +00:00
Kuzma Simonov
457d30cd80 Translated using Weblate (Russian)
Currently translated at 100.0% (1709 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-20 07:03:27 +00:00
Hosted Weblate
9caa058b18 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-20 07:03:26 +00:00
Elian Doran
33bde688c1 chore(deps): update dependency @redocly/cli to v2.14.0 (#8118) 2025-12-20 09:03:18 +02:00
Elian Doran
e79da6b0f3 chore(deps): update pnpm to v10.26.1 (#8117) 2025-12-20 09:02:39 +02:00
Elian Doran
3e527b9f5c chore(deps): update dependency openai to v6.15.0 (#8119) 2025-12-20 09:01:46 +02:00
renovate[bot]
ba242a6169 chore(deps): update dependency openai to v6.15.0 2025-12-20 01:00:35 +00:00
renovate[bot]
489113f582 chore(deps): update dependency @redocly/cli to v2.14.0 2025-12-20 00:59:47 +00:00
renovate[bot]
0fa6335d0f chore(deps): update pnpm to v10.26.1 2025-12-20 00:58:53 +00:00
Elian Doran
8f1614f603 chore(right_pane_widget): basic support for custom widgets 2025-12-20 00:01:21 +02:00
Elian Doran
a5f322617d chore(script): remove node-detail-pane 2025-12-19 23:44:19 +02:00
Elian Doran
6da42fac20 feat(right_pane_widget): handle zero headings 2025-12-19 23:32:58 +02:00
Elian Doran
fad6414e1d feat(right_pane_widget): handle zero highlights 2025-12-19 23:29:52 +02:00
Elian Doran
c0cd9e36d9 feat(right_pane_widget): hide highlights if disabled in settings 2025-12-19 23:25:58 +02:00
Elian Doran
e94704ce64 chore(right_pane_widget): respect highlight settings 2025-12-19 23:18:28 +02:00
Elian Doran
7a5d24f968 feat(right_pane_widget): options modal for highlight list 2025-12-19 23:02:32 +02:00
Elian Doran
9d351ae479 chore(options/text_notes): adapt to new layout sidebar 2025-12-19 22:33:26 +02:00
Elian Doran
01d4fa8afd chore(right_pane_widget): add padding to no items 2025-12-19 22:26:17 +02:00
Elian Doran
7d386c249a fix(right_pane_widget): toggle button clipped on desktop 2025-12-19 22:23:46 +02:00
Elian Doran
45dd47d039 feat(layout): button to toggle right pane on vertical layout 2025-12-19 21:20:36 +02:00
Elian Doran
06ad0bfa90 feat(hooks): react faster to setting options 2025-12-19 21:12:49 +02:00
Elian Doran
3d9efb23ec chore(right_pane): make right pane collapsible 2025-12-19 21:11:46 +02:00
Elian Doran
9acef4d502 feat(layout): button to toggle right pane on horizontal layout 2025-12-19 21:05:21 +02:00
Elian Doran
d22583457f style(right_pane): left-align title 2025-12-19 20:51:27 +02:00
Elian Doran
290469d1df Translations update from Hosted Weblate (#8115) 2025-12-19 14:28:57 +02:00
Kuzma Simonov
de5b766d0c Translated using Weblate (Russian)
Currently translated at 17.7% (27 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2025-12-19 12:26:27 +01:00
Kuzma Simonov
cbeb5dfb58 Translated using Weblate (Russian)
Currently translated at 98.0% (1676 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-19 12:26:27 +01:00
Kuzma Simonov
be5448eba2 Translated using Weblate (Russian)
Currently translated at 65.5% (76 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ru/
2025-12-19 12:26:24 +01:00
Kuzma Simonov
200e5d04a4 Translated using Weblate (Russian)
Currently translated at 97.6% (380 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ru/
2025-12-19 12:26:24 +01:00
Elian Doran
c7bbf709a1 Translations update from Hosted Weblate (#8108) 2025-12-19 08:39:18 +02:00
Elian Doran
23c2a59eba chore(deps): update dependency fs-extra to v11.3.3 (#8103) 2025-12-19 08:38:40 +02:00
Hosted Weblate
bf74c40f73 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-19 07:38:21 +01:00
Elian Doran
2af1ceda0b fix(deps): update dependency preact-iso to v2.11.1 (#8105) 2025-12-19 08:38:15 +02:00
renovate[bot]
44ae60005c chore(deps): update dependency fs-extra to v11.3.3 2025-12-19 06:38:07 +00:00
Elian Doran
3d591922bb chore(deps): update dependency @redocly/cli to v2.13.0 (#8106) 2025-12-19 08:37:41 +02:00
Elian Doran
31b2aba44c fix(deps): update dependency @codemirror/commands to v6.10.1 (#8104) 2025-12-19 08:37:01 +02:00
Elian Doran
df1c6196bf chore(deps): update dependency esbuild to v0.27.2 (#8102) 2025-12-19 08:36:18 +02:00
Elian Doran
96f4567ba1 chore(deps): update dependency @smithy/middleware-retry to v4.4.17 (#8101) 2025-12-19 08:32:52 +02:00
Adorian Doran
3e783817b6 style/quick edit dialog: tweak title 2025-12-19 02:51:04 +02:00
Adorian Doran
f7f3f707f1 client/note title row: use distinct style when used as a note split title 2025-12-19 02:46:50 +02:00
Adorian Doran
5ce81f1a32 client/note title widget: add support for custom CSS class name 2025-12-19 02:30:46 +02:00
renovate[bot]
53df319aeb chore(deps): update dependency @redocly/cli to v2.13.0 2025-12-19 00:17:52 +00:00
renovate[bot]
c56a253e49 fix(deps): update dependency preact-iso to v2.11.1 2025-12-19 00:17:17 +00:00
renovate[bot]
551b2aa33a fix(deps): update dependency @codemirror/commands to v6.10.1 2025-12-19 00:16:42 +00:00
Adorian Doran
8e245ccad8 Note header: apply note custom colors over the note icons (#8100) 2025-12-19 02:15:30 +02:00
renovate[bot]
03cea8b702 chore(deps): update dependency esbuild to v0.27.2 2025-12-19 00:15:28 +00:00
renovate[bot]
c94b5bc6c9 chore(deps): update dependency @smithy/middleware-retry to v4.4.17 2025-12-19 00:14:36 +00:00
Adorian Doran
69dc1ba68f Merge branch 'main' into feat/note-header/custom-colors 2025-12-19 02:12:25 +02:00
Adorian Doran
32f7ae1edd client: refactor 2025-12-19 02:12:00 +02:00
Adorian Doran
0de05ed16e style/note icon: apply note custom colors over the icons of the quick edit dialog as well 2025-12-19 02:01:03 +02:00
Adorian Doran
58e24c98ed style/note icon: fix the parent note color being applied over note links and board items 2025-12-19 01:55:52 +02:00
Adorian Doran
46da118749 style/note icon: cleanup 2025-12-19 01:43:11 +02:00
Adorian Doran
3f7514c9c7 style/note icon: tweak dark mode colors 2025-12-19 01:39:36 +02:00
Adorian Doran
5123f7b678 style/note icon: fix broken hover color for monochrome icons 2025-12-19 01:38:34 +02:00
Adorian Doran
b8af961690 style/note icon: refactor variable names 2025-12-19 01:23:56 +02:00
Adorian Doran
5bcec9fcfd style/note icon: add hover effect for custom colors 2025-12-19 01:21:52 +02:00
Elian Doran
0a2d4131d7 feat(docs): improve link docs organization (#8057) 2025-12-19 00:26:09 +02:00
Elian Doran
9ef4ab9983 fix(search): add null check for canvas elements in fulltext search (#8090) 2025-12-19 00:22:43 +02:00
Elian Doran
2a237e9a49 Translations update from Hosted Weblate (#8081) 2025-12-19 00:20:30 +02:00
Elian Doran
29115f5e61 Translated using Weblate (Dutch)
Currently translated at 25.8% (30 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/nl/
2025-12-18 23:19:45 +01:00
noobhjy
3411ed79d8 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1709 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-18 23:14:32 +01:00
green
b9c6cae5b4 Translated using Weblate (Japanese)
Currently translated at 100.0% (1709 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-18 23:14:31 +01:00
Luk On
cc84d09230 Translated using Weblate (Polish)
Currently translated at 100.0% (1709 of 1709 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-18 23:14:30 +01:00
Luk On
a82b12a599 Translated using Weblate (Polish)
Currently translated at 100.0% (1703 of 1703 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-18 23:14:29 +01:00
noobhjy
65ebbc71f5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1703 of 1703 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-18 23:14:29 +01:00
green
77c1a00831 Translated using Weblate (Japanese)
Currently translated at 100.0% (1703 of 1703 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-18 23:14:28 +01:00
Hosted Weblate
8eb6bf402d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-18 23:14:27 +01:00
Elian Doran
e45beb541e Fix translation text for database cleaning button (#8080) 2025-12-19 00:14:19 +02:00
Elian Doran
334c31e79d fix(right_pane): table of contents no longer visible 2025-12-18 17:00:11 +02:00
Elian Doran
a986c84ce7 chore(right_pane): remove redundant check for note type 2025-12-18 16:59:40 +02:00
Elian Doran
9b21e042ec feat(floating_buttons): handle case when empty 2025-12-18 16:58:15 +02:00
Elian Doran
c44bb6c203 chore(floating_buttons): revert changes due to new layout 2025-12-18 16:33:12 +02:00
Elian Doran
ddb6b3ea8a feat(right_pane): store expansion state 2025-12-18 16:29:35 +02:00
Elian Doran
a4024d17ba fix(highlights_list): empty results 2025-12-18 16:18:31 +02:00
Elian Doran
57081a1bfb feat(right_pane): make whole title clickable 2025-12-18 16:16:52 +02:00
Elian Doran
7af063e7cd feat(right_pane): simplify collapsing mechanism 2025-12-18 16:15:21 +02:00
Elian Doran
7b04ca8cc7 style(right_pane): improve header space slightly 2025-12-18 15:57:48 +02:00
Elian Doran
02294206ec chore(right_pane): revert note data store 2025-12-18 15:42:44 +02:00
Elian Doran
7f7ec5d858 chore(right_pane): make the gutter slightly bigger 2025-12-18 15:40:09 +02:00
Elian Doran
ea3222cf12 chore(right_pane): more advanced expand/collapse 2025-12-18 14:57:42 +02:00
Elian Doran
5dacfd3ac6 chore(right_pane): basic expand support 2025-12-18 14:52:22 +02:00
Elian Doran
682c61305c chore(right_pane): basic collapse support 2025-12-18 14:43:54 +02:00
Elian Doran
b5bfb02d96 chore(right_pane): experiment with resizable sections 2025-12-18 14:12:28 +02:00
Elian Doran
fc3692333a chore(right_pane): improve style slightly 2025-12-18 14:10:16 +02:00
Elian Doran
28d9d98964 fix(highlights_list): unable to scroll to text fragments 2025-12-18 13:45:45 +02:00
Elian Doran
751a874c51 chore(highlights_list): improve performance by matching fewer elements 2025-12-18 13:38:52 +02:00
Elian Doran
d18ac0c613 fix(highlights_list): displaying non-highlighted attributes 2025-12-18 13:33:08 +02:00
Elian Doran
cd9654cd5f chore(highlights_list): reintroduce support for read-only notes 2025-12-18 13:29:36 +02:00
Elian Doran
925049357a fix(highlights_list): missing key 2025-12-18 13:20:04 +02:00
Elian Doran
d920da9e6f chore(highlights_list): render highlights 2025-12-18 13:17:41 +02:00
Elian Doran
b42a4dcb36 chore(highlights_list): react to changes 2025-12-18 13:13:05 +02:00
Elian Doran
7085e62cfc chore(highlights_list): reintroduce navigation 2025-12-18 13:09:53 +02:00
Elian Doran
73f2f56932 chore(highlights_list): read highlights from CK 2025-12-18 12:52:22 +02:00
Elian Doran
dbf29ed23f chore(highlights_list): start from scratch 2025-12-18 12:38:45 +02:00
Elian Doran
b0e1751dc7 chore(toc): reintroduce navigation in readonly text notes 2025-12-18 12:09:05 +02:00
Elian Doran
bf5c56a61a chore(toc): reintroduce navigation in editable text notes 2025-12-18 12:04:42 +02:00
Elian Doran
96ccb1e67e fix(toc): sometimes not reacting to read-only note switching 2025-12-18 11:46:21 +02:00
Elian Doran
704dcd011e feat(toc): basic support for docs 2025-12-18 11:46:13 +02:00
Elian Doran
b93c80fe7b feat(toc): basic support for read-only text 2025-12-18 11:12:36 +02:00
Elian Doran
41751c205c refactor(toc): reorder according to purpose 2025-12-18 10:58:25 +02:00
Elian Doran
852398426e chore(toc): add unique keys to headings 2025-12-18 10:56:44 +02:00
Elian Doran
73f1b91d34 chore(toc): reintroduce basic collapse support 2025-12-18 10:49:33 +02:00
Xen0r
28da93fc65 Merge branch 'main' into main 2025-12-18 09:36:55 +01:00
Elian Doran
87a98201b4 chore(toc): reintroduce hierarchy 2025-12-18 10:30:39 +02:00
Elian Doran
60342c0f6f fix(toc): not working on note switch 2025-12-18 10:08:10 +02:00
Elian Doran
97a3e439d2 refactor(toc): decouple CKEditor TOC 2025-12-18 09:58:10 +02:00
Jason Wasem
ee6f988c35 refactor(search): simplify null check and use join for text concatenation
根据代码审查建议优化代码:
- 移除多余的 `elements &&` 检查,因为 Array.isArray() 本身可处理 null/undefined
- 使用 `join(" ")` 替代 `toString()` 以确保文本元素用空格分隔,更适合全文搜索
- 移除显式类型声明,让 TypeScript 自动推断

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 10:01:13 +08:00
Adorian Doran
7cfc67cf9f style/note icon: tweak colors 2025-12-18 03:14:21 +02:00
Adorian Doran
ea2dd0293f style/note icon: tweak colors 2025-12-18 02:05:49 +02:00
Adorian Doran
76c16f3a62 style/note icon: apply the note custom color on the icon 2025-12-18 00:53:09 +02:00
Elian Doran
094f77b1af chore(toc): react to changes 2025-12-18 00:38:40 +02:00
Elian Doran
b2bcbdde3f style(attachment): top padding not matching side padding 2025-12-18 00:25:16 +02:00
Elian Doran
eceb7179b8 style(attachment): code block cuts off card 2025-12-18 00:20:48 +02:00
Elian Doran
3a46a9fbc3 chore(toc): attempt to read using CKEditor API 2025-12-18 00:16:03 +02:00
Elian Doran
2e484a11e6 feat(layout/right_pane): basic store to read content without blob 2025-12-17 23:40:25 +02:00
Elian Doran
98ed442d27 chore(layout/right_pane): empty table of contents 2025-12-17 23:19:42 +02:00
Elian Doran
dac923e45d chore(layout/right_pane): bring back resizer 2025-12-17 23:17:25 +02:00
Elian Doran
f46de50f17 refactor(layout/right_pane): CSS for container 2025-12-17 23:03:57 +02:00
Elian Doran
616af1502f feat(layout/right_pane): create empty container 2025-12-17 23:01:44 +02:00
Adorian Doran
c9fae88a86 style/note: add custom note color CSS variables on the split containers 2025-12-17 22:48:47 +02:00
Elian Doran
9872a3d522 feat(call_to_action): add more info button for new layout 2025-12-17 22:46:25 +02:00
Elian Doran
851169e061 fix(edited_notes): no message if there are no edited notes on a day 2025-12-17 22:39:24 +02:00
Elian Doran
f3b274650e docs(user): mark new layout in feature highlights 2025-12-17 22:34:01 +02:00
Elian Doran
3293ed2ce0 docs(user): document the new layout 2025-12-17 22:34:01 +02:00
Elian Doran
231ec39025 New layout refinement (#8088) 2025-12-17 18:51:38 +02:00
Jason Wasem
ecb972c71c fix(search): add null check for canvas elements in fulltext search
添加对 Canvas 笔记 elements 字段的空值检查,防止当 elements 为 null 或非数组时搜索功能报错。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-18 00:42:12 +08:00
Elian Doran
c7f1e46b26 e2e(server): disable new layout and call-to-action for now 2025-12-17 18:05:39 +02:00
Elian Doran
b9c39d757b style(next): match attachment code border radius with context menu 2025-12-17 17:42:03 +02:00
Elian Doran
ea4a3b7f07 chore(client): address requested changes 2025-12-17 17:24:15 +02:00
Elian Doran
cbecc24999 feat(call_to_action): inform about the layout change 2025-12-17 16:05:55 +02:00
Elian Doran
261c1f77cf fix(layout): 2px margin in code notes 2025-12-17 15:47:11 +02:00
Elian Doran
87d99aaffa fix(layout): experimental styles not applied 2025-12-17 15:46:00 +02:00
Elian Doran
d0b0a13b6d chore(options/appearance): use translations 2025-12-17 15:44:01 +02:00
Elian Doran
1ed83b3598 Merge remote-tracking branch 'origin/main' into layout/refinement 2025-12-17 15:43:51 +02:00
Elian Doran
2759beb5d0 feat(options/appearance): improve new layout 2025-12-17 15:34:44 +02:00
Elian Doran
d623b2ffa0 feat(options/appearance): switch between layouts 2025-12-17 15:17:54 +02:00
Elian Doran
3ed613cf1d style(options/appearance): improve layout slightly 2025-12-17 12:40:36 +02:00
Elian Doran
948a6f84d6 feat(options/appearance): add basic illustration for old layout 2025-12-17 12:37:12 +02:00
Xen0r
334024b2d1 Merge branch 'main' into main 2025-12-17 10:28:53 +01:00
Elian Doran
6aa14d17d7 chore(deps): update dependency openai to v6.14.0 (#8086) 2025-12-17 11:26:29 +02:00
Elian Doran
e1da74d4d1 fix(deps): update dependency mind-elixir to v5.3.8 (#8085) 2025-12-17 11:26:15 +02:00
Elian Doran
a6012283da fix(deps): update dependency lodash-es to v4.17.22 (#8084) 2025-12-17 11:25:23 +02:00
Elian Doran
e450e0299f chore(deps): update vitest monorepo to v4.0.16 (#8083) 2025-12-17 11:25:03 +02:00
Xen0r
816f851709 Update translation.json 2025-12-17 10:22:13 +01:00
Xen0r
514ded5b8d Merge branch 'main' into main 2025-12-17 10:20:10 +01:00
Nate River
35a109616c Update apps/client/src/stylesheets/theme-light.css
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-17 11:25:41 +08:00
Nate River
641176011f Update apps/client/src/stylesheets/theme-dark.css
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-17 11:25:35 +08:00
renovate[bot]
304fd37ce2 chore(deps): update dependency openai to v6.14.0 2025-12-17 00:03:10 +00:00
renovate[bot]
51ef473964 fix(deps): update dependency mind-elixir to v5.3.8 2025-12-17 00:02:27 +00:00
Adorian Doran
96f148a870 Tweak note title and icon (#8082) 2025-12-17 02:01:58 +02:00
renovate[bot]
150bc07d28 fix(deps): update dependency lodash-es to v4.17.22 2025-12-17 00:01:46 +00:00
renovate[bot]
dcec780846 chore(deps): update vitest monorepo to v4.0.16 2025-12-17 00:01:03 +00:00
Adorian Doran
03602addc5 Merge branch 'main' into feat/tweak/note-title-and-icon-widgets 2025-12-17 01:59:07 +02:00
Adorian Doran
fc9f47a801 style/note title & icon: fix an issue pointed by gemini-code-assist 2025-12-17 01:58:20 +02:00
Adorian Doran
3b6a823556 style/note title & icon: tweak icon size 2025-12-17 01:48:35 +02:00
Adorian Doran
b8ceb10e68 style/note title & icon: update the colors for the dark color scheme 2025-12-17 01:39:14 +02:00
Adorian Doran
ddca68eafa style/note title & icon: add support for both dark and light color schemes 2025-12-17 01:33:23 +02:00
Adorian Doran
468d4a4369 style/inline title: fix (again) icon hover color 2025-12-17 01:15:35 +02:00
Adorian Doran
c31f35dbd7 style/inline title: handle empty icons when the icon selection is disabled 2025-12-17 01:11:23 +02:00
Elian Doran
b5129402be chore(deps): update dependency vite to v7.3.0 (#8071) 2025-12-17 01:05:15 +02:00
Elian Doran
f38dfb035a chore(deps): update typescript-eslint monorepo to v8.50.0 (#8073) 2025-12-17 01:04:19 +02:00
Elian Doran
c2bee1a968 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54 (#8074) 2025-12-17 01:03:46 +02:00
Adorian Doran
84f7ae9f05 style/inline title: fix icon hover color 2025-12-17 01:03:01 +02:00
Elian Doran
c4df640ea4 chore(deps): update pnpm to v10.26.0 (#8072) 2025-12-17 01:01:04 +02:00
Elian Doran
fe5e1eb066 chore(deps): update dependency openai to v6.13.0 (#8070) 2025-12-17 01:00:32 +02:00
Elian Doran
6abc5a777f fix(deps): update dependency i18next to v25.7.3 (#8069) 2025-12-17 01:00:14 +02:00
Adorian Doran
08730dd821 style/inline title: add an intro animation for the note type selector 2025-12-17 01:00:07 +02:00
Elian Doran
b08ea33eb0 chore(deps): update dependency @smithy/middleware-retry to v4.4.16 (#8068) 2025-12-17 00:59:52 +02:00
Elian Doran
f389fc3414 chore(deps): update dependency @redocly/cli to v2.12.7 (#8067) 2025-12-17 00:59:27 +02:00
Elian Doran
cb024d0455 Translations update from Hosted Weblate (#8066) 2025-12-17 00:59:02 +02:00
Adorian Doran
286d2e8e5b style/inline title: tweak the layout of the note type selector 2025-12-17 00:52:47 +02:00
Adorian Doran
5446d8a932 style/inline title: improve the note icon focus indicator 2025-12-17 00:33:33 +02:00
Adorian Doran
07dcdf3078 style/inline title: replace the hover effect of the note icon 2025-12-17 00:16:45 +02:00
Adorian Doran
143b1827e6 style/inline title: tweak appearance 2025-12-17 00:09:15 +02:00
Adorian Doran
6f494e3e38 style/inline title: use a better layout 2025-12-16 23:33:57 +02:00
Xen0r
e88623e9fa Fix translation text for database cleaning button 2025-12-16 22:00:21 +01:00
Adorian Doran
cad86d4b21 style/note title & icon: tweak appearance 2025-12-16 22:54:55 +02:00
Adorian Doran
5ffae303c4 style/forms/buttons: add a generic class for focusable buttons 2025-12-16 22:54:12 +02:00
Adorian Doran
35218aca71 style/note title & icon: add a circular background for the note icon 2025-12-16 22:34:42 +02:00
Adorian Doran
9542c9776a style/note title & icon: use CSS variables to adjust size, improve the layout on different sizes 2025-12-16 20:30:58 +02:00
Elian Doran
af02685f2f chore(options): fix misalignment in options row 2025-12-16 20:14:21 +02:00
Elian Doran
737e5b85b4 fix(badges): "temporarily editable" remaining after changing editability 2025-12-16 19:40:24 +02:00
Elian Doran
90a7217b32 feat(layout): preserve newlines in tooltips 2025-12-16 19:29:59 +02:00
Elian Doran
77b92385cb feat(layout): keyboard shortcut for promoted attributes 2025-12-16 19:21:50 +02:00
Elian Doran
22e0776049 fix(layout): toggleRibbonTabNoteMap not working in old layout 2025-12-16 19:12:42 +02:00
Elian Doran
db51198449 feat(layout): keyboard shortcut for similar notes 2025-12-16 19:01:03 +02:00
Elian Doran
998a16ab87 feat(layout): keyboard shortcut for note paths 2025-12-16 18:59:36 +02:00
Elian Doran
4f1c19f1e2 feat(layout): keyboard shortcut for note info 2025-12-16 18:57:25 +02:00
Elian Doran
1835676d09 feat(layout): keyboard shortcut for owned & inherited attributes 2025-12-16 18:54:15 +02:00
Elian Doran
2eaa4ef206 fix(note_info): fixed note types do not have icon 2025-12-16 18:43:04 +02:00
Elian Doran
a5fcee500e feat(layout): keyboard shortcut for image properties 2025-12-16 18:40:37 +02:00
Elian Doran
613764d423 feat(layout): keyboard shortcut for file properties 2025-12-16 18:39:31 +02:00
Elian Doran
0518e64576 feat(layout): keyboard shortcut for collection properties 2025-12-16 18:32:33 +02:00
Elian Doran
713900b2b3 feat(layout): focus exactly on basic properties 2025-12-16 18:29:46 +02:00
Elian Doran
c0eb34927f feat(layout): keyboard shortcut for basic properties 2025-12-16 18:23:14 +02:00
Elian Doran
ec3c9a9ae5 feat(layout): keyboard shortcut for formatting toolbar 2025-12-16 18:16:05 +02:00
noobhjy
70374b622e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1700 of 1700 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-16 17:02:40 +01:00
green
7fcc0ae290 Translated using Weblate (Japanese)
Currently translated at 99.8% (1697 of 1700 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-16 17:02:40 +01:00
Luk On
1de4db1a08 Translated using Weblate (Polish)
Currently translated at 100.0% (1700 of 1700 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-16 17:02:40 +01:00
Hosted Weblate
74ac58d3a6 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/
2025-12-16 17:02:40 +01:00
Luk On
993f56976e Translated using Weblate (Polish)
Currently translated at 99.9% (1698 of 1699 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-16 17:02:40 +01:00
Giovi
94859f2303 Translated using Weblate (Italian)
Currently translated at 100.0% (1699 of 1699 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-16 17:02:40 +01:00
Elian Doran
db116981b8 fix(note_info): MIME type entry shown even when empty 2025-12-16 17:53:05 +02:00
Elian Doran
2e563b0a1f feat(note_info): display user-friendly note type name & icon 2025-12-16 17:53:05 +02:00
Elian Doran
7222b233f0 feat(note_info): separate note type from mime type 2025-12-16 17:53:05 +02:00
Adorian Doran
bea15c46e5 style/new layout: fix the title actions container wasting vertical space when empty 2025-12-16 17:49:17 +02:00
Elian Doran
d96157de47 New layout: Power breadcrumbs (#8077) 2025-12-16 17:43:19 +02:00
Elian Doran
120b5c678d chore(client): fix typecheck 2025-12-16 17:27:29 +02:00
Elian Doran
8c008e2e3a chore(breadcrumb): address requested changes 2025-12-16 17:17:58 +02:00
Elian Doran
7e07280eb3 feat(breadcrumb): relocate copy note path to empty area 2025-12-16 16:14:08 +02:00
Elian Doran
d28c3f0851 feat(breadcrumb): hide archived notes if needed 2025-12-16 16:08:41 +02:00
Elian Doran
0390fd3174 fix(breadcrumb): hiding archived notes doesn't update tree 2025-12-16 16:03:09 +02:00
Elian Doran
193c9d8fa6 feat(breadcrumb): option to hide archived notes 2025-12-16 16:00:23 +02:00
Elian Doran
3a4cff6529 feat(breadcrumb): allow creating notes from separator menu 2025-12-16 15:35:14 +02:00
Elian Doran
5449d033bf feat(breadcrumb): indicate archived in separator menu 2025-12-16 15:05:33 +02:00
Elian Doran
66ed88c409 fix(breadcrumb): archived not respected by last item 2025-12-16 15:03:47 +02:00
Elian Doran
94df5c9126 feat(breadcrumb): respect note color class in context menu 2025-12-16 15:02:16 +02:00
Elian Doran
181ea31c1c chore(client): improve error handling message in events 2025-12-16 14:06:12 +02:00
Elian Doran
f235839d03 feat(breadcrumb): indicate archived notes 2025-12-16 13:50:53 +02:00
Elian Doran
d97b68fcd7 feat(breadcrumb): maintain note color on hover 2025-12-16 13:44:35 +02:00
Elian Doran
79d1a509e5 feat(breadcrumb): respect note color in last item 2025-12-16 13:42:41 +02:00
Elian Doran
0af5fa9f0c feat(breadcrumb): respect note color 2025-12-16 13:40:22 +02:00
Elian Doran
2693b18ee6 refactor(breadcrumb): use new component for rendering note links 2025-12-16 13:36:50 +02:00
Elian Doran
34343ce356 fix(server): autocomplete shows empty name for hoisted note 2025-12-16 12:53:51 +02:00
Elian Doran
c9025f2304 fix(breadcrumb): not reacting to changes in note path 2025-12-16 12:43:26 +02:00
Elian Doran
ec22fd9e99 refactor(breadcrumb): use existing function to parse note path 2025-12-16 10:56:00 +02:00
Elian Doran
15f9b2cadf feat(breadcrumb): add context menu on last item 2025-12-16 10:52:03 +02:00
Elian Doran
7c85fe1c37 feat(breadcrumb): add search in subtree 2025-12-16 10:46:56 +02:00
Elian Doran
d1820a6bc3 feat(breadcrumb): color selector 2025-12-16 10:43:56 +02:00
Elian Doran
d1575a28ad feat(breadcrumb): add duplicate/archive/delete options 2025-12-16 10:34:43 +02:00
Elian Doran
d13e19cf59 feat(breadcrumb): copy note path to clipboard & recent changes in subtree options 2025-12-16 10:26:46 +02:00
Elian Doran
0b7ffdf109 chore(breadcrumb): remove keyboard shortcuts 2025-12-16 10:20:15 +02:00
Elian Doran
e91cb1a198 feat(breadcrumb): add clone to/move to options 2025-12-16 10:16:09 +02:00
Elian Doran
51fcda646d feat(breadcrumb): add hoist option 2025-12-16 10:09:10 +02:00
Elian Doran
cb8e35c4dc feat(breadcrumb): get link menu items 2025-12-16 10:03:28 +02:00
Elian Doran
9d581347f1 Revert "feat(breadcrumb): get tree menu to show"
This reverts commit 96a6ea4c7a.
2025-12-16 09:56:21 +02:00
Elian Doran
96a6ea4c7a feat(breadcrumb): get tree menu to show 2025-12-16 09:35:19 +02:00
Elian Doran
587ea42700 chore(breadcrumb): get a menu to render on note link 2025-12-16 09:27:40 +02:00
renovate[bot]
8a5c7b3551 chore(deps): update dependency @ckeditor/ckeditor5-dev-build-tools to v54 2025-12-16 01:20:12 +00:00
renovate[bot]
f1fa44feb6 chore(deps): update typescript-eslint monorepo to v8.50.0 2025-12-16 01:19:39 +00:00
renovate[bot]
569cb6bf53 chore(deps): update pnpm to v10.26.0 2025-12-16 01:19:08 +00:00
renovate[bot]
3445b594e8 chore(deps): update dependency vite to v7.3.0 2025-12-16 01:18:58 +00:00
renovate[bot]
676595dd6b chore(deps): update dependency openai to v6.13.0 2025-12-16 01:18:25 +00:00
renovate[bot]
f8c84602f4 fix(deps): update dependency i18next to v25.7.3 2025-12-16 01:17:51 +00:00
renovate[bot]
d41842bc2a chore(deps): update dependency @smithy/middleware-retry to v4.4.16 2025-12-16 01:17:16 +00:00
renovate[bot]
ced47e64db chore(deps): update dependency @redocly/cli to v2.12.7 2025-12-16 01:16:41 +00:00
Elian Doran
16a6344687 fix(layout/badges): not reacting to changes in share 2025-12-16 00:10:42 +02:00
Elian Doran
7bac0b25ce feat(layout/badges): interactive share badge 2025-12-16 00:07:19 +02:00
Meinzzzz
d2391f94c0 Fix offline math rendering by bundling local fonts 2025-12-15 21:32:50 +01:00
Meinzzzz
050ddb8c55 Improve css to fix tooltips 2025-12-15 20:17:58 +01:00
Elian Doran
6094f738f2 New layout refinements (#8064) 2025-12-15 17:47:03 +02:00
Elian Doran
c3a6d1bba8 feat(badge): bigger dropdown arrow 2025-12-15 17:17:01 +02:00
Elian Doran
1feeb350ce chore(client): address requested changes 2025-12-15 17:14:00 +02:00
Elian Doran
f21ba207fe feat(inline_title): improve fit on small splits 2025-12-15 16:50:26 +02:00
Elian Doran
07c8ff4571 chore(note_type_switcher): shorten last modified date 2025-12-15 16:49:09 +02:00
Elian Doran
71d8588091 feat(note_type_switcher): use arrows next to dropdowns 2025-12-15 16:47:53 +02:00
Elian Doran
3c41b7e5a9 feat(note_type_switcher): use singular form 2025-12-15 16:45:56 +02:00
Elian Doran
41b7a295b9 feat(breadcrumb): add tooltip about jumping to top 2025-12-15 16:40:36 +02:00
Elian Doran
ca8e889e1e feat(breadcrumb): respect workspace color 2025-12-15 16:37:33 +02:00
Elian Doran
6d4e52c928 feat(breadcrumb): prefer workspace icon class 2025-12-15 16:31:43 +02:00
Elian Doran
e2fac8ab05 feat(breadcrumb): distinguish between workspace and hoisted notes 2025-12-15 16:26:25 +02:00
Elian Doran
af3883fdac feat(breadcrumb): react to hosted note ID change 2025-12-15 16:20:17 +02:00
Elian Doran
83777d7ea0 feat(breadcrumb): unhoist badge 2025-12-15 16:02:31 +02:00
Elian Doran
c6854c84b9 feat(breadcrumb): use different approach for displaying workspace icon 2025-12-15 15:52:55 +02:00
Elian Doran
eb99352fff Revert "feat(breadcrumb): display workspace text"
This reverts commit 441958028d.
2025-12-15 15:29:53 +02:00
Elian Doran
7dac5d424b Translations update from Hosted Weblate (#8061) 2025-12-15 15:24:27 +02:00
Elian Doran
441958028d feat(breadcrumb): display workspace text 2025-12-15 15:10:37 +02:00
Elian Doran
80b61a35a9 feat(breadcrumb): display correct icon for first note when hoisted 2025-12-15 15:10:37 +02:00
Elian Doran
61df0f3d31 feat(breadcrumb): trim path when hoisted 2025-12-15 15:10:37 +02:00
green
23ece9fc86 Translated using Weblate (Japanese)
Currently translated at 100.0% (1695 of 1695 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-15 13:15:48 +01:00
Kuzma Simonov
220e3d7195 Translated using Weblate (Russian)
Currently translated at 95.0% (1602 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ru/
2025-12-15 13:15:47 +01:00
Kuzma Simonov
392c0311e5 Translated using Weblate (Russian)
Currently translated at 17.1% (26 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ru/
2025-12-15 13:15:46 +01:00
Adorian Doran
3abdcfa7a5 style/status bar/breadcrumb: tweak the last segment 2025-12-15 14:15:36 +02:00
Elian Doran
4896042fc4 fix(layout): classic toolbar not appearing after making a note temporarily editable 2025-12-15 12:45:27 +02:00
Elian Doran
7edfd5d7b4 fix(layout): classic toolbar in zen mode 2025-12-15 12:41:34 +02:00
Elian Doran
ad8e52f744 feat(layout): disable transition when promoted attributes are shown by default 2025-12-15 12:14:44 +02:00
Elian Doran
455dc5dc11 feat(layout): automatically collapse promoted attributes in full-height notes 2025-12-15 12:10:12 +02:00
Elian Doran
158f5ac310 feat(layout): use collapsible for promoted attributes 2025-12-15 12:10:12 +02:00
Elian Doran
fb70029091 fix(icon-list): border-left icon regular missing 2025-12-15 12:10:12 +02:00
Elian Doran
b370512893 fix(status_bar): unable to dismiss panes 2025-12-15 12:10:12 +02:00
Elian Doran
764607314c feat(tree): change icon for note paths 2025-12-15 12:10:12 +02:00
Adorian Doran
dbcf9b01c5 style/status bar/note paths flyout: document 2025-12-15 12:06:32 +02:00
Adorian Doran
93ce77438f style/status bar/note paths flyout: tweak the current path arrow 2025-12-15 12:04:39 +02:00
Adorian Doran
14c30661e6 Restyle the backlinks flyout (#8063) 2025-12-15 12:01:48 +02:00
Adorian Doran
37efc44f43 Merge branch 'main' into feat/restyle/backlinks-panel 2025-12-15 11:54:03 +02:00
Adorian Doran
6aaade846a style/status bar/backlinks flyout: restyle 2025-12-15 11:52:04 +02:00
Elian Doran
bd2402396b New layout: Remove floating buttons (#8059) 2025-12-15 11:11:18 +02:00
Elian Doran
a9b3479216 fix(mermaid): preview not 100% height on vertical layout 2025-12-15 10:54:39 +02:00
Adorian Doran
05e98877b0 style/status bar/backlinks flyout: rename HTML tag 2025-12-15 10:52:14 +02:00
Elian Doran
862ddf3a71 fix(mermaid): 1px border visible in read-only mode 2025-12-15 10:51:54 +02:00
Elian Doran
c1df2c45de fix(context_menu): regression on mobile sub-menu 2025-12-15 10:47:28 +02:00
Elian Doran
065e97c940 chore(layout/note_actions): address requested changes 2025-12-15 10:41:58 +02:00
Elian Doran
adae7fa03b feat(layout/note_actions): integrate in-app help button 2025-12-15 10:24:24 +02:00
Elian Doran
b725dbea7e feat(layout/note_actions): export as image 2025-12-15 10:06:01 +02:00
Elian Doran
4b80eec000 feat(layout/note_actions): integrate geo map add button 2025-12-15 09:14:19 +02:00
Elian Doran
d7722a1e05 feat(layout/note_actions): integrate add child for relation map 2025-12-15 09:07:45 +02:00
Elian Doran
35cfcc59f6 feat(layout/note_actions): integrate zoom buttons into relation map 2025-12-15 09:04:31 +02:00
Elian Doran
192190d685 fix(layout/note_actions): save to note not disappearing after save 2025-12-15 08:46:27 +02:00
Elian Doran
d6cc4bfa9c fix(layout/note_actions): buttons not reacting to mime type changes 2025-12-15 08:34:40 +02:00
Elian Doran
ed284fbc5f feat(layout/note_actions): integrate open API docs 2025-12-15 08:33:15 +02:00
Elian Doran
cb0efe25f5 feat(layout/note_actions): integrate save to note button 2025-12-15 08:24:59 +02:00
Elian Doran
906fe4f8da feat(layout/note_actions): integrate execute script button 2025-12-15 08:19:40 +02:00
Elian Doran
04a641199b feat(layout/note_actions): prevent layout shift by disabling button 2025-12-15 08:17:01 +02:00
Elian Doran
50cbad22d0 feat(layout/note_actions): integrate toggle read-only button 2025-12-15 08:15:00 +02:00
Elian Doran
4cfe59271f feat(layout/note_actions): integrate switch split orientation 2025-12-15 08:12:39 +02:00
Elian Doran
ba7969dad4 style(backend-log): remove extra padding & decrease font size 2025-12-15 08:08:45 +02:00
Elian Doran
43b6440bf9 style: borderless and paddingless code editor 2025-12-15 08:05:18 +02:00
Elian Doran
3e19a163c2 feat(layout/note_actions): integrate refresh backend log 2025-12-15 08:00:03 +02:00
Elian Doran
ca39282269 fix(global_menu): alignment of advanced button 2025-12-15 07:53:34 +02:00
Elian Doran
016389df68 fix(context_menu): clicking on a submenu dismisses it 2025-12-15 07:51:53 +02:00
Elian Doran
17db2a6b38 Revert "fix(context_menu): clicking submenu dismisses the menu"
This reverts commit 34bc444b18.
2025-12-15 07:46:36 +02:00
Elian Doran
bc8f17ee5c New layout refinement (#8053) 2025-12-15 07:36:58 +02:00
Elian Doran
0c8944ab8e chore(layout): address requested changes 2025-12-15 07:27:55 +02:00
Elian Doran
0960b585bd fix(layout): note actions button duplicated in old layout 2025-12-15 07:25:49 +02:00
Chris Cavalluzzi
55649c3750 fix(docs): mismatching phrasing in reference link notes 2025-12-14 21:17:59 -07:00
Chris Cavalluzzi
51bbf71577 feat(docs): improve link docs organization 2025-12-14 19:30:21 -07:00
Adorian Doran
728fab1dda Tweak note info (#8056) 2025-12-15 02:24:41 +02:00
Adorian Doran
d5ec80d85d client/status bar/note info: fix an issue pointed by gemini-code-assist 2025-12-15 02:23:57 +02:00
Adorian Doran
15ef93d7e6 style/status bar/note info: tweak appearance 2025-12-15 02:07:14 +02:00
Adorian Doran
9711b22ea9 style/status bar/note info: replace the "calculate" button with a link 2025-12-15 01:55:43 +02:00
Adorian Doran
b9c7d2b01d Tweak note paths (#8055) 2025-12-15 01:48:03 +02:00
Adorian Doran
9834846a23 client/status bar/note paths: fix the issues pointed by gemini-code-assist 2025-12-15 01:43:36 +02:00
Adorian Doran
65f425df2c style/status bar/note paths: fix an issue pointed by gemini-code-assist 2025-12-15 01:37:36 +02:00
Adorian Doran
a551dfe4d6 style/status bar/note paths: fix the final path segment color when icons are displayed 2025-12-15 01:18:35 +02:00
Adorian Doran
e9bfacdb7c style/status bar/note paths: highlight the current path 2025-12-15 01:12:51 +02:00
Adorian Doran
3ba7b7d439 style/status bar/note paths: refactor 2025-12-15 00:49:25 +02:00
Adorian Doran
381943818d client/status bar/note paths: improve appearance 2025-12-15 00:37:23 +02:00
Adorian Doran
d1ae2db587 client/status bar/note paths: replace the "Clone note to new location" button with a link 2025-12-15 00:31:36 +02:00
Elian Doran
8fa6e38382 refactor(ribbon): decouple completely from new layout 2025-12-14 23:50:40 +02:00
Elian Doran
749074ea94 chore(layout/status_bar): enforce single pane opened at a time 2025-12-14 23:35:16 +02:00
Elian Doran
f1bb786a49 chore(layout): support for similar notes in the status bar 2025-12-14 23:29:01 +02:00
Elian Doran
42bde3873b fix(layout): extra spacing between badges when no custom actions are available 2025-12-14 23:02:24 +02:00
Meinzzzz
bc23e0984a Undo unnecessary formatting changes 2025-12-14 22:00:56 +01:00
Elian Doran
4877238015 refactor(layout): integrate copy image reference to button to all supported note types 2025-12-14 22:56:39 +02:00
Elian Doran
16374aaf1d refactor(layout): use note.isContentAvailable() 2025-12-14 22:51:50 +02:00
Elian Doran
19709f749a feat(layout): integrate copy reference to clipboard button 2025-12-14 22:50:26 +02:00
Elian Doran
09c7affc16 feat(layout): integrate file-similar options to image 2025-12-14 22:45:23 +02:00
Elian Doran
01e197fd46 feat(layout): integrate original file name into status bar 2025-12-14 22:34:13 +02:00
Elian Doran
0fe129ac16 feat(layout): add a margin for custom note action buttons 2025-12-14 22:22:41 +02:00
Elian Doran
3c52ceb4e6 refactor(layout): extract file actions to separate file 2025-12-14 22:16:54 +02:00
Elian Doran
8ba2357d91 feat(layout/file): upload new revision button 2025-12-14 22:14:05 +02:00
Elian Doran
20f4990d48 feat(layout/file): open externally & download buttons 2025-12-14 22:07:25 +02:00
Elian Doran
8c793bf0fe fix(layout): title area gets wrong note context 2025-12-14 21:58:10 +02:00
Elian Doran
12a0eebafe refactor(layout): handle note actions differently 2025-12-14 21:57:16 +02:00
Elian Doran
092c7dff6b fix(layout): extra padding when no note title actions 2025-12-14 21:38:02 +02:00
Elian Doran
7a1ff42d67 feat(layout/search_properties): expand search properties 2025-12-14 21:29:10 +02:00
Meinzzzz
07de353207 Adding comments and improving code quality in math input views 2025-12-14 20:21:42 +01:00
Elian Doran
5a09a80902 feat(layout): basic ARIA & some padding 2025-12-14 21:20:55 +02:00
Elian Doran
a7ca839afb feat(layout/search_definition): integrate view options directly in search parameters 2025-12-14 20:48:50 +02:00
Elian Doran
6b9b9a96c3 feat(layout): integrate search parameters ribbon tab 2025-12-14 20:34:55 +02:00
Elian Doran
272888acab refactor(ribbon): format SearchDefinitionTab 2025-12-14 20:27:34 +02:00
Elian Doran
283e3c9de1 style(layout/edited_notes): increase vertical padding slighly 2025-12-14 20:23:08 +02:00
Elian Doran
859087b850 feat(layout/edited_notes): respect choice to auto-open 2025-12-14 20:21:30 +02:00
Elian Doran
f7b911dc0b feat(layout): make edited notes collapsible 2025-12-14 20:15:17 +02:00
Elian Doran
9d7e2855d3 feat(layout): edited notes underneath title details 2025-12-14 19:39:55 +02:00
Elian Doran
8e6ea87754 fix(context_menu): regressions on mobile 2025-12-14 12:02:40 +02:00
Elian Doran
34bc444b18 fix(context_menu): clicking submenu dismisses the menu 2025-12-14 12:02:32 +02:00
Elian Doran
860a903336 feat(breadcrumb): hide note paths if only one 2025-12-14 11:49:16 +02:00
Elian Doran
be923ad2b7 chore(breadcrumb): add text span for hiding them at some point 2025-12-14 11:47:18 +02:00
Elian Doran
d2da1ed1e7 feat(breadcrumb): make tooltips shorter 2025-12-14 11:26:09 +02:00
Elian Doran
1c05f5e5c3 feat(breadcrumb): disable tooltip animation for faster input 2025-12-14 11:21:27 +02:00
Elian Doran
e043f30cc6 feat(breadcrumb): add text for all items 2025-12-14 11:20:24 +02:00
Elian Doran
e1611d83a3 fix(breadcrumb): tree displayed in root navigation 2025-12-14 11:12:14 +02:00
Elian Doran
58e2111a8f chore(nix): update flake lock 2025-12-14 11:01:25 +02:00
Elian Doran
c5e4c484dc chore(deps): update react monorepo to v19.2.3 (#8033) 2025-12-14 10:58:02 +02:00
Elian Doran
75a6dece7a Translations update from Hosted Weblate (#8052) 2025-12-14 10:57:37 +02:00
Hosted Weblate
5c0e7736d6 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-14 09:52:47 +01:00
Elian Doran
2562ecd055 feat(renovate): enable nix 2025-12-14 10:52:29 +02:00
Elian Doran
aaaa47b575 Translations update from Hosted Weblate (#8049) 2025-12-14 10:33:29 +02:00
green
21d82ec1d7 Translated using Weblate (Japanese)
Currently translated at 100.0% (1686 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-14 09:21:46 +01:00
Luk On
5af8444cac Translated using Weblate (Polish)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pl/
2025-12-14 09:21:46 +01:00
Wojciech O
cd82c34b93 Translated using Weblate (Polish)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pl/
2025-12-14 09:21:45 +01:00
noobhjy
d182659d62 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1686 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-14 09:21:44 +01:00
Mik Piet
171f428b9d Translated using Weblate (Polish)
Currently translated at 100.0% (1686 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-14 09:21:43 +01:00
kamykO
da4ca9c804 Translated using Weblate (Polish)
Currently translated at 100.0% (1686 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-14 09:21:43 +01:00
Luk On
c019341503 Translated using Weblate (Polish)
Currently translated at 100.0% (1686 of 1686 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-14 09:21:42 +01:00
Hosted Weblate
7234f04b56 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-14 09:21:42 +01:00
Elian Doran
1998cbc005 chore(deps): update dependency @types/node to v24.10.4 (#8050) 2025-12-14 10:21:26 +02:00
renovate[bot]
5914073c3f chore(deps): update dependency @types/node to v24.10.4 2025-12-14 02:09:59 +00:00
Elian Doran
d5aadf2604 chore: add dev shell and direnv support (#8011) 2025-12-13 22:49:35 +02:00
renovate[bot]
1fe22f940b chore(deps): update react monorepo to v19.2.3 2025-12-13 20:46:52 +00:00
Elian Doran
0cdaf70efe chore(deps): update dependency electron to v39.2.7 (#8030) 2025-12-13 22:46:30 +02:00
Elian Doran
8174c65243 chore(deps): update dependency eslint to v9.39.2 (#8031) 2025-12-13 22:45:31 +02:00
Elian Doran
2645801277 chore(deps): update dependency vite to v7.2.7 (#8032) 2025-12-13 22:44:45 +02:00
Elian Doran
fb8c31cb9c fix(deps): update dependency i18next to v25.7.2 (#8035) 2025-12-13 22:44:05 +02:00
Elian Doran
7287dbd64f fix(deps): update dependency preact-render-to-string to v6.6.4 (#8036) 2025-12-13 22:43:19 +02:00
renovate[bot]
6569d64931 fix(deps): update dependency preact-render-to-string to v6.6.4 2025-12-13 19:24:56 +00:00
renovate[bot]
e9f3216926 fix(deps): update dependency i18next to v25.7.2 2025-12-13 19:24:28 +00:00
renovate[bot]
ca0af9646d chore(deps): update dependency vite to v7.2.7 2025-12-13 19:23:33 +00:00
renovate[bot]
92dfafd1ff chore(deps): update dependency eslint to v9.39.2 2025-12-13 19:23:05 +00:00
renovate[bot]
d04dde3b97 chore(deps): update dependency electron to v39.2.7 2025-12-13 19:22:36 +00:00
Elian Doran
4c520c6df3 e2e(server): broken test after submenu 2025-12-13 21:10:59 +02:00
Adorian Doran
65d6ed1cdc Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-12-13 20:42:24 +02:00
Adorian Doran
3352a92445 style/new layout/inline title: tweak 2025-12-13 20:42:16 +02:00
Elian Doran
bc8c55b8fb Translations update from Hosted Weblate (#8047) 2025-12-13 20:37:28 +02:00
Elian Doran
7660914eb8 Merge branch 'main' into weblate-trilium-client 2025-12-13 20:34:04 +02:00
Elian Doran
869aec778c New layout: Shared formatting toolbar (#8046) 2025-12-13 20:15:43 +02:00
noobhjy
255726dcc4 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1685 of 1685 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-13 18:09:10 +00:00
Adorian Doran
9969000807 style/new layout/note title: tweak 2025-12-13 20:08:57 +02:00
Elian Doran
3b909fd739 chore(layout/formatting_toolbar): address requested changes 2025-12-13 19:59:45 +02:00
Elian Doran
ad08fb8132 chore(formatting_toolbar): address self-review 2025-12-13 19:32:44 +02:00
Elian Doran
8d536a6040 fix(formatting_toolbar): view mode check not working in multi-split 2025-12-13 19:29:13 +02:00
Elian Doran
2b1bc8e2b9 feat(inline_title): in split, avoid layout shift by maintaining the toolbar 2025-12-13 16:54:04 +02:00
Adorian Doran
563194ff6c client/note menu: localize string 2025-12-13 16:45:09 +02:00
Elian Doran
0c9ff4dae4 chore(inline_title): fix type error 2025-12-13 16:43:27 +02:00
Elian Doran
b10e7f1811 fix(inline_title): some badges not visible in split 2025-12-13 16:42:06 +02:00
Elian Doran
f93ad499e2 feat(layout/formatting_toolbar): move above sidebar 2025-12-13 16:35:48 +02:00
Adorian Doran
87a51251ca client/layout/status bar: replace some icons 2025-12-13 16:33:33 +02:00
Elian Doran
b56e5b2483 fix(inline_title): note type switcher visible for options 2025-12-13 16:33:33 +02:00
Elian Doran
476c162016 fix(layout/formatting_toolbar): memory leak for closed tabs 2025-12-13 16:31:19 +02:00
Elian Doran
4182f6043a feat(layout/formatting_toolbar): render cached components 2025-12-13 16:26:01 +02:00
Elian Doran
aa528c65b7 chore(layout/formatting_toolbar): render without adapter 2025-12-13 16:05:11 +02:00
Elian Doran
4998560e31 chore(layout/inline_title): show note type switcher for code notes as well 2025-12-13 15:55:56 +02:00
Elian Doran
86f36922c4 Translations update from Hosted Weblate (#8025) 2025-12-13 15:42:54 +02:00
Hosted Weblate
4f617b86d3 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/
2025-12-13 13:34:47 +00:00
noobhjy
b28527e10d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1683 of 1683 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-13 13:34:46 +00:00
green
fbb8924ebf Translated using Weblate (Japanese)
Currently translated at 100.0% (1683 of 1683 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-13 13:34:45 +00:00
Hosted Weblate
f68c9b751f 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/
2025-12-13 13:34:45 +00:00
Luk On
8091f02b16 Translated using Weblate (Polish)
Currently translated at 100.0% (1677 of 1677 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-13 13:34:44 +00:00
noobhjy
f4c68d115b Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1677 of 1677 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-13 13:34:43 +00:00
Luk On
6c70d6b9ae Translated using Weblate (Polish)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pl/
2025-12-13 13:34:43 +00:00
Luk On
1ea12567a3 Translated using Weblate (Polish)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pl/
2025-12-13 13:34:42 +00:00
Luk On
2d16ab7a70 Translated using Weblate (Polish)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pl/
2025-12-13 13:34:41 +00:00
Luk On
a228ba5273 Translated using Weblate (Polish)
Currently translated at 100.0% (1677 of 1677 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/pl/
2025-12-13 13:34:41 +00:00
Luk On
d0477e9ebf Translated using Weblate (Polish)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pl/
2025-12-13 13:34:40 +00:00
Luk On
c99907972d Translated using Weblate (Polish)
Currently translated at 11.2% (13 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pl/
2025-12-13 13:34:39 +00:00
Luk On
b9ebc7d7ea Translated using Weblate (Polish)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/pl/
2025-12-13 13:34:39 +00:00
green
4f9e2c5eca Translated using Weblate (Japanese)
Currently translated at 100.0% (389 of 389 strings)

Translation: Trilium Notes/Server
Translate-URL: https://hosted.weblate.org/projects/trilium/server/ja/
2025-12-13 13:34:38 +00:00
green
ab1f8ee5ae Translated using Weblate (Japanese)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ja/
2025-12-13 13:34:37 +00:00
green
89276ad51a Translated using Weblate (Japanese)
Currently translated at 100.0% (1677 of 1677 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-13 13:34:36 +00:00
green
eca533a517 Translated using Weblate (Japanese)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ja/
2025-12-13 13:34:36 +00:00
Elian Doran
0be578c517 chore(deps): update dependency @electron/rebuild to v4.0.2 (#8028) 2025-12-13 15:34:09 +02:00
Elian Doran
198b315602 chore(deps): update dependency @redocly/cli to v2.12.6 (#8029) 2025-12-13 15:33:28 +02:00
Elian Doran
6474abc983 fix(deps): update dependency eslint-linter-browserify to v9.39.2 (#8034) 2025-12-13 15:32:33 +02:00
Elian Doran
2137dbe849 chore(deps): update node.js to v24.12.0 (#8037) 2025-12-13 15:32:07 +02:00
Elian Doran
b7b46703d9 chore(deps): update pnpm to v10.25.0 (#8038) 2025-12-13 15:31:13 +02:00
Elian Doran
d2d96a1421 chore(deps): update typescript-eslint monorepo to v8.49.0 (#8039) 2025-12-13 15:30:26 +02:00
Elian Doran
cfcc309e5a fix(deps): update dependency @codemirror/view to v6.39.4 (#8040) 2025-12-13 15:28:35 +02:00
Elian Doran
7d87ec942e fix(deps): update dependency react-i18next to v16.5.0 (#8041) 2025-12-13 15:26:40 +02:00
Elian Doran
4def13272f chore(deps): update github artifact actions (major) (#8042) 2025-12-13 15:25:59 +02:00
Elian Doran
c4f914bb7b New layout: Title bar & inline title (#8044) 2025-12-13 15:09:30 +02:00
Elian Doran
6bf213a0b0 fix(layout/status_bar): some popups not dismissing 2025-12-13 15:02:16 +02:00
Elian Doran
694cd2bc7c chore(layout/title_bar): address LLM review 2025-12-13 14:58:11 +02:00
Elian Doran
3851a94400 fix(layout/title_bar): badges not collapsing 2025-12-13 14:51:58 +02:00
Elian Doran
e296416a54 fix(layout/inline-title): title not shown when switching to other types of notes 2025-12-13 14:38:58 +02:00
Elian Doran
0bd89a659c chore(layout/inline-title): disable pointer events while hidden 2025-12-13 14:00:27 +02:00
Elian Doran
0ada6523a8 feat(layout/inline-title): add transition 2025-12-13 13:58:20 +02:00
Elian Doran
56570d7ba1 fix(layout/inline-title): text displayed even when note is not empty 2025-12-13 13:46:28 +02:00
Elian Doran
0ffdedcfa6 feat(layout/inline-title): dropdown for collections 2025-12-13 13:45:34 +02:00
Elian Doran
f391bb8eec feat(layout/inline-title): support built-in templates 2025-12-13 13:35:32 +02:00
Elian Doran
7000076961 feat(layout/inline-title): react to template add/remove 2025-12-13 13:26:48 +02:00
Elian Doran
e0f6ba808c feat(layout/inline-title): template switcher 2025-12-13 13:24:32 +02:00
Elian Doran
4c2fe8a846 feat(layout/inline-title): group some note types 2025-12-13 13:12:03 +02:00
Elian Doran
2ea23368bc feat(vscode): eslint on save 2025-12-13 12:59:32 +02:00
Elian Doran
87666005a6 feat(layout/inline-title): add an intro text 2025-12-13 12:57:33 +02:00
Elian Doran
7666f44b7a fix(layout/inline-title): hide note type switcher on other note types 2025-12-13 12:49:51 +02:00
Elian Doran
470f6e5334 feat(layout/inline-title): hide note type switcher when empty 2025-12-13 12:48:20 +02:00
Elian Doran
a2b007874b feat(layout/inline-title): not reacting to note type changes 2025-12-13 12:43:15 +02:00
Elian Doran
9946d8c6b9 fix(layout/statusbar): code note switcher displayed for other note types 2025-12-13 12:29:59 +02:00
Elian Doran
02fab16475 feat(layout/inline-title): add icons 2025-12-13 12:28:22 +02:00
Elian Doran
5145ce2d23 feat(layout/inline-title): horizontal scroll via wheel 2025-12-13 12:27:45 +02:00
Elian Doran
e06abe6e5b fix(layout/inline-title): current note type displayed in switcher 2025-12-13 12:26:02 +02:00
Elian Doran
50a847777e feat(layout/inline-title): basic note type switcher 2025-12-13 12:25:01 +02:00
Elian Doran
4473f80d73 refactor(layout): remove floating title bar experiment 2025-12-13 12:02:17 +02:00
Elian Doran
70c918c9c6 feat(layout/inline-title): support in options as well 2025-12-13 12:01:06 +02:00
Elian Doran
0939975631 style(layout/inline-title): use muted text color 2025-12-13 11:58:35 +02:00
Elian Doran
0ef90c6165 fix(layout/inline-title): hide in attachments and other view scopes 2025-12-13 11:57:53 +02:00
Elian Doran
cef14a3b19 feat(layout/inline-title): support code 2025-12-13 11:51:57 +02:00
Elian Doran
61d3141bce refactor(layout/inline-title): extract specific styles 2025-12-13 11:49:05 +02:00
Elian Doran
f040a0b6d1 refactor(layout/inline-title): separate old title details into title actions 2025-12-13 11:46:42 +02:00
Elian Doran
e9dfec88c9 feat(layout/inline-title): bring back creation and modification date 2025-12-13 11:43:27 +02:00
Elian Doran
6fa97c845a fix(layout/inline-title): still visible in other note types 2025-12-13 11:37:56 +02:00
Elian Doran
f686d9ecd0 feat(layout/inline-title): keep header bar visible 2025-12-13 11:34:29 +02:00
Elian Doran
621ebe4396 feat(layout/inline-title): title and icon 2025-12-13 11:33:02 +02:00
Adorian Doran
ac2a566685 client/note menu: reorganize menu items 2025-12-13 11:29:39 +02:00
Elian Doran
ac3d57d5da chore(layout): remove ribbon border 2025-12-13 11:28:52 +02:00
Elian Doran
9ab5eef984 feat(layout/inline-title): intersection observer 2025-12-13 11:26:42 +02:00
Elian Doran
912f90accf feat(layout/inline-title): collapse title for text notes 2025-12-13 11:17:39 +02:00
Elian Doran
6463b0dcaa chore(layout/inline-title): placeholder for the title 2025-12-13 11:08:34 +02:00
Elian Doran
0b45fb6764 feat(layout/title): hide note badges while editing title 2025-12-13 10:57:34 +02:00
Elian Doran
330d71847b refactor(layout/title): rename to note badges 2025-12-13 10:54:19 +02:00
Elian Doran
60c8f0c78b refactor(layout/title): relocate badges to layouts dir 2025-12-13 10:47:46 +02:00
Elian Doran
fcbd1ab0b1 chore(layout/title): remove spacer 2025-12-13 10:44:33 +02:00
Elian Doran
3549bfb328 feat(layout/title): collapse badges and note title while constrained in size 2025-12-13 10:43:32 +02:00
Elian Doran
c97038fffd chore(layout): revert breadcrumb row 2025-12-13 10:26:25 +02:00
Elian Doran
15b5885982 New layout: status bar (#8021) 2025-12-13 10:23:12 +02:00
renovate[bot]
6aa8d9fbf9 chore(deps): update github artifact actions 2025-12-13 01:19:13 +00:00
renovate[bot]
eccf4620ac fix(deps): update dependency react-i18next to v16.5.0 2025-12-13 01:19:08 +00:00
renovate[bot]
f08fbe9bb2 fix(deps): update dependency @codemirror/view to v6.39.4 2025-12-13 01:18:39 +00:00
renovate[bot]
bfa87af489 chore(deps): update typescript-eslint monorepo to v8.49.0 2025-12-13 01:18:08 +00:00
renovate[bot]
a7899b7505 chore(deps): update pnpm to v10.25.0 2025-12-13 01:17:37 +00:00
renovate[bot]
e80b5cddcd chore(deps): update node.js to v24.12.0 2025-12-13 01:17:27 +00:00
renovate[bot]
db12f9b8dc fix(deps): update dependency eslint-linter-browserify to v9.39.2 2025-12-13 01:15:42 +00:00
renovate[bot]
f4c95195c9 chore(deps): update dependency @redocly/cli to v2.12.6 2025-12-13 01:12:39 +00:00
renovate[bot]
e2cbff7b3a chore(deps): update dependency @electron/rebuild to v4.0.2 2025-12-13 01:11:53 +00:00
Elian Doran
98a3c8150c feat(breadcrumb): replace title editing with jump to top 2025-12-13 01:45:02 +02:00
Elian Doran
447e09fec1 feat(note_actions): hide code notes from new layout 2025-12-13 01:24:37 +02:00
Elian Doran
7d2a1bb2e5 feat(status_bar): modal for configuring list of code languages 2025-12-13 01:19:20 +02:00
Elian Doran
40fcf79778 fix(status_bar): code mime not updating between notes 2025-12-13 01:14:50 +02:00
Elian Doran
88a779bbdb feat(status_bar): indicate selected code mime in menu 2025-12-13 01:10:38 +02:00
Elian Doran
db04514769 fix(status_bar): code mime switcher is clipped 2025-12-13 01:07:30 +02:00
Elian Doran
23062470f5 feat(status_bar): code mime switcher 2025-12-13 01:03:57 +02:00
Elian Doran
5bad043ed5 chore(status_bar): address requested changes 2025-12-13 00:43:00 +02:00
Elian Doran
4ab8af0995 feat(status_bar): keep button active when dropdown is shown 2025-12-13 00:37:29 +02:00
Elian Doran
1a65c5e13e feat(status_bar): hide note paths in hidden notes 2025-12-13 00:30:22 +02:00
Elian Doran
fc08946038 chore(status_bar): avoid shifting due to language switcher 2025-12-13 00:27:51 +02:00
Elian Doran
4d6dba06ad refactor(ribbon): remove left-over logic for calculating ribbon height 2025-12-13 00:24:05 +02:00
Elian Doran
d7887fe25f chore(layout): hide ribbon in new layout 2025-12-13 00:22:34 +02:00
Elian Doran
81dd50e752 fix(status_bar): wrong font size for language selector text 2025-12-13 00:13:06 +02:00
Elian Doran
fe13065ef8 lint: status bar 2025-12-13 00:11:28 +02:00
Meinzzzz
c02491d2e6 Remove unnecessary any casts in math plugin 2025-12-12 23:09:20 +01:00
Elian Doran
eb02330fdf feat(status_bar): integrate note paths widget 2025-12-13 00:05:33 +02:00
Elian Doran
738fa6fd0e lint: note paths 2025-12-12 23:51:03 +02:00
Elian Doran
0c1c7e4f8e feat(status_bar): note paths (no interaction yet) 2025-12-12 23:47:31 +02:00
Elian Doran
9eb9b66398 fix(status_bar): keyboard shortcuts to add attributes not working 2025-12-12 23:34:26 +02:00
Elian Doran
9db046b401 fix(status_bar): attributes not editable from modal 2025-12-12 23:29:59 +02:00
Elian Doran
914272eee0 style(status_bar): improve layout and spacing slightly 2025-12-12 23:13:31 +02:00
Elian Doran
2b7e203bcc refactor(status_bar): remove wrapper container for breadcrumb 2025-12-12 23:07:57 +02:00
Elian Doran
a61ddedc0b refactor(status_bar): remove old breadcrumb styles 2025-12-12 23:06:47 +02:00
Meinzzzz
a6ede8f905 Improve mathinputview 2025-12-12 21:33:59 +01:00
Elian Doran
60fc34ffac feat(status_bar): functional attribute toggle button 2025-12-12 21:57:42 +02:00
Elian Doran
685109556c chore(ribbon): hide inherited & owned attributes on new layout 2025-12-12 21:49:42 +02:00
Elian Doran
45927053f3 fix(ribbon): links in inherited attributes not visible 2025-12-12 21:48:11 +02:00
Elian Doran
5d438a877b feat(status_bar): improve alignment of attribute editor 2025-12-12 21:44:27 +02:00
Elian Doran
870499bc3a feat(status_bar): basic integration of inherited attributes 2025-12-12 21:41:05 +02:00
Elian Doran
c6d97e3d4b feat(status_bar): basic integration of attribute editor 2025-12-12 21:30:32 +02:00
Elian Doran
efff38b116 feat(status_bar): attribute button (not yet interactive) 2025-12-12 21:19:23 +02:00
Elian Doran
1b725175c6 refactor(status_bar): solve warnings 2025-12-12 20:59:57 +02:00
Elian Doran
6eff62f73f feat(status_bar): add new attachment count 2025-12-12 20:55:54 +02:00
Meinzzzz
22941a9ce0 Fix sync issues 2025-12-12 19:48:09 +01:00
Elian Doran
95d2160c76 feat(status_bar): integrate backlinks 2025-12-12 20:31:19 +02:00
Elian Doran
2b195155ed fix(note_details): appearing in options 2025-12-12 20:21:55 +02:00
Elian Doran
28e9abc8bb chore(status_bar): re-order icons to avoid layout shifting 2025-12-12 20:19:37 +02:00
Elian Doran
0162b9d441 fix(status_bar): language selector appearing for non-text notes 2025-12-12 20:18:50 +02:00
Elian Doran
0545b929e1 fix(status_bar): react to active note context 2025-12-12 20:17:53 +02:00
Elian Doran
d2b32ff5af feat(status_bar): relocate to outside split area 2025-12-12 19:47:47 +02:00
Elian Doran
2d3776cd5f feat(status_bar): integrate note info badge 2025-12-12 19:31:00 +02:00
Elian Doran
2638963171 feat(status_bar/language): add tooltip 2025-12-12 18:58:54 +02:00
Elian Doran
24ed97f65d feat(status_bar/language): improve display of more languages 2025-12-12 18:53:54 +02:00
Elian Doran
c099634e39 feat(status_bar/language): improve display of Asian languages 2025-12-12 18:50:48 +02:00
Elian Doran
12be14e6cf feat(status_bar/language): display icon 2025-12-12 18:47:34 +02:00
Elian Doran
4dc773c1a3 refactor(status_bar/language): stop reusing UI for greater customisibility 2025-12-12 18:29:40 +02:00
Elian Doran
31c5323fd9 feat(status_bar/language): compact locale name 2025-12-12 18:05:10 +02:00
Elian Doran
74b6e7bf63 fix(breadcrumb): some dropdowns not visible 2025-12-12 17:55:22 +02:00
Elian Doran
34025fa646 fix(global_menu): dev menu wrongly positioned on horizontal layout 2025-12-12 14:41:10 +02:00
Elian Doran
df9554194a feat(layout/status_bar): integrate language selector basically 2025-12-12 00:34:47 +02:00
Elian Doran
4e1188484d refactor(layout/status_bar): move breadcrumbs into layout dir 2025-12-12 00:24:30 +02:00
Elian Doran
2f44b9dc59 feat(layout/status_bar): integrate breadcrumbs 2025-12-12 00:21:40 +02:00
Elian Doran
9ee3c48485 chore(layout): relocate ribbon on the top temporarily 2025-12-12 00:15:58 +02:00
Elian Doran
78b9c94829 chore(layout/status_bar): create empty component 2025-12-12 00:13:38 +02:00
Meinzzzz
633a09d414 Fix sync bug 2025-12-11 23:06:13 +01:00
Elian Doran
4c8225ed73 Translations update from Hosted Weblate (#8020) 2025-12-11 23:58:41 +02:00
noobhjy
88aad6d351 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1668 of 1668 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-11 20:38:41 +00:00
Elian Doran
d99d701095 feat(global_menu): add support for all experimental options 2025-12-11 22:38:22 +02:00
Elian Doran
61fe27abbe feat(layout): extract floating titlebar into its own experimental feature 2025-12-11 22:29:22 +02:00
Elian Doran
24cd5006d5 chore(note_map): open in reusable split 2025-12-11 22:14:08 +02:00
Elian Doran
726d6aad65 feat(layout): integrate note map 2025-12-11 22:01:22 +02:00
Elian Doran
bd9fe14a6c chore(layout): remove title extra spacing for now 2025-12-11 21:08:36 +02:00
Elian Doran
792a10ace5 New layout: Integrate small ribbon categories + collection properties (#8018) 2025-12-11 20:59:31 +02:00
Elian Doran
e9ac69b8e5 chore(note_bars/collection): address change request 2025-12-11 20:33:52 +02:00
Elian Doran
c76ff2d371 feat(note_bars/collection): add a help button 2025-12-11 20:19:06 +02:00
Elian Doran
8ab9e30404 chore(note_bars/collection): disable ribbon tab 2025-12-11 20:13:04 +02:00
Elian Doran
53b7d93efb feat(note_bars/collection): support comboboxes 2025-12-11 20:09:25 +02:00
Elian Doran
00df3c3d1f feat(note_bars/collection): support number fields 2025-12-11 19:51:40 +02:00
Elian Doran
e766b82418 feat(note_bars/collection): add icon to checkboxes 2025-12-11 19:44:22 +02:00
Elian Doran
9f4757af5b chore(note_bars/collection): put archived notes at the end 2025-12-11 19:39:07 +02:00
Elian Doran
1a9fb34a6e feat(note_bars/collection): support dropdown menu click action 2025-12-11 19:37:04 +02:00
Elian Doran
a1513a3567 feat(note_bars/collection): support split button properties 2025-12-11 19:34:22 +02:00
Elian Doran
0de67b6a69 feat(note_bars/collection): support button properties 2025-12-11 19:29:27 +02:00
Elian Doran
fec5ee9335 feat(note_bars/collection): integrate show archived notes 2025-12-11 19:21:51 +02:00
Elian Doran
b540111fa4 feat(note_bars): add icons to view type switcher 2025-12-11 18:59:28 +02:00
Elian Doran
0eed72b888 feat(note_bars): view type switcher 2025-12-11 18:53:48 +02:00
Elian Doran
0856d3dbdf fix(layout): note title padding on full-height note 2025-12-11 18:02:52 +02:00
Elian Doran
a9b453c27a feat(breadcrumb_badges): integrate query/script tab 2025-12-11 17:43:00 +02:00
Elian Doran
fa8287269f feat(breadcrumb_badges): integrate note properties tab 2025-12-11 17:34:04 +02:00
Elian Doran
1eee471018 fix(breadcrumb_badges): temporarily editable showing up always in popup editor 2025-12-11 17:20:28 +02:00
Elian Doran
c3829f82ab New layout: Note info (#8015) 2025-12-11 17:18:19 +02:00
Elian Doran
a51820f5df chore(note_info): address requested changes 2025-12-11 16:57:04 +02:00
Elian Doran
68591fb511 feat(note_info): hide ribbon on new layout 2025-12-11 16:48:49 +02:00
Elian Doran
3795ce2143 feat(note_info): integrate near the note title 2025-12-11 16:47:44 +02:00
Elian Doran
3561a4f14d feat(note_info): add back tooltip for note size 2025-12-11 16:38:31 +02:00
Elian Doran
84cda001aa feat(note_info): improve layout slightly 2025-12-11 16:33:18 +02:00
Elian Doran
481127a560 docs(user): mention version for board custom attributes 2025-12-11 10:57:30 +02:00
Elian Doran
c708e7cd61 Translations update from Hosted Weblate (#8016) 2025-12-11 10:49:33 +02:00
Elian Doran
fee0268792 Translated using Weblate (Romanian)
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ro/
2025-12-11 09:47:52 +01:00
green
953593c9d4 Translated using Weblate (Japanese)
Currently translated at 100.0% (1668 of 1668 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-11 09:47:52 +01:00
Elian Doran
5ff60e53cb Translated using Weblate (Romanian)
Currently translated at 100.0% (1668 of 1668 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ro/
2025-12-11 09:47:51 +01:00
Giovi
b38ee36fae Translated using Weblate (Italian)
Currently translated at 100.0% (1668 of 1668 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/it/
2025-12-11 09:47:49 +01:00
Hosted Weblate
38a415faf0 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-11 09:32:25 +01:00
Elian Doran
1e26864842 Translations update from Hosted Weblate (#8006) 2025-12-11 10:32:09 +02:00
Elian Doran
4b74ad5577 feat(breadcrumb/note_info): note size 2025-12-11 00:34:25 +02:00
Elian Doran
e5696713de feat(breadcrumb/note_info): modification/creation date 2025-12-11 00:23:32 +02:00
Elian Doran
2e44397c88 feat(breadcrumb/note_info): get basic dropdown 2025-12-11 00:18:56 +02:00
Meinzzzz
29f0881c5a Fix clicking issue in Mathfield 2025-12-10 22:44:02 +01:00
Francis C.
5d19881981 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1658 of 1658 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-10 22:38:11 +01:00
Tomas Adamek
1711384eaa Translated using Weblate (Czech)
Currently translated at 35.3% (41 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/cs/
2025-12-10 22:38:11 +01:00
Tomas Adamek
9897efe4af Translated using Weblate (Czech)
Currently translated at 5.3% (88 of 1658 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/cs/
2025-12-10 22:38:10 +01:00
Francis C.
884578ea95 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/zh_Hans/
2025-12-10 22:38:09 +01:00
Francis C.
e404e76299 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (116 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/zh_Hant/
2025-12-10 22:38:09 +01:00
green
1db54cba3e Translated using Weblate (Japanese)
Currently translated at 100.0% (1658 of 1658 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-10 22:38:08 +01:00
Francis C.
77e3cc4021 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1658 of 1658 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-10 22:38:07 +01:00
pythaac
242c63dfb4 Translated using Weblate (Korean)
Currently translated at 65.1% (99 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-10 22:38:06 +01:00
Abdulmajeed Alaskar
f5440576b5 Translated using Weblate (Arabic)
Currently translated at 53.9% (82 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ar/
2025-12-10 22:38:06 +01:00
Abdulmajeed Alaskar
b020365af4 Translated using Weblate (Arabic)
Currently translated at 29.3% (34 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/ar/
2025-12-10 22:38:05 +01:00
green
25e5bf0b86 Translated using Weblate (Japanese)
Currently translated at 100.0% (1648 of 1648 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-10 22:38:05 +01:00
Elian Doran
19b32dd3a6 New layout: Integrate Basic properties (#8014) 2025-12-10 23:37:54 +02:00
Elian Doran
1ab89d0db0 fix(status_bar): language selector not updating properly 2025-12-10 23:36:47 +02:00
Elian Doran
6e8e10323f chore(client): address requested changes 2025-12-10 23:19:17 +02:00
Elian Doran
58bc5dc66a chore(ribbon): hide basic properties from the ribbon on new layout 2025-12-10 23:07:35 +02:00
Elian Doran
db42bb603b feat(status_bar): add help item 2025-12-10 23:04:32 +02:00
Elian Doran
cb382c9537 fix(global_menu): layout switcher sometimes fails 2025-12-10 22:57:52 +02:00
Elian Doran
a4b79a2dc9 fix(ribbon): content code modal hidden behind backdrop 2025-12-10 22:56:29 +02:00
Elian Doran
0f867e02c4 fix(ribbon): content languages modal hidden behind backdrop 2025-12-10 22:52:13 +02:00
Elian Doran
ab1b4b37f4 feat(global_menu): add an option to switch layouts 2025-12-10 22:51:47 +02:00
Elian Doran
5a1d138f29 feat(status_bar): language selector 2025-12-10 22:39:07 +02:00
Elian Doran
06a5298efa feat(note_actions): hide options in attachments 2025-12-10 22:27:56 +02:00
Elian Doran
db720acc18 feat(note_actions): hide options in help pages 2025-12-10 22:25:09 +02:00
Elian Doran
8d8ff25bae feat(note_actions): reintroduce help pages 2025-12-10 22:21:15 +02:00
Elian Doran
6f85b7cc09 feat(note_actions): integrate note type 2025-12-10 21:54:17 +02:00
Elian Doran
77f5770bff feat(note_actions): protect note switch 2025-12-10 20:57:15 +02:00
Elian Doran
14cda5b921 fix(note_actions): editability context menu is too narrow 2025-12-10 20:46:58 +02:00
Elian Doran
36b1182565 feat(widgets/toggle): disable if going too fast 2025-12-10 20:33:30 +02:00
Elian Doran
483327c808 fix(widgets/toggle): double event triggering when in menu 2025-12-10 20:30:55 +02:00
Elian Doran
efb2f9a048 chore(note_actions): reintroduce disabled logic for toggles 2025-12-10 20:20:21 +02:00
Meinzzzz
60debca37b Improve comments 2025-12-10 18:36:34 +01:00
Elian Doran
01978dabf0 fix(breadcrumb_badges): doesn't refresh when switching editability 2025-12-10 19:05:33 +02:00
Elian Doran
cfbd2bf53a feat(note_actions): integrate editability menu into new layout 2025-12-10 18:58:46 +02:00
Elian Doran
9262f94190 feat(note_actions): integrate template switch into new layout 2025-12-10 18:43:34 +02:00
Elian Doran
b36a0bd10b feat(note_actions): integrate shared switch into new layout 2025-12-10 18:40:56 +02:00
Elian Doran
2dc8948f33 chore(breadcrumb_badges): chagne icon for shared locally 2025-12-10 18:35:29 +02:00
Elian Doran
9f2ed2f9d4 feat(widgets/toggle): disable transitions on first render 2025-12-10 18:33:29 +02:00
Elian Doran
e0f7d65f77 feat(widgets): toggle from label 2025-12-10 18:24:31 +02:00
Elian Doran
f18ac3a923 feat(note_actions): integrate bookmark into new layout 2025-12-10 18:20:36 +02:00
Elian Doran
b39a6bcc97 feat(widgets): prevent clicks in toggle from dismissing menu 2025-12-10 18:17:39 +02:00
Elian Doran
8fa9c25f2a feat(widgets): menu item with toggle 2025-12-10 18:07:38 +02:00
Elian Doran
84bde62e05 New layout improvements (#8012) 2025-12-10 17:50:31 +02:00
Elian Doran
5bb4621097 chore(layout): address requested changes 2025-12-10 17:42:08 +02:00
Elian Doran
f1edf84f4d fix(layout): title background for code notes 2025-12-10 17:13:52 +02:00
Elian Doran
f7955a9040 fix(client/dropdown): tooltip flickering due to child elements 2025-12-10 17:02:11 +02:00
Elian Doran
7c5df21685 feat(note_actions): group development options 2025-12-10 16:51:07 +02:00
Elian Doran
2060bb8cdd feat(breadcrumb): show note preview 2025-12-10 16:14:40 +02:00
Elian Doran
a9b4e7b1e2 style(layout): apply heavy padding to title only in normal view 2025-12-10 16:11:17 +02:00
Elian Doran
82528c4478 style(layout): slightly smaller note title in full-height note type 2025-12-10 15:23:42 +02:00
Zexin Yuan
4dcfc3e0bc chore: add dev shell and direnv support 2025-12-10 21:17:11 +08:00
Elian Doran
999315d3c6 feat(breadcrumb): basic rename note support 2025-12-10 15:16:45 +02:00
Elian Doran
aef0b03c34 feat(breadcrumb_row): collapse badges sooner 2025-12-10 13:38:15 +02:00
Elian Doran
49f008c46f feat(breadcrumb_row): improve button fit on constrained width 2025-12-10 13:37:07 +02:00
Elian Doran
bd81db4117 feat(breadcrumb_row): improve badge fit on constrained width 2025-12-10 13:33:33 +02:00
Elian Doran
9f274883e3 feat(breadcrumb_badges): basic shrink support 2025-12-10 13:06:25 +02:00
Elian Doran
07b76b80f4 feat(layout): hide note details in attachment view 2025-12-10 12:52:03 +02:00
Elian Doran
0014f0a88d feat(layout): minor improvements to title/icon alignment 2025-12-10 12:50:05 +02:00
Elian Doran
63f7a78d31 chore(note_actions): use dedicated translation for note revisions 2025-12-10 12:46:23 +02:00
Elian Doran
e556c090ff fix(ribbon): attribute details not shown in new layout 2025-12-10 12:40:06 +02:00
Elian Doran
c4f483c250 feat(options/advanced): automatically refresh 2025-12-10 12:29:12 +02:00
Elian Doran
4031332b98 feat(note_title_details): tooltips for values 2025-12-10 12:25:38 +02:00
Elian Doran
10cb7c8d6a feat(note_title_details): hide creation dates on hidden notes 2025-12-10 12:10:32 +02:00
Elian Doran
be190bfe33 feat(layout): improve layout for full-height notes 2025-12-10 12:06:05 +02:00
Elian Doran
4d7d642952 fix(layout): floating toolbar displayed in attachments 2025-12-10 11:58:17 +02:00
Elian Doran
737711e5eb fix(layout): weird title in full-width & attachments 2025-12-10 11:56:34 +02:00
Elian Doran
42fc128f97 chore(breadcrumb_badges/backlinks): display actual count of backlinks 2025-12-10 11:51:09 +02:00
Elian Doran
b03e6c3b19 chore(breadcrumb_badges/backlinks): display list of backlinks on click 2025-12-10 11:41:14 +02:00
Elian Doran
66008489c4 chore(breadcrumb_badges): fake backlink widget 2025-12-10 11:21:06 +02:00
Elian Doran
3262e3490a feat(breadcrumb_badges): integrate into quick edit 2025-12-10 11:10:26 +02:00
Elian Doran
16a73b0848 fix(popup_editor): wrong margin for title 2025-12-10 11:03:12 +02:00
Elian Doran
52bb4d7a0e feat(breadcrumb_badges): make badge not wrap-around 2025-12-10 09:52:46 +02:00
Elian Doran
40b5e4d549 feat(breadcrumb_badges): proper link handling support 2025-12-10 09:47:05 +02:00
Elian Doran
b014ea8950 feat(breadcrumb_badges): add colors to the badges 2025-12-10 09:38:55 +02:00
Elian Doran
61592716f9 feat(breadcrumb_badges): add tooltips for the badges 2025-12-10 09:27:44 +02:00
Elian Doran
efe7fc0ee7 chore(layout): hide breadcrumb badges if not on new layout 2025-12-10 09:12:57 +02:00
Elian Doran
a810db3641 feat(breadcrumb_badges): display badge when editing is unlocked 2025-12-10 09:11:28 +02:00
Elian Doran
f8b292dfa3 Experimental layout (#8005) 2025-12-09 23:35:15 +02:00
Elian Doran
fc2ab91280 feat(options/advanced): add description for experimental 2025-12-09 23:16:30 +02:00
Elian Doran
668ee219c6 chore(layout): use translation for badges 2025-12-09 23:02:21 +02:00
Elian Doran
ee6512a1a6 refactor(layout): align name for breadcrumb badges 2025-12-09 23:00:41 +02:00
Elian Doran
fe1f590286 chore(layout): use translations for note title details 2025-12-09 23:00:02 +02:00
Elian Doran
876e8f843a chore(layout): use i18n for options 2025-12-09 22:58:16 +02:00
Elian Doran
a45c1a1dc8 chore(layout): fix regressions after merge 2025-12-09 22:57:36 +02:00
Elian Doran
f8377169e6 Merge remote-tracking branch 'origin/main' into feature/new_layout 2025-12-09 22:46:18 +02:00
Elian Doran
a197a33d35 chore(experimental_features): address review 2025-12-09 22:43:39 +02:00
Elian Doran
3060207d04 feat(layout): created & modification date 2025-12-09 22:22:28 +02:00
Elian Doran
28c1d0b3f5 feat(layout): indicate clickable badges 2025-12-09 21:50:38 +02:00
Elian Doran
644d051477 feat(layout): add shared badge 2025-12-09 21:44:39 +02:00
Elian Doran
f42031c8de feat(layout): add icon to the badge 2025-12-09 21:11:30 +02:00
Elian Doran
6b50d9b087 feat(layout): implement read-only badge 2025-12-09 21:06:28 +02:00
Elian Doran
a0f0da64b4 feat(layout): new icon for note actions & fix padding 2025-12-09 20:59:28 +02:00
Elian Doran
1e72ebd104 feat(layout): move revisions button to note actions 2025-12-09 20:48:54 +02:00
Elian Doran
1184a95697 style(layout): missed bottom border in ribbon buttons 2025-12-09 20:44:58 +02:00
Elian Doran
cd0e4a5678 feat(layout): move fixed formatting toolbar above 2025-12-09 20:36:48 +02:00
Elian Doran
394f6c3110 feat(layout): respect content width for title 2025-12-09 20:22:31 +02:00
Elian Doran
e2b6d0c256 feat(layout): move the note title into the scrollable region 2025-12-09 20:09:06 +02:00
Elian Doran
fe7ca210dd feat(layout): move the note actions into the breadcrumb area 2025-12-09 20:05:42 +02:00
Elian Doran
e58d6bf2a3 feat(layout): reverse the layout of the ribbon 2025-12-09 19:51:53 +02:00
Elian Doran
460d20d6b2 feat(layout): move ribbon to the bottom as experimental layout 2025-12-09 19:42:37 +02:00
Elian Doran
ae154212fe feat(client/server): basic support for experimental features 2025-12-09 19:34:03 +02:00
Elian Doran
28bb4edbac chore(layout): revert work on floating panel 2025-12-09 19:18:48 +02:00
Elian Doran
1ceed1b47b chore(layout): revert work on floating panel 2025-12-09 19:11:27 +02:00
Elian Doran
9445e64c2e Translations update from Hosted Weblate (#8004) 2025-12-09 17:49:32 +02:00
Matheus Fongaro (MatioZG)
e6fba03ba7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 3.2% (5 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-09 16:16:40 +01:00
green
b027ca5c09 Translated using Weblate (Japanese)
Currently translated at 100.0% (1646 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-09 16:16:39 +01:00
Elian Doran
e98df30500 fix(popup-editor): broken title bar layout 2025-12-09 17:16:29 +02:00
Elian Doran
111c44dadf fix(content_header): z-index issues 2025-12-09 17:11:28 +02:00
Elian Doran
cb31c25e6c fix(content_header): note icon dropdown broken when scrolling 2025-12-09 17:08:03 +02:00
Elian Doran
5d59c953c2 fix(layout): title row background in settings 2025-12-09 16:39:31 +02:00
Elian Doran
a2cff42981 fix(layout): scrollbar design in code note 2025-12-09 16:31:47 +02:00
Elian Doran
cae892a971 fix(layout): title not visible on dark code theme 2025-12-09 16:27:19 +02:00
Elian Doran
f8447d923e feat(ribbon): hide when in options 2025-12-09 16:22:03 +02:00
Elian Doran
3b8dabc9d2 Back/forward navigation in tab bar (#8003) 2025-12-09 16:11:17 +02:00
Elian Doran
cda39e967c chore(tab_navigation): address requested changes 2025-12-09 16:02:24 +02:00
Elian Doran
7da9367dc9 fix(tab_navigation): affecting server and mobile views 2025-12-09 15:59:15 +02:00
Elian Doran
82d97ef26f feat(tab_navigation): hide buttons if launcher ones are used 2025-12-09 15:30:46 +02:00
Elian Doran
9e094f1d96 feat(tab_navigation): add it to horizontal layout as well 2025-12-09 15:14:13 +02:00
Elian Doran
da7e15c268 refactor(tab_navigation): sort imports 2025-12-09 15:06:54 +02:00
Elian Doran
24806a810c feat(tab_navigation): display note icon in history navigation 2025-12-09 15:02:52 +02:00
Elian Doran
a2ace4510a Translations update from Hosted Weblate (#8000) 2025-12-09 15:00:26 +02:00
Elian Doran
5c8132088f feat(client): use chevrons to display note path 2025-12-09 14:49:19 +02:00
Elian Doran
7ee060b228 feat(tab_navigation): improve indicator for current item in context menu 2025-12-09 14:41:17 +02:00
Elian Doran
4b2a4b8f7b feat(tab_navigation): reflect state of history by disabling the buttons 2025-12-09 14:29:45 +02:00
Elian Doran
5a668ede01 chore(tab_navigation): reintroduce history cleaning 2025-12-09 14:24:39 +02:00
Elian Doran
9e099444b6 feat(tab_navigation): functional context menu 2025-12-09 14:23:06 +02:00
Elian Doran
e3f5b3535a feat(tab_navigation): functional back/forward buttons 2025-12-09 14:10:11 +02:00
Elian Doran
346ad1e8a3 feat(tab_navigation): add the buttons on vertical layout 2025-12-09 14:06:57 +02:00
Elian Doran
2a9558e9c5 style(ribbon): make icons slightly bigger 2025-12-09 13:18:25 +02:00
Hosted Weblate
c324f66aef Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-09 12:15:20 +01:00
Elian Doran
e688f2cdb6 Add breadcrumbs to navigation (#7995) 2025-12-09 13:15:03 +02:00
Elian Doran
2ff8762a22 chore(client): fix typecheck 2025-12-09 13:07:41 +02:00
Elian Doran
4d75221938 chore(breadcrumbs): address requested changes 2025-12-09 12:41:54 +02:00
Elian Doran
658b699b71 fix(collections/geomap): fake floating buttons mispositioned 2025-12-09 12:24:28 +02:00
Elian Doran
72b0d03546 chore(layout): remove some title margins 2025-12-09 12:23:05 +02:00
Elian Doran
19980807f2 style(ribbon): fix some alignment issues & decrease button size 2025-12-09 12:20:27 +02:00
Elian Doran
3514e3d057 fix(floating_buttons): wrong position when at the top of the note 2025-12-09 11:46:16 +02:00
Elian Doran
fb6c82740c chore(ribbon): remove top transition completely 2025-12-09 11:38:04 +02:00
Elian Doran
8df5a010c9 fix(ribbon): note buttons cut off 2025-12-09 11:36:00 +02:00
Elian Doran
895e9b8bf0 chore(client/layout): reduce transitions 2025-12-09 11:30:18 +02:00
Elian Doran
bfcf85e0d2 fix(client/layout): content header height not properly resized when switching notes 2025-12-09 11:27:05 +02:00
Elian Doran
5770222304 fix(client/floating_buttons): clipped by floating content header 2025-12-09 11:08:28 +02:00
Elian Doran
d5d2815bdf fix(client/floating_buttons): clipped by ribbon 2025-12-09 09:20:31 +02:00
Elian Doran
7fc3d413e5 fix(client): 1px scroll in full-height note 2025-12-09 09:13:18 +02:00
Elian Doran
474228b630 style(client): remove bottom border & box-shadow for content header 2025-12-09 08:22:51 +02:00
Elian Doran
0805e077a1 feat(ribbon): basic implementation for scroll pinning 2025-12-09 08:18:27 +02:00
Elian Doran
6b059a9a75 feat(ribbon): context menu for root item 2025-12-09 08:01:52 +02:00
Elian Doran
7377e4e34d chore(ribbon): improve paddings slightly 2025-12-09 07:58:59 +02:00
Elian Doran
6fac947d9c chore(ribbon): address requested changes 2025-12-09 07:50:21 +02:00
Elian Doran
5973e5ca26 chore(ribbon): remove label for the root entirely 2025-12-09 07:46:46 +02:00
Nriver
5907b7090e fix note title color for legacy themes 2025-12-09 10:49:28 +08:00
Meinzzzz
30ea81d0fb Improve virtual keyboard logic and fix Tab issues 2025-12-08 22:59:08 +01:00
Elian Doran
6570a55e7e Translations update from Hosted Weblate (#7993) 2025-12-08 23:56:13 +02:00
Elian Doran
608ab53933 chore(ribbon): reduce note title padding 2025-12-08 23:41:17 +02:00
Meinzzzz
b1d92c4fe6 Fix Tab issues 2025-12-08 22:39:12 +01:00
Elian Doran
05679f7a8d feat(ribbon): prototype sticky ribbon 2025-12-08 23:33:55 +02:00
noobhjy
f4838bb3b5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.1% (115 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/zh_Hans/
2025-12-08 21:21:24 +00:00
pythaac
edb2a65196 Translated using Weblate (Korean)
Currently translated at 61.8% (94 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-08 21:21:23 +00:00
Hosted Weblate
fd721cac51 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-08 21:21:23 +00:00
pythaac
a1ff3cc8f7 Translated using Weblate (Korean)
Currently translated at 57.2% (87 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-08 21:21:21 +00:00
Adorian Doran
3fa6b264e5 style/note colors: include the original custom color 2025-12-08 23:18:14 +02:00
Elian Doran
fcf51ec6da chore(eslint): apply to .tsx as well 2025-12-08 23:14:09 +02:00
Elian Doran
d15b5f8cbc style(next): basic styling of ribbon as a floating toolbar 2025-12-08 23:13:58 +02:00
Elian Doran
ef3cbcac6d refactor(breadcrumb): fix eslint issues 2025-12-08 23:09:00 +02:00
Elian Doran
b16893c4d2 feat(breadcrumb): collapse items if the list is too big 2025-12-08 23:03:31 +02:00
Elian Doran
a365814aaa feat(breadcrumb): improve overflow slightly 2025-12-08 22:54:31 +02:00
Elian Doran
eca2116adc feat(breadcrumb): make the root note clickable 2025-12-08 22:46:04 +02:00
Elian Doran
4cfa403657 feat(breadcrumb): apply ellipsis to dropdown 2025-12-08 22:40:37 +02:00
Elian Doran
70ded4c2cd chore(breadcrumb): use bold for highlighting active entry 2025-12-08 22:38:06 +02:00
Elian Doran
3fe45db6ef feat(breadcrumb): improve overflow support 2025-12-08 22:17:49 +02:00
Elian Doran
11467775b6 feat(breadcrumb): basic overflow support 2025-12-08 22:09:46 +02:00
Elian Doran
1e5fcf635e feat(breadcrumb): show root title if it's the one active 2025-12-08 22:05:09 +02:00
Elian Doran
223ba4643f fix(breadcrumb): breadcrumb shown if there are no children 2025-12-08 21:57:51 +02:00
Elian Doran
200fd76929 feat(breadcrumb): display a checkbox for the current note 2025-12-08 21:52:36 +02:00
Meinzzzz
70f46de2d8 MathLive virtual keyboard only appears when focusing the mathfield 2025-12-08 20:30:07 +01:00
Meinzzzz
f1b2d0b870 Increas Mathfield font size and ensure virtual keyboard appears above CKEditor 2025-12-08 20:22:52 +01:00
Meinzzzz
8a385972fc Close Virtual Keyboard when Mathinput is closed 2025-12-08 18:49:06 +01:00
Elian Doran
c5c4ecd6e6 feat(breadcrumb): show current item 2025-12-08 17:04:08 +02:00
Elian Doran
bedca9f82c feat(breadcrumb): hide root icon 2025-12-08 16:45:26 +02:00
Elian Doran
adc356eff3 fix(breadcrumb): navigation on first level not working 2025-12-08 16:41:06 +02:00
Elian Doran
c4285772b3 feat(breadcrumb): basic navigation in separator 2025-12-08 16:34:40 +02:00
Elian Doran
a02235f2bd feat(breadcrumb): set up dropdown 2025-12-08 16:03:12 +02:00
Elian Doran
5f215b14c2 feat(breadcrumb): start implementing interactive breadcrumbs 2025-12-08 16:01:34 +02:00
Elian Doran
6e29fe8d58 feat(breadcrumb): hide preview 2025-12-08 15:56:03 +02:00
Elian Doran
43ceb1982d feat(breadcrumb): hide last note 2025-12-08 15:53:17 +02:00
Elian Doran
d02ec47d77 feat(breadcrumb): get breadcrumb to render 2025-12-08 15:16:06 +02:00
Elian Doran
9942950710 feat(layout): relocate title into scrollable region 2025-12-08 14:54:57 +02:00
Elian Doran
a6682be251 fix(webview): layout issues when when no webviewSrc 2025-12-08 14:21:59 +02:00
Elian Doran
baee2cd6b2 chore(scripts): add proper typechecking 2025-12-08 14:13:18 +02:00
Elian Doran
3ee8eac635 chore(readme): order languages alphabetically 2025-12-08 14:13:17 +02:00
Elian Doran
ce5a775160 Translations update from Hosted Weblate (#7991) 2025-12-08 13:22:19 +02:00
Elian Doran
2cf66d1c53 Delete CNAME 2025-12-08 12:59:39 +02:00
Hosted Weblate
eaee67d742 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-08 11:59:30 +01:00
Elian Doran
c681496b1b chore(readme): fix links to old docs 2025-12-08 12:58:48 +02:00
Elian Doran
829d3e046d chore(readme): fix links to docs 2025-12-08 12:51:07 +02:00
Elian Doran
5c8df540db chore(readme): fix language switcher links (closes #7246) 2025-12-08 12:43:00 +02:00
Elian Doran
8920e6e448 chore(readme): disable translation for language switcher 2025-12-08 12:00:30 +02:00
Elian Doran
3cb860232e Translations update from Hosted Weblate (#7990) 2025-12-08 11:57:43 +02:00
Hosted Weblate
d588518ba1 Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-08 10:55:07 +01:00
Elian Doran
d6f727d17a Translations update from Hosted Weblate (#7989) 2025-12-08 11:54:51 +02:00
Wen QI
82c40302bd Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1646 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-08 10:53:50 +01:00
Andreas H.
c40c62a247 Translated using Weblate (German)
Currently translated at 99.2% (1634 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-12-08 10:53:50 +01:00
Wen QI
8048b5ebca Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.4% (1637 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-08 10:53:49 +01:00
green
03ba43df5d Translated using Weblate (Japanese)
Currently translated at 100.0% (1646 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-08 10:53:49 +01:00
Elian Doran
a4b95b45ec chore(readme): rewrite links 2025-12-08 11:52:28 +02:00
Elian Doran
59513962fe chore(readme): add script to copy README into docs 2025-12-08 11:49:12 +02:00
Elian Doran
a201b43cde fix(launch_bar): sync status not correctly rendered 2025-12-08 11:30:33 +02:00
Elian Doran
f98c77bd16 Translations update from Hosted Weblate (#7988) 2025-12-08 09:44:13 +02:00
green
2596359b25 Translated using Weblate (Japanese)
Currently translated at 100.0% (1646 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-08 08:30:46 +01:00
minkipark
e0a0263607 Translated using Weblate (Korean)
Currently translated at 3.0% (50 of 1646 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ko/
2025-12-08 08:30:45 +01:00
green
2d3feedb07 Translated using Weblate (Japanese)
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ja/
2025-12-08 08:30:45 +01:00
Hosted Weblate
af392fad3d Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-08 08:30:44 +01:00
Elian Doran
ef74490c44 chore(deps): update dependency jsonc-eslint-parser to v2.4.2 (#7982) 2025-12-08 09:30:31 +02:00
Elian Doran
b6856e18a8 fix(deps): update dependency katex to v0.16.27 (#7983) 2025-12-08 09:30:08 +02:00
renovate[bot]
de1d4424d9 fix(deps): update dependency katex to v0.16.27 2025-12-08 01:03:42 +00:00
renovate[bot]
5ec45bb575 chore(deps): update dependency jsonc-eslint-parser to v2.4.2 2025-12-08 01:02:52 +00:00
Meinzzzz
28dd85c1d1 Merge upstream changes and resolve conflicts 2025-12-07 23:51:41 +01:00
meinzzzz
827c8e0e72 Refactor: Combine MathLive and LaTeX inputs into one single component 2025-12-07 23:19:48 +01:00
Elian Doran
cb3aced2ed docs(script): clarify originEntity and activeContext 2025-12-07 23:58:02 +02:00
Elian Doran
2f13a1ad21 fix(mind-map): show text in links between nodes on export (#7938) 2025-12-07 23:20:18 +02:00
Elian Doran
045127adee chore(vscode): add errorlens to recommendations 2025-12-07 23:11:07 +02:00
Elian Doran
db1a0c0362 i18n(client): update renamed key 2025-12-07 23:07:30 +02:00
Elian Doran
dcaf91a878 CKEditor stability improvements (#7979) 2025-12-07 22:40:52 +02:00
Elian Doran
a9209f5103 chore(ckeditor/watchdog): accidental comment 2025-12-07 22:32:06 +02:00
Elian Doran
ea613986c2 chore(server): add OpenAPI spec for to-markdown 2025-12-07 22:26:21 +02:00
Elian Doran
1ed46bd47c refactor(server): add validation for HTML/Markdown rendering 2025-12-07 22:24:14 +02:00
Elian Doran
f1ee79e75a refactor(client): inconsistent prefix for messages 2025-12-07 22:23:19 +02:00
Elian Doran
cd27160905 chore(ckeditor/watchdog): fix typecheck issues 2025-12-07 22:09:30 +02:00
Elian Doran
9ddf4a1308 feat(ckeditor/watchdog): ignore model out of bounds (closes #7739) 2025-12-07 22:00:10 +02:00
Elian Doran
a1c5ed9eb5 feat(ckeditor/watchdog): ignore parent check (closes #5776) 2025-12-07 21:59:52 +02:00
Elian Doran
7a4f19eada chore(ckeditor): revert breaking change 2025-12-07 21:34:10 +02:00
Elian Doran
397fb785d6 feat(ckeditor/watchdog): functional copy to clipboard button 2025-12-07 21:21:55 +02:00
Elian Doran
75a1fcc933 chore(dialog): fix mandatory parameter 2025-12-07 21:06:56 +02:00
Elian Doran
292cbf1383 feat(ckeditor/watchdog): add a title to the details screen 2025-12-07 21:03:39 +02:00
Elian Doran
37a14fefb3 feat(ckeditor/watchdog): improve layout of info dialog 2025-12-07 21:01:56 +02:00
Elian Doran
f424633d8c feat(ckeditor/watchdog): improve dialog size 2025-12-07 20:59:53 +02:00
Elian Doran
048258d2d1 feat(toast): improve button layout 2025-12-07 20:59:41 +02:00
Elian Doran
f779108b6c feat(ckeditor/watchdog): add a more details button 2025-12-07 20:40:29 +02:00
Elian Doran
522f3ae0a1 feat(toast): support buttons 2025-12-07 20:39:27 +02:00
Elian Doran
3fc7067c59 feat(dialog): support React nodes in info dialog 2025-12-07 20:39:11 +02:00
Elian Doran
c600e8ef89 feat(ckeditor/watchdog): add a toast to indicate the error 2025-12-07 20:16:34 +02:00
Elian Doran
5ad267fe1b chore(ckeditor): try to disable watchdog timer 2025-12-07 20:09:56 +02:00
Elian Doran
d8b3e438f8 chore(ckeditor): solve a few eslint warnings 2025-12-07 19:46:06 +02:00
Elian Doran
2834af66e9 feat(ckeditor/watchdog): restore focus after crash 2025-12-07 19:36:46 +02:00
Elian Doran
1bbf86fbeb feat(ckeditor/watchdog): use data stored in the spaced update 2025-12-07 19:30:35 +02:00
Elian Doran
f662b95dc9 chore(vscode): remove now unnecessary extension recommendations 2025-12-07 19:03:05 +02:00
Elian Doran
9ad4b725ac chore(eslint): report missing semicolons 2025-12-07 00:42:25 +02:00
Elian Doran
0182c61aec chore(vscode): mark all eslint rules as warnings 2025-12-07 00:42:00 +02:00
Elian Doran
78362535c7 chore(eslint): downgrade import sorting to warning 2025-12-07 00:36:39 +02:00
Elian Doran
ae7b31f343 chore(dx): trim trailing whitespace in CSS 2025-12-07 00:35:28 +02:00
Elian Doran
f16441bba4 Port toast to React (#7975) 2025-12-07 00:34:25 +02:00
Elian Doran
d3f9bb6def chore(toast): address requested changes 2025-12-07 00:33:37 +02:00
Elian Doran
e02440aa59 fix(toast): persistent toasts no longer working 2025-12-07 00:20:56 +02:00
Elian Doran
f1d87c29d3 chore(toast): fix typecheck issues 2025-12-07 00:15:00 +02:00
Elian Doran
21335b1b00 refactor(toast): get rid of autohide 2025-12-07 00:07:43 +02:00
Elian Doran
7463570e76 refactor(toast): rename delay to timeout 2025-12-07 00:06:42 +02:00
Elian Doran
da17a63ef5 refactor(toast): get rid of closeAfter in favor of delay 2025-12-07 00:04:12 +02:00
Elian Doran
eb8f2021cb fix(toast): add fallback if icon doesn't start with bx- 2025-12-06 23:49:56 +02:00
Elian Doran
888ff33be1 fix(toast): missing message icon 2025-12-06 23:46:21 +02:00
Elian Doran
b46850e86e refactor(toast): relocate styles into own file 2025-12-06 23:38:40 +02:00
Elian Doran
f053587f09 chore(toast): port toast to react 2025-12-06 23:37:56 +02:00
Elian Doran
7a3092a23b fix(read-only-bar): button shrinks when window is small 2025-12-06 23:00:57 +02:00
Elian Doran
d95450ae07 feat(client): detect communication errors with Traefik 2025-12-06 22:57:13 +02:00
Elian Doran
230def10fe feat(client): improve error message for HTTP errors 2025-12-06 22:12:06 +02:00
Elian Doran
036f8e49a4 docs(server/proxy): breaking change in Traefik 3.6.4 2025-12-06 21:51:56 +02:00
Elian Doran
4eca8a5640 feat(dx): add a way to run traefik reverse proxy 2025-12-06 21:37:24 +02:00
Elian Doran
08f96a91f3 docs(readme): fix a space 2025-12-06 19:37:25 +02:00
Elian Doran
2e915eccd6 Clean up _regroup (#7970) 2025-12-06 19:27:15 +02:00
Elian Doran
c05c58c82b Merge remote-tracking branch 'origin/main' into chore/cleanup_regroup 2025-12-06 19:18:23 +02:00
Elian Doran
572feed918 chore(regroup): address requested changes 2025-12-06 19:17:56 +02:00
Elian Doran
d30d207ab5 test(server): solve some local timeouts 2025-12-06 19:08:19 +02:00
Elian Doran
2a6b91dd04 chore(regroup): reintroduce code coverage 2025-12-06 19:08:03 +02:00
Elian Doran
3dee1725b3 chore(regroup): reintroduce old e2e tests 2025-12-06 18:54:38 +02:00
Elian Doran
f7ac465e67 chore(eslint): integrate preact config 2025-12-06 15:31:38 +02:00
Elian Doran
912f14549c chore(eslint): integrate playwright e2e 2025-12-06 15:20:05 +02:00
Elian Doran
21079335e7 chore(eslint): remove unnecessary files 2025-12-06 14:58:38 +02:00
Elian Doran
85741240f1 chore(eslint): reintegrate simple import sort 2025-12-06 14:50:41 +02:00
Elian Doran
3df9a87b29 chore(eslint): clean up unnecessary dependency in client 2025-12-06 14:48:00 +02:00
Elian Doran
acd60007ac chore(eslint): integrate formatter config 2025-12-06 14:43:08 +02:00
Elian Doran
f66c9630e3 chore(deps): update dependency vite to v7.2.6 (#7951) 2025-12-06 12:25:52 +02:00
Elian Doran
50a5892a31 chore(deps): update typescript-eslint monorepo to v8.48.1 (#7953) 2025-12-06 12:25:14 +02:00
renovate[bot]
c5f58437b1 chore(deps): update typescript-eslint monorepo to v8.48.1 2025-12-06 10:10:32 +00:00
Elian Doran
a915424d9a chore(deps): update softprops/action-gh-release action to v2.5.0 (#7958) 2025-12-06 12:10:09 +02:00
renovate[bot]
be7699c600 chore(deps): update dependency vite to v7.2.6 2025-12-06 10:09:14 +00:00
Elian Doran
a7c946ddae fix(deps): update codemirror themes (#7955) 2025-12-06 12:08:43 +02:00
Elian Doran
4b741a9434 chore(deps): update dependency openai to v6.10.0 (#7957) 2025-12-06 12:07:52 +02:00
Elian Doran
cd963272d4 chore(deps): update vitest monorepo to v4.0.15 (#7954) 2025-12-06 12:07:11 +02:00
Elian Doran
9de17ead91 Translations update from Hosted Weblate (#7966) 2025-12-06 12:05:21 +02:00
pythaac
d98b133d63 Translated using Weblate (Korean)
Currently translated at 52.6% (80 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/ko/
2025-12-06 11:04:27 +01:00
Elian Doran
361e8e0066 chore(deps): update dependency electron to v39 (#7536) 2025-12-06 12:04:20 +02:00
Elian Doran
523c44b796 fix(deps): update dependency mermaid to v11.12.2 (#7956) 2025-12-06 11:53:14 +02:00
Elian Doran
4bd60ed6a1 chore(deps): update react monorepo to v19.2.1 (#7952) 2025-12-06 11:52:11 +02:00
Elian Doran
7d57f08baf chore(deps): update dependency express to v5.2.1 (#7950) 2025-12-06 11:35:10 +02:00
Elian Doran
5e7f54dbc3 chore(deps): update dependency esbuild to v0.27.1 (#7949) 2025-12-06 11:34:36 +02:00
Elian Doran
dfb9ce990d chore(eslint): basic config 2025-12-06 11:05:34 +02:00
Elian Doran
e87a368e87 chore(regroup): remove some redundant files 2025-12-06 10:52:41 +02:00
Elian Doran
44506057fd chore(regroup): clean bin dir 2025-12-06 10:50:42 +02:00
Elian Doran
ff08eadb23 fix(server/scripts): generate-document failing (closes #5422) 2025-12-06 10:49:14 +02:00
Elian Doran
ebb1a3feb2 chore(regroup): integrate generate_document 2025-12-06 10:35:57 +02:00
Elian Doran
81bc85b8e4 chore(regroup): adapt export-schema script 2025-12-06 10:29:08 +02:00
Elian Doran
4a749f52e9 chore(regroup): remove old release scripts 2025-12-06 10:11:28 +02:00
Elian Doran
25e37ddd78 chore(regroup): relocate icon scripts 2025-12-06 10:07:23 +02:00
renovate[bot]
46a9cfcc67 chore(deps): update react monorepo to v19.2.1 2025-12-06 08:00:01 +00:00
Elian Doran
66ff944660 chore(deps): update dependency @types/reveal.js to v5.2.2 (#7948) 2025-12-06 09:58:15 +02:00
Elian Doran
988db59197 chore(deps): update dependency @smithy/middleware-retry to v4.4.14 (#7947) 2025-12-06 09:57:54 +02:00
Elian Doran
34e321407a chore(deps): update dependency @redocly/cli to v2.12.3 (#7946) 2025-12-06 09:57:31 +02:00
Elian Doran
44c0028a51 chore(regroup): clean up translation scripts 2025-12-06 09:43:54 +02:00
Elian Doran
264b75cd68 chore(regroup): clean up migration scripts 2025-12-06 09:43:02 +02:00
renovate[bot]
cf9ccdcab6 chore(deps): update dependency electron to v39 2025-12-06 07:36:35 +00:00
renovate[bot]
48e18e533c chore(deps): update dependency openai to v6.10.0 2025-12-06 07:35:53 +00:00
renovate[bot]
12ef778bf4 fix(deps): update dependency mermaid to v11.12.2 2025-12-06 07:35:15 +00:00
Elian Doran
6c31a1788a Port launch bar to React (#7934) 2025-12-06 09:35:06 +02:00
renovate[bot]
aa7c6da8ef fix(deps): update codemirror themes 2025-12-06 07:34:30 +00:00
renovate[bot]
1ff40cace0 chore(deps): update vitest monorepo to v4.0.15 2025-12-06 07:33:52 +00:00
renovate[bot]
0a1dadbea1 chore(deps): update dependency express to v5.2.1 2025-12-06 07:30:48 +00:00
renovate[bot]
24edbdba5e chore(deps): update dependency esbuild to v0.27.1 2025-12-06 07:30:09 +00:00
renovate[bot]
db504eff88 chore(deps): update dependency @types/reveal.js to v5.2.2 2025-12-06 07:29:26 +00:00
renovate[bot]
5e09925659 chore(deps): update dependency @smithy/middleware-retry to v4.4.14 2025-12-06 07:28:42 +00:00
renovate[bot]
d1b2b351c8 chore(deps): update dependency @redocly/cli to v2.12.3 2025-12-06 07:28:02 +00:00
Elian Doran
50b0dc178e chore(regroup): clean up old ETAPI tests
They are already integrated in apps/server/spec/etapi.
2025-12-06 09:27:56 +02:00
Elian Doran
b34118e395 fix(deps): update ckeditor monorepo to v47.3.0 (#7959) 2025-12-06 09:26:03 +02:00
Elian Doran
e6436f9021 fix(text): code block background not correct 2025-12-06 09:16:20 +02:00
Elian Doran
831d1b4f3a chore(text): remove workaround for superscript icon 2025-12-06 09:15:40 +02:00
renovate[bot]
0456d1ca29 fix(deps): update ckeditor monorepo to v47.3.0 2025-12-06 07:04:27 +00:00
Elian Doran
fcd151022e fix(deps): update dependency i18next to v25.7.1 (#7960) 2025-12-06 08:59:19 +02:00
Elian Doran
0c1998002e fix(deps): update dependency preact to v10.28.0 (#7961) 2025-12-06 08:58:33 +02:00
Elian Doran
b8c33ce7fa e2e(server): locator failing due to removal of "visible" 2025-12-06 08:54:35 +02:00
Elian Doran
454cd633e8 fix(read_only_text): missing padding in <pre> without <code> 2025-12-06 08:46:44 +02:00
renovate[bot]
53cb9a6e10 fix(deps): update dependency preact to v10.28.0 2025-12-06 06:43:06 +00:00
renovate[bot]
8d6ff763d6 fix(deps): update dependency i18next to v25.7.1 2025-12-06 06:42:29 +00:00
Elian Doran
ecf15af3a0 fix(deps): update dependency react-i18next to v16.4.0 (#7962) 2025-12-06 08:38:20 +02:00
Elian Doran
716823789d chore(deps): update apple-actions/import-codesign-certs action to v6 (#7963) 2025-12-06 08:37:58 +02:00
Elian Doran
3c192badce chore(deps): update dependency @anthropic-ai/sdk to v0.71.2 (#7945) 2025-12-06 08:37:04 +02:00
renovate[bot]
d94b611d10 chore(deps): update apple-actions/import-codesign-certs action to v6 2025-12-06 02:54:20 +00:00
renovate[bot]
e2ce329b6c fix(deps): update dependency react-i18next to v16.4.0 2025-12-06 02:54:16 +00:00
renovate[bot]
ef902fc706 chore(deps): update softprops/action-gh-release action to v2.5.0 2025-12-06 02:51:28 +00:00
renovate[bot]
a7fc94c303 chore(deps): update dependency @anthropic-ai/sdk to v0.71.2 2025-12-06 02:42:34 +00:00
Elian Doran
c8c6d1bb1e docs(user): document launch bar widgets 2025-12-05 23:24:18 +02:00
Elian Doran
a205108681 chore(react/launch_bar): address requested changes 2025-12-05 22:57:07 +02:00
Elian Doran
31561879b3 chore(launch_bar): address requested changes 2025-12-05 19:40:36 +02:00
Elian Doran
fdb6677153 chore(launch_bar): reintroduce keyboard shortcuts 2025-12-05 19:23:40 +02:00
Elian Doran
17241be4bc fix(launch_bar): note context not properly set for legacy widgets 2025-12-05 18:01:46 +02:00
Elian Doran
3bf6de9c76 fix(launch_bar): calendar month selector dropdown wrongly positioned 2025-12-05 17:36:04 +02:00
Elian Doran
a53322e7cb refactor(launch_bar): deduplicate launcher note props 2025-12-05 17:16:55 +02:00
Elian Doran
a107c126e4 fix(launch_bar): dynamic dropdown not repositioning 2025-12-05 17:11:53 +02:00
Elian Doran
3a8dcae53a fix(launch_bar): wrong position of dropdown 2025-12-05 16:45:38 +02:00
Elian Doran
b99d4532df fix(react): duplicate tooltips on focus vs hover 2025-12-05 16:39:55 +02:00
Elian Doran
6e8f8ea357 fix(launch_bar): horizontal launch bar scrolling with dropdown open 2025-12-05 15:53:17 +02:00
Elian Doran
83838bbe76 fix(launch_bar): calendar month selector not actually working 2025-12-05 15:50:25 +02:00
Elian Doran
66620aabe2 fix(launch_bar): calendar dropdown hiding on month selection 2025-12-05 15:43:44 +02:00
Elian Doran
74fcf8270d chore(launch_bar): improve positioning of the month dropdown 2025-12-05 15:31:14 +02:00
Elian Doran
91b4e32a38 fix(launch_bar): calendar month selector clipped 2025-12-05 15:29:25 +02:00
Elian Doran
3f0c114f24 fix(launch_bar): handle week number wrap-around for next year in calendar 2025-12-05 15:17:51 +02:00
Elian Doran
30fe6b93c4 chore(launch_bar): remove now redundant file 2025-12-05 15:10:48 +02:00
Elian Doran
ec99242314 fix(launch_bar): dropdown tooltip visible when hovering popup 2025-12-05 15:06:55 +02:00
Elian Doran
9149fb7a85 fix(launch_bar): tooltip error for dropdowns 2025-12-05 15:02:13 +02:00
Elian Doran
3c919d9a8a fix(launch_bar): global menu size 2025-12-05 14:52:57 +02:00
Elian Doran
22f9ce1e2e Merge remote-tracking branch 'origin/main' into react/launch_bar 2025-12-05 12:22:36 +02:00
Elian Doran
72b01cec70 Merge branch 'stable' 2025-12-05 12:22:14 +02:00
Elian Doran
57b8bc2645 feat(launch_bar): faster tooltips 2025-12-05 12:12:02 +02:00
Elian Doran
3ad4ca3943 fix(launch_bar): bootstrap tooltip for dropdown 2025-12-05 12:06:53 +02:00
Elian Doran
237ffeff52 fix(launch_bar): title position of action buttons 2025-12-05 12:04:26 +02:00
Elian Doran
fb491d9790 Fix (NoteMap): map overflows when switching type in ribbon (#7939) 2025-12-05 11:59:08 +02:00
Elian Doran
facd03b6ad feat(launch_bar): horizontal scroll using mouse wheel 2025-12-05 11:54:31 +02:00
Elian Doran
f5f38ca670 refactor(widgets): relocate AI chat button 2025-12-05 11:53:38 +02:00
Elian Doran
83e599f0e9 refactor(widgets): relocate note launcher & command button 2025-12-05 11:52:52 +02:00
Elian Doran
48cd06f37e feat(bundle): improve error handling message 2025-12-05 11:49:09 +02:00
Elian Doran
aac9d2d1c4 fix(toast): icon missing for error messages 2025-12-05 11:45:02 +02:00
Elian Doran
7e2e1c12b9 chore(widgets): fix error icon in toast & improve message reporting 2025-12-05 11:42:56 +02:00
Elian Doran
3410dd4eba chore(react/launch_bar): bring back reporting widget that failed 2025-12-05 11:40:50 +02:00
Elian Doran
d511085db3 chore(react/launch_bar): port launcher container & launcher 2025-12-05 11:31:52 +02:00
Elian Doran
caaa3583a7 chore(react/launch_bar): port sync status 2025-12-05 10:22:34 +02:00
Elian Doran
185e5691a4 chore(react/launch_bar): bring back week highlighting 2025-12-05 09:52:46 +02:00
Elian Doran
20f44cc64f chore(react/launch_bar): reimplement week notes 2025-12-05 09:36:21 +02:00
SiriusXT
07ef94afd9 Merge branch 'main' into fix/note_map 2025-12-05 15:21:30 +08:00
SiriusXT
2d33b8a958 fix(note_map): initialize map state when switching map type in ribbon 2025-12-05 15:21:06 +08:00
Elian Doran
d283f5dbb4 chore(react/launch_bar): hide dropdown when selecting date 2025-12-05 09:15:01 +02:00
Elian Doran
1af76c4d06 chore(react/launch_bar): clicking on calendar days 2025-12-05 09:11:50 +02:00
Elian Doran
07498c6bef chore(react/launch_bar): add link to existing days 2025-12-05 09:02:51 +02:00
Elian Doran
18f9ebbc4f chore(react/launch_bar): reintroduce day highlighting 2025-12-05 08:56:35 +02:00
SiriusXT
85b4f652f4 chore(note_map): improve the icon for expanding/collapsing the map 2025-12-05 11:21:44 +08:00
SiriusXT
eec6f7336c fix (note_map): map overflows when switching map type in ribbon 2025-12-05 11:21:33 +08:00
Lucas
f976dd8d30 Apply suggestions from code review
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-04 13:08:28 -08:00
Lucas
2d3aa3a96e Merge branch 'main' into main 2025-12-04 13:04:24 -08:00
Elian Doran
1195cbd772 Translations update from Hosted Weblate (#7937) 2025-12-04 22:59:00 +02:00
Hosted Weblate
cebfa674ef Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/
2025-12-04 21:56:39 +01:00
Elian Doran
3ed596496d Merge branch 'main' of github.com:TriliumNext/Trilium 2025-12-04 22:56:20 +02:00
Elian Doran
d99ef78348 fix(client): find and replace in floating toolbar 2025-12-04 22:56:15 +02:00
lzinga
2666c1e196 feat(snapdom): update screenshot generation options for SVG and PNG exports 2025-12-04 12:52:01 -08:00
Lucas
4b8c8888ee Merge branch 'TriliumNext:main' into main 2025-12-04 12:49:37 -08:00
lzinga
ce1fd64aa9 feat(export): enhance SVG and PNG export functionality with snapdom integration 2025-12-04 12:49:10 -08:00
Elian Doran
bd1479b14a Translations update from Hosted Weblate (#7935) 2025-12-04 22:32:47 +02:00
Elian Doran
baee9520d1 Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-04 22:32:08 +02:00
green
adb30a526e Translated using Weblate (Japanese)
Currently translated at 100.0% (1636 of 1636 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-04 21:27:44 +01:00
noobhjy
60c40457fc Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1636 of 1636 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/
2025-12-04 21:27:43 +01:00
noobhjy
a6df457c9c Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1636 of 1636 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-04 21:27:42 +01:00
Francis C.
3fd8fb0308 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1636 of 1636 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-04 21:27:41 +01:00
Elian Doran
7d4a7d4ab6 feat(dev): option to crash CKEditor 2025-12-04 22:15:13 +02:00
Elian Doran
4571b95683 chore(react/launch_bar): fix style for month selector 2025-12-04 21:37:02 +02:00
Elian Doran
a65d2a1bba chore(react/launch_bar): reintroduce year selector 2025-12-04 21:24:35 +02:00
lzinga
5c9503732d fix(mind-map): show text in links between nodes on export 2025-12-04 11:08:44 -08:00
Elian Doran
2dbbf7f350 chore(react/launch_bar): reintroduce month dropdown 2025-12-04 20:13:46 +02:00
Elian Doran
e1cce220b3 chore(react/launch_bar): add back icons for previous/next month 2025-12-04 19:56:44 +02:00
Elian Doran
e0aed26f63 refactor(react/launch_bar): extract calendar impl into single file 2025-12-04 19:46:25 +02:00
Elian Doran
62fd07258e chore(react/launch_bar): get days of the week to render 2025-12-04 19:43:47 +02:00
Elian Doran
0d8127140f chore(react/launch_bar): get week numbers to render 2025-12-04 19:38:56 +02:00
Elian Doran
bab5326d7c refactor(react/launch_bar): use different mechanism for gathering calendar info 2025-12-04 19:22:28 +02:00
Elian Doran
5c8445f3fe chore(react/launch_bar): get calendar to render days 2025-12-04 19:03:28 +02:00
Elian Doran
604488b166 chore(react/launch_bar): port custom widget 2025-12-04 17:55:38 +02:00
Elian Doran
b7d7fc8b67 chore(react/launch_bar): remove an old file 2025-12-04 17:17:41 +02:00
Elian Doran
1f4872f72b chore(react/launch_bar): port script launcher 2025-12-04 17:16:47 +02:00
Elian Doran
f89c40cde6 fix(react/launch_bar): type error in bookmarks 2025-12-04 17:07:13 +02:00
Elian Doran
670edbc22a chore(react/launch_bar): port quick search launcher 2025-12-04 17:01:35 +02:00
Elian Doran
54f70c8158 chore(react/launch_bar): port today launcher 2025-12-04 16:53:05 +02:00
Elian Doran
0d6bcba023 chore(react/launch_bar): port note launcher 2025-12-04 16:33:58 +02:00
Elian Doran
06a9f95979 chore(react/launch_bar): remove old file 2025-12-04 16:23:14 +02:00
Elian Doran
03cdfc259e refactor(react/launch_bar): deduplicate launcher icon and title 2025-12-04 16:22:46 +02:00
Elian Doran
1963b5732a refactor(react/launch_bar): extract note launcher from bookmark button 2025-12-04 16:13:16 +02:00
Elian Doran
1e05dc937c refactor(react/launch_bar): port command buttons 2025-12-04 16:04:14 +02:00
Elian Doran
c3b22ff737 refactor(react/launch_bar): port protected session status widget 2025-12-04 15:54:27 +02:00
Elian Doran
991f07e148 refactor(react/launch_bar): port ai chat button 2025-12-04 15:47:42 +02:00
Elian Doran
8efb849391 refactor(react/launch_bar): port history navigation 2025-12-04 15:44:10 +02:00
Elian Doran
5b310f3e46 refactor(react/launch_bar): port spacer 2025-12-04 15:02:46 +02:00
Elian Doran
5f54e42a43 refactor(react/launch_bar): extract icon class 2025-12-04 14:47:30 +02:00
Elian Doran
a83f20e454 refactor(react/launch_bar): extract dropdown & button style 2025-12-04 14:46:23 +02:00
Elian Doran
cdab86bd83 chore(react/launch_bar): port bookmark_folder 2025-12-04 14:41:07 +02:00
Elian Doran
48cbb80e79 chore(react/launch_bar): port open_note_button_widget 2025-12-04 14:18:04 +02:00
Elian Doran
1af6200655 chore(react/launch_bar): get launch bar to render React widgets 2025-12-04 13:24:47 +02:00
Elian Doran
b8585594cd fix(text): duplicate search dialogs (closes #5735) 2025-12-04 13:03:08 +02:00
meinzzzz
162c076a14 Improve MathLive integration and lazy loading 2025-12-02 22:30:37 +01:00
meinzzzz
9386465de7 Added mathrender error class for better error handling in math rendering 2025-12-02 22:29:20 +01:00
meinzzzz
acca22f3a1 Improve Synchronization Between Mathlive and rawlatex input 2025-12-02 22:28:16 +01:00
meinzzzz
f8d84814e0 Fix differential d problems 2025-11-26 23:02:34 +01:00
meinzzzz
c46cf41842 Small improvements 2025-11-26 22:48:57 +01:00
meinzzzz
64ab1c4116 Imrovement for Latex 2025-11-26 22:29:29 +01:00
meinzzzz
a6de1041c7 Fix bug in math rendering where old content was not cleared 2025-11-26 21:59:33 +01:00
meinzzzz
c8d34e65ea Improve max window size 2025-11-26 21:49:09 +01:00
meinzzzz
51db729546 Improve and simplify Mathfield integration 2025-11-25 23:27:06 +01:00
meinzzzz
d2052ad236 Disable mathlive sound effects 2025-11-24 21:51:59 +01:00
meinzzzz
9c4301467f Remove unused icons from ckeditor5-math package 2025-11-24 19:46:04 +01:00
meinzzzz
e7355dc0e4 remove gitignore unneccesary changes 2025-11-24 18:43:52 +01:00
meinzzzz
4110fec94f Removed unnecessary declare keyboard 2025-11-24 18:28:59 +01:00
meinzzzz
d5e601eae9 Simpliyfied resize logic for math input form and improved css 2025-11-24 17:56:18 +01:00
meinzzzz
4f044c4a57 Use icons form CKEditor5 icons, instead of testing icons. 2025-11-23 22:43:07 +01:00
meinzzzz
5821c350e1 Fixing class property initialization order 2025-11-23 17:58:51 +01:00
meinzzzz
edba8188fe Fix dark selection colors in MathLive math-field 2025-11-23 13:44:28 +01:00
meinzzzz
1471a72633 refactor: avoid recursive updates in mathLiveInput by normalizing value before updateing 2025-11-23 13:34:22 +01:00
meinzzzz
56834cb88a Improve MathLive and Raw LaTeX input views to propagate mousedown events 2025-11-23 13:29:26 +01:00
meinzzzz
a0f16f9184 Fix typos in mathform.css 2025-11-23 13:09:56 +01:00
meinzzzz
de80eb4806 Improve mathform.css styling for better visual integration 2025-11-22 22:42:34 +01:00
meinzzzz
48a4b81fbe remove automated screenshot files 2025-11-22 21:40:55 +01:00
meinzzzz
e225794f72 Better window focus handling in MathFormView 2025-11-22 21:35:37 +01:00
meinzzzz
4eef30f8b5 Fix names 2025-11-22 00:20:20 +01:00
meinzzzz
569b09609d Remove mathlive dependency and chunking 2025-11-22 00:01:14 +01:00
meinzzzz
39838c25c2 Fixed chaching problems 2025-11-21 23:50:49 +01:00
meinzzzz
49e90c08a9 Better Names for Math UI Components 2025-11-20 22:45:21 +01:00
meinzzzz
e777b06fb8 Math 2025-11-20 18:53:39 +01:00
meinzzzz
497ec2ac74 Merge branch 'main' of https://github.com/Meinzzzz/Trilium-Mathlive 2025-11-20 18:00:18 +01:00
meinzzzz
c5d282d203 Mathlive 2025-11-20 00:09:10 +01:00
1270 changed files with 190031 additions and 35831 deletions

View File

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

3
.envrc Normal file
View File

@@ -0,0 +1,3 @@
if has nix; then
use flake
fi

View File

@@ -21,7 +21,7 @@ runs:
# Certificate setup
- name: Import Apple certificates
if: inputs.os == 'macos'
uses: apple-actions/import-codesign-certs@v5
uses: apple-actions/import-codesign-certs@v6
with:
p12-file-base64: ${{ env.APPLE_APP_CERTIFICATE_BASE64 }}
p12-password: ${{ env.APPLE_APP_CERTIFICATE_PASSWORD }}
@@ -30,7 +30,7 @@ runs:
- name: Install Installer certificate
if: inputs.os == 'macos'
uses: apple-actions/import-codesign-certs@v5
uses: apple-actions/import-codesign-certs@v6
with:
p12-file-base64: ${{ env.APPLE_INSTALLER_CERTIFICATE_BASE64 }}
p12-password: ${{ env.APPLE_INSTALLER_CERTIFICATE_PASSWORD }}

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

@@ -0,0 +1,65 @@
name: Deploy Standalone App
on:
# Trigger on push to main branch
push:
branches:
- main
# Only run when docs files change
paths:
- 'apps/client/**'
- 'apps/client-standalone/**'
- 'packages/trilium-core/**'
# 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/**'
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

@@ -86,12 +86,12 @@ jobs:
- name: Upload Playwright trace
if: failure()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: Playwright trace (${{ matrix.dockerfile }})
path: test-output/playwright/output
- uses: actions/upload-artifact@v5
- uses: actions/upload-artifact@v6
if: ${{ !cancelled() }}
with:
name: Playwright report (${{ matrix.dockerfile }})
@@ -213,7 +213,7 @@ jobs:
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ matrix.dockerfile }}
path: /tmp/digests/*
@@ -227,7 +227,7 @@ jobs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
path: /tmp/digests
pattern: digests-*

View File

@@ -90,7 +90,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Publish release
uses: softprops/action-gh-release@v2.4.2
uses: softprops/action-gh-release@v2.5.0
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false
@@ -102,7 +102,7 @@ jobs:
name: Nightly Build
- name: Publish artifacts
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
if: ${{ github.event_name == 'pull_request' }}
with:
name: TriliumNotes ${{ matrix.os.name }} ${{ matrix.arch }}
@@ -131,7 +131,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Publish release
uses: softprops/action-gh-release@v2.4.2
uses: softprops/action-gh-release@v2.5.0
if: ${{ github.event_name != 'pull_request' }}
with:
make_latest: false

View File

@@ -77,7 +77,7 @@ jobs:
- name: Upload test report
if: failure()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: e2e report ${{ matrix.arch }}
path: apps/server-e2e/test-output

View File

@@ -73,7 +73,7 @@ jobs:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGN_KEY }}
- name: Upload the artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: release-desktop-${{ matrix.os.name }}-${{ matrix.arch }}
path: apps/desktop/upload/*.*
@@ -100,7 +100,7 @@ jobs:
arch: ${{ matrix.arch }}
- name: Upload the artifact
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v6
with:
name: release-server-linux-${{ matrix.arch }}
path: upload/*.*
@@ -120,14 +120,14 @@ jobs:
docs/Release Notes
- name: Download all artifacts
uses: actions/download-artifact@v6
uses: actions/download-artifact@v7
with:
merge-multiple: true
pattern: release-*
path: upload
- name: Publish stable release
uses: softprops/action-gh-release@v2.4.2
uses: softprops/action-gh-release@v2.5.0
with:
draft: false
body_path: docs/Release Notes/Release Notes/${{ github.ref_name }}.md

3
.gitignore vendored
View File

@@ -44,8 +44,11 @@ upload
.rollup.cache
*.tsbuildinfo
/.direnv
/result
.svelte-kit
# docs
site/
apps/*/coverage
scripts/translation/.language*.json

2
.nvmrc
View File

@@ -1 +1 @@
24.11.1
24.12.0

View File

@@ -9,7 +9,6 @@
"tobermory.es6-string-html",
"vitest.explorer",
"yzhang.markdown-all-in-one",
"svelte.svelte-vscode",
"bradlc.vscode-tailwindcss"
"usernamehw.errorlens"
]
}

View File

@@ -36,5 +36,11 @@
"docs/**/*.png": true,
"apps/server/src/assets/doc_notes/**": true,
"apps/edit-docs/demo/**": true
}
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.rules.customizations": [
{ "rule": "*", "severity": "warn" }
]
}

View File

@@ -16,13 +16,14 @@
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/trilium/total)
[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [![Translation status](https://hosted.weblate.org/widget/trilium/svg-badge.svg)](https://hosted.weblate.org/engage/trilium/)
[English](./README.md) | [Chinese (Simplified)](./docs/README-ZH_CN.md) | [Chinese (Traditional)](./docs/README-ZH_TW.md) | [Russian](./docs/README-ru.md) | [Japanese](./docs/README-ja.md) | [Italian](./docs/README-it.md) | [Spanish](./docs/README-es.md)
<!-- translate:off -->
<!-- LANGUAGE SWITCHER -->
[Chinese (Simplified Han script)](./docs/README-ZH_CN.md) | [Chinese (Traditional Han script)](./docs/README-ZH_TW.md) | [English](./docs/README.md) | [French](./docs/README-fr.md) | [German](./docs/README-de.md) | [Greek](./docs/README-el.md) | [Italian](./docs/README-it.md) | [Japanese](./docs/README-ja.md) | [Romanian](./docs/README-ro.md) | [Spanish](./docs/README-es.md)
<!-- translate:on -->
Trilium Notes is a free and open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
See [screenshots](https://triliumnext.github.io/Docs/Wiki/screenshot-tour) for quick overview:
<a href="https://triliumnext.github.io/Docs/Wiki/screenshot-tour"><img src="./docs/app.png" alt="Trilium Screenshot" width="1000"></a>
<img src="./docs/app.png" alt="Trilium Screenshot" width="1000">
## ⏬ Download
- [Latest release](https://github.com/TriliumNext/Trilium/releases/latest) stable version, recommended for most users.
@@ -39,39 +40,39 @@ Our documentation is available in multiple formats:
### Quick Links
- [Getting Started Guide](https://docs.triliumnotes.org/)
- [Installation Instructions](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation.md)
- [Docker Setup](./docs/User%20Guide/User%20Guide/Installation%20&%20Setup/Server%20Installation/1.%20Installing%20the%20server/Using%20Docker.md)
- [Upgrading TriliumNext](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Upgrading%20TriliumNext.md)
- [Basic Concepts and Features](./docs/User%20Guide/User%20Guide/Basic%20Concepts%20and%20Features/Notes.md)
- [Patterns of Personal Knowledge Base](https://triliumnext.github.io/Docs/Wiki/patterns-of-personal-knowledge)
- [Installation Instructions](https://docs.triliumnotes.org/user-guide/setup)
- [Docker Setup](https://docs.triliumnotes.org/user-guide/setup/server/installation/docker)
- [Upgrading TriliumNext](https://docs.triliumnotes.org/user-guide/setup/upgrading)
- [Basic Concepts and Features](https://docs.triliumnotes.org/user-guide/concepts/notes)
- [Patterns of Personal Knowledge Base](https://docs.triliumnotes.org/user-guide/misc/patterns-of-personal-knowledge)
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/cloning-notes))
* Rich WYSIWYG note editor including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/text-notes#autoformat)
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/code-notes), including syntax highlighting
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/note-hoisting)
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/note-revisions)
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/scripts)
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://docs.triliumnotes.org/user-guide/concepts/notes/cloning))
* Rich WYSIWYG note editor including e.g. tables, images and [math](https://docs.triliumnotes.org/user-guide/note-types/text) with markdown [autoformat](https://docs.triliumnotes.org/user-guide/note-types/text/markdown-formatting)
* Support for editing [notes with source code](https://docs.triliumnotes.org/user-guide/note-types/code), including syntax highlighting
* Fast and easy [navigation between notes](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-navigation), full text search and [note hoisting](https://docs.triliumnotes.org/user-guide/concepts/navigation/note-hoisting)
* Seamless [note versioning](https://docs.triliumnotes.org/user-guide/concepts/notes/note-revisions)
* Note [attributes](https://docs.triliumnotes.org/user-guide/advanced-usage/attributes) can be used for note organization, querying and advanced [scripting](https://docs.triliumnotes.org/user-guide/scripts)
* UI available in English, German, Spanish, French, Romanian, and Chinese (simplified and traditional)
* Direct [OpenID and TOTP integration](./docs/User%20Guide/User%20Guide/Installation%20%26%20Setup/Server%20Installation/Multi-Factor%20Authentication.md) for more secure login
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/synchronization) with self-hosted sync server
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
* [Sharing](https://triliumnext.github.io/Docs/Wiki/sharing) (publishing) notes to public internet
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/protected-notes) with per-note granularity
* Direct [OpenID and TOTP integration](https://docs.triliumnotes.org/user-guide/setup/server/mfa) for more secure login
* [Synchronization](https://docs.triliumnotes.org/user-guide/setup/synchronization) with self-hosted sync server
* there are [3rd party services for hosting synchronisation server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
* [Sharing](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing) (publishing) notes to public internet
* Strong [note encryption](https://docs.triliumnotes.org/user-guide/concepts/notes/protected-notes) with per-note granularity
* Sketching diagrams, based on [Excalidraw](https://excalidraw.com/) (note type "canvas")
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/link-map) for visualizing notes and their relations
* [Relation maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and [note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map) for visualizing notes and their relations
* Mind maps, based on [Mind Elixir](https://docs.mind-elixir.com/)
* [Geo maps](./docs/User%20Guide/User%20Guide/Note%20Types/Geo%20Map.md) with location pins and GPX tracks
* [Scripting](https://triliumnext.github.io/Docs/Wiki/scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/advanced-showcases)
* [REST API](https://triliumnext.github.io/Docs/Wiki/etapi) for automation
* [Geo maps](https://docs.triliumnotes.org/user-guide/collections/geomap) with location pins and GPX tracks
* [Scripting](https://docs.triliumnotes.org/user-guide/scripts) - see [Advanced showcases](https://docs.triliumnotes.org/user-guide/advanced-usage/advanced-showcases)
* [REST API](https://docs.triliumnotes.org/user-guide/advanced-usage/etapi) for automation
* Scales well in both usability and performance upwards of 100 000 notes
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/mobile-frontend) for smartphones and tablets
* Built-in [dark theme](https://triliumnext.github.io/Docs/Wiki/themes), support for user themes
* [Evernote](https://triliumnext.github.io/Docs/Wiki/evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/markdown)
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/web-clipper) for easy saving of web content
* Touch optimized [mobile frontend](https://docs.triliumnotes.org/user-guide/setup/mobile-frontend) for smartphones and tablets
* Built-in [dark theme](https://docs.triliumnotes.org/user-guide/concepts/themes), support for user themes
* [Evernote](https://docs.triliumnotes.org/user-guide/concepts/import-export/evernote) and [Markdown import & export](https://docs.triliumnotes.org/user-guide/concepts/import-export/markdown)
* [Web Clipper](https://docs.triliumnotes.org/user-guide/setup/web-clipper) for easy saving of web content
* Customizable UI (sidebar buttons, user-defined widgets, ...)
* [Metrics](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics.md), along with a [Grafana Dashboard](./docs/User%20Guide/User%20Guide/Advanced%20Usage/Metrics/grafana-dashboard.json)
* [Metrics](https://docs.triliumnotes.org/user-guide/advanced-usage/metrics), along with a Grafana Dashboard.
✨ Check out the following third-party resources/communities for more TriliumNext related goodies:
@@ -131,7 +132,7 @@ Note: It is best to disable automatic updates on your server installation (see b
### Server
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server installation docs](https://triliumnext.github.io/Docs/Wiki/server-installation).
To install TriliumNext on your own server (including via Docker from [Dockerhub](https://hub.docker.com/r/triliumnext/trilium)) follow [the server installation docs](https://docs.triliumnotes.org/user-guide/setup/server).
## 💻 Contribute
@@ -198,7 +199,7 @@ Trilium would not be possible without the technologies behind it:
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical maps.
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive table used in collections.
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library without real competition.
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/relation-map.html) and [link maps](https://triliumnext.github.io/Docs/Wiki/note-map.html#link-map)
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library. Used in [relation maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and [link maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
## 🤝 Support

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env node
import anonymizationService from "../src/services/anonymization.js";
import fs from "fs";
import path from "path";
fs.writeFileSync(path.resolve(__dirname, "tpl", "anonymize-database.sql"), anonymizationService.getFullAnonymizationScript());

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
SCHEMA_FILE_PATH=db/schema.sql
sqlite3 ./data/document.db .schema | grep -v "sqlite_sequence" > "$SCHEMA_FILE_PATH"
echo "DB schema exported to $SCHEMA_FILE_PATH"

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
VERSION=$1
SERIES=${VERSION:0:4}-latest
docker push zadam/trilium:$VERSION
docker push zadam/trilium:$SERIES
if [[ $1 != *"beta"* ]]; then
docker push zadam/trilium:latest
fi

View File

@@ -1,57 +0,0 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
VERSION=$1
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
then
echo "Version ${VERSION} isn't in format X.Y.Z"
exit 1
fi
VERSION_DATE=$(git log -1 --format=%aI "v${VERSION}" | cut -c -10)
VERSION_COMMIT=$(git rev-list -n 1 "v${VERSION}")
# expecting the directory at a specific path
cd ~/trilium-flathub || exit
if ! git diff-index --quiet HEAD --; then
echo "There are uncommitted changes"
exit 1
fi
BASE_BRANCH=main
if [[ "$VERSION" == *"beta"* ]]; then
BASE_BRANCH=beta
fi
git switch "${BASE_BRANCH}"
git pull
BRANCH=b${VERSION}
git branch "${BRANCH}"
git switch "${BRANCH}"
echo "Updating files with version ${VERSION}, date ${VERSION_DATE} and commit ${VERSION_COMMIT}"
flatpak-node-generator npm ../trilium/package-lock.json
xmlstarlet ed --inplace --update "/component/releases/release/@version" --value "${VERSION}" --update "/component/releases/release/@date" --value "${VERSION_DATE}" ./com.github.zadam.trilium.metainfo.xml
yq --inplace "(.modules[0].sources[0].tag = \"v${VERSION}\") | (.modules[0].sources[0].commit = \"${VERSION_COMMIT}\")" ./com.github.zadam.trilium.yml
git add ./generated-sources.json
git add ./com.github.zadam.trilium.metainfo.xml
git add ./com.github.zadam.trilium.yml
git commit -m "release $VERSION"
git push --set-upstream origin "${BRANCH}"
gh pr create --fill -B "${BASE_BRANCH}"
gh pr merge --auto --merge --delete-branch

View File

@@ -1,49 +0,0 @@
#!/usr/bin/env bash
set -e
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
if ! command -v jq &> /dev/null; then
echo "Missing command: jq"
exit 1
fi
VERSION=$1
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
then
echo "Version ${VERSION} isn't in format X.Y.Z"
exit 1
fi
if ! git diff-index --quiet HEAD --; then
echo "There are uncommitted changes"
exit 1
fi
echo "Releasing Trilium $VERSION"
jq '.version = "'$VERSION'"' package.json > package.json.tmp
mv package.json.tmp package.json
git add package.json
npm run chore:update-build-info
git add src/services/build.ts
TAG=v$VERSION
echo "Committing package.json version change"
git commit -m "chore(release): $VERSION"
git push
echo "Tagging commit with $TAG"
git tag $TAG
git push origin $TAG

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>

View File

@@ -1,51 +0,0 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import simpleImportSort from "eslint-plugin-simple-import-sort";
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
// consider using rules below, once we have a full TS codebase and can be more strict
// tseslint.configs.strictTypeChecked,
// tseslint.configs.stylisticTypeChecked,
// tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname
}
}
},
{
plugins: {
"simple-import-sort": simpleImportSort
}
},
{
rules: {
// add rule overrides here
"no-undef": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error"
}
},
{
ignores: [
"build/*",
"dist/*",
"docs/*",
"demo/*",
"src/public/app-dist/*",
"src/public/app/doc_notes/*"
]
}
);

View File

@@ -1,47 +0,0 @@
import stylistic from "@stylistic/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
// eslint config just for formatting rules
// potentially to be merged with the linting rules into one single config,
// once we have fixed the majority of lint errors
// Go to https://eslint.style/rules/default/${rule_without_prefix} to check the rule details
export const stylisticRules = {
"@stylistic/indent": [ "error", 4 ],
"@stylistic/quotes": [ "error", "double", { avoidEscape: true, allowTemplateLiterals: "always" } ],
"@stylistic/semi": [ "error", "always" ],
"@stylistic/quote-props": [ "error", "consistent-as-needed" ],
"@stylistic/max-len": [ "error", { code: 100 } ],
"@stylistic/comma-dangle": [ "error", "never" ],
"@stylistic/linebreak-style": [ "error", "unix" ],
"@stylistic/array-bracket-spacing": [ "error", "always" ],
"@stylistic/object-curly-spacing": [ "error", "always" ],
"@stylistic/padded-blocks": [ "error", { classes: "always" } ]
};
export default [
{
files: [ "**/*.{js,ts,mjs,cjs}" ],
languageOptions: {
parser: tsParser
},
plugins: {
"@stylistic": stylistic
},
rules: {
...stylisticRules
}
},
{
ignores: [
"build/*",
"dist/*",
"docs/*",
"demo/*",
// TriliumNextTODO: check if we want to format packages here as well - for now skipping it
"packages/*",
"src/public/app-dist/*",
"src/public/app/doc_notes/*"
]
}
];

View File

@@ -1,17 +0,0 @@
import { test as setup, expect } from "@playwright/test";
const authFile = "playwright/.auth/user.json";
const ROOT_URL = "http://localhost:8082";
const LOGIN_PASSWORD = "demo1234";
// Reference: https://playwright.dev/docs/auth#basic-shared-account-in-all-tests
setup("authenticate", async ({ page }) => {
await page.goto(ROOT_URL);
await expect(page).toHaveURL(`${ROOT_URL}/login`);
await page.getByRole("textbox", { name: "Password" }).fill(LOGIN_PASSWORD);
await page.getByRole("button", { name: "Login" }).click();
await page.context().storageState({ path: authFile });
});

View File

@@ -1,9 +0,0 @@
import { test, expect } from "@playwright/test";
test("Can duplicate note with broken links", async ({ page }) => {
await page.goto(`http://localhost:8082/#2VammGGdG6Ie`);
await page.locator(".tree-wrapper .fancytree-active").getByText("Note map").click({ button: "right" });
await page.getByText("Duplicate subtree").click();
await expect(page.locator(".toast-body")).toBeHidden();
await expect(page.locator(".tree-wrapper").getByText("Note map (dup)")).toBeVisible();
});

View File

@@ -1,18 +0,0 @@
import { test, expect } from "@playwright/test";
test("has title", async ({ page }) => {
await page.goto("https://playwright.dev/");
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});
test("get started link", async ({ page }) => {
await page.goto("https://playwright.dev/");
// Click the get started link.
await page.getByRole("link", { name: "Get started" }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole("heading", { name: "Installation" })).toBeVisible();
});

View File

@@ -1,21 +0,0 @@
import test, { expect } from "@playwright/test";
test("Native Title Bar not displayed on web", async ({ page }) => {
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsAppearance");
await expect(page.getByRole("heading", { name: "Theme" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Native Title Bar (requires" })).toBeHidden();
});
test("Tray settings not displayed on web", async ({ page }) => {
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsOther");
await expect(page.getByRole("heading", { name: "Note Erasure Timeout" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
});
test("Spellcheck settings not displayed on web", async ({ page }) => {
await page.goto("http://localhost:8082/#root/_hidden/_options/_optionsSpellcheck");
await expect(page.getByRole("heading", { name: "Spell Check" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Tray" })).toBeHidden();
await expect(page.getByText("These options apply only for desktop builds")).toBeVisible();
await expect(page.getByText("Enable spellcheck")).toBeHidden();
});

View File

@@ -1,18 +0,0 @@
import test, { expect } from "@playwright/test";
test("Renders on desktop", async ({ page, context }) => {
await page.goto("http://localhost:8082");
await expect(page.locator(".tree")).toContainText("Trilium Integration Test");
});
test("Renders on mobile", async ({ page, context }) => {
await context.addCookies([
{
url: "http://localhost:8082",
name: "trilium-device",
value: "mobile"
}
]);
await page.goto("http://localhost:8082");
await expect(page.locator(".tree")).toContainText("Trilium Integration Test");
});

View File

@@ -1,12 +0,0 @@
import { test, expect } from "@playwright/test";
const expectedVersion = "0.90.3";
test("Displays update badge when there is a version available", async ({ page }) => {
await page.goto("http://localhost:8080");
await page.getByRole("button", { name: "" }).click();
await page.getByText(`Version ${expectedVersion} is available,`).click();
const page1 = await page.waitForEvent("popup");
expect(page1.url()).toBe(`https://github.com/TriliumNext/Trilium/releases/tag/v${expectedVersion}`);
});

View File

@@ -1,56 +0,0 @@
{
"main": "./electron-main.js",
"bin": {
"trilium": "src/main.js"
},
"type": "module",
"scripts": {
"server:start-safe": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nodemon src/main.ts",
"server:start-no-dir": "cross-env TRILIUM_ENV=dev nodemon src/main.ts",
"server:start-test": "npm run server:switch && rimraf ./data-test && cross-env TRILIUM_DATA_DIR=./data-test TRILIUM_ENV=dev TRILIUM_PORT=9999 nodemon src/main.ts",
"server:qstart": "npm run server:switch && npm run server:start",
"server:switch": "rimraf ./node_modules/better-sqlite3 && npm install",
"electron:start-no-dir": "cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 electron --inspect=5858 .",
"electron:start-nix": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
"electron:start-nix-no-dir": "electron-rebuild --version 33.3.1 && cross-env NODE_OPTIONS=\"--import tsx\" TRILIUM_ENV=dev TRILIUM_PORT=37742 nix-shell -p electron_33 --run \"electron ./electron-main.ts --inspect=5858 .\"",
"electron:start-prod-no-dir": "npm run build:prepare-dist && cross-env TRILIUM_ENV=prod electron --inspect=5858 .",
"electron:start-prod-nix": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:start-prod-nix-no-dir": "electron-rebuild --version 33.3.1 && npm run build:prepare-dist && cross-env TRILIUM_ENV=dev nix-shell -p electron_33 --run \"electron ./dist/electron-main.js --inspect=5858 .\"",
"electron:qstart": "npm run electron:switch && npm run electron:start",
"electron:switch": "electron-rebuild",
"docs:build": "typedoc",
"test": "npm run client:test && npm run server:test",
"client:test": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app",
"client:coverage": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db TRILIUM_INTEGRATION_TEST=memory vitest --root src/public/app --coverage",
"test:playwright": "playwright test --workers 1",
"test:integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"test:integration-mem-db": "cross-env nodemon src/main.ts",
"test:integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"dev:watch-dist": "tsx ./bin/watch-dist.ts",
"dev:format-check": "eslint -c eslint.format.config.js .",
"dev:format-fix": "eslint -c eslint.format.config.js . --fix",
"dev:linter-check": "eslint .",
"dev:linter-fix": "eslint . --fix",
"chore:generate-document": "cross-env nodemon ./bin/generate_document.ts 1000",
"chore:generate-openapi": "tsx bin/generate-openapi.js"
},
"devDependencies": {
"@playwright/test": "1.57.0",
"@stylistic/eslint-plugin": "5.6.1",
"@types/express": "5.0.5",
"@types/node": "24.10.1",
"@types/yargs": "17.0.35",
"@vitest/coverage-v8": "4.0.14",
"eslint": "9.39.1",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.5",
"lorem-ipsum": "2.0.8",
"rcedit": "5.0.2",
"rimraf": "6.1.2",
"tslib": "2.8.1"
},
"optionalDependencies": {
"appdmg": "0.6.6"
}
}

View File

@@ -1,9 +0,0 @@
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("app_info", () => {
it("get", async () => {
const appInfo = await etapi.getEtapi("app-info");
expect(appInfo.clipperProtocolVersion).toEqual("1.0");
});
});
*/

View File

@@ -1,10 +0,0 @@
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("backup", () => {
it("create", async () => {
const response = await etapi.putEtapiContent("backup/etapi_test");
expect(response.status).toEqual(204);
});
});
*/

View File

@@ -1,26 +0,0 @@
import etapi from "../support/etapi.js";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("import", () => {
// temporarily skip this test since test-export.zip is missing
xit("import", async () => {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const zipFileBuffer = fs.readFileSync(path.resolve(scriptDir, "test-export.zip"));
const response = await etapi.postEtapiContent("notes/root/import", zipFileBuffer);
expect(response.status).toEqual(201);
const { note, branch } = await response.json();
expect(note.title).toEqual("test-export");
expect(branch.parentNoteId).toEqual("root");
const content = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
expect(content).toContain("test export content");
});
});
*/

View File

@@ -1,103 +0,0 @@
import crypto from "crypto";
import etapi from "../support/etapi.js";
/* TriliumNextTODO: port to Vitest
etapi.describeEtapi("notes", () => {
it("create", async () => {
const { note, branch } = await etapi.postEtapi("create-note", {
parentNoteId: "root",
type: "text",
title: "Hello World!",
content: "Content",
prefix: "Custom prefix"
});
expect(note.title).toEqual("Hello World!");
expect(branch.parentNoteId).toEqual("root");
expect(branch.prefix).toEqual("Custom prefix");
const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
expect(rNote.title).toEqual("Hello World!");
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
expect(rContent).toEqual("Content");
const rBranch = await etapi.getEtapi(`branches/${branch.branchId}`);
expect(rBranch.parentNoteId).toEqual("root");
expect(rBranch.prefix).toEqual("Custom prefix");
});
it("patch", async () => {
const { note } = await etapi.postEtapi("create-note", {
parentNoteId: "root",
type: "text",
title: "Hello World!",
content: "Content"
});
await etapi.patchEtapi(`notes/${note.noteId}`, {
title: "new title",
type: "code",
mime: "text/apl",
dateCreated: "2000-01-01 12:34:56.999+0200",
utcDateCreated: "2000-01-01 10:34:56.999Z"
});
const rNote = await etapi.getEtapi(`notes/${note.noteId}`);
expect(rNote.title).toEqual("new title");
expect(rNote.type).toEqual("code");
expect(rNote.mime).toEqual("text/apl");
expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200");
expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z");
});
it("update content", async () => {
const { note } = await etapi.postEtapi("create-note", {
parentNoteId: "root",
type: "text",
title: "Hello World!",
content: "Content"
});
await etapi.putEtapiContent(`notes/${note.noteId}/content`, "new content");
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).text();
expect(rContent).toEqual("new content");
});
it("create / update binary content", async () => {
const { note } = await etapi.postEtapi("create-note", {
parentNoteId: "root",
type: "file",
title: "Hello World!",
content: "ZZZ"
});
const updatedContent = crypto.randomBytes(16);
await etapi.putEtapiContent(`notes/${note.noteId}/content`, updatedContent);
const rContent = await (await etapi.getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer();
expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent);
});
it("delete note", async () => {
const { note } = await etapi.postEtapi("create-note", {
parentNoteId: "root",
type: "text",
title: "Hello World!",
content: "Content"
});
await etapi.deleteEtapi(`notes/${note.noteId}`);
const resp = await etapi.getEtapiResponse(`notes/${note.noteId}`);
expect(resp.status).toEqual(404);
const error = await resp.json();
expect(error.status).toEqual(404);
expect(error.code).toEqual("NOTE_NOT_FOUND");
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
});
});
*/

View File

@@ -1,152 +0,0 @@
import { describe, beforeAll, afterAll } from "vitest";
let etapiAuthToken: string | undefined;
const getEtapiAuthorizationHeader = (): string => "Basic " + Buffer.from(`etapi:${etapiAuthToken}`).toString("base64");
const PORT: string = "9999";
const HOST: string = "http://localhost:" + PORT;
type SpecDefinitionsFunc = () => void;
function describeEtapi(description: string, specDefinitions: SpecDefinitionsFunc): void {
describe(description, () => {
beforeAll(async () => {});
afterAll(() => {});
specDefinitions();
});
}
async function getEtapiResponse(url: string): Promise<Response> {
return await fetch(`${HOST}/etapi/${url}`, {
method: "GET",
headers: {
Authorization: getEtapiAuthorizationHeader()
}
});
}
async function getEtapi(url: string): Promise<any> {
const response = await getEtapiResponse(url);
return await processEtapiResponse(response);
}
async function getEtapiContent(url: string): Promise<Response> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "GET",
headers: {
Authorization: getEtapiAuthorizationHeader()
}
});
checkStatus(response);
return response;
}
async function postEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: getEtapiAuthorizationHeader()
},
body: JSON.stringify(data)
});
return await processEtapiResponse(response);
}
async function postEtapiContent(url: string, data: BodyInit): Promise<Response> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
Authorization: getEtapiAuthorizationHeader()
},
body: data
});
checkStatus(response);
return response;
}
async function putEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: getEtapiAuthorizationHeader()
},
body: JSON.stringify(data)
});
return await processEtapiResponse(response);
}
async function putEtapiContent(url: string, data?: BodyInit): Promise<Response> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "PUT",
headers: {
"Content-Type": "application/octet-stream",
Authorization: getEtapiAuthorizationHeader()
},
body: data
});
checkStatus(response);
return response;
}
async function patchEtapi(url: string, data: Record<string, unknown> = {}): Promise<any> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: getEtapiAuthorizationHeader()
},
body: JSON.stringify(data)
});
return await processEtapiResponse(response);
}
async function deleteEtapi(url: string): Promise<any> {
const response = await fetch(`${HOST}/etapi/${url}`, {
method: "DELETE",
headers: {
Authorization: getEtapiAuthorizationHeader()
}
});
return await processEtapiResponse(response);
}
async function processEtapiResponse(response: Response): Promise<any> {
const text = await response.text();
if (response.status < 200 || response.status >= 300) {
throw new Error(`ETAPI error ${response.status}: ${text}`);
}
return text?.trim() ? JSON.parse(text) : null;
}
function checkStatus(response: Response): void {
if (response.status < 200 || response.status >= 300) {
throw new Error(`ETAPI error ${response.status}`);
}
}
export default {
describeEtapi,
getEtapi,
getEtapiResponse,
getEtapiContent,
postEtapi,
postEtapiContent,
putEtapi,
putEtapiContent,
patchEtapi,
deleteEtapi
};

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"module": "NodeNext",
"declaration": false,
"sourceMap": true,
"outDir": "./build",
"strict": true,
"noImplicitAny": true,
"resolveJsonModule": true,
"lib": ["ES2023"],
"downlevelIteration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowJs": true
},
"include": ["./src/public/app/**/*"],
"files": [
"./src/public/app/types.d.ts",
"./src/public/app/types-lib.d.ts",
"./src/types.d.ts"
]
}

View File

@@ -9,13 +9,13 @@
"keywords": [],
"author": "Elian Doran <contact@eliandoran.me>",
"license": "AGPL-3.0-only",
"packageManager": "pnpm@10.24.0",
"packageManager": "pnpm@10.27.0",
"devDependencies": {
"@redocly/cli": "2.12.0",
"@redocly/cli": "2.14.3",
"archiver": "7.0.1",
"fs-extra": "11.3.2",
"react": "19.2.0",
"react-dom": "19.2.0",
"fs-extra": "11.3.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"typedoc": "0.28.15",
"typedoc-plugin-missing-exports": "4.1.2"
}

View File

@@ -0,0 +1,86 @@
{
"name": "@triliumnext/client-standalone",
"version": "1.0.0",
"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.1",
"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

View File

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

View File

@@ -0,0 +1,35 @@
<!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><%= t("javascript-required") %></noscript>
<script>
// hide body to reduce flickering on the startup. This is done through JS and not CSS to not hide <noscript>
document.getElementsByTagName("body")[0].style.display = "none";
</script>
<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,92 @@
/**
* Browser route definitions.
* This integrates with the shared route builder from @triliumnext/core.
*/
import { routes, icon_packs as iconPackService } from '@triliumnext/core';
import { BrowserRouter, type BrowserRequest } 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) {
return (req: BrowserRequest) => {
// Create an Express-like request object
const expressLikeReq = {
params: req.params,
query: req.query,
body: req.body
};
return handler(expressLikeReq);
};
}
/**
* Creates an apiRoute function compatible with buildSharedApiRoutes.
* This bridges the core's route registration to the BrowserRouter.
*/
function createApiRoute(router: BrowserRouter) {
return (method: HttpMethod, path: string, handler: (req: any) => unknown) => {
router.register(method, path, wrapHandler(handler));
};
}
/**
* 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);
routes.buildSharedApiRoutes(apiRoute);
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/search/:searchString", () => []);
apiRoute("get", "/api/search-templates", () => []);
apiRoute("get", "/api/autocomplete", () => []);
}
function bootstrapRoute() {
const iconPacks = iconPackService.getIconPacks();
const assetPath = ".";
return {
triliumVersion: "1.2.3",
assetPath,
baseApiUrl: "../api/",
themeCssUrl: null,
themeUseNextAsBase: "next",
device: "desktop",
headingStyle: "default",
layoutOrientation: "vertical",
platform: "web",
isElectron: false,
isStandalone: true,
hasNativeTitleBar: false,
hasBackgroundEffects: true,
currentLocale: { id: "en", rtl: false },
iconPackCss: iconPacks
.map(p => iconPackService.generateCss(p, p.builtin
? `${assetPath}/fonts/${p.fontAttachmentId}.${iconPackService.MIME_TO_EXTENSION_MAPPINGS[p.fontMime]}`
: `api/attachments/download/${p.fontAttachmentId}`))
.filter(Boolean)
.join("\n\n"),
iconRegistry: iconPackService.generateIconRegistry(iconPacks),
};
}
/**
* 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,46 @@
import { ExecutionContext } from "@triliumnext/core";
export default class BrowserExecutionContext implements ExecutionContext {
private store: Map<string, any> | null = null;
get<T = any>(key: string): T | undefined {
return this.store?.get(key);
}
set(key: string, value: any): void {
if (!this.store) {
throw new Error("ExecutionContext not initialized");
}
this.store.set(key, value);
}
reset(): void {
this.store = null;
}
init<T>(callback: () => T): T {
// Create a fresh context for this request
const prev = this.store;
this.store = new Map();
try {
const result = callback();
// If the result is a Promise, we need to handle cleanup after it resolves
if (result && typeof result === 'object' && 'then' in result && 'catch' in result) {
const promise = result as unknown as Promise<any>;
return promise.finally(() => {
this.store = prev;
}) as T;
} else {
// Synchronous result, clean up immediately
this.store = prev;
return result;
}
} catch (error) {
// Always clean up on error (for synchronous errors)
this.store = prev;
throw error;
}
}
}

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,92 @@
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);
console.log("[WorkerMessagingProvider] Initialized");
}
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 = [];
console.log("[WorkerMessagingProvider] Disposed");
}
}

View File

@@ -0,0 +1,615 @@
import type { DatabaseProvider, RunResult, Statement, Transaction } from "@triliumnext/core";
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
import type { BindableValue } from "@sqlite.org/sqlite-wasm";
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
) {}
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();
this.stmt.reset();
return {
changes,
lastInsertRowid: 0 // Would need sqlite3_last_insert_rowid for this
};
} 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;
}
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 $)
// better-sqlite3 automatically maps unprefixed names to @name
// We need to add the @ prefix for compatibility
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 {
// Add @ prefix to match better-sqlite3 behavior
bindings[`@${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.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,89 @@
// public/local-bridge.js
let localWorker: Worker | null = null;
const pending = new Map();
export function startLocalServerWorker() {
if (localWorker) return localWorker;
localWorker = new Worker(new URL("./local-server-worker.js", import.meta.url), { type: "module" });
// Handle worker errors during initialization
localWorker.onerror = (event) => {
console.error("[LocalBridge] Worker error:", event);
// Reject all pending requests
for (const [id, 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 [id, 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,222 @@
// public/local-server-worker.js
// This will eventually import your core server and DB provider.
// import { createCoreServer } from "@trilium/core"; (bundled)
import BrowserExecutionContext from './lightweight/cls_provider';
import BrowserCryptoProvider from './lightweight/crypto_provider';
import BrowserSqlProvider from './lightweight/sql_provider';
import WorkerMessagingProvider from './lightweight/messaging_provider';
import { BrowserRouter } from './lightweight/browser_router';
import { createConfiguredRouter } from './lightweight/browser_routes';
// Global error handlers - MUST be set up before any async imports
self.onerror = (message, source, lineno, colno, error) => {
console.error("[Worker] Uncaught error:", message, source, lineno, colno, error);
// Try to notify the main thread about the error
try {
self.postMessage({
type: "WORKER_ERROR",
error: {
message: String(message),
source,
lineno,
colno,
stack: error?.stack
}
});
} catch (e) {
// Can't even post message, just log
console.error("[Worker] Failed to report error:", e);
}
return false; // Don't suppress the error
};
self.onunhandledrejection = (event) => {
console.error("[Worker] Unhandled rejection:", event.reason);
try {
self.postMessage({
type: "WORKER_ERROR",
error: {
message: String(event.reason?.message || event.reason),
stack: event.reason?.stack
}
});
} catch (e) {
console.error("[Worker] Failed to report rejection:", e);
}
};
console.log("[Worker] Error handlers installed");
// Shared SQL provider instance
const sqlProvider = new BrowserSqlProvider();
// Messaging provider for worker-to-main-thread communication
const messagingProvider = new WorkerMessagingProvider();
// 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;
/**
* 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 {
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");
coreModule.initializeCore({
executionContext: new BrowserExecutionContext(),
crypto: new BrowserCryptoProvider(),
messaging: messagingProvider,
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;
}
const encoder = new TextEncoder();
function jsonResponse(obj: unknown, status = 200, extraHeaders = {}) {
const body = encoder.encode(JSON.stringify(obj)).buffer;
return {
status,
headers: { "content-type": "application/json; charset=utf-8", ...extraHeaders },
body
};
}
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;
console.log("[Worker] Received LOCAL_REQUEST:", id, request.method, request.url);
try {
const response = await dispatch(request);
console.log("[Worker] Dispatch completed, sending response:", id);
// 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,189 @@
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
const registration = 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 fetchWithRetry(url: string, maxRetries = 3, delayMs = 500): Promise<Response> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
console.log(`[Bootstrap] Fetching ${url} (attempt ${attempt + 1}/${maxRetries})`);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Check if response has content
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
throw new Error(`Invalid content-type: ${contentType || "none"}`);
}
return response;
} catch (err) {
lastError = err as Error;
console.warn(`[Bootstrap] Fetch attempt ${attempt + 1} failed:`, err);
if (attempt < maxRetries - 1) {
// Exponential backoff
const delay = delayMs * Math.pow(2, attempt);
console.log(`[Bootstrap] Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw new Error(`Failed to fetch ${url} after ${maxRetries} attempts: ${lastError?.message}`);
}
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();
// 4) Now fetch bootstrap - SW is guaranteed to intercept this
await setupGlob();
loadStylesheets();
loadIcons();
setBodyAttributes();
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 setupGlob() {
const response = await fetchWithRetry("/bootstrap");
console.log("Service worker state", navigator.serviceWorker.controller);
console.log("Resp", response);
const json = await response.json();
console.log("Bootstrap", json);
window.glob = {
...json,
activeDialog: null
};
}
function loadStylesheets() {
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const cssToLoad = [];
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`)
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`)
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`)
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
for (const href of cssToLoad) {
const linkEl = document.createElement("link");
linkEl.href = href;
linkEl.rel = "stylesheet";
document.body.appendChild(linkEl);
}
}
function loadIcons() {
const styleEl = document.createElement("style");
styleEl.innerText = window.glob.iconPackCss;
document.head.appendChild(styleEl);
}
function setBodyAttributes() {
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
const classesToSet = [
device,
`heading-style-${headingStyle}`,
`layout-${layoutOrientation}`,
`platform-${platform}`,
isElectron && "isElectron",
hasNativeTitleBar && "native-titlebar",
hasBackgroundEffects && "background-effects"
].filter(Boolean);
for (const classToSet of classesToSet) {
document.body.classList.add(classToSet);
}
document.body.lang = currentLocale.id;
document.body.dir = currentLocale.rtl ? "rtl" : "ltr";
}
async function loadScripts() {
await import("./runtime.js");
await import("./desktop.js");
}
bootstrap();

View File

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

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,173 @@
import preact from "@preact/preset-vite";
import { defineConfig } from 'vite';
import { join } from 'path';
import { viteStaticCopy } from 'vite-plugin-static-copy';
const assets = ["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 = [
preact({
babel: {
compact: !isDev
}
}),
sqliteWasmPlugin, // Always include SQLite WASM files
viteStaticCopy({
targets: assets.map((asset) => ({
src: `../../client/src/${asset}/*`,
dest: asset
})),
// Enable watching in development
...(isDev && {
watch: {
reloadPageOnChange: true
}
})
}),
// Watch client files for changes in development
...(isDev ? [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 /
cacheDir: '../../../node_modules/.vite/apps/client-standalone',
base: "",
plugins,
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'),
'local-server-worker': join(__dirname, 'src', 'local-server-worker.ts')
},
output: {
entryFileNames: (chunkInfo) => {
// Service worker and other workers should be at root level
if (chunkInfo.name === 'sw' || chunkInfo.name === 'local-server-worker') {
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,5 +0,0 @@
import baseConfig from "../../eslint.config.mjs";
export default [
...baseConfig
];

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.100.0",
"version": "0.101.1",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -12,27 +12,29 @@
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
"test": "vitest",
"coverage": "vitest --coverage",
"circular-deps": "dpdm -T src/**/*.ts --tree=false --warning=false --skip-dynamic-imports=circular"
},
"dependencies": {
"@eslint/js": "9.39.1",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.19",
"@fullcalendar/daygrid": "6.1.19",
"@fullcalendar/interaction": "6.1.19",
"@fullcalendar/list": "6.1.19",
"@fullcalendar/multimonth": "6.1.19",
"@fullcalendar/timegrid": "6.1.19",
"@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",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
"@triliumnext/commons": "workspace:*",
"@triliumnext/highlightjs": "workspace:*",
"@triliumnext/share-theme": "workspace:*",
"@triliumnext/split.js": "workspace:*",
"@zumer/snapdom": "2.0.1",
"autocomplete.js": "0.38.1",
"bootstrap": "5.3.8",
"boxicons": "2.1.4",
@@ -41,24 +43,25 @@
"debounce": "3.0.0",
"draggabilly": "3.0.0",
"force-graph": "1.51.0",
"globals": "16.5.0",
"i18next": "25.6.3",
"globals": "17.0.0",
"i18next": "25.7.3",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery.fancytree": "2.38.5",
"jquery.fancytree": "2.38.5",
"jsplumb": "2.15.6",
"katex": "0.16.25",
"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.1",
"mind-elixir": "5.3.7",
"mermaid": "11.12.2",
"mind-elixir": "5.4.0",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.27.2",
"react-i18next": "16.3.5",
"preact": "10.28.1",
"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",
@@ -72,8 +75,8 @@
"@types/leaflet": "1.9.21",
"@types/leaflet-gpx": "1.3.8",
"@types/mark.js": "8.11.12",
"@types/reveal.js": "5.2.1",
"@types/tabulator-tables": "6.3.0",
"@types/reveal.js": "5.2.2",
"@types/tabulator-tables": "6.3.1",
"copy-webpack-plugin": "13.0.1",
"happy-dom": "20.0.11",
"script-loader": "0.7.2",

View File

@@ -1,39 +1,41 @@
import froca from "../services/froca.js";
import RootCommandExecutor from "./root_command_executor.js";
import Entrypoints from "./entrypoints.js";
import options from "../services/options.js";
import utils, { hasTouchBar } from "../services/utils.js";
import zoomComponent from "./zoom.js";
import TabManager from "./tab_manager.js";
import Component from "./component.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import linkService, { type ViewScope } from "../services/link.js";
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
import MainTreeExecutors from "./main_tree_executors.js";
import toast from "../services/toast.js";
import ShortcutComponent from "./shortcut_component.js";
import { t, initLocale } from "../services/i18n.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import type LoadResults from "../services/load_results.js";
import type { Attribute } from "../services/attribute_parser.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import type { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { StartupChecks } from "./startup_checks.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import { ColumnComponent } from "tabulator-tables";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type RootContainer from "../widgets/containers/root_container.js";
import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
import type { Attribute } from "../services/attribute_parser.js";
import froca from "../services/froca.js";
import { initLocale, t } from "../services/i18n.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import linkService, { type ViewScope } from "../services/link.js";
import type LoadResults from "../services/load_results.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import options from "../services/options.js";
import toast from "../services/toast.js";
import utils, { hasTouchBar } from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type RootContainer from "../widgets/containers/root_container.js";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { InfoProps } from "../widgets/dialogs/info.jsx";
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import Component from "./component.js";
import Entrypoints from "./entrypoints.js";
import MainTreeExecutors from "./main_tree_executors.js";
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import RootCommandExecutor from "./root_command_executor.js";
import ShortcutComponent from "./shortcut_component.js";
import { StartupChecks } from "./startup_checks.js";
import TabManager from "./tab_manager.js";
import TouchBarComponent from "./touch_bar.js";
import zoomComponent from "./zoom.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer;
@@ -124,7 +126,7 @@ export type CommandMappings = {
isNewNote?: boolean;
};
showPromptDialog: PromptDialogOptions;
showInfoDialog: ConfirmWithMessageOptions;
showInfoDialog: InfoProps;
showConfirmDialog: ConfirmWithMessageOptions;
showRecentChanges: CommandData & { ancestorNoteId: string };
showImportDialog: CommandData & { noteId: string };
@@ -264,7 +266,7 @@ export type CommandMappings = {
reEvaluateRightPaneVisibility: CommandData;
runActiveNote: CommandData;
scrollContainerToCommand: CommandData & {
scrollContainerTo: CommandData & {
position: number;
};
scrollToEnd: CommandData;
@@ -380,7 +382,8 @@ export type CommandMappings = {
reloadTextEditor: CommandData;
chooseNoteType: CommandData & {
callback: ChooseNoteTypeCallback
}
};
customDownload: CommandData;
};
type EventMappings = {
@@ -446,6 +449,7 @@ type EventMappings = {
};
searchRefreshed: { ntxId?: string | null };
textEditorRefreshed: { ntxId?: string | null, editor: CKTextEditor };
contentElRefreshed: { ntxId?: string | null, contentEl: HTMLElement };
hoistedNoteChanged: {
noteId: string;
ntxId: string | null;
@@ -470,6 +474,11 @@ type EventMappings = {
noteContextRemoved: {
ntxIds: string[];
};
contextDataChanged: {
noteContext: NoteContext;
key: string;
value: unknown;
};
exportSvg: { ntxId: string | null | undefined; };
exportPng: { ntxId: string | null | undefined; };
geoMapCreateChildNote: {
@@ -694,10 +703,8 @@ $(window).on("beforeunload", () => {
console.log(`Component ${component.componentId} is not finished saving its state.`);
allSaved = false;
}
} else {
if (!listener()) {
allSaved = false;
}
} else if (!listener()) {
allSaved = false;
}
}
@@ -707,7 +714,7 @@ $(window).on("beforeunload", () => {
}
});
$(window).on("hashchange", function () {
$(window).on("hashchange", () => {
const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href);
if (notePath || ntxId) {

View File

@@ -57,6 +57,18 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
return this;
}
/**
* Removes a child component from this component's children array.
* This is used for cleanup when a widget is unmounted to prevent event listener accumulation.
*/
removeChild(component: ChildT) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
component.parent = undefined;
}
}
handleEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null | undefined {
try {
const callMethodPromise = this.initialized ? this.initialized.then(() => this.callMethod((this as any)[`${name}Event`], data)) : this.callMethod((this as any)[`${name}Event`], data);
@@ -65,8 +77,8 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
// don't create promises if not needed (optimization)
return callMethodPromise && childrenPromise ? Promise.all([callMethodPromise, childrenPromise]) : callMethodPromise || childrenPromise;
} catch (e: any) {
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error ${e.message} ${e.stack}`);
} catch (e: unknown) {
console.error(`Handling of event '${name}' failed in ${this.constructor.name} with error`, e);
return null;
}

View File

@@ -1,18 +1,20 @@
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
import utils from "../services/utils.js";
import appContext, { type EventData, type EventListener } from "./app_context.js";
import treeService from "../services/tree.js";
import Component from "./component.js";
import froca from "../services/froca.js";
import hoistedNoteService from "../services/hoisted_note.js";
import options from "../services/options.js";
import type { ViewScope } from "../services/link.js";
import type FNote from "../entities/fnote.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import type FNote from "../entities/fnote.js";
import { closeActiveDialog } from "../services/dialog.js";
import froca from "../services/froca.js";
import hoistedNoteService from "../services/hoisted_note.js";
import type { ViewScope } from "../services/link.js";
import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type { HeadingContext } from "../widgets/sidebar/TableOfContents.js";
import appContext, { type EventData, type EventListener } from "./app_context.js";
import Component from "./component.js";
export interface SetNoteOpts {
triggerSwitchEvent?: unknown;
@@ -21,6 +23,31 @@ export interface SetNoteOpts {
export type GetTextEditorCallback = (editor: CKTextEditor) => void;
export type SaveState = "saved" | "saving" | "unsaved" | "error";
export interface NoteContextDataMap {
toc: HeadingContext;
pdfPages: {
totalPages: number;
currentPage: number;
scrollToPage(page: number): void;
requestThumbnail(page: number): void;
};
pdfAttachments: {
attachments: PdfAttachment[];
downloadAttachment(filename: string): void;
};
pdfLayers: {
layers: PdfLayer[];
toggleLayer(layerId: string, visible: boolean): void;
};
saveState: {
state: SaveState;
};
}
type ContextDataKey = keyof NoteContextDataMap;
class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
ntxId: string | null;
hoistedNoteId: string;
@@ -31,6 +58,13 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
parentNoteId?: string | null;
viewScope?: ViewScope;
/**
* Metadata storage for UI components (e.g., table of contents, PDF page list, code outline).
* This allows type widgets to publish data that sidebar/toolbar components can consume.
* Data is automatically cleared when navigating to a different note.
*/
private contextData: Map<string, unknown> = new Map();
constructor(ntxId: string | null = null, hoistedNoteId: string = "root", mainNtxId: string | null = null) {
super();
@@ -90,6 +124,22 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
this.viewScope = opts.viewScope;
({ noteId: this.noteId, parentNoteId: this.parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(resolvedNotePath));
// Clear context data when switching notes and notify subscribers
const oldKeys = Array.from(this.contextData.keys());
this.contextData.clear();
if (oldKeys.length > 0) {
// Notify subscribers asynchronously to avoid blocking navigation
window.setTimeout(() => {
for (const key of oldKeys) {
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value: undefined
});
}
}, 0);
}
this.saveToRecentNotes(resolvedNotePath);
protectedSessionHolder.touchProtectedSessionIfNecessary(this.note);
@@ -389,7 +439,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
* If no content could be determined `null` is returned instead.
*/
async getContentElement() {
return this.timeout<JQuery<HTMLElement>>(
return this.timeout<JQuery<HTMLElement> | null>(
new Promise((resolve) =>
appContext.triggerCommand("executeWithContentElement", {
resolve,
@@ -442,6 +492,52 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
return title;
}
/**
* Set metadata for this note context (e.g., table of contents, PDF pages, code outline).
* This data can be consumed by sidebar/toolbar components.
*
* @param key - Unique identifier for the data type (e.g., "toc", "pdfPages", "codeOutline")
* @param value - The data to store (will be cleared when switching notes)
*/
setContextData<K extends ContextDataKey>(key: K, value: NoteContextDataMap[K]): void {
this.contextData.set(key, value);
// Trigger event so subscribers can react
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value
});
}
/**
* Get metadata for this note context.
*
* @param key - The data key to retrieve
* @returns The stored data, or undefined if not found
*/
getContextData<K extends ContextDataKey>(key: K): NoteContextDataMap[K] | undefined {
return this.contextData.get(key) as NoteContextDataMap[K] | undefined;
}
/**
* Check if context data exists for a given key.
*/
hasContextData(key: ContextDataKey): boolean {
return this.contextData.has(key);
}
/**
* Clear specific context data.
*/
clearContextData(key: ContextDataKey): void {
this.contextData.delete(key);
this.triggerEvent("contextDataChanged", {
noteContext: this,
key,
value: undefined
});
}
}
export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, notePath: string, viewScope?: ViewScope) {

View File

@@ -1,14 +1,14 @@
import Component from "./component.js";
import appContext, { type CommandData, type CommandListenerData } from "./app_context.js";
import dateNoteService from "../services/date_notes.js";
import treeService from "../services/tree.js";
import openService from "../services/open.js";
import protectedSessionService from "../services/protected_session.js";
import options from "../services/options.js";
import froca from "../services/froca.js";
import utils from "../services/utils.js";
import toastService from "../services/toast.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";
import Component from "./component.js";
export default class RootCommandExecutor extends Component {
editReadOnlyNoteCommand() {
@@ -193,6 +193,19 @@ export default class RootCommandExecutor extends Component {
appContext.triggerEvent("zenModeChanged", { isEnabled });
}
async toggleRibbonTabNoteMapCommand(data: CommandListenerData<"toggleRibbonTabNoteMap">) {
const { isExperimentalFeatureEnabled } = await import("../services/experimental_features.js");
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
if (!isNewLayout) {
this.triggerEvent("toggleRibbonTabNoteMap", data);
return;
}
const activeContext = appContext.tabManager.getActiveContext();
if (!activeContext?.notePath) return;
openInReusableSplit(activeContext.notePath, "note-map");
}
firstTabCommand() {
this.#goToTab(1);
}
@@ -262,7 +275,7 @@ export default class RootCommandExecutor extends Component {
}
catch (e) {
console.error("Error creating AI Chat note:", e);
toastService.showError("Failed to create AI Chat note: " + (e as Error).message);
toastService.showError(`Failed to create AI Chat note: ${(e as Error).message}`);
}
}
}

View File

@@ -0,0 +1,115 @@
<!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><%= t("javascript-required") %></noscript>
<script>
// hide body to reduce flickering on the startup. This is done through JS and not CSS to not hide <noscript>
document.getElementsByTagName("body")[0].style.display = "none";
</script>
<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>
async function bootstrap() {
await setupGlob();
loadStylesheets();
loadIcons();
setBodyAttributes();
await loadScripts();
}
async function setupGlob() {
const response = await fetch("/bootstrap");
console.log("Service worker state", navigator.serviceWorker.controller);
console.log("Resp", response);
const json = await response.json();
console.log("Bootstrap", json);
global = globalThis; /* fixes https://github.com/webpack/webpack/issues/10035 */
window.glob = {
...json,
activeDialog: null
};
}
function loadStylesheets() {
const { assetPath, themeCssUrl, themeUseNextAsBase } = window.glob;
const cssToLoad = [];
cssToLoad.push(`${assetPath}/stylesheets/theme-light.css`);
if (themeCssUrl) {
cssToLoad.push(themeCssUrl);
}
if (themeUseNextAsBase === "next") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next.css`)
} else if (themeUseNextAsBase === "next-dark") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-dark.css`)
} else if (themeUseNextAsBase === "next-light") {
cssToLoad.push(`${assetPath}/stylesheets/theme-next-light.css`)
}
cssToLoad.push(`${assetPath}/stylesheets/style.css`);
for (const href of cssToLoad) {
const linkEl = document.createElement("link");
linkEl.href = href;
linkEl.rel = "stylesheet";
document.body.appendChild(linkEl);
}
}
function loadIcons() {
const styleEl = document.createElement("style");
styleEl.innerText = window.glob.iconPackCss;
document.head.appendChild(styleEl);
}
function setBodyAttributes() {
const { device, headingStyle, layoutOrientation, platform, isElectron, hasNativeTitleBar, hasBackgroundEffects, currentLocale } = window.glob;
const classesToSet = [
device,
`heading-style-${headingStyle}`,
`layout-${layoutOrientation}`,
`platform-${platform}`,
isElectron && "isElectron",
hasNativeTitleBar && "native-titlebar",
hasBackgroundEffects && "background-effects"
].filter(Boolean);
for (const classToSet of classesToSet) {
document.body.classList.add(classToSet);
}
document.body.lang = currentLocale.id;
document.body.dir = currentLocale.rtl ? "rtl" : "ltr";
}
async function loadScripts() {
const assetPath = glob.assetPath;
await import(`./${assetPath}/runtime.js`);
await import(`./${assetPath}/desktop.js`);
}
bootstrap();
</script>
<!-- Required for correct loading of scripts in Electron -->
<script>
if (typeof module === 'object') {window.module = module; module = undefined;}
</script>
</body>
</html>

View File

@@ -1,17 +1,18 @@
import appContext from "./components/app_context.js";
import utils from "./services/utils.js";
import noteTooltipService from "./services/note_tooltip.js";
import bundleService from "./services/bundle.js";
import toastService from "./services/toast.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
import options from "./services/options.js";
import "autocomplete.js/index_jquery.js";
import type ElectronRemote from "@electron/remote";
import type Electron from "electron";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";
import appContext from "./components/app_context.js";
import electronContextMenu from "./menus/electron_context_menu.js";
import bundleService from "./services/bundle.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import noteTooltipService from "./services/note_tooltip.js";
import options from "./services/options.js";
import toastService from "./services/toast.js";
import utils from "./services/utils.js";
await appContext.earlyInit();
@@ -22,6 +23,7 @@ bundleService.getWidgetBundlesByParent().then(async (widgetBundles) => {
appContext.setLayout(new DesktopLayout(widgetBundles));
appContext.start().catch((e) => {
toastService.showPersistent({
id: "critical-error",
title: t("toast.critical-error.title"),
icon: "alert",
message: t("toast.critical-error.message", { message: e.message })
@@ -63,6 +65,9 @@ function initOnElectron() {
if (options.get("nativeTitleBarVisible") !== "true") {
initTitleBarButtons(style, currentWindow);
}
// Clear navigation history on frontend refresh.
currentWindow.webContents.navigationHistory.clear();
}
function initTitleBarButtons(style: CSSStyleDeclaration, currentWindow: Electron.BrowserWindow) {

View File

@@ -1,17 +1,19 @@
import server from "../services/server.js";
import noteAttributeCache from "../services/note_attribute_cache.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import { MIME_TYPES_DICT } from "@triliumnext/commons";
import cssClassManager from "../services/css_class_manager.js";
import type { Froca } from "../services/froca-interface.js";
import type FAttachment from "./fattachment.js";
import type { default as FAttribute, AttributeType } from "./fattribute.js";
import utils from "../services/utils.js";
import noteAttributeCache from "../services/note_attribute_cache.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import search from "../services/search.js";
import server from "../services/server.js";
import utils from "../services/utils.js";
import type FAttachment from "./fattachment.js";
import type { AttributeType, default as FAttribute } from "./fattribute.js";
const LABEL = "label";
const RELATION = "relation";
const NOTE_TYPE_ICONS = {
export const NOTE_TYPE_ICONS = {
file: "bx bx-file",
image: "bx bx-image",
code: "bx bx-code",
@@ -268,13 +270,12 @@ export default class FNote {
}
}
return results;
} else {
return this.children;
}
return this.children;
}
async getSubtreeNoteIds(includeArchived = false) {
let noteIds: (string | string[])[] = [];
const noteIds: (string | string[])[] = [];
for (const child of await this.getChildNotes()) {
if (child.isArchived && !includeArchived) continue;
@@ -471,9 +472,8 @@ export default class FNote {
return a.isHidden ? 1 : -1;
} else if (a.isSearch !== b.isSearch) {
return a.isSearch ? 1 : -1;
} else {
return a.notePath.length - b.notePath.length;
}
return a.notePath.length - b.notePath.length;
});
return notePaths;
@@ -582,6 +582,10 @@ export default class FNote {
}
getIcon() {
return `tn-icon ${this.#getIconInternal()}`;
}
#getIconInternal() {
const iconClassLabels = this.getLabels("iconClass");
const workspaceIconClass = this.getWorkspaceIconClass();
@@ -597,14 +601,13 @@ export default class FNote {
} else if (this.type === "text") {
if (this.isFolder()) {
return "bx bx-folder";
} else {
return "bx bx-note";
}
} else if (this.type === "code" && this.mime.startsWith("text/x-sql")) {
return "bx bx-data";
} else {
return NOTE_TYPE_ICONS[this.type];
return "bx bx-note";
} else if (this.type === "code") {
const correspondingMimeType = MIME_TYPES_DICT.find(m => m.mime === this.mime);
return correspondingMimeType?.icon ?? NOTE_TYPE_ICONS.code;
}
return NOTE_TYPE_ICONS[this.type];
}
getColorClass() {
@@ -617,7 +620,7 @@ export default class FNote {
}
getFilteredChildBranches() {
let childBranches = this.getChildBranches();
const childBranches = this.getChildBranches();
if (!childBranches) {
console.error(`No children for '${this.noteId}'. This shouldn't happen.`);
@@ -811,9 +814,9 @@ export default class FNote {
return this.getLabelValue(nameWithPrefix.substring(1));
} else if (nameWithPrefix.startsWith("~")) {
return this.getRelationValue(nameWithPrefix.substring(1));
} else {
return this.getLabelValue(nameWithPrefix);
}
return this.getLabelValue(nameWithPrefix);
}
/**
@@ -878,10 +881,10 @@ export default class FNote {
promotedAttrs.sort((a, b) => {
if (a.noteId === b.noteId) {
return a.position < b.position ? -1 : 1;
} else {
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
return a.noteId < b.noteId ? -1 : 1;
}
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
return a.noteId < b.noteId ? -1 : 1;
});
return promotedAttrs;
@@ -993,6 +996,10 @@ export default class FNote {
);
}
isJsx() {
return (this.type === "code" && this.mime === "text/jsx");
}
/** @returns true if this note is HTML */
isHtml() {
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
@@ -1000,7 +1007,7 @@ export default class FNote {
/** @returns JS script environment - either "frontend" or "backend" */
getScriptEnv() {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend"))) {
if (this.isHtml() || (this.isJavaScript() && this.mime.endsWith("env=frontend")) || this.isJsx()) {
return "frontend";
}
@@ -1022,7 +1029,7 @@ export default class FNote {
* @returns a promise that resolves when the script has been run. Additionally, for front-end notes, the promise will contain the value that is returned by the script.
*/
async executeScript() {
if (!this.isJavaScript()) {
if (!(this.isJavaScript() || this.isJsx())) {
throw new Error(`Note ${this.noteId} is of type ${this.type} and mime ${this.mime} and thus cannot be executed`);
}

Binary file not shown.

View File

@@ -1,50 +1,59 @@
import { applyModals } from "./layout_commons.js";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import { isExperimentalFeatureEnabled } from "../services/experimental_features.js";
import options from "../services/options.js";
import utils from "../services/utils.js";
import ApiLog from "../widgets/api_log.jsx";
import ClosePaneButton from "../widgets/buttons/close_pane_button.js";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import ContentHeader from "../widgets/containers/content_header.js";
import CreatePaneButton from "../widgets/buttons/create_pane_button.js";
import FindWidget from "../widgets/find.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import GlobalMenu from "../widgets/buttons/global_menu.jsx";
import HighlightsListWidget from "../widgets/highlights_list.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import LeftPaneToggle from "../widgets/buttons/left_pane_toggle.js";
import MovePaneButton from "../widgets/buttons/move_pane_button.js";
import NoteIconWidget from "../widgets/note_icon.jsx";
import RightPaneToggle from "../widgets/buttons/right_pane_toggle.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.jsx";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import options from "../services/options.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import ContentHeader from "../widgets/containers/content_header.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import LeftPaneContainer from "../widgets/containers/left_pane_container.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import FindWidget from "../widgets/find.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { DESKTOP_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import HighlightsListWidget from "../widgets/highlights_list.js";
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
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 StatusBar from "../widgets/layout/StatusBar.jsx";
import NoteIconWidget from "../widgets/note_icon.jsx";
import NoteTitleWidget from "../widgets/note_title.jsx";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import QuickSearchWidget from "../widgets/quick_search.js";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import { FixedFormattingToolbar } from "../widgets/ribbon/FormattingToolbar.jsx";
import NoteActions from "../widgets/ribbon/NoteActions.jsx";
import Ribbon from "../widgets/ribbon/Ribbon.jsx";
import ScrollPadding from "../widgets/scroll_padding.js";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfo from "../widgets/shared_info.jsx";
import SpacerWidget from "../widgets/spacer.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import RightPanelContainer from "../widgets/sidebar/RightPanelContainer.jsx";
import SqlResults from "../widgets/sql_result.js";
import SqlTableSchemas from "../widgets/sql_table_schemas.js";
import TabRowWidget from "../widgets/tab_row.js";
import TabHistoryNavigationButtons from "../widgets/TabHistoryNavigationButtons.jsx";
import TitleBarButtons from "../widgets/title_bar_buttons.jsx";
import TocWidget from "../widgets/toc.js";
import type { AppContext } from "../components/app_context.js";
import type { WidgetsByParent } from "../services/bundle.js";
import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js";
import utils from "../services/utils.js";
import WatchedFileUpdateStatusWidget from "../widgets/watched_file_update_status.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import { applyModals } from "./layout_commons.js";
export default class DesktopLayout {
@@ -70,17 +79,20 @@ export default class DesktopLayout {
*/
const fullWidthTabBar = launcherPaneIsHorizontal || (isElectron && !hasNativeTitleBar && isMac);
const customTitleBarButtons = !hasNativeTitleBar && !isMac && !isWindows;
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
const rootContainer = new RootContainer(true)
.setParent(appContext)
.class((launcherPaneIsHorizontal ? "horizontal" : "vertical") + "-layout")
.class(`${launcherPaneIsHorizontal ? "horizontal" : "vertical" }-layout`)
.optChild(
fullWidthTabBar,
new FlexContainer("row")
.class("tab-row-container")
.child(new FlexContainer("row").id("tab-row-left-spacer"))
.optChild(launcherPaneIsHorizontal, <LeftPaneToggle isHorizontalLayout={true} />)
.child(<TabHistoryNavigationButtons />)
.child(new TabRowWidget().class("full-width"))
.optChild(isNewLayout, <RightPaneToggle />)
.optChild(customTitleBarButtons, <TitleBarButtons />)
.css("height", "40px")
.css("background-color", "var(--launcher-pane-background-color)")
@@ -102,7 +114,17 @@ export default class DesktopLayout {
new FlexContainer("column")
.id("rest-pane")
.css("flex-grow", "1")
.optChild(!fullWidthTabBar, new FlexContainer("row").child(new TabRowWidget()).optChild(customTitleBarButtons, <TitleBarButtons />).css("height", "40px"))
.optChild(!fullWidthTabBar,
new FlexContainer("row")
.class("tab-row-container")
.child(<TabHistoryNavigationButtons />)
.child(new TabRowWidget())
.optChild(isNewLayout, <RightPaneToggle />)
.optChild(customTitleBarButtons, <TitleBarButtons />)
.css("height", "40px")
.css("align-items", "center")
)
.optChild(isNewLayout, <FixedFormattingToolbar />)
.child(
new FlexContainer("row")
.filling()
@@ -116,32 +138,31 @@ export default class DesktopLayout {
.child(
new SplitNoteContainer(() =>
new NoteWrapperWidget()
.child(
new FlexContainer("row")
.class("title-row")
.css("height", "50px")
.css("min-height", "50px")
.css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
.child(new SpacerWidget(0, 1))
.child(<MovePaneButton direction="left" />)
.child(<MovePaneButton direction="right" />)
.child(<ClosePaneButton />)
.child(<CreatePaneButton />)
)
.child(<Ribbon />)
.child(new FlexContainer("row")
.class("title-row note-split-title")
.cssBlock(".title-row > * { margin: 5px; }")
.child(<NoteIconWidget />)
.child(<NoteTitleWidget />)
.optChild(isNewLayout, <NoteBadges />)
.child(<SpacerWidget baseSize={0} growthFactor={1} />)
.optChild(!isNewLayout, <MovePaneButton direction="left" />)
.optChild(!isNewLayout, <MovePaneButton direction="right" />)
.optChild(!isNewLayout, <ClosePaneButton />)
.optChild(!isNewLayout, <CreatePaneButton />)
.optChild(isNewLayout, <NoteActions />))
.optChild(!isNewLayout, <Ribbon />)
.child(new WatchedFileUpdateStatusWidget())
.child(<FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
.optChild(!isNewLayout, <FloatingButtons items={DESKTOP_FLOATING_BUTTONS} />)
.child(
new ScrollingContainer()
.filling()
.child(new ContentHeader()
.optChild(isNewLayout, <InlineTitle />)
.optChild(isNewLayout, <NoteTitleActions />)
.optChild(!isNewLayout, new ContentHeader()
.child(<ReadOnlyNoteInfoBar />)
.child(<SharedInfo />)
)
.child(<PromotedAttributes />)
.optChild(!isNewLayout, <PromotedAttributes />)
.child(<SqlTableSchemas />)
.child(<NoteDetail />)
.child(<NoteList media="screen" />)
@@ -151,23 +172,24 @@ export default class DesktopLayout {
)
.child(<ApiLog />)
.child(new FindWidget())
.child(
...this.customWidgets.get("node-detail-pane"), // typo, let's keep it for a while as BC
...this.customWidgets.get("note-detail-pane")
)
.child(...this.customWidgets.get("note-detail-pane"))
)
)
.child(...this.customWidgets.get("center-pane"))
)
.child(
.optChild(!isNewLayout,
new RightPaneContainer()
.child(new TocWidget())
.child(new HighlightsListWidget())
.child(...this.customWidgets.get("right-pane"))
)
.optChild(isNewLayout, <RightPanelContainer widgetsByParent={this.customWidgets} />)
)
.optChild(!launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
)
)
.optChild(launcherPaneIsHorizontal && isNewLayout, <StatusBar />)
.child(<CloseZenModeButton />)
// Desktop-specific dialogs.
@@ -185,14 +207,14 @@ export default class DesktopLayout {
launcherPane = new FlexContainer("row")
.css("height", "53px")
.class("horizontal")
.child(new LauncherContainer(true))
.child(<LauncherContainer isHorizontalLayout={true} />)
.child(<GlobalMenu isHorizontalLayout={true} />);
} else {
launcherPane = new FlexContainer("column")
.css("width", "53px")
.class("vertical")
.child(<GlobalMenu isHorizontalLayout={false} />)
.child(new LauncherContainer(false))
.child(<LauncherContainer isHorizontalLayout={false} />)
.child(<LeftPaneToggle isHorizontalLayout={false} />);
}

View File

@@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
import ToastContainer from "../widgets/Toast.jsx";
export function applyModals(rootContainer: RootContainer) {
rootContainer
@@ -50,5 +51,6 @@ export function applyModals(rootContainer: RootContainer) {
.child(<PromptDialog />)
.child(<IncorrectCpuArchDialog />)
.child(<PopupEditorDialog />)
.child(<CallToActionDialog />);
.child(<CallToActionDialog />)
.child(<ToastContainer />);
}

View File

@@ -1,35 +1,35 @@
import { applyModals } from "./layout_commons.js";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import FlexContainer from "../widgets/containers/flex_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import type AppContext from "../components/app_context.js";
import GlobalMenuWidget from "../widgets/buttons/global_menu.js";
import LauncherContainer from "../widgets/containers/launcher_container.js";
import MobileDetailMenu from "../widgets/mobile_widgets/mobile_detail_menu.js";
import CloseZenModeButton from "../widgets/close_zen_button.js";
import NoteList from "../widgets/collections/NoteList.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import ContentHeader from "../widgets/containers/content_header.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import RootContainer from "../widgets/containers/root_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import FloatingButtons from "../widgets/FloatingButtons.jsx";
import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx";
import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx";
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";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import NoteTitleWidget from "../widgets/note_title.js";
import NoteTreeWidget from "../widgets/note_tree.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import QuickSearchWidget from "../widgets/quick_search.js";
import { useNoteContext } from "../widgets/react/hooks.jsx";
import ReadOnlyNoteInfoBar from "../widgets/ReadOnlyNoteInfoBar.jsx";
import RootContainer from "../widgets/containers/root_container.js";
import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx";
import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx";
import SearchResult from "../widgets/search_result.jsx";
import SharedInfoWidget from "../widgets/shared_info.js";
import SidebarContainer from "../widgets/mobile_widgets/sidebar_container.js";
import StandaloneRibbonAdapter from "../widgets/ribbon/components/StandaloneRibbonAdapter.jsx";
import TabRowWidget from "../widgets/tab_row.js";
import ToggleSidebarButton from "../widgets/mobile_widgets/toggle_sidebar_button.jsx";
import type AppContext from "../components/app_context.js";
import NoteDetail from "../widgets/NoteDetail.jsx";
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
import PromotedAttributes from "../widgets/PromotedAttributes.jsx";
import SplitNoteContainer from "../widgets/containers/split_note_container.js";
import { applyModals } from "./layout_commons.js";
const MOBILE_CSS = `
<style>
@@ -183,7 +183,7 @@ export default class MobileLayout {
.child(new FlexContainer("row")
.class("horizontal")
.css("height", "53px")
.child(new LauncherContainer(true))
.child(<LauncherContainer isHorizontalLayout />)
.child(<GlobalMenuWidget isHorizontalLayout />)
.id("launcher-pane"))
)
@@ -194,11 +194,11 @@ export default class MobileLayout {
}
function FilePropertiesWrapper() {
const { note } = useNoteContext();
const { note, ntxId } = useNoteContext();
return (
<div>
{note?.type === "file" && <FilePropertiesTab note={note} />}
{note?.type === "file" && <FilePropertiesTab note={note} ntxId={ntxId} />}
</div>
);
}

View File

@@ -1,10 +1,11 @@
import { t } from "../services/i18n.js";
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
import type { LeafletMouseEvent } from "leaflet";
import appContext, { type CommandNames } from "../components/app_context.js";
import { t } from "../services/i18n.js";
import type { ViewScope } from "../services/link.js";
import utils, { isMobile } from "../services/utils.js";
import { getClosestNtxId } from "../widgets/widget_utils.js";
import type { LeafletMouseEvent } from "leaflet";
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) {
contextMenu.show({
@@ -34,15 +35,21 @@ function handleLinkContextMenuItem(command: string | undefined, e: ContextMenuEv
if (command === "openNoteInNewTab") {
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
return true;
} else if (command === "openNoteInNewSplit") {
const ntxId = getNtxId(e);
if (!ntxId) return;
if (!ntxId) return false;
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
return true;
} else if (command === "openNoteInNewWindow") {
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
return true;
} else if (command === "openNoteInPopup") {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
return true;
}
return false;
}
function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
@@ -52,9 +59,9 @@ function getNtxId(e: ContextMenuEvent | LeafletMouseEvent) {
return subContexts[subContexts.length - 1].ntxId;
} else if (e.target instanceof HTMLElement) {
return getClosestNtxId(e.target);
} else {
return null;
}
return null;
}
export default {

View File

@@ -1,9 +1,9 @@
import appContext from "./components/app_context.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import glob from "./services/glob.js";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";
import appContext from "./components/app_context.js";
import glob from "./services/glob.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
glob.setupGlobs();
await appContext.earlyInit();

View File

@@ -1,17 +1,25 @@
import FNote from "./entities/fnote";
import { render } from "preact";
import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList";
import { useCallback, useLayoutEffect, useRef } from "preact/hooks";
import FNote from "./entities/fnote";
import content_renderer from "./services/content_renderer";
import { dynamicRequire, isElectron } from "./services/utils";
import { applyInlineMermaid } from "./services/content_renderer_text";
import { dynamicRequire, isElectron } from "./services/utils";
import { CustomNoteList, useNoteViewType } from "./widgets/collections/NoteList";
interface RendererProps {
note: FNote;
onReady: () => void;
onReady: (data: PrintReport) => void;
onProgressChanged?: (progress: number) => void;
}
export type PrintReport = {
type: "single-note";
} | {
type: "collection";
ignoredNoteIds: string[];
};
async function main() {
const notePath = window.location.hash.substring(1);
const noteId = notePath.split("/").at(-1);
@@ -34,15 +42,17 @@ function App({ note, noteId }: { note: FNote | null | undefined, noteId: string
window.dispatchEvent(new CustomEvent("note-load-progress", { detail: { progress } }));
}
}, []);
const onReady = useCallback(() => {
const onReady = useCallback((printReport: PrintReport) => {
if (sentReadyEvent.current) return;
window.dispatchEvent(new Event("note-ready"));
window._noteReady = true;
window.dispatchEvent(new CustomEvent("note-ready", {
detail: printReport
}));
window._noteReady = printReport;
sentReadyEvent.current = true;
}, []);
const props: RendererProps | undefined | null = note && { note, onReady, onProgressChanged };
if (!note || !props) return <Error404 noteId={noteId} />
if (!note || !props) return <Error404 noteId={noteId} />;
useLayoutEffect(() => {
document.body.dataset.noteType = note.type;
@@ -51,8 +61,8 @@ function App({ note, noteId }: { note: FNote | null | undefined, noteId: string
return (
<>
{note.type === "book"
? <CollectionRenderer {...props} />
: <SingleNoteRenderer {...props} />
? <CollectionRenderer {...props} />
: <SingleNoteRenderer {...props} />
}
</>
);
@@ -91,7 +101,9 @@ function SingleNoteRenderer({ note, onReady }: RendererProps) {
await loadCustomCss(note);
}
load().then(() => requestAnimationFrame(onReady))
load().then(() => requestAnimationFrame(() => onReady({
type: "single-note"
})));
}, [ note ]);
return <>
@@ -110,9 +122,9 @@ function CollectionRenderer({ note, onReady, onProgressChanged }: RendererProps)
ntxId="print"
highlightedTokens={null}
media="print"
onReady={async () => {
onReady={async (data: PrintReport) => {
await loadCustomCss(note);
onReady();
onReady(data);
}}
onProgressChanged={onProgressChanged}
/>;
@@ -124,12 +136,12 @@ function Error404({ noteId }: { noteId: string }) {
<p>The note you are trying to print could not be found.</p>
<small>{noteId}</small>
</main>
)
);
}
async function loadCustomCss(note: FNote) {
const printCssNotes = await note.getRelationTargets("printCss");
let loadPromises: JQueryPromise<void>[] = [];
const loadPromises: JQueryPromise<void>[] = [];
for (const printCssNote of printCssNotes) {
if (!printCssNote || (printCssNote.type !== "code" && printCssNote.mime !== "text/css")) continue;

View File

@@ -1,6 +1,6 @@
import utils from "./utils.js";
import server from "./server.js";
import toastService, { type ToastOptions } from "./toast.js";
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import ws from "./ws.js";
@@ -195,11 +195,11 @@ function filterRootNote(branchIds: string[]) {
});
}
function makeToast(id: string, message: string): ToastOptions {
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
return {
id: id,
id,
title: t("branches.delete-status"),
message: message,
message,
icon: "trash"
};
}
@@ -216,7 +216,7 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
toast.closeAfter = 5000;
toast.timeout = 5000;
toastService.showPersistent(toast);
}
@@ -234,7 +234,7 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
toast.closeAfter = 5000;
toast.timeout = 5000;
toastService.showPersistent(toast);
}
@@ -242,7 +242,7 @@ ws.subscribeToMessages(async (message) => {
async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, prefix?: string) {
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-branch/${parentBranchId}`, {
prefix: prefix
prefix
});
if (!resp.success) {
@@ -252,7 +252,7 @@ async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, pr
async function cloneNoteToParentNote(childNoteId: string, parentNoteId: string, prefix?: string) {
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
prefix: prefix
prefix
});
if (!resp.success) {

View File

@@ -1,10 +1,15 @@
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";
import ScriptContext from "./script_context.js";
import server from "./server.js";
import toastService, { showError } from "./toast.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { t } from "./i18n.js";
import type { Entity } from "./frontend_script_api.js";
import toastService, { showErrorForScriptNote } from "./toast.js";
import utils, { getErrorMessage } from "./utils.js";
// TODO: Deduplicate with server.
export interface Bundle {
@@ -14,9 +19,13 @@ export interface Bundle {
allNoteIds: string[];
}
interface Widget {
type LegacyWidget = (BasicWidget | RightPanelWidget) & {
parentWidget?: string;
}
};
type WithNoteId<T> = T & {
_noteId: string;
};
export type Widget = WithNoteId<(LegacyWidget | WidgetDefinitionWithType)>;
async function getAndExecuteBundle(noteId: string, originEntity = null, script = null, params = null) {
const bundle = await server.post<Bundle>(`script/bundle/${noteId}`, {
@@ -27,6 +36,8 @@ async function getAndExecuteBundle(noteId: string, originEntity = null, script =
return await executeBundle(bundle, originEntity);
}
export type ParentName = "left-pane" | "center-pane" | "note-detail-pane" | "right-pane";
export async function executeBundle(bundle: Bundle, originEntity?: Entity | null, $container?: JQuery<HTMLElement>) {
const apiContext = await ScriptContext(bundle.noteId, bundle.allNoteIds, originEntity, $container);
@@ -35,17 +46,14 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
return eval(`const apiContext = this; (async function() { ${bundle.script}\r\n})()`);
}.call(apiContext);
} catch (e: any) {
const note = await froca.getNote(bundle.noteId);
const message = `Execution of JS note "${note?.title}" with ID ${bundle.noteId} failed with error: ${e?.message}`;
showError(message);
logError(message);
showErrorForScriptNote(bundle.noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
}
}
async function executeStartupBundles() {
const isMobile = utils.isMobile();
const scriptBundles = await server.get<Bundle[]>("script/startup" + (isMobile ? "?mobile=true" : ""));
const scriptBundles = await server.get<Bundle[]>(`script/startup${ isMobile ? "?mobile=true" : ""}`);
for (const bundle of scriptBundles) {
await executeBundle(bundle);
@@ -53,67 +61,99 @@ async function executeStartupBundles() {
}
export class WidgetsByParent {
private byParent: Record<string, Widget[]>;
private legacyWidgets: Record<string, WithNoteId<LegacyWidget>[]>;
private preactWidgets: Record<string, WithNoteId<WidgetDefinitionWithType>[]>;
constructor() {
this.byParent = {};
this.legacyWidgets = {};
this.preactWidgets = {};
}
add(widget: Widget) {
if (!widget.parentWidget) {
console.log(`Custom widget does not have mandatory 'parentWidget' property defined`);
return;
let hasParentWidget = false;
let isPreact = false;
if ("type" in widget && widget.type === "preact-widget") {
// React-based script.
const reactWidget = widget as WithNoteId<WidgetDefinitionWithType>;
this.preactWidgets[reactWidget.parent] = this.preactWidgets[reactWidget.parent] || [];
this.preactWidgets[reactWidget.parent].push(reactWidget);
isPreact = true;
hasParentWidget = !!reactWidget.parent;
} else if ("parentWidget" in widget && widget.parentWidget) {
this.legacyWidgets[widget.parentWidget] = this.legacyWidgets[widget.parentWidget] || [];
this.legacyWidgets[widget.parentWidget].push(widget);
hasParentWidget = !!widget.parentWidget;
}
this.byParent[widget.parentWidget] = this.byParent[widget.parentWidget] || [];
this.byParent[widget.parentWidget].push(widget);
if (!hasParentWidget) {
showErrorForScriptNote(widget._noteId, t("toast.widget-missing-parent", {
property: isPreact ? "parent" : "parentWidget"
}));
}
}
get(parentName: string) {
if (!this.byParent[parentName]) {
return [];
get(parentName: ParentName) {
const widgets: (BasicWidget | VNode)[] = this.getLegacyWidgets(parentName);
for (const preactWidget of this.getPreactWidgets(parentName)) {
const el = h(preactWidget.render, {});
const widget = new ReactWrappedWidget(el);
widget.contentSized();
if (preactWidget.position) {
widget.position = preactWidget.position;
}
widgets.push(widget);
}
return widgets;
}
getLegacyWidgets(parentName: ParentName): (BasicWidget | RightPanelWidget)[] {
if (!this.legacyWidgets[parentName]) return [];
return (
this.byParent[parentName]
this.legacyWidgets[parentName]
// previously, custom widgets were provided as a single instance, but that has the disadvantage
// for splits where we actually need multiple instaces and thus having a class to instantiate is better
// https://github.com/zadam/trilium/issues/4274
.map((w: any) => (w.prototype ? new w() : w))
);
}
getPreactWidgets(parentName: ParentName) {
return this.preactWidgets[parentName] ?? [];
}
}
async function getWidgetBundlesByParent() {
const scriptBundles = await server.get<Bundle[]>("script/widgets");
const widgetsByParent = new WidgetsByParent();
for (const bundle of scriptBundles) {
let widget;
try {
const scriptBundles = await server.get<Bundle[]>("script/widgets");
try {
widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
for (const bundle of scriptBundles) {
let widget;
try {
widget = await executeBundle(bundle);
if (widget) {
widget._noteId = bundle.noteId;
widgetsByParent.add(widget);
}
} catch (e: any) {
const noteId = bundle.noteId;
showErrorForScriptNote(noteId, t("toast.bundle-error.message", { message: e.message }));
logError("Widget initialization failed: ", e);
continue;
}
} catch (e: any) {
const noteId = bundle.noteId;
const note = await froca.getNote(noteId);
toastService.showPersistent({
title: t("toast.bundle-error.title"),
icon: "alert",
message: t("toast.bundle-error.message", {
id: noteId,
title: note?.title,
message: e.message
})
});
logError("Widget initialization failed: ", e);
continue;
}
} catch (e) {
toastService.showPersistent({
id: `custom-widget-list-failure`,
title: t("toast.widget-list-error.title"),
message: getErrorMessage(e),
icon: "bx bx-error-circle"
});
}
return widgetsByParent;

View File

@@ -1,18 +1,19 @@
import renderService from "./render.js";
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
import WheelZoom from 'vanilla-js-wheel-zoom';
import FAttachment from "../entities/fattachment.js";
import FNote from "../entities/fnote.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import { t } from "../services/i18n.js";
import renderText from "./content_renderer_text.js";
import renderDoc from "./doc_renderer.js";
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
import openService from "./open.js";
import protectedSessionService from "./protected_session.js";
import protectedSessionHolder from "./protected_session_holder.js";
import openService from "./open.js";
import utils from "./utils.js";
import FNote from "../entities/fnote.js";
import FAttachment from "../entities/fattachment.js";
import imageContextMenuService from "../menus/image_context_menu.js";
import renderService from "./render.js";
import { applySingleBlockSyntaxHighlight } from "./syntax_highlight.js";
import { loadElkIfNeeded, postprocessMermaidSvg } from "./mermaid.js";
import renderDoc from "./doc_renderer.js";
import { t } from "../services/i18n.js";
import WheelZoom from 'vanilla-js-wheel-zoom';
import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons";
import renderText from "./content_renderer_text.js";
import utils from "./utils.js";
let idCounter = 1;
@@ -152,7 +153,7 @@ function renderImage(entity: FNote | FAttachment, $renderedContent: JQuery<HTMLE
const $img = $("<img>")
.attr("src", url || "")
.attr("id", "attachment-image-" + idCounter++)
.attr("id", `attachment-image-${idCounter++}`)
.css("max-width", "100%");
$renderedContent.append($img);
@@ -193,7 +194,7 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
if (type === "pdf") {
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
$pdfPreview.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open`));
$pdfPreview.attr("src", openService.getUrlForDownload(`pdfjs/web/viewer.html?file=../../api/${entityType}/${entityId}/open`));
$content.append($pdfPreview);
} else if (type === "audio") {
@@ -217,28 +218,28 @@ function renderFile(entity: FNote | FAttachment, type: string, $renderedContent:
// in attachment list
const $downloadButton = $(`
<button class="file-download btn btn-primary" type="button">
<span class="bx bx-download"></span>
<span class="tn-icon bx bx-download"></span>
${t("file_properties.download")}
</button>
`);
const $openButton = $(`
<button class="file-open btn btn-primary" type="button">
<span class="bx bx-link-external"></span>
<span class="tn-icon bx bx-link-external"></span>
${t("file_properties.open")}
</button>
`);
$downloadButton.on("click", (e) => {
e.stopPropagation();
openService.downloadFileNote(entity.noteId)
openService.downloadFileNote(entity, null, null);
});
$openButton.on("click", async (e) => {
const iconEl = $openButton.find("> .bx");
iconEl.removeClass("bx bx-link-external");
iconEl.addClass("bx bx-loader spin");
e.stopPropagation();
await openService.openNoteExternally(entity.noteId, entity.mime)
await openService.openNoteExternally(entity.noteId, entity.mime);
iconEl.removeClass("bx bx-loader spin");
iconEl.addClass("bx bx-link-external");
});
@@ -266,7 +267,7 @@ async function renderMermaid(note: FNote | FAttachment, $renderedContent: JQuery
try {
await loadElkIfNeeded(mermaid, content);
const { svg } = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content);
const { svg } = await mermaid.mermaidAPI.render(`in-mermaid-graph-${idCounter++}`, content);
$renderedContent.append($(postprocessMermaidSvg(svg)));
} catch (e) {

View File

@@ -1,13 +1,13 @@
import { formatCodeBlocks } from "./syntax_highlight.js";
import { getMermaidConfig } from "./mermaid.js";
import { renderMathInElement } from "./math.js";
import FNote from "../entities/fnote.js";
import FAttachment from "../entities/fattachment.js";
import tree from "./tree.js";
import FNote from "../entities/fnote.js";
import { default as content_renderer, type RenderOptions } from "./content_renderer.js";
import froca from "./froca.js";
import link from "./link.js";
import { renderMathInElement } from "./math.js";
import { getMermaidConfig } from "./mermaid.js";
import { formatCodeBlocks } from "./syntax_highlight.js";
import tree from "./tree.js";
import { isHtmlEmpty } from "./utils.js";
import { default as content_renderer, type RenderOptions } from "./content_renderer.js";
export default async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>, options: RenderOptions = {}) {
// entity must be FNote
@@ -22,12 +22,14 @@ export default async function renderText(note: FNote | FAttachment, $renderedCon
}
const getNoteIdFromLink = (el: HTMLElement) => tree.getNoteIdFromUrl($(el).attr("href") || "");
const referenceLinks = $renderedContent.find("a.reference-link");
const referenceLinks = $renderedContent.find<HTMLAnchorElement>("a.reference-link");
const noteIdsToPrefetch = referenceLinks.map((i, el) => getNoteIdFromLink(el));
await froca.getNotes(noteIdsToPrefetch);
for (const el of referenceLinks) {
await link.loadReferenceLinkTitle($(el));
const innerSpan = document.createElement("span");
await link.loadReferenceLinkTitle($(innerSpan), el.href);
el.replaceChildren(innerSpan);
}
await rewriteMermaidDiagramsInContainer($renderedContent[0] as HTMLDivElement);

View File

@@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { getReadableTextColor } from "./css_class_manager";
describe("getReadableTextColor", () => {
it("doesn't crash for invalid color", () => {
expect(getReadableTextColor("RandomColor")).toBe("#000");
});
it("tolerates different casing", () => {
expect(getReadableTextColor("Blue"))
.toBe(getReadableTextColor("blue"));
});
});

View File

@@ -1,21 +1,22 @@
import clsx from "clsx";
import {readCssVar} from "../utils/css-var";
import Color, { ColorInstance } from "color";
import {readCssVar} from "../utils/css-var";
const registeredClasses = new Set<string>();
const colorsWithHue = new Set<string>();
// Read the color lightness limits defined in the theme as CSS variables
const lightThemeColorMaxLightness = readCssVar(
document.documentElement,
"tree-item-light-theme-max-color-lightness"
).asNumber(70);
document.documentElement,
"tree-item-light-theme-max-color-lightness"
).asNumber(70);
const darkThemeColorMinLightness = readCssVar(
document.documentElement,
"tree-item-dark-theme-min-color-lightness"
).asNumber(50);
document.documentElement,
"tree-item-dark-theme-min-color-lightness"
).asNumber(50);
function createClassForColor(colorString: string | null) {
if (!colorString?.trim()) return "";
@@ -27,11 +28,12 @@ function createClassForColor(colorString: string | null) {
if (!registeredClasses.has(className)) {
const adjustedColor = adjustColorLightness(color, lightThemeColorMaxLightness!,
darkThemeColorMinLightness!);
darkThemeColorMinLightness!);
const hue = getHue(color);
$("head").append(`<style>
.${className}, span.fancytree-active.${className} {
--original-custom-color: ${color.hex()};
--light-theme-custom-color: ${adjustedColor.lightThemeColor};
--dark-theme-custom-color: ${adjustedColor.darkThemeColor};
--custom-color-hue: ${hue ?? 'unset'};
@@ -49,7 +51,7 @@ function createClassForColor(colorString: string | null) {
function parseColor(color: string) {
try {
return Color(color);
return Color(color.toLowerCase());
} catch (ex) {
console.error(ex);
}
@@ -83,8 +85,8 @@ function getHue(color: ColorInstance) {
}
export function getReadableTextColor(bgColor: string) {
const colorInstance = Color(bgColor);
return colorInstance.isLight() ? "#000" : "#fff";
const colorInstance = parseColor(bgColor);
return !colorInstance || colorInstance?.isLight() ? "#000" : "#fff";
}
export default {

View File

@@ -1,8 +1,9 @@
import { Modal } from "bootstrap";
import appContext from "../components/app_context.js";
import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptions } from "../widgets/dialogs/confirm.js";
import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptions, MessageType } from "../widgets/dialogs/confirm.js";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import { focusSavedElement, saveFocusedElement } from "./focus.js";
import { InfoExtraProps } from "../widgets/dialogs/info.jsx";
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true, config?: Partial<Modal.Options>) {
if (closeActDialog) {
@@ -37,8 +38,8 @@ export function closeActiveDialog() {
}
}
async function info(message: string) {
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
async function info(message: MessageType, extraProps?: InfoExtraProps) {
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { ...extraProps, message, callback: res }));
}
/**

View File

@@ -0,0 +1,60 @@
import { t } from "./i18n";
import options from "./options";
export interface ExperimentalFeature {
id: string;
name: string;
description: string;
}
export const experimentalFeatures = [
{
id: "new-layout",
name: t("experimental_features.new_layout_name"),
description: t("experimental_features.new_layout_description"),
}
] as const satisfies ExperimentalFeature[];
export type ExperimentalFeatureId = typeof experimentalFeatures[number]["id"];
let enabledFeatures: Set<ExperimentalFeatureId> | null = null;
export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): boolean {
if (featureId === "new-layout") {
return options.is("newLayout");
}
return getEnabledFeatures().has(featureId);
}
export function getEnabledExperimentalFeatureIds() {
const values = [ ...getEnabledFeatures().values() ];
if (options.is("newLayout")) {
values.push("new-layout");
}
return values;
}
export async function toggleExperimentalFeature(featureId: ExperimentalFeatureId, enable: boolean) {
const features = new Set(getEnabledFeatures());
if (enable) {
features.add(featureId);
} else {
features.delete(featureId);
}
await options.save("experimentalFeatures", JSON.stringify(Array.from(features)));
}
function getEnabledFeatures() {
if (!enabledFeatures) {
let features: ExperimentalFeatureId[] = [];
try {
features = JSON.parse(options.get("experimentalFeatures")) as ExperimentalFeatureId[];
} catch (e) {
console.warn("Failed to parse experimental features from options:", e);
}
enabledFeatures = new Set(features);
enabledFeatures.delete("new-layout"); // handled separately.
}
return enabledFeatures;
}

View File

@@ -1,26 +1,27 @@
import server from "./server.js";
import utils from "./utils.js";
import toastService from "./toast.js";
import linkService from "./link.js";
import { dayjs, formatLogMessage } from "@triliumnext/commons";
import appContext from "../components/app_context.js";
import type Component from "../components/component.js";
import type NoteContext from "../components/note_context.js";
import type FNote from "../entities/fnote.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import dateNotesService from "./date_notes.js";
import dialogService from "./dialog.js";
import froca from "./froca.js";
import { preactAPI } from "./frontend_script_api_preact.js";
import { t } from "./i18n.js";
import linkService from "./link.js";
import noteTooltipService from "./note_tooltip.js";
import protectedSessionService from "./protected_session.js";
import dateNotesService from "./date_notes.js";
import searchService from "./search.js";
import RightPanelWidget from "../widgets/right_panel_widget.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js";
import server from "./server.js";
import shortcutService from "./shortcuts.js";
import dialogService from "./dialog.js";
import type FNote from "../entities/fnote.js";
import { t } from "./i18n.js";
import { dayjs } from "@triliumnext/commons";
import type NoteContext from "../components/note_context.js";
import type Component from "../components/component.js";
import { formatLogMessage } from "@triliumnext/commons";
import SpacedUpdate from "./spaced_update.js";
import toastService from "./toast.js";
import utils from "./utils.js";
import ws from "./ws.js";
/**
* A whole number
@@ -77,6 +78,10 @@ export interface Api {
/**
* Entity whose event triggered this execution.
*
* <p>
* For front-end scripts, generally there's no origin entity specified since the scripts are run by the user or automatically by the UI (widgets).
* If there is an origin entity specified, then it's going to be a note entity.
*/
originEntity: unknown | null;
@@ -278,12 +283,16 @@ export interface Api {
getActiveContextNote(): FNote;
/**
* @returns returns active context (split)
* Obtains the currently active/focused split in the current tab.
*
* Note that this method does not return the note context of the "Quick edit" panel, it will return the note context behind it.
*/
getActiveContext(): NoteContext;
/**
* @returns returns active main context (first split in a tab, represents the tab as a whole)
* Obtains the main context of the current tab. This is the left-most split.
*
* Note that this method does not return the note context of the "Quick edit" panel, it will return the note context behind it.
*/
getActiveMainContext(): NoteContext;
@@ -456,6 +465,8 @@ export interface Api {
* Log given message to the log pane in UI
*/
log(message: string | object): void;
preact: typeof preactAPI;
}
/**
@@ -525,9 +536,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
return params.map((p) => {
if (typeof p === "function") {
return `!@#Function: ${p.toString()}`;
} else {
return p;
}
return p;
});
}
@@ -554,9 +564,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
await ws.waitForMaxKnownEntityChangeId();
return ret.executionResult;
} else {
throw new Error(`server error: ${ret.error}`);
}
throw new Error(`server error: ${ret.error}`);
};
this.runOnBackend = async (func, params = []) => {
@@ -713,6 +722,8 @@ function FrontendScriptApi(this: Api, startNote: FNote, currentNote: FNote, orig
this.logMessages[noteId].push(message);
this.logSpacedUpdates[noteId].scheduleUpdate();
};
this.preact = preactAPI;
}
export default FrontendScriptApi as any as {

View File

@@ -0,0 +1,101 @@
import { Fragment, h, VNode } from "preact";
import * as hooks from "preact/hooks";
import ActionButton from "../widgets/react/ActionButton";
import Admonition from "../widgets/react/Admonition";
import Button from "../widgets/react/Button";
import CKEditor from "../widgets/react/CKEditor";
import Collapsible from "../widgets/react/Collapsible";
import Dropdown from "../widgets/react/Dropdown";
import FormCheckbox from "../widgets/react/FormCheckbox";
import FormDropdownList from "../widgets/react/FormDropdownList";
import { FormFileUploadActionButton, FormFileUploadButton } from "../widgets/react/FormFileUpload";
import FormGroup from "../widgets/react/FormGroup";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../widgets/react/FormList";
import FormRadioGroup from "../widgets/react/FormRadioGroup";
import FormText from "../widgets/react/FormText";
import FormTextArea from "../widgets/react/FormTextArea";
import FormTextBox from "../widgets/react/FormTextBox";
import FormToggle from "../widgets/react/FormToggle";
import * as triliumHooks from "../widgets/react/hooks";
import Icon from "../widgets/react/Icon";
import LinkButton from "../widgets/react/LinkButton";
import LoadingSpinner from "../widgets/react/LoadingSpinner";
import Modal from "../widgets/react/Modal";
import NoteAutocomplete from "../widgets/react/NoteAutocomplete";
import NoteLink from "../widgets/react/NoteLink";
import RawHtml from "../widgets/react/RawHtml";
import Slider from "../widgets/react/Slider";
import RightPanelWidget from "../widgets/sidebar/RightPanelWidget";
export interface WidgetDefinition {
parent: "right-pane",
render: () => VNode,
position?: number,
}
export interface WidgetDefinitionWithType extends WidgetDefinition {
type: "preact-widget"
}
export interface LauncherWidgetDefinitionWithType {
type: "preact-launcher-widget"
render: () => VNode
}
export const preactAPI = Object.freeze({
// Core
h,
Fragment,
/**
* Method that must be run for widget scripts that run on Preact, using JSX. The method just returns the same definition, reserved for future typechecking and perhaps validation purposes.
*
* @param definition the widget definition.
*/
defineWidget(definition: WidgetDefinition) {
return {
type: "preact-widget",
...definition
};
},
defineLauncherWidget(definition: Omit<LauncherWidgetDefinitionWithType, "type">) {
return {
type: "preact-launcher-widget",
...definition
};
},
// Basic widgets
ActionButton,
Admonition,
Button,
CKEditor,
Collapsible,
Dropdown,
FormCheckbox,
FormDropdownList,
FormFileUploadButton, FormFileUploadActionButton,
FormGroup,
FormListItem, FormDropdownDivider, FormDropdownSubmenu,
FormRadioGroup,
FormText,
FormTextArea,
FormTextBox,
FormToggle,
Icon,
LinkButton,
LoadingSpinner,
Modal,
NoteAutocomplete,
NoteLink,
RawHtml,
Slider,
// Specialized widgets
RightPanelWidget,
...hooks,
...triliumHooks
});

View File

@@ -1,4 +1,4 @@
import toastService, { type ToastOptions } from "./toast.js";
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
import server from "./server.js";
import ws from "./ws.js";
import utils from "./utils.js";
@@ -57,11 +57,11 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
}
}
function makeToast(id: string, message: string): ToastOptions {
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
return {
id: id,
id,
title: t("import.import-status"),
message: message,
message,
icon: "plus"
};
}
@@ -78,7 +78,7 @@ ws.subscribeToMessages(async (message) => {
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("import.successful"));
toast.closeAfter = 5000;
toast.timeout = 5000;
toastService.showPersistent(toast);
@@ -100,7 +100,7 @@ ws.subscribeToMessages(async (message: WebSocketMessage) => {
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
} else if (message.type === "taskSucceeded") {
const toast = makeToast(message.taskId, t("import.successful"));
toast.closeAfter = 5000;
toast.timeout = 5000;
toastService.showPersistent(toast);

View File

@@ -1,10 +1,11 @@
import treeService from "./tree.js";
import linkContextMenuService from "../menus/link_context_menu.js";
import appContext, { type NoteCommandData } from "../components/app_context.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
import appContext, { type NoteCommandData } from "../components/app_context.js";
import { openInCurrentNoteContext } from "../components/note_context.js";
import linkContextMenuService from "../menus/link_context_menu.js";
import froca from "./froca.js";
import treeService from "./tree.js";
import utils from "./utils.js";
function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);
@@ -27,7 +28,7 @@ async function getLinkIcon(noteId: string, viewMode: ViewMode | undefined) {
return icon;
}
export type ViewMode = "default" | "source" | "attachments" | "contextual-help";
export type ViewMode = "default" | "source" | "attachments" | "contextual-help" | "note-map";
export interface ViewScope {
/**
@@ -99,7 +100,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
const viewMode = viewScope.viewMode || "default";
let linkTitle = options.title;
if (!linkTitle) {
if (linkTitle === undefined) {
if (viewMode === "attachments" && viewScope.attachmentId) {
const attachment = await froca.getAttachment(viewScope.attachmentId);
@@ -122,7 +123,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
const $container = $("<span>");
if (showNoteIcon) {
let icon = await getLinkIcon(noteId, viewMode);
const icon = await getLinkIcon(noteId, viewMode);
if (icon) {
$container.append($("<span>").addClass(`bx ${icon}`)).append(" ");
@@ -131,7 +132,7 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
const hash = calculateHash({
notePath,
viewScope: viewScope
viewScope
});
const $noteLink = $("<a>", {
@@ -171,11 +172,11 @@ async function createLink(notePath: string | undefined, options: CreateLinkOptio
return $container;
}
function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) {
export function calculateHash({ notePath, ntxId, hoistedNoteId, viewScope = {} }: NoteCommandData) {
notePath = notePath || "";
const params = [
ntxId ? { ntxId: ntxId } : null,
hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId: hoistedNoteId } : null,
ntxId ? { ntxId } : null,
hoistedNoteId && hoistedNoteId !== "root" ? { hoistedNoteId } : null,
viewScope.viewMode && viewScope.viewMode !== "default" ? { viewMode: viewScope.viewMode } : null,
viewScope.attachmentId ? { attachmentId: viewScope.attachmentId } : null
].filter((p) => !!p);
@@ -219,7 +220,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
}
const hash = url.substr(hashIdx + 1); // strip also the initial '#'
let [notePath, paramString] = hash.split("?");
const [notePath, paramString] = hash.split("?");
const viewScope: ViewScope = {
viewMode: "default"
@@ -252,7 +253,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
}
if (searchString) {
return { searchString }
return { searchString };
}
if (!notePath.match(/^[_a-z0-9]{4,}(\/[_a-z0-9]{4,})*$/i)) {
@@ -334,7 +335,7 @@ export function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDo
window.open(hrefLink, "_blank");
} else {
// Enable protocols supported by CKEditor 5 to be clickable.
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(protocol + ":"))) {
if (ALLOWED_PROTOCOLS.some((protocol) => hrefLink.toLowerCase().startsWith(`${protocol}:`))) {
if ( utils.isElectron()) {
const electron = utils.dynamicRequire("electron");
electron.shell.openExternal(hrefLink);
@@ -395,7 +396,7 @@ async function loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string | n
href = href || $link.attr("href");
if (!href) {
console.warn("Empty URL for parsing: " + $el[0].outerHTML);
console.warn(`Empty URL for parsing: ${$el[0].outerHTML}`);
return;
}
@@ -438,9 +439,9 @@ async function getReferenceLinkTitle(href: string) {
const attachment = await note.getAttachmentById(viewScope.attachmentId);
return attachment ? attachment.title : "[missing attachment]";
} else {
return note.title;
}
return note.title;
}
function getReferenceLinkTitleSync(href: string) {
@@ -462,9 +463,9 @@ function getReferenceLinkTitleSync(href: string) {
const attachment = note.attachments.find((att) => att.attachmentId === viewScope.attachmentId);
return attachment ? attachment.title : "[missing attachment]";
} else {
return note.title;
}
return note.title;
}
if (glob.device !== "print") {

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,6 +1,8 @@
import utils from "./utils.js";
import Component from "../components/component.js";
import FNote from "../entities/fnote.js";
import options from "./options.js";
import server from "./server.js";
import utils from "./utils.js";
type ExecFunction = (command: string, cb: (err: string, stdout: string, stderror: string) => void) => void;
@@ -36,9 +38,14 @@ function download(url: string) {
}
}
export function downloadFileNote(noteId: string) {
const url = `${getFileUrl("notes", noteId)}?${Date.now()}`; // don't use cache
export function downloadFileNote(note: FNote, parentComponent: Component | null, ntxId: string | null | undefined) {
if (note.type === "file" && note.mime === "application/pdf" && parentComponent) {
// Special handling, manages its own downloading process.
parentComponent.triggerEvent("customDownload", { ntxId });
return;
}
const url = `${getFileUrl("notes", note.noteId)}?${Date.now()}`; // don't use cache
download(url);
}
@@ -97,7 +104,7 @@ async function openCustom(type: string, entityId: string, mime: string) {
// Note that the path separator must be \ instead of /
filePath = filePath.replace(/\//g, "\\");
}
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ` + filePath;
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ${filePath}`;
exec(command, (err, stdout, stderr) => {
if (err) {
console.error("Open Note custom: ", err);
@@ -131,10 +138,10 @@ export function getUrlForDownload(url: string) {
if (utils.isElectron()) {
// electron needs absolute URL, so we extract current host, port, protocol
return `${getHost()}/${url}`;
} else {
// web server can be deployed on subdomain, so we need to use a relative path
return url;
}
// web server can be deployed on subdomain, so we need to use a relative path
return url;
}
function canOpenInBrowser(mime: string) {

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());

View File

@@ -1,7 +1,7 @@
import server from "./server.js";
import protectedSessionHolder from "./protected_session_holder.js";
import toastService from "./toast.js";
import type { ToastOptions } from "./toast.js";
import type { ToastOptionsWithRequiredId } from "./toast.js";
import ws from "./ws.js";
import appContext from "../components/app_context.js";
import froca from "./froca.js";
@@ -97,7 +97,7 @@ async function protectNote(noteId: string, protect: boolean, includingSubtree: b
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
}
function makeToast(message: Message, title: string, text: string): ToastOptions {
function makeToast(message: Message, title: string, text: string): ToastOptionsWithRequiredId {
return {
id: message.taskId,
title,
@@ -124,7 +124,7 @@ ws.subscribeToMessages(async (message) => {
} else if (message.type === "taskSucceeded") {
const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully");
const toast = makeToast(message, title, text);
toast.closeAfter = 3000;
toast.timeout = 3000;
toastService.showPersistent(toast);
}

View File

@@ -1,6 +1,10 @@
import server from "./server.js";
import bundleService, { type Bundle } from "./bundle.js";
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>) {
const relations = note.getRelations("renderNote");
@@ -17,12 +21,34 @@ async function render(note: FNote, $el: JQuery<HTMLElement>) {
$scriptContainer.append(bundle.html);
// async so that scripts cannot block trilium execution
bundleService.executeBundle(bundle, note, $scriptContainer);
bundleService.executeBundle(bundle, note, $scriptContainer).then(result => {
// Render JSX
if (bundle.html === "") {
renderIfJsx(bundle, result, $el);
}
});
}
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

@@ -85,13 +85,15 @@ async function remove<T>(url: string, componentId?: string) {
return await call<T>("DELETE", url, componentId);
}
async function upload(url: string, fileToUpload: File) {
async function upload(url: string, fileToUpload: File, componentId?: string) {
const formData = new FormData();
formData.append("upload", fileToUpload);
return await $.ajax({
url: window.glob.baseApiUrl + url,
headers: await getHeaders(),
headers: await getHeaders(componentId ? {
"trilium-component-id": componentId
} : undefined),
data: formData,
type: "PUT",
timeout: 60 * 60 * 1000,
@@ -133,11 +135,11 @@ async function call<T>(method: string, url: string, componentId?: string, option
};
ipc.send("server-request", {
requestId: requestId,
headers: headers,
method: method,
requestId,
headers,
method,
url: `/${window.glob.baseApiUrl}${url}`,
data: data
data
});
})) as any;
} else {
@@ -161,7 +163,7 @@ function ajax(url: string, method: string, data: unknown, headers: Headers, sile
const options: JQueryAjaxSettings = {
url: window.glob.baseApiUrl + url,
type: method,
headers: headers,
headers,
timeout: 60000,
success: (body, textStatus, jqXhr) => {
const respHeaders: Headers = {};
@@ -263,7 +265,7 @@ async function reportError(method: string, url: string, statusCode: number, resp
const toastService = (await import("./toast.js")).default;
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
const messageStr = (typeof message === "string" ? message : JSON.stringify(message)) || "-";
if ([400, 404].includes(statusCode) && response && typeof response === "object") {
toastService.showError(messageStr);
@@ -274,10 +276,22 @@ async function reportError(method: string, url: string, statusCode: number, resp
...response
});
} else {
const title = `${statusCode} ${method} ${url}`;
toastService.showErrorTitleAndMessage(title, messageStr);
const { throwError } = await import("./ws.js");
throwError(`${title} - ${message}`);
const { t } = await import("./i18n.js");
if (statusCode === 400 && (url.includes("%23") || url.includes("%2F"))) {
toastService.showPersistent({
id: "trafik-blocked",
icon: "bx bx-unlink",
title: t("server.unknown_http_error_title"),
message: t("server.traefik_blocks_requests")
});
} else {
toastService.showErrorTitleAndMessage(
t("server.unknown_http_error_title"),
t("server.unknown_http_error_content", { statusCode, method, url, message: messageStr }),
15_000);
}
const { logError } = await import("./ws.js");
logError(`${statusCode} ${method} ${url} - ${message}`);
}
}

View File

@@ -1,7 +1,7 @@
import utils from "./utils.js";
type ElementType = HTMLElement | Document;
type Handler = (e: KeyboardEvent) => void;
export type Handler = (e: KeyboardEvent) => void;
export interface ShortcutBinding {
element: HTMLElement | Document;

View File

@@ -1,22 +1,30 @@
import type { SaveState } from "../components/note_context";
import { getErrorMessage } from "./utils";
type Callback = () => Promise<void> | void;
export type StateCallback = (state: SaveState) => void;
export default class SpacedUpdate {
private updater: Callback;
private lastUpdated: number;
private changed: boolean;
private updateInterval: number;
private changeForbidden?: boolean;
private stateCallback?: StateCallback;
constructor(updater: Callback, updateInterval = 1000) {
constructor(updater: Callback, updateInterval = 1000, stateCallback?: StateCallback) {
this.updater = updater;
this.lastUpdated = Date.now();
this.changed = false;
this.updateInterval = updateInterval;
this.stateCallback = stateCallback;
}
scheduleUpdate() {
if (!this.changeForbidden) {
this.changed = true;
this.stateCallback?.("unsaved");
setTimeout(() => this.triggerUpdate());
}
}
@@ -26,10 +34,13 @@ export default class SpacedUpdate {
this.changed = false; // optimistic...
try {
this.stateCallback?.("saving");
await this.updater();
this.stateCallback?.("saved");
} catch (e) {
this.changed = true;
this.stateCallback?.("error");
logError(getErrorMessage(e));
throw e;
}
}
@@ -59,15 +70,22 @@ export default class SpacedUpdate {
this.updateInterval = interval;
}
triggerUpdate() {
async triggerUpdate() {
if (!this.changed) {
return;
}
if (Date.now() - this.lastUpdated > this.updateInterval) {
this.updater();
this.stateCallback?.("saving");
try {
await this.updater();
this.stateCallback?.("saved");
this.changed = false;
} catch (e) {
this.stateCallback?.("error");
logError(getErrorMessage(e));
}
this.lastUpdated = Date.now();
this.changed = false;
} else {
// update isn't triggered but changes are still pending, so we need to schedule another check
this.scheduleUpdate();

View File

@@ -1,121 +1,111 @@
import utils from "./utils.js";
import { signal } from "@preact/signals";
import appContext from "../components/app_context.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import utils, { randomString } from "./utils.js";
export interface ToastOptions {
id?: string;
icon: string;
title?: string;
message: string;
delay?: number;
autohide?: boolean;
closeAfter?: number;
timeout?: number;
progress?: number;
buttons?: {
text: string;
onClick: (api: { dismissToast: () => void }) => void;
}[];
}
function toast({ title, icon, message, id, delay, autohide, progress }: ToastOptions) {
const $toast = $(title
? `\
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">
<span class="bx bx-${icon}"></span>
<span class="toast-title"></span>
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body"></div>
<div class="toast-progress"></div>
</div>`
: `
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-icon">
<span class="bx bx-${icon}"></span>
</div>
<div class="toast-body"></div>
<div class="toast-header">
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-progress"></div>
</div>`
);
export type ToastOptionsWithRequiredId = Omit<ToastOptions, "id"> & Required<Pick<ToastOptions, "id">>;
$toast.toggleClass("no-title", !title);
$toast.find(".toast-title").text(title ?? "");
$toast.find(".toast-body").html(message);
$toast.find(".toast-progress").css("width", `${(progress ?? 0) * 100}%`);
if (id) {
$toast.attr("id", `toast-${id}`);
}
$("#toast-container").append($toast);
$toast.toast({
delay: delay || 3000,
autohide: !!autohide
});
$toast.on("hidden.bs.toast", (e) => e.target.remove());
$toast.toast("show");
return $toast;
}
function showPersistent(options: ToastOptions) {
let $toast = $(`#toast-${options.id}`);
if ($toast.length > 0) {
$toast.find(".toast-body").html(options.message);
$toast.find(".toast-progress").css("width", `${(options.progress ?? 0) * 100}%`);
function showPersistent(options: ToastOptionsWithRequiredId) {
const existingToast = toasts.value.find(toast => toast.id === options.id);
if (existingToast) {
updateToast(options.id, options);
} else {
options.autohide = false;
$toast = toast(options);
}
if (options.closeAfter) {
setTimeout(() => $toast.remove(), options.closeAfter);
addToast(options);
}
}
function closePersistent(id: string) {
$(`#toast-${id}`).remove();
removeToastFromStore(id);
}
function showMessage(message: string, delay = 2000, icon = "check") {
function showMessage(message: string, timeout = 2000, icon = "bx bx-check") {
console.debug(utils.now(), "message:", message);
toast({
addToast({
icon,
message: message,
autohide: true,
delay
message,
timeout
});
}
export function showError(message: string, delay = 10000) {
export function showError(message: string, timeout = 10000) {
console.log(utils.now(), "error: ", message);
toast({
icon: "alert",
message: message,
autohide: true,
delay
addToast({
icon: "bx bx-error-circle",
message,
timeout
});
}
function showErrorTitleAndMessage(title: string, message: string, delay = 10000) {
function showErrorTitleAndMessage(title: string, message: string, timeout = 10000) {
console.log(utils.now(), "error: ", message);
toast({
title: title,
icon: "alert",
message: message,
autohide: true,
delay
addToast({
title,
icon: "bx bx-error-circle",
message,
timeout
});
}
export async function showErrorForScriptNote(noteId: string, message: string) {
const note = await froca.getNote(noteId, true);
showPersistent({
id: `custom-widget-failure-${noteId}`,
title: t("toast.scripting-error", { title: note?.title ?? "" }),
icon: note?.getIcon() ?? "bx bx-error-circle",
message,
timeout: 15_000,
buttons: [
{
text: t("toast.open-script-note"),
onClick: () => appContext.tabManager.openInNewTab(noteId, null, true)
}
]
});
}
//#region Toast store
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
function addToast(opts: ToastOptions) {
const id = opts.id ?? randomString();
const toast = { ...opts, id };
toasts.value = [ ...toasts.value, toast ];
return id;
}
function updateToast(id: string, partial: Partial<ToastOptions>) {
toasts.value = toasts.value.map(toast => {
if (toast.id === id) {
return { ...toast, ...partial };
}
return toast;
});
}
export function removeToastFromStore(id: string) {
toasts.value = toasts.value.filter(toast => toast.id !== id);
}
//#endregion
export default {
showMessage,
showError,

View File

@@ -4,6 +4,8 @@ import froca from "./froca.js";
import hoistedNoteService from "./hoisted_note.js";
import appContext from "../components/app_context.js";
export const NOTE_PATH_TITLE_SEPARATOR = " ";
async function resolveNotePath(notePath: string, hoistedNoteId = "root") {
const runPath = await resolveNotePathToSegments(notePath, hoistedNoteId);
@@ -254,7 +256,7 @@ async function getNotePathTitle(notePath: string) {
const titlePath = await getNotePathTitleComponents(notePath);
return titlePath.join(" / ");
return titlePath.join(NOTE_PATH_TITLE_SEPARATOR);
}
async function getNoteTitleWithPathAsSuffix(notePath: string) {

View File

@@ -1,6 +1,8 @@
import { dayjs } from "@triliumnext/commons";
import type { ViewScope } from "./link.js";
import { snapdom } from "@zumer/snapdom";
import FNote from "../entities/fnote";
import type { ViewMode, ViewScope } from "./link.js";
const SVG_MIME = "image/svg+xml";
@@ -112,9 +114,8 @@ function formatDateISO(date: Date) {
export function formatDateTime(date: Date, userSuppliedFormat?: string): string {
if (userSuppliedFormat?.trim()) {
return dayjs(date).format(userSuppliedFormat);
} else {
return `${formatDate(date)} ${formatTime(date)}`;
}
return `${formatDate(date)} ${formatTime(date)}`;
}
function localNowDateTime() {
@@ -132,6 +133,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`.
*/
@@ -150,7 +153,7 @@ export function isMac() {
export const hasTouchBar = (isMac() && isElectron());
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) {
export function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) {
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
}
@@ -186,13 +189,15 @@ export function formatSize(size: number | null | undefined) {
return "";
}
size = Math.max(Math.round(size / 1024), 1);
if (size < 1024) {
return `${size} KiB`;
} else {
return `${Math.round(size / 102.4) / 10} MiB`;
if (size === 0) {
return "0 B";
}
const k = 1024;
const sizes = ["B", "KiB", "MiB", "GiB"];
const i = Math.floor(Math.log(size) / Math.log(k));
return `${Math.round((size / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;
}
function toObject<T, R>(array: T[], fn: (arg0: T) => [key: string, value: R]) {
@@ -207,7 +212,7 @@ function toObject<T, R>(array: T[], fn: (arg0: T) => [key: string, value: R]) {
return obj;
}
export function randomString(len: number) {
export function randomString(len: number = 16) {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -236,7 +241,7 @@ export function isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent);
}
function isDesktop() {
export function isDesktop() {
return (
window.glob?.device === "desktop" ||
// window.glob.device is not available in setup
@@ -296,18 +301,18 @@ function formatHtml(html: string) {
let indent = "\n";
const tab = "\t";
let i = 0;
let pre: { indent: string; tag: string }[] = [];
const pre: { indent: string; tag: string }[] = [];
html = html
.replace(new RegExp("<pre>([\\s\\S]+?)?</pre>"), function (x) {
.replace(new RegExp("<pre>([\\s\\S]+?)?</pre>"), (x) => {
pre.push({ indent: "", tag: x });
return "<--TEMPPRE" + i++ + "/-->";
return `<--TEMPPRE${i++}/-->`;
})
.replace(new RegExp("<[^<>]+>[^<]?", "g"), function (x) {
.replace(new RegExp("<[^<>]+>[^<]?", "g"), (x) => {
let ret;
const tagRegEx = /<\/?([^\s/>]+)/.exec(x);
let tag = tagRegEx ? tagRegEx[1] : "";
let p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
const tag = tagRegEx ? tagRegEx[1] : "";
const p = new RegExp("<--TEMPPRE(\\d+)/-->").exec(x);
if (p) {
const pInd = parseInt(p[1]);
@@ -317,24 +322,22 @@ function formatHtml(html: string) {
if (["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"].indexOf(tag) >= 0) {
// self closing tag
ret = indent + x;
} else if (x.indexOf("</") < 0) {
//open tag
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
else ret = indent + x;
!p && (indent += tab);
} else {
if (x.indexOf("</") < 0) {
//open tag
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + tab + x.substr(x.length - 1, x.length);
else ret = indent + x;
!p && (indent += tab);
} else {
//close tag
indent = indent.substr(0, indent.length - 1);
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
else ret = indent + x;
}
//close tag
indent = indent.substr(0, indent.length - 1);
if (x.charAt(x.length - 1) !== ">") ret = indent + x.substr(0, x.length - 1) + indent + x.substr(x.length - 1, x.length);
else ret = indent + x;
}
return ret;
});
for (i = pre.length; i--;) {
html = html.replace("<--TEMPPRE" + i + "/-->", pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", pre[i].indent + "</pre>"));
html = html.replace(`<--TEMPPRE${i}/-->`, pre[i].tag.replace("<pre>", "<pre>\n").replace("</pre>", `${pre[i].indent}</pre>`));
}
return html.charAt(0) === "\n" ? html.substr(1, html.length - 1) : html;
@@ -363,11 +366,11 @@ type dynamicRequireMappings = {
export function dynamicRequire<T extends keyof dynamicRequireMappings>(moduleName: T): Awaited<dynamicRequireMappings[T]>{
if (typeof __non_webpack_require__ !== "undefined") {
return __non_webpack_require__(moduleName);
} else {
// explicitly pass as string and not as expression to suppress webpack warning
// 'Critical dependency: the request of a dependency is an expression'
return require(`${moduleName}`);
}
// explicitly pass as string and not as expression to suppress webpack warning
// 'Critical dependency: the request of a dependency is an expression'
return require(`${moduleName}`);
}
function timeLimit<T>(promise: Promise<T>, limitMs: number, errorMessage?: string) {
@@ -438,7 +441,20 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
* @param inAppHelpPage the ID of the help note (excluding the `_help_` prefix).
* @returns a promise that resolves once the help has been opened.
*/
export async function openInAppHelpFromUrl(inAppHelpPage: string) {
export function openInAppHelpFromUrl(inAppHelpPage: string) {
return openInReusableSplit(`_help_${inAppHelpPage}`, "contextual-help");
}
/**
* Similar to opening a new note in a split, but re-uses an existing split if there is already one open with the same view mode.
*
* @param targetNoteId the note ID to open in the split.
* @param targetViewMode the view mode of the split to open the note in.
* @param openOpts additional options for opening the note.
*/
export async function openInReusableSplit(targetNoteId: string, targetViewMode: ViewMode, openOpts: {
hoistedNoteId?: string;
} = {}) {
// Dynamic import to avoid import issues in tests.
const appContext = (await import("../components/app_context.js")).default;
const activeContext = appContext.tabManager.getActiveContext();
@@ -446,23 +462,20 @@ export async function openInAppHelpFromUrl(inAppHelpPage: string) {
return;
}
const subContexts = activeContext.getSubContexts();
const targetNote = `_help_${inAppHelpPage}`;
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
const viewScope: ViewScope = {
viewMode: "contextual-help",
};
if (!helpSubcontext) {
// The help is not already open, open a new split with it.
const existingSubcontext = subContexts.find((s) => s.viewScope?.viewMode === targetViewMode);
const viewScope: ViewScope = { viewMode: targetViewMode };
if (!existingSubcontext) {
// The target split is not already open, open a new split with it.
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {
ntxId,
notePath: targetNote,
hoistedNoteId: "_help",
notePath: targetNoteId,
hoistedNoteId: openOpts.hoistedNoteId,
viewScope
})
});
} else {
// There is already a help window open, make sure it opens on the right note.
helpSubcontext.setNote(targetNote, { viewScope });
// There is already a target split open, make sure it opens on the right note.
existingSubcontext.setNote(targetNoteId, { viewScope });
}
}
@@ -498,8 +511,8 @@ export function escapeRegExp(str: string) {
function areObjectsEqual(...args: unknown[]) {
let i;
let l;
let leftChain: Object[];
let rightChain: Object[];
let leftChain: object[];
let rightChain: object[];
function compare2Objects(x: unknown, y: unknown) {
let p;
@@ -628,16 +641,69 @@ export function createImageSrcUrl(note: FNote) {
return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
}
/**
* Given a string representation of an SVG, triggers a download of the file on the client device.
* Helper function to prepare an element for snapdom rendering.
* Handles string parsing and temporary DOM attachment for style computation.
*
* @param source - Either an SVG/HTML string to be parsed, or an existing SVG/HTML element.
* @returns An object containing the prepared element and a cleanup function.
* The cleanup function removes temporarily attached elements from the DOM,
* or is a no-op if the element was already in the DOM.
*/
function prepareElementForSnapdom(source: string | SVGElement | HTMLElement): {
element: SVGElement | HTMLElement;
cleanup: () => void;
} {
if (typeof source === 'string') {
const parser = new DOMParser();
// Detect if content is SVG or HTML
const isSvg = source.trim().startsWith('<svg');
const mimeType = isSvg ? SVG_MIME : 'text/html';
const doc = parser.parseFromString(source, mimeType);
const element = doc.documentElement;
// Temporarily attach to DOM for proper style computation
element.style.position = 'absolute';
element.style.left = '-9999px';
element.style.top = '-9999px';
document.body.appendChild(element);
return {
element,
cleanup: () => document.body.removeChild(element)
};
}
return {
element: source,
cleanup: () => {} // No-op for existing elements
};
}
/**
* Downloads an SVG using snapdom for proper rendering. Can accept either an SVG string, an SVG element, or an HTML element.
*
* @param nameWithoutExtension the name of the file. The .svg suffix is automatically added to it.
* @param svgContent the content of the SVG file download.
* @param svgSource either an SVG string, an SVGElement, or an HTMLElement to be downloaded.
*/
function downloadSvg(nameWithoutExtension: string, svgContent: string) {
const filename = `${nameWithoutExtension}.svg`;
const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
triggerDownload(filename, dataUrl);
async function downloadAsSvg(nameWithoutExtension: string, svgSource: string | SVGElement | HTMLElement) {
const { element, cleanup } = prepareElementForSnapdom(svgSource);
try {
const result = await snapdom(element, {
backgroundColor: "transparent",
scale: 2
});
triggerDownload(`${nameWithoutExtension}.svg`, result.url);
} finally {
cleanup();
}
}
/**
@@ -658,62 +724,26 @@ function triggerDownload(fileName: string, dataUrl: string) {
document.body.removeChild(element);
}
/**
* Given a string representation of an SVG, renders the SVG to PNG and triggers a download of the file on the client device.
*
* Note that the SVG must specify its width and height as attributes in order for it to be rendered.
* Downloads an SVG as PNG using snapdom. Can accept either an SVG string, an SVG element, or an HTML element.
*
* @param nameWithoutExtension the name of the file. The .png suffix is automatically added to it.
* @param svgContent the content of the SVG file download.
* @returns a promise which resolves if the operation was successful, or rejects if it failed (permissions issue or some other issue).
* @param svgSource either an SVG string, an SVGElement, or an HTMLElement to be converted to PNG.
*/
function downloadSvgAsPng(nameWithoutExtension: string, svgContent: string) {
return new Promise<void>((resolve, reject) => {
// First, we need to determine the width and the height from the input SVG.
const result = getSizeFromSvg(svgContent);
if (!result) {
reject();
return;
}
async function downloadAsPng(nameWithoutExtension: string, svgSource: string | SVGElement | HTMLElement) {
const { element, cleanup } = prepareElementForSnapdom(svgSource);
// Convert the image to a blob.
const { width, height } = result;
// Create an image element and load the SVG.
const imageEl = new Image();
imageEl.width = width;
imageEl.height = height;
imageEl.crossOrigin = "anonymous";
imageEl.onload = () => {
try {
// Draw the image with a canvas.
const canvasEl = document.createElement("canvas");
canvasEl.width = imageEl.width;
canvasEl.height = imageEl.height;
document.body.appendChild(canvasEl);
const ctx = canvasEl.getContext("2d");
if (!ctx) {
reject();
}
ctx?.drawImage(imageEl, 0, 0);
const imgUri = canvasEl.toDataURL("image/png")
triggerDownload(`${nameWithoutExtension}.png`, imgUri);
document.body.removeChild(canvasEl);
resolve();
} catch (e) {
console.warn(e);
reject();
}
};
imageEl.onerror = (e) => reject(e);
imageEl.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgContent)}`;
});
try {
const result = await snapdom(element, {
backgroundColor: "transparent",
scale: 2
});
const pngImg = await result.toPng();
await triggerDownload(`${nameWithoutExtension}.png`, pngImg.src);
} finally {
cleanup();
}
}
export function getSizeFromSvg(svgContent: string) {
const svgDocument = (new DOMParser()).parseFromString(svgContent, SVG_MIME);
@@ -735,11 +765,11 @@ export function getSizeFromSvg(svgContent: string) {
return {
width: parseFloat(width),
height: parseFloat(height)
}
} else {
console.warn("SVG export error", svgDocument.documentElement);
return null;
};
}
console.warn("SVG export error", svgDocument.documentElement);
return null;
}
/**
@@ -786,7 +816,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;
}
@@ -868,9 +898,9 @@ export function mapToKeyValueArray<K extends string | number | symbol, V>(map: R
export function getErrorMessage(e: unknown) {
if (e && typeof e === "object" && "message" in e && typeof e.message === "string") {
return e.message;
} else {
return "Unknown error";
}
return "Unknown error";
}
/**
@@ -925,8 +955,8 @@ export default {
areObjectsEqual,
copyHtmlToClipboard,
createImageSrcUrl,
downloadSvg,
downloadSvgAsPng,
downloadAsSvg,
downloadAsPng,
compareVersions,
isUpdateAvailable,
isLaunchBarConfig

View File

@@ -0,0 +1,498 @@
.bx-ul
{
margin-left: 2em;
padding-left: 0;
list-style: none;
}
.bx-ul > li
{
position: relative;
}
.bx-ul .bx
{
font-size: inherit;
line-height: inherit;
position: absolute;
left: -2em;
width: 2em;
text-align: center;
}
@-webkit-keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@-webkit-keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@-webkit-keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@-webkit-keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@-webkit-keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
.bx-spin
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-spin-hover:hover
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-tada
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-tada-hover:hover
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-flashing
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-flashing-hover:hover
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-burst
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-burst-hover:hover
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-fade-up
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-up-hover:hover
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-down
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-down-hover:hover
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-left
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-left-hover:hover
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-right
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-fade-right-hover:hover
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-xs
{
font-size: 1rem!important;
}
.bx-sm
{
font-size: 1.55rem!important;
}
.bx-md
{
font-size: 2.25rem!important;
}
.bx-lg
{
font-size: 3.0rem!important;
}
.bx-fw
{
font-size: 1.2857142857em;
line-height: .8em;
width: 1.2857142857em;
height: .8em;
margin-top: -.2em!important;
vertical-align: middle;
}
.bx-pull-left
{
float: left;
margin-right: .3em!important;
}
.bx-pull-right
{
float: right;
margin-left: .3em!important;
}
.bx-rotate-90
{
transform: rotate(90deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
}
.bx-rotate-180
{
transform: rotate(180deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
}
.bx-rotate-270
{
transform: rotate(270deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
}
.bx-flip-horizontal
{
transform: scaleX(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';
}
.bx-flip-vertical
{
transform: scaleY(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
}
.bx-border
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: .25em;
}
.bx-border-circle
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: 50%;
}
/** Custom icon **/
.bx-empty {
width: 1em;
display: inline-block;
}

View File

@@ -1,3 +1,5 @@
@import "./boxicons-compat.css";
@font-face {
font-family: Montserrat;
src: url(../fonts/Montserrat-Light.ttf);
@@ -24,8 +26,8 @@
--bs-body-font-family: var(--main-font-family) !important;
--bs-body-font-weight: var(--main-font-weight) !important;
--bs-body-color: var(--main-text-color) !important;
--bs-body-bg: var(--main-background-color) !important;
--ck-mention-list-max-height: 500px;
--bs-body-bg: var(--main-background-color) !important;
--ck-mention-list-max-height: 500px;
--tn-modal-max-height: 90vh;
--tree-item-light-theme-max-color-lightness: 50;
@@ -423,20 +425,19 @@ body.desktop .tabulator-popup-container,
pointer-events: none;
}
.dropdown-menu .disabled .disabled-tooltip {
.dropdown-menu .disabled .contextual-help {
pointer-events: all;
margin-inline-start: 8px;
font-size: 0.75rem;
color: var(--disabled-tooltip-icon-color);
color: var(--contextual-help-icon-color);
cursor: help;
opacity: 0.75;
}
.dropdown-menu .disabled .disabled-tooltip:hover {
.dropdown-menu .disabled .contextual-help:hover {
opacity: 1;
}
.dropdown-menu a:hover:not(.disabled),
.dropdown-item:hover:not(.disabled, .dropdown-container-item),
.tabulator-menu-item:hover,
:root .excalidraw .context-menu .context-menu-item:hover {
@@ -458,6 +459,7 @@ body.desktop .tabulator-popup-container,
}
body.desktop .dropdown-menu:not(#context-menu-container) .dropdown-item,
body.desktop .dropdown-menu .dropdown-toggle,
body #context-menu-container .dropdown-item > span,
body.mobile .dropdown .dropdown-submenu > span {
display: flex;
@@ -471,7 +473,7 @@ body.mobile .dropdown .dropdown-submenu > span {
padding-inline-start: 12px;
}
.dropdown-menu kbd {
.dropdown-menu kbd {
color: var(--muted-text-color);
border: none;
background-color: transparent;
@@ -487,7 +489,7 @@ body.mobile .dropdown .dropdown-submenu > span {
border: 1px solid transparent !important;
}
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
/* This is a workaround for Firefox not supporting break-before / break-after: avoid on columns.
* It usually wraps a menu item followed by a separator / header and another menu item. */
.dropdown-no-break {
break-inside: avoid;
@@ -521,9 +523,7 @@ body.mobile .dropdown .dropdown-submenu > span {
.cm-editor {
height: 100%;
outline: none !important;
border-radius: 6px;
overflow: hidden;
margin: 4px;
font-size: var(--monospace-font-size);
}
@@ -629,6 +629,11 @@ pre:not(.hljs) {
padding: var(--padding-size);
}
pre:has(> .cm-editor) {
padding: 0;
margin: 0;
}
pre > button.copy-button {
position: absolute;
top: var(--copy-button-margin-size);
@@ -720,6 +725,10 @@ table.promoted-attributes-in-tooltip th {
z-index: 32767 !important;
}
.pre-wrap-text {
white-space: pre-wrap;
}
.bs-tooltip-bottom .tooltip-arrow::before {
border-bottom-color: var(--main-border-color) !important;
}
@@ -1015,15 +1024,9 @@ div[data-notify="container"] {
font-family: var(--monospace-font-family);
}
svg.ck-icon {
&.ck-icon_inherit-color path[fill="#333"] {
fill: currentColor;
}
&.note-icon {
color: var(--main-text-color);
font-size: 20px;
}
svg.ck-icon.note-icon {
color: var(--main-text-color);
font-size: 20px;
}
.ck-content {
@@ -1127,11 +1130,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
border-color: var(--main-border-color) !important;
}
.bx-empty {
width: 1em;
display: inline-block;
}
.modal-header {
padding: 0.5rem 1rem 0.5rem 1rem !important; /* make modal header padding slightly smaller */
}
@@ -1141,61 +1139,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
margin: 0 12px;
}
#toast-container {
position: absolute;
width: 100%;
top: 20px;
pointer-events: none;
}
.toast {
--bs-toast-bg: var(--accented-background-color);
--bs-toast-color: var(--main-text-color);
z-index: 9999999999 !important;
pointer-events: all;
overflow: hidden;
}
.toast-header {
background-color: var(--more-accented-background-color) !important;
color: var(--main-text-color) !important;
}
.toast-body {
white-space: preserve-breaks;
overflow: hidden;
}
.toast.no-title {
display: flex;
flex-direction: row;
}
.toast.no-title .toast-icon {
display: flex;
align-items: center;
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
}
.toast.no-title .toast-body {
padding-inline-start: 0;
padding-inline-end: 0;
}
.toast.no-title .toast-header {
background-color: unset !important;
}
.toast .toast-progress {
position: absolute;
bottom: 0;
inset-inline-start: 0;
inset-inline-end: 0;
background-color: var(--toast-text-color) !important;
height: 4px;
transition: width 0.1s linear;
}
.ck-mentions .ck-button {
font-size: var(--detail-font-size) !important;
padding: 5px;
@@ -1376,12 +1319,21 @@ body.desktop li.dropdown-submenu:hover > ul.dropdown-menu {
top: 0;
inset-inline-start: calc(100% - 2px); /* -2px, otherwise there's a small gap between menu and submenu where the hover can disappear */
margin-top: -10px;
min-width: 15rem;
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
max-height: 600px;
overflow: auto;
}
body.desktop .dropdown-submenu > .dropdown-menu {
min-width: max-content;
max-width: 300px;
}
.dropdown-submenu.dropstart > .dropdown-menu {
inset-inline-start: auto;
inset-inline-end: calc(100% - 2px);
}
body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: calc(-100% + 10px);
}
@@ -1428,6 +1380,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
background-color: var(--scrollbar-background-color);
}
::-webkit-scrollbar-button {
display: none;
}
::-webkit-scrollbar-corner {
background-color: inherit;
}
@@ -1652,7 +1608,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
inset-inline-start: 0;
inset-inline-end: 0;
margin: 0 !important;
max-height: 85vh;
max-height: 85vh;
display: flex;
}
@@ -1840,7 +1796,7 @@ button.close:hover {
display: none;
}
.reference-link .bx {
.reference-link .tn-icon {
position: relative;
top: 1px;
margin-inline-end: 3px;
@@ -1992,6 +1948,10 @@ body.electron.platform-darwin:not(.native-titlebar) .tab-row-container {
padding-inline-start: 1em;
}
.tab-row-widget {
contain: inline-size;
}
#tab-row-left-spacer {
width: env(titlebar-area-x);
-webkit-app-region: drag;
@@ -2001,7 +1961,7 @@ body.electron.platform-darwin:not(.native-titlebar):not(.full-screen) #tab-row-l
width: 80px;
}
.tab-row-widget {
body.electron:not(.platform-darwin) .tab-row-container {
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw));
}
@@ -2052,8 +2012,10 @@ body.zen .shared-info-widget,
body.zen .ribbon-container:not(:has(.classic-toolbar-widget)),
body.zen .ribbon-container:has(.classic-toolbar-widget) .ribbon-top-row,
body.zen .ribbon-container .ribbon-body:not(:has(.classic-toolbar-widget)),
body.zen .note-icon-widget,
body.zen .title-row .icon-action,
body.zen .note-badges > *:not(.read-only-badge),
body.zen .ribbon-button-container,
body.zen .inline-title,
body.zen .promoted-attributes-widget,
body.zen .floating-buttons-children > *:not(.bx-edit-alt),
body.zen .action-button,
@@ -2076,11 +2038,11 @@ body.zen #launcher-pane {
}
body.zen .title-row {
display: block !important;
height: unset !important;
-webkit-app-region: drag;
padding-inline-start: env(titlebar-area-x);
padding-inline-end: calc(100vw - env(titlebar-area-width, 100vw) + 2.5em);
border-bottom: none !important;
}
body.zen .floating-buttons {
@@ -2100,8 +2062,6 @@ body.zen .floating-buttons-children .button-widget {
body.zen .note-title-widget,
body.zen .note-title-widget input {
font-size: 1rem !important;
background: transparent !important;
pointer-events: none;
}
body.zen #detail-container {
@@ -2154,65 +2114,114 @@ body.zen .note-split.type-text .scrolling-container {
body.zen:not(.backdrop-effects-disabled) .note-split.type-text .scrolling-container {
--padding-top: 50px; /* Should be enough to cover the title row */
padding-top: var(--padding-top);
scroll-padding-top: var(--padding-top);
}
/* Fixed formatting toolbar */
body.zen .note-split .ribbon-container {
position: fixed;
left: 0;
bottom: 20px;
width: 100%;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
}
body.zen .note-split:focus-within .ribbon-container {
opacity: 1; /* Show when the note split is focused */
}
body.zen .note-split .ribbon-container .ribbon-body {
border: 0;
}
body.zen .note-split .ribbon-container .classic-toolbar-widget {
margin: auto;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color);
}
body.zen .note-split .ribbon-container .classic-toolbar-widget:not(:has(> .ck-toolbar)) {
/* Hide the toolbar wrapper if the toolbar is missing */
display: none;
}
body.zen .note-split:focus-within .ribbon-container .classic-toolbar-widget {
pointer-events: all;
}
@media (max-width: 1300px) {
body.zen .note-split .ribbon-container .classic-toolbar-widget {
/* Set the toolbar to full with */
body.zen:not(.experimental-feature-new-layout) {
.note-split .ribbon-container {
position: fixed;
left: 0;
bottom: 20px;
width: 100%;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
}
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
body.zen .classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
.note-split:focus-within .ribbon-container {
opacity: 1; /* Show when the note split is focused */
}
.note-split .ribbon-container .ribbon-body {
border: 0;
}
.note-split .ribbon-container .classic-toolbar-widget {
margin: auto;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color);
}
.note-split .ribbon-container .classic-toolbar-widget:not(:has(> .ck-toolbar)) {
/* Hide the toolbar wrapper if the toolbar is missing */
display: none;
}
.note-split:focus-within .ribbon-container .classic-toolbar-widget {
pointer-events: all;
}
@media (max-width: 1300px) {
.note-split .ribbon-container .classic-toolbar-widget {
/* Set the toolbar to full with */
width: 100%;
}
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
}
}
}
body.zen.experimental-feature-new-layout {
.status-bar {
display: none;
}
.classic-toolbar-widget {
position: fixed;
left: 50%;
bottom: 20px;
z-index: 1000;
opacity: 0; /* Hidden unless the current note split is focused */
pointer-events: none;
transition: opacity 100ms linear;
width: fit-content;
box-shadow: 0px 10px 20px rgba(0, 0, 0, .1);
border-radius: 8px;
border: 1px solid var(--main-border-color);
padding: 4px;
background: var(--menu-background-color) !important;
transform: translateX(-50%);
backdrop-filter: blur(6px);
}
#root-widget:has(.note-split.type-text:focus-within) .classic-toolbar-widget,
.classic-toolbar-widget:focus-within {
opacity: 1; /* Show when the note split is focused */
pointer-events: all;
}
@media (max-width: 1300px) {
.classic-toolbar-widget {
/* Set the toolbar to full with */
width: 100%;
}
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_se,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sw,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_smw,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_sme,
.classic-toolbar-widget .ck.ck-dropdown .ck-dropdown__panel.ck-dropdown__panel_s {
/* Force toolbar items overflow dropdowns open upwards */
top: auto;
bottom: 100%;
}
}
}
@@ -2406,7 +2415,7 @@ footer.webview-footer button {
gap: 5px;
}
.right-pane-tab .tab-title .bx {
.right-pane-tab .tab-title .tn-icon {
font-size: 1.1em;
}
@@ -2426,7 +2435,7 @@ footer.webview-footer button {
margin-bottom: 0;
}
.admonition::before {
.admonition::before {
color: var(--accent-color);
font-family: boxicons !important;
position: absolute;
@@ -2452,7 +2461,7 @@ footer.webview-footer button {
.ck-content ul.todo-list li:has(> span.todo-list__label input[type="checkbox"]:checked) > span.todo-list__label span.todo-list__label__description {
text-decoration: line-through;
opacity: 0.6;
opacity: 0.6;
}
.chat-options-container {
@@ -2519,6 +2528,11 @@ footer.webview-footer button {
inset-inline-start: 10px;
}
.content-floating-buttons.top-right {
top: 10px;
inset-inline-end: 10px;
}
.content-floating-buttons.bottom-left {
bottom: 10px;
inset-inline-start: 10px;
@@ -2529,18 +2543,11 @@ footer.webview-footer button {
inset-inline-end: 10px;
}
.content-floating-buttons button.bx {
.content-floating-buttons button.tn-icon {
font-size: 130%;
padding: 1px 10px 1px 10px;
}
/* Customized icons */
.bx-tn-toc::before {
content: "\ec24";
transform: rotate(180deg);
}
/* CK Editor */
/* Insert text snippet: limit the width of the listed items to avoid overly long names */
@@ -2585,6 +2592,7 @@ iframe.print-iframe {
position: relative;
flex-grow: 1;
width: 100%;
overflow: hidden;
}
/* Calendar collection */
@@ -2599,7 +2607,7 @@ iframe.print-iframe {
body.mobile {
.split-note-container-widget {
flex-direction: column !important;
.note-split {
width: 100%;
}
@@ -2614,4 +2622,10 @@ iframe.print-iframe {
opacity: 0.4;
}
}
}
}
body.desktop .title-row {
height: 50px;
min-height: 50px;
align-items: center;
}

View File

@@ -19,7 +19,7 @@
--dropdown-border-color: #555;
--dropdown-shadow-opacity: 0.4;
--dropdown-item-icon-destructive-color: #de6e5b;
--disabled-tooltip-icon-color: #7fd2ef;
--contextual-help-icon-color: #7fd2ef;
--accented-background-color: #555;
--more-accented-background-color: #777;
@@ -114,4 +114,8 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.use-note-color {
--custom-color: var(--dark-theme-custom-color);
}
}
span.fancytree-active {
color: var(--dark-theme-custom-color, var(--active-item-text-color));
}

View File

@@ -23,7 +23,7 @@ html {
--dropdown-border-color: #ccc;
--dropdown-shadow-opacity: 0.2;
--dropdown-item-icon-destructive-color: #ec5138;
--disabled-tooltip-icon-color: #004382;
--contextual-help-icon-color: #004382;
--accented-background-color: #f5f5f5;
--more-accented-background-color: #ddd;
@@ -89,13 +89,17 @@ html {
--custom-color: var(--light-theme-custom-color);
}
:root .reference-link,
:root .reference-link:hover,
.ck-content a.reference-link > span,
.board-note {
:root .reference-link.use-note-color,
:root .reference-link.use-note-color:hover,
.ck-content a.reference-link.use-note-color > span,
.board-note.use-note-color {
color: var(--light-theme-custom-color, inherit);
}
.use-note-color {
--custom-color: var(--light-theme-custom-color);
}
}
span.fancytree-active {
color: var(--light-theme-custom-color, var(--active-item-text-color));
}

View File

@@ -6,7 +6,7 @@
*/
:root {
/*
/*
* ⚠️ NOTICE: This theme is currently in the beta stage of development.
* The names and purposes of these CSS variables are subject to frequent changes.
*/
@@ -21,8 +21,8 @@
--subtle-border-color: #313131;
--dropdown-border-color: #404040;
--dropdown-shadow-opacity: 0.6;
--dropdown-item-icon-destructive-color: #de6e5b;
--disabled-tooltip-icon-color: #7fd2ef;
--dropdown-item-icon-destructive-color: #d58477;
--contextual-help-icon-color: #7fd2ef;
--accented-background-color: #555;
@@ -74,11 +74,13 @@
--select-arrow-svg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='transparent' stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/></svg>");
--select-group-heading-text-color: gray;
--link-hover-background: #ffffff26;
--link-hover-color: white;
--link-color: #95c3d9;
--link-hover-background: #75c2e324;
--link-hover-color: var(--link-color);
--link-selection-outline-color: #75c2e385;
--hover-item-text-color: #efefef;
--hover-item-background-color: #ffffff24;
--hover-item-background-color: #ffffff16;
--hover-item-border-color: transparent;
--active-item-text-color: var(--left-pane-text-color);
@@ -170,6 +172,9 @@
--protected-session-active-icon-color: #8edd8e;
--sync-status-error-pulse-color: #f47871;
--classic-toolbar-vert-layout-background-color: #ffffff0d;
--classic-toolbar-horiz-layout-background-color: var(--main-background-color);
--center-pane-vert-layout-background-color-bgfx: #0c0c0c69;
--center-pane-horiz-layout-background-color-bgfx: #1e1e1ec7;
@@ -182,7 +187,7 @@
--tab-close-button-hover-background: #a45353;
--tab-close-button-hover-color: white;
--active-tab-background-color: #ffffff1c;
--active-tab-hover-background-color: var(--active-tab-background-color);
--active-tab-icon-color: #a9a9a9;
@@ -199,9 +204,19 @@
--badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color);
--badge-temporaraily-editable-background-color: #297331;
--badge-read-only-background-color: #af4340;
--badge-share-background-color: #4d4d4d;
--badge-clipped-note-background-color: #295773;
--badge-execute-background-color: #604180;
--note-icon-background-color: #444444;
--note-icon-color: #d4d4d4;
--note-icon-hover-background-color: #555555;
--promoted-attribute-card-background-color: #ffffff21;
--promoted-attribute-card-shadow: none;
--floating-button-shadow-color: #00000080;
--floating-button-background-color: #494949d2;
--floating-button-color: var(--button-text-color);
@@ -220,14 +235,17 @@
--right-pane-item-hover-background: #ffffff26;
--right-pane-item-hover-color: white;
--bottom-panel-background-color: #11111180;
--bottom-panel-title-bar-background-color: #3F3F3F80;
--status-bar-border-color: var(--main-border-color);
--scrollbar-thumb-color: #fdfdfd5c;
--scrollbar-thumb-hover-color: #ffffff7d;
--scrollbar-background-color: transparent;
--scrollbar-border-color: unset; /* Deprecated */
--selection-background-color: #3399FF70;
--link-color: lightskyblue;
--mermaid-theme: dark;
@@ -293,10 +311,10 @@
--custom-bg-color: hsl(var(--custom-color-hue), 20%, 33%, 0.4);
}
:root .reference-link,
:root .reference-link:hover,
.ck-content a.reference-link > span,
.board-note {
:root .reference-link.use-note-color,
:root .reference-link.use-note-color:hover,
.ck-content a.reference-link.use-note-color > span,
.board-note.use-note-color {
color: var(--dark-theme-custom-color, inherit);
}
@@ -320,4 +338,16 @@ body .todo-list input[type="checkbox"]:not(:checked):before {
.use-note-color {
--custom-color: var(--dark-theme-custom-color);
}
.note-split.with-hue,
.quick-edit-dialog-wrapper.with-hue {
--note-icon-custom-background-color: hsl(var(--custom-color-hue), 15.8%, 30.9%);
--note-icon-custom-color: hsl(var(--custom-color-hue), 100%, 76.5%);
--note-icon-hover-custom-background-color: hsl(var(--custom-color-hue), 28.3%, 36.7%);
}
.note-split.with-hue *::selection,
.quick-edit-dialog-wrapper.with-hue *::selection {
--selection-background-color: hsl(var(--custom-color-hue), 49.2%, 35%);
}

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