Compare commits

...

963 Commits

Author SHA1 Message Date
Elian Doran
f6bc65471d fix(fs_sync): sync error on froca 2025-07-26 20:35:20 +03:00
Elian Doran
eb07d4b0ed fix(fs_sync): unique constraint failed 2025-07-26 20:33:54 +03:00
Elian Doran
2c096f3080 fix(fs_sync): new files from server not synced 2025-07-26 19:30:41 +03:00
Elian Doran
bac95c97e5 fix(fs_sync): missing autocomplete 2025-07-26 19:13:40 +03:00
Elian Doran
fe6daac979 fix(fs_sync): modal not showing 2025-07-26 19:05:52 +03:00
Elian Doran
770281214b fix(fs_sync): option not readable/writable by client 2025-07-26 18:56:48 +03:00
Elian Doran
15bd5aa4e4 fix(fs_sync): cls errors in router 2025-07-26 18:40:22 +03:00
Elian Doran
3da6838395 fix(fs_sync): modal shown immediately when entering advanced 2025-07-26 18:31:34 +03:00
Elian Doran
16cdd9e137 feat(fs_sync): draft implementation 2025-07-26 18:31:16 +03:00
Elian Doran
28f88f2407 chore(deps): update dependency dotenv to v17.2.1 (#6476) 2025-07-26 15:48:30 +03:00
Elian Doran
e525a7a0ff chore(deps): update dependency svelte to v5.36.17 (#6484) 2025-07-26 15:48:12 +03:00
Elian Doran
3415f38e0a fix(deps): update dependency eslint-linter-browserify to v9.32.0 (#6485) 2025-07-26 15:47:54 +03:00
Elian Doran
910c0faade chore(deps): update dependency cross-env to v10 (#6486) 2025-07-26 15:47:41 +03:00
renovate[bot]
4ad1bb5e3a chore(deps): update dependency cross-env to v10 2025-07-26 10:38:57 +00:00
renovate[bot]
97f6f0a945 fix(deps): update dependency eslint-linter-browserify to v9.32.0 2025-07-26 10:38:01 +00:00
renovate[bot]
bc78c17a11 chore(deps): update dependency svelte to v5.36.17 2025-07-26 10:37:05 +00:00
Elian Doran
b8e813f7bd feat(promoted_attributes): better indicate no value 2025-07-26 10:12:46 +03:00
Elian Doran
db3581eb26 feat(promoted_attributes): improve color picker aspect 2025-07-26 09:53:26 +03:00
Elian Doran
d23230df68 feat(promoted_attributes): support removing color 2025-07-26 09:49:32 +03:00
Elian Doran
b29781b614 feat(promoted_attributes): add color type 2025-07-26 09:29:27 +03:00
Elian Doran
7d7c3e7cdb fix(ws): new attachments' title not decrypted (closes #6473) 2025-07-25 23:21:44 +03:00
Elian Doran
cbd8cb80ab chore(deps): update nx monorepo to v21.3.7 (#6479) 2025-07-25 23:20:55 +03:00
renovate[bot]
bfcdc34faf chore(deps): update nx monorepo to v21.3.7 2025-07-25 20:20:42 +00:00
Elian Doran
c728e6047d chore(deps): update dependency jiti to v2.5.1 (#6478) 2025-07-25 23:18:45 +03:00
renovate[bot]
4c53a9ba8c chore(deps): update dependency jiti to v2.5.1 2025-07-25 19:47:38 +00:00
Elian Doran
e10a7da7e3 chore(deps): update dependency @sveltejs/kit to v2.26.1 (#6475) 2025-07-25 22:42:39 +03:00
Elian Doran
5cc431b1bf chore(deps): update dependency dotenv to v17.2.1 (#6477) 2025-07-25 22:42:22 +03:00
Elian Doran
734aa2fcb5 fix(deps): update dependency mind-elixir to v5.0.4 (#6480) 2025-07-25 22:42:06 +03:00
Elian Doran
5e37319d9b fix(deps): update eslint monorepo to v9.32.0 (#6481) 2025-07-25 22:41:56 +03:00
renovate[bot]
2e9eb6e3e9 fix(deps): update eslint monorepo to v9.32.0 2025-07-25 19:13:01 +00:00
renovate[bot]
9ce57b123a fix(deps): update dependency mind-elixir to v5.0.4 2025-07-25 19:11:23 +00:00
renovate[bot]
e793168afa chore(deps): update dependency dotenv to v17.2.1 2025-07-25 19:08:36 +00:00
renovate[bot]
d1513424e7 chore(deps): update dependency dotenv to v17.2.1 2025-07-25 19:07:41 +00:00
renovate[bot]
1436a01dbe chore(deps): update dependency @sveltejs/kit to v2.26.1 2025-07-25 19:06:44 +00:00
Elian Doran
b9b936b92a chore(client): change book type to collection (closes #6471) 2025-07-25 19:09:03 +03:00
Elian Doran
adf14bec31 fix(views/board): unable to scroll vertically 2025-07-25 19:00:12 +03:00
Elian Doran
ca1403ffea docs(guide): creating collections & adding a description 2025-07-25 18:06:40 +03:00
Elian Doran
06672e439e docs(guide): board view 2025-07-25 17:57:34 +03:00
Elian Doran
e851701a9e fix(views/board): unable to click while editing column 2025-07-25 16:29:25 +03:00
Elian Doran
9589164008 fix(views/board): column duplication after batch rename 2025-07-25 16:20:33 +03:00
Elian Doran
a88b067081 refactor(views/board): use in-memory model 2025-07-25 16:17:54 +03:00
Elian Doran
b3777e6900 fix(views/board): column desynchronising due to API management 2025-07-25 16:11:26 +03:00
Elian Doran
d2646e291d chore(views/board): remove unnecessary highlight 2025-07-25 15:16:17 +03:00
Elian Doran
99ab9ee66b chore(views/board): set up context menu on the header 2025-07-25 15:15:10 +03:00
Elian Doran
08678e74e6 refactor(views/board): unnecessary fields 2025-07-25 14:57:51 +03:00
Elian Doran
62de52ab17 refactor(views/board): unnecessary API to manually refresh the board 2025-07-25 14:56:50 +03:00
Elian Doran
d9820d9725 fix(views/board): column not clickable after dragging 2025-07-25 14:54:50 +03:00
Elian Doran
fe8a8eeac9 feat(views/board): react to icon and color changes 2025-07-25 14:42:05 +03:00
Elian Doran
dfeb414aff feat(views/board): reintroduce one-click title edit 2025-07-25 12:00:04 +03:00
Elian Doran
69f12a2916 feat(views/board): drag columns by the title and not by a handle 2025-07-25 11:56:44 +03:00
Elian Doran
2b062e938e chore(views/board): use translations 2025-07-25 11:46:18 +03:00
Elian Doran
e0299bd1ae style(views/board): improve new buttons 2025-07-25 11:42:44 +03:00
Elian Doran
ac2f1b56fe style(views/board): shorter cards and smaller gaps 2025-07-25 11:35:07 +03:00
Elian Doran
06d98f6fcf refactor(views/board): unnecessary imports 2025-07-25 11:31:57 +03:00
Elian Doran
bb660d15b2 style(next): improve excalidraw dropdown fit 2025-07-25 11:06:20 +03:00
Elian Doran
4d73cdefef style(client): fix checkboxes override causing issues for canvas (closes #6463) 2025-07-25 10:08:14 +03:00
Elian Doran
313ba3df80 chore(deps): update dependency vite to v7.0.6 (#6465) 2025-07-25 08:24:18 +03:00
renovate[bot]
15377c32c2 chore(deps): update dependency vite to v7.0.6 2025-07-24 20:42:01 +00:00
Elian Doran
22b52f7c4a Merge branch 'main' of github.com:TriliumNext/trilium 2025-07-24 23:39:48 +03:00
Elian Doran
7055f77c91 docs(guide): document new features for geomap 2025-07-24 23:39:25 +03:00
Elian Doran
051fe67176 feat(views/geo): react to icon changes 2025-07-24 22:46:36 +03:00
Elian Doran
90accfcc48 chore(client): fix type errors 2025-07-24 22:26:29 +03:00
Elian Doran
4f99db0c90 refactor(views/geo): use a different attribute 2025-07-24 22:18:30 +03:00
Elian Doran
aeb356bf54 feat(views/geo): allow displaying scale 2025-07-24 22:14:51 +03:00
Elian Doran
0dffa0f333 feat(book_properties): group dark map styles 2025-07-24 21:54:57 +03:00
Elian Doran
d17f5b8447 feat(book_properties): group map style into vector & raster 2025-07-24 21:49:55 +03:00
Elian Doran
b5a57b3c66 style(book_properties): align label properly 2025-07-24 21:32:02 +03:00
Elian Doran
987a3404a9 chore(deps): update ckeditor5 config packages to v12.1.0 (#6466) 2025-07-24 21:24:43 +03:00
Elian Doran
eddc30769f chore(deps): update svelte monorepo (#6467) 2025-07-24 21:23:49 +03:00
Elian Doran
4d455650ba refactor(views/board): split row/column handling 2025-07-24 21:18:49 +03:00
Elian Doran
e2157aab26 fix(views/board): reordering same column not working 2025-07-24 21:18:49 +03:00
Elian Doran
b277f4bf3f feat(views/board): basic refresh after column change 2025-07-24 21:18:48 +03:00
Elian Doran
4047452b0f feat(views/board): drag works in between columns 2025-07-24 21:18:48 +03:00
Elian Doran
cb37724879 feat(views/board): basic column drag support 2025-07-24 21:18:48 +03:00
renovate[bot]
8890893412 chore(deps): update svelte monorepo 2025-07-24 18:01:27 +00:00
renovate[bot]
d0cbda7c0d chore(deps): update ckeditor5 config packages to v12.1.0 2025-07-24 18:00:32 +00:00
Elian Doran
60e7b9ffb0 feat(views/geo): set default theme 2025-07-24 15:52:01 +03:00
Elian Doran
45457c6f76 feat(views/geo): invert marker label on dark themes 2025-07-24 15:46:11 +03:00
Elian Doran
737f41d92b refactor(views/geo): get rid of empty theme 2025-07-24 15:36:06 +03:00
Elian Doran
180841f364 refactor(views/geo): remove dependency to leaflet in map layer 2025-07-24 15:35:03 +03:00
Elian Doran
bea40d4c2f feat(views/geo): add the rest of the map layers 2025-07-24 15:33:39 +03:00
Elian Doran
5f9a054441 refactor(book_properties): use translations 2025-07-24 15:20:32 +03:00
Elian Doran
f90bf1ce7c feat(views/geo): add combobox to adjust style 2025-07-24 15:14:43 +03:00
Elian Doran
8c4ed2d4da feat(views/geo): support vector maps 2025-07-24 15:07:47 +03:00
Elian Doran
0e590a1bbf chore(views/geo): add versatiles vector styles 2025-07-24 15:07:34 +03:00
Elian Doran
218a096135 chore(nx): update instructions 2025-07-24 13:57:41 +03:00
Elian Doran
8407bce370 chore(package): add output style to server:start 2025-07-24 13:57:23 +03:00
Elian Doran
43229f0b99 chore(deps): update nx monorepo to v21.3.5 (#6455) 2025-07-24 10:53:08 +03:00
renovate[bot]
84fa0002b9 chore(deps): update nx monorepo to v21.3.5 2025-07-24 07:22:18 +00:00
Elian Doran
e79c705b20 chore(deps): update dependency webdriverio to v9.18.4 (#6459) 2025-07-24 10:20:10 +03:00
Elian Doran
894d7ce15d chore(deps): update dependency express-openid-connect to v2.19.2 (#6456) 2025-07-24 10:05:07 +03:00
renovate[bot]
5830880582 chore(deps): update dependency webdriverio to v9.18.4 2025-07-24 06:52:40 +00:00
renovate[bot]
caab0f70ff chore(deps): update dependency express-openid-connect to v2.19.2 2025-07-24 06:51:51 +00:00
Elian Doran
641966fcdd chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.2 (#6449) 2025-07-24 09:48:34 +03:00
renovate[bot]
24c22e9bbf chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.2 2025-07-24 06:24:28 +00:00
Elian Doran
795f597bda chore(deps): update dependency jiti to v2.5.0 (#6457) 2025-07-24 09:22:19 +03:00
renovate[bot]
2228663a7e chore(deps): update dependency jiti to v2.5.0 2025-07-24 06:10:37 +00:00
Elian Doran
0c97df357d chore(deps): update dependency eslint-config-prettier to v10.1.8 (#6453) 2025-07-24 09:03:52 +03:00
Elian Doran
19f63f1be0 chore(deps): update dependency esbuild to v0.25.8 (#6452) 2025-07-24 09:03:26 +03:00
Elian Doran
fc000caf73 chore(deps): update svelte monorepo (#6436) 2025-07-24 09:03:08 +03:00
renovate[bot]
78929e0293 chore(deps): update svelte monorepo 2025-07-24 05:49:14 +00:00
renovate[bot]
71e22da987 chore(deps): update dependency esbuild to v0.25.8 2025-07-24 05:45:25 +00:00
renovate[bot]
24e99d9654 chore(deps): update dependency eslint-config-prettier to v10.1.8 2025-07-24 05:39:35 +00:00
Elian Doran
98299da424 chore(deps): update dependency @types/tabulator-tables to v6.2.8 (#6450) 2025-07-24 08:39:04 +03:00
Elian Doran
7014af66b6 chore(deps): update dependency electron to v37.2.4 (#6451) 2025-07-24 08:38:36 +03:00
Elian Doran
659bd90027 chore(deps): update dependency vite to v7.0.5 (#6454) 2025-07-24 08:37:54 +03:00
Elian Doran
146b0c284b chore(deps): update dependency stylelint to v16.22.0 (#6458) 2025-07-24 08:36:59 +03:00
Elian Doran
4a0ac8807f chore(deps): update typescript-eslint monorepo to v8.38.0 (#6460) 2025-07-24 08:36:20 +03:00
renovate[bot]
d67734832e chore(deps): update typescript-eslint monorepo to v8.38.0 2025-07-24 02:16:52 +00:00
renovate[bot]
1673bf026a chore(deps): update dependency stylelint to v16.22.0 2025-07-24 02:14:32 +00:00
renovate[bot]
1f29b000a9 chore(deps): update dependency vite to v7.0.5 2025-07-24 02:10:42 +00:00
renovate[bot]
a6d024123e chore(deps): update dependency electron to v37.2.4 2025-07-24 02:08:10 +00:00
renovate[bot]
fb1a7239ce chore(deps): update dependency @types/tabulator-tables to v6.2.8 2025-07-24 02:07:19 +00:00
Elian Doran
4f71d508cb chore(deps): audit 2025-07-23 22:32:49 +03:00
Elian Doran
2072bd61d1 fix(mermaid): lag during editing (closes #6443) 2025-07-23 22:28:15 +03:00
Elian Doran
6021178b7d feat(hidden_subtree): enforce original title in help 2025-07-23 21:22:58 +03:00
Elian Doran
179b0be2bb chore(deps): update dependency axios to v1.11.0 [security] (#6446) 2025-07-23 21:19:12 +03:00
renovate[bot]
bf2b45dd4a chore(deps): update dependency axios to v1.11.0 [security] 2025-07-23 16:53:39 +00:00
Elian Doran
513561234c chore(deps): update nx monorepo to v21.3.2 (#6438) 2025-07-23 08:57:04 +03:00
renovate[bot]
33da990ae7 chore(deps): update nx monorepo to v21.3.2 2025-07-23 05:43:38 +00:00
Elian Doran
4003946e68 chore(deps): fix pnpm-lock 2025-07-23 08:40:44 +03:00
Elian Doran
21f8d40789 chore(deps): update dependency @stylistic/eslint-plugin to v5.2.2 (#6432) 2025-07-23 08:30:28 +03:00
Elian Doran
d6c698e1d6 chore(deps): update dependency cheerio to v1.1.2 (#6433) 2025-07-23 08:30:04 +03:00
Elian Doran
6c227852ae chore(deps): update dependency openai to v5.10.2 (#6434) 2025-07-23 08:29:50 +03:00
Elian Doran
29cb22c4fd chore(deps): update dependency supertest to v7.1.4 (#6435) 2025-07-23 08:29:32 +03:00
Elian Doran
d040bc9e2d chore(deps): update dependency webdriverio to v9.18.3 (#6437) 2025-07-23 08:29:07 +03:00
Elian Doran
abb92f23a6 fix(deps): update dependency mind-elixir to v5.0.3 (#6439) 2025-07-23 08:28:47 +03:00
Elian Doran
da5c86bb69 chore(deps): update dependency @anthropic-ai/sdk to v0.57.0 (#6440) 2025-07-23 08:28:33 +03:00
Elian Doran
a0d428b12c chore(deps): update dependency express-openid-connect to v2.19.2 (#6441) 2025-07-23 08:28:20 +03:00
Elian Doran
e22fe20e23 chore(deps): update typescript-eslint monorepo to v8.38.0 (#6442) 2025-07-23 08:27:59 +03:00
renovate[bot]
1e6659aff9 chore(deps): update typescript-eslint monorepo to v8.38.0 2025-07-23 02:37:13 +00:00
renovate[bot]
60b32d5b05 chore(deps): update dependency express-openid-connect to v2.19.2 2025-07-23 02:35:35 +00:00
renovate[bot]
e2ee9053a0 chore(deps): update dependency @anthropic-ai/sdk to v0.57.0 2025-07-23 02:34:45 +00:00
renovate[bot]
d2f0422ecc fix(deps): update dependency mind-elixir to v5.0.3 2025-07-23 02:33:49 +00:00
renovate[bot]
bfd97da626 chore(deps): update dependency webdriverio to v9.18.3 2025-07-23 02:32:01 +00:00
renovate[bot]
1fd163f0bb chore(deps): update dependency supertest to v7.1.4 2025-07-23 02:30:18 +00:00
renovate[bot]
d15ce575df chore(deps): update dependency openai to v5.10.2 2025-07-23 02:29:21 +00:00
renovate[bot]
9999ff5a89 chore(deps): update dependency cheerio to v1.1.2 2025-07-23 02:28:26 +00:00
renovate[bot]
4653941082 chore(deps): update dependency @stylistic/eslint-plugin to v5.2.2 2025-07-23 02:27:33 +00:00
Elian Doran
fa509661ab Add grid to canvas (#6429) 2025-07-22 23:53:22 +03:00
Elian Doran
d9a289bf18 refactor(views/board): unnecessary re-render 2025-07-22 23:40:12 +03:00
Papierkorb2292
98c76b713d Save gridModeEnabled in CanvasContent 2025-07-22 19:12:08 +02:00
Papierkorb2292
05ed917a56 Removed disabling grid mode in ExcalidrawTypeWidget 2025-07-22 19:12:08 +02:00
Elian Doran
b833806ec7 feat(share): render inline mermaid (closes #5438) 2025-07-22 20:05:29 +03:00
Elian Doran
7fdef3418a refactor(share): check note type 2025-07-22 19:54:01 +03:00
Elian Doran
49e14ec542 feat(hidden_subtree): remove unexpected branches 2025-07-22 19:19:46 +03:00
Elian Doran
efd9244684 fix(help): missing branches if it was relocated 2025-07-22 18:52:39 +03:00
Elian Doran
318f2d1f8c docs(guide): relocate note list documentation 2025-07-22 18:33:46 +03:00
Elian Doran
92fa1cf052 fix(quick_edit): read-only notes not editable (closes #6425) 2025-07-22 17:30:03 +03:00
Elian Doran
17c6eb1680 fix(export/markdown): simple tables rendered as HTML (closes #6366) 2025-07-22 09:09:50 +03:00
Elian Doran
7c6af568d8 fix(share): ck text on dark theme not visible (closes #6427) 2025-07-22 08:44:45 +03:00
Elian Doran
23c9c6826e chore(env): add some instructions 2025-07-21 19:41:29 +03:00
Elian Doran
b08fda5e10 Kanban board (#6402) 2025-07-21 18:45:41 +03:00
Elian Doran
5ec3a49377 Merge remote-tracking branch 'origin/main' into feature/kanban_board 2025-07-21 18:24:36 +03:00
Elian Doran
1c728ae432 Merge branch 'release/v0.97.1' 2025-07-21 17:52:57 +03:00
Elian Doran
fd25c735c1 chore(release): bump version 2025-07-21 17:52:08 +03:00
Elian Doran
7de33907c5 docs(release): add change log for v0.97.1 2025-07-21 17:51:13 +03:00
Elian Doran
ec021be16c feat(views/board): display even if no children 2025-07-21 15:02:44 +03:00
Elian Doran
8b6826ffa4 feat(views/board): react to changes in "groupBy" 2025-07-21 15:02:38 +03:00
Elian Doran
00cc1ffe74 feat(views/board): add into view type switcher 2025-07-21 15:02:34 +03:00
Elian Doran
2384fdbaad chore(views/board): fix type errors 2025-07-21 15:02:31 +03:00
Elian Doran
08a93d81d7 feat(views/board): allow changing group by attribute 2025-07-21 15:02:28 +03:00
Elian Doran
86911100df refactor(views/board): use single point for obtaining status attribute 2025-07-21 15:02:22 +03:00
Elian Doran
ff01656268 chore(vscode): set up NX LLM integration 2025-07-21 15:02:20 +03:00
Elian Doran
d0ea6d9e8d feat(views/board): use same note title editing mechanism for insert above/below 2025-07-21 15:02:15 +03:00
Elian Doran
96ca3d5e38 fix(views/board): creating new notes would render as HTML 2025-07-21 13:14:07 +03:00
Elian Doran
3a569499cb feat(views/board): edit the note title inline on new 2025-07-21 11:28:46 +03:00
Elian Doran
545b19f978 fix(views/board): drop indicator remaining stuck 2025-07-21 11:19:14 +03:00
Elian Doran
d98be19c9a feat(views/board): set up differential renderer 2025-07-21 11:13:41 +03:00
Elian Doran
4826898c55 refactor(views/board): move drag logic to separate file 2025-07-21 11:01:49 +03:00
Elian Doran
482b592f77 feat(views/board): add drag preview when using touch 2025-07-21 11:01:49 +03:00
Elian Doran
939ebfe47b chore(deps): update dependency cheerio to v1.1.1 (#6417) 2025-07-21 10:07:37 +03:00
Elian Doran
c6dee1339b chore(deps): update dependency svelte to v5.36.12 (#6418) 2025-07-21 10:07:27 +03:00
Elian Doran
23f8c3ad3c chore(deps): update nx monorepo to v21.3.1 (#6419) 2025-07-21 10:06:25 +03:00
renovate[bot]
81c1b88376 chore(deps): update nx monorepo to v21.3.1 2025-07-21 02:58:10 +00:00
renovate[bot]
c4a85db698 chore(deps): update dependency svelte to v5.36.12 2025-07-21 02:57:09 +00:00
renovate[bot]
e6eda45c04 chore(deps): update dependency cheerio to v1.1.1 2025-07-21 02:56:06 +00:00
Elian Doran
a3014434cf chore(release): update version number 2025-07-20 23:39:58 +03:00
Elian Doran
3ebab2c126 docs(release): add changelog 2025-07-20 21:30:17 +03:00
Elian Doran
954619bd36 fix(views/table): note ID column being editable 2025-07-20 21:21:01 +03:00
Elian Doran
eb76362de4 chore(views/board): improve header 2025-07-20 20:55:41 +03:00
Elian Doran
1cde14859b feat(views/board): touch support 2025-07-20 20:31:07 +03:00
Elian Doran
c752b98995 chore(views/board): smaller add new column 2025-07-20 20:22:41 +03:00
Elian Doran
1f792ca418 feat(views/board): add new column 2025-07-20 20:06:54 +03:00
Elian Doran
b22e08b1eb refactor(views/board): use bulk API for renaming columns 2025-07-20 19:59:21 +03:00
Elian Doran
2b5029cc38 chore(views/board): delete values when deleting column 2025-07-20 19:52:16 +03:00
Elian Doran
9e936cb57b feat(views/board): delete empty columns 2025-07-20 19:52:10 +03:00
Elian Doran
e8fd2c1b3c fix(views/board): old column not removed when changing it 2025-07-20 19:52:06 +03:00
Elian Doran
977fbf54ee refactor(views/board): delegate storage to API 2025-07-20 19:52:01 +03:00
Elian Doran
3e5c91415d feat(views/board): rename columns 2025-07-20 19:51:56 +03:00
Elian Doran
d60b855f74 chore(views/board): disable move to for the current column 2025-07-20 19:51:52 +03:00
Elian Doran
4146192b6d chore(views/board): add icon to menu item 2025-07-20 19:51:46 +03:00
Elian Doran
26ee0ff48f feat(views/board): insert above/below 2025-07-20 17:35:52 +03:00
Elian Doran
6995fbfd06 chore(deps): update dependency esbuild to v0.25.8 (#6404) 2025-07-20 16:07:54 +03:00
Elian Doran
1763d80d5f feat(views/board): add move to in context menu 2025-07-20 13:24:22 +03:00
Elian Doran
a594e5147c feat(views/board): set up open in context menu 2025-07-20 12:42:19 +03:00
Elian Doran
e51ea1a619 feat(views/board): add context menu with delete 2025-07-20 12:40:30 +03:00
Elian Doran
83b72eafa6 Merge branch 'main' into renovate/esbuild-0.x-lockfile 2025-07-20 11:31:50 +03:00
Elian Doran
757a6777be chore(deps): update dependency svelte to v5.36.10 (#6405) 2025-07-20 11:30:49 +03:00
Elian Doran
37c9260dca feat(views/board): keep empty columns 2025-07-20 10:50:26 +03:00
Elian Doran
e1a8f4f5db chore(views/board): hide promoted attributes of collection 2025-07-20 10:50:13 +03:00
Elian Doran
b7b0b39afc feat(views/board): add preset notes 2025-07-20 10:36:36 +03:00
Elian Doran
af797489e8 feat(views/board): set up template 2025-07-20 10:30:48 +03:00
renovate[bot]
d003e91b89 chore(deps): update dependency svelte to v5.36.10 2025-07-20 01:09:12 +00:00
renovate[bot]
4a35df745a chore(deps): update dependency esbuild to v0.25.8 2025-07-20 01:08:07 +00:00
Elian Doran
b1b756b179 feat(views/board): store new columns into config 2025-07-19 22:21:24 +03:00
Elian Doran
9e3372df72 feat(views/board): react to changes in note title 2025-07-19 21:50:57 +03:00
Elian Doran
657df7a728 feat(views/board): add new item 2025-07-19 21:45:48 +03:00
Elian Doran
944f0b694b feat(views/board): open in popup 2025-07-19 21:09:55 +03:00
Elian Doran
efd409da17 fix(views/board): some runtime errors 2025-07-19 21:07:29 +03:00
Elian Doran
08d60c554c feat(views/board): set up reordering for same column 2025-07-19 20:44:54 +03:00
Elian Doran
a428ea7beb refactor(views/board): store both branch and note 2025-07-19 20:34:54 +03:00
Elian Doran
f69878b082 refactor(views/board): use branches instead of notes 2025-07-19 20:30:05 +03:00
Elian Doran
c5ffc2882b feat(views/board): react to changes 2025-07-19 19:57:02 +03:00
Elian Doran
765691751a feat(views/board): bypass horizontal scroll if column needs scrolling 2025-07-19 19:53:48 +03:00
Elian Doran
f19e5977c2 feat(views/board): set up dragging 2025-07-19 19:48:03 +03:00
Elian Doran
8f8b9af862 feat(views/board): set up scroll via mouse wheel 2025-07-19 19:31:13 +03:00
Elian Doran
3e7dc71995 feat(views/board): make scrollable 2025-07-19 19:23:42 +03:00
Elian Doran
2a25cd8686 feat(views/board): fixed column size 2025-07-19 19:20:32 +03:00
Elian Doran
7664839135 feat(views/board): display note icon 2025-07-19 19:16:39 +03:00
Elian Doran
47daebc65a feat(views/board): improve display of the notes 2025-07-19 19:03:09 +03:00
Elian Doran
0d18b944b6 feat(views/board): display columns 2025-07-19 18:44:50 +03:00
Elian Doran
951b5384a3 chore(views/board): prepare to group by attribute 2025-07-19 18:39:24 +03:00
Elian Doran
11547ecaa3 chore(views/board): create empty board 2025-07-19 18:29:31 +03:00
Adorian Doran
713a0f5b09 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-07-19 16:28:56 +03:00
Adorian Doran
2cf9c98b43 style/table view: tweak table footer 2025-07-19 16:28:52 +03:00
Elian Doran
d7af196a0c feat(views/table): hide multiplicity when adding a new column 2025-07-19 16:03:58 +03:00
Adorian Doran
c363be57b7 client/table view: tweak icons 2025-07-19 16:02:11 +03:00
Adorian Doran
10645790de Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-07-19 15:55:06 +03:00
Adorian Doran
8b18cf382c style(next)/dropdown menus: fix rotated icons 2025-07-19 15:54:56 +03:00
Elian Doran
7a131e0bcc feat(views/table): support color class for title 2025-07-19 15:44:43 +03:00
Elian Doran
3d264379cc fix(views/table): color no longer shown for reference links 2025-07-19 15:44:42 +03:00
Elian Doran
f405682ec1 Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-19 14:56:21 +03:00
Elian Doran
3debf3ce1c fix(views/calendar): not refreshing on note title change 2025-07-19 14:53:27 +03:00
Adorian Doran
5a76883969 Merge branch 'main' of https://github.com/TriliumNext/Trilium 2025-07-19 14:41:51 +03:00
Adorian Doran
6f51c5e0cc style/attribute detail dialog: fix stretched close button 2025-07-19 14:41:48 +03:00
Elian Doran
2c730d1f0b Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-19 14:24:12 +03:00
Elian Doran
d487da0b2f feat(views/table): update new column in context menu to support relations also 2025-07-19 14:23:42 +03:00
Elian Doran
cb8a5cbb62 chore(views/table): add icons to add new column/row context menu 2025-07-19 14:06:00 +03:00
Elian Doran
ceb08593d8 chore(views/table): use translations for new label/relation 2025-07-19 14:04:25 +03:00
Elian Doran
9dd0eb7b9b fix(views/table): not reacting to external attribute changes 2025-07-19 14:02:19 +03:00
Elian Doran
ebff644d24 fix(views/table): changing column inheritability not working 2025-07-19 13:31:46 +03:00
Elian Doran
beb1c15fa5 fix(views/table): inheritable checkbox not respected 2025-07-19 13:25:54 +03:00
Elian Doran
40a5eee211 docs(views/table): describe exactly how to remove relation 2025-07-19 13:00:08 +03:00
Elian Doran
8f393d0bae refactor(bulk_action): fix type error 2025-07-19 12:57:58 +03:00
Elian Doran
94dad49e2f refactor(bulk_action): full type safety for client 2025-07-19 12:56:37 +03:00
Elian Doran
409638151c refactor(bulk_action): add basic type safety for client 2025-07-19 12:54:16 +03:00
Elian Doran
0d3de92890 refactor(views/table): move bulk action implementation in service 2025-07-19 12:46:38 +03:00
Elian Doran
5d619131ec fix(views/table): bulk actions sometimes not working 2025-07-19 12:44:55 +03:00
Elian Doran
e2c8443778 refactor(bulk_action): remake types & change method signature 2025-07-19 12:32:47 +03:00
Elian Doran
daa4743967 refactor(server): add some type safety to bulk actions 2025-07-19 12:15:33 +03:00
Elian Doran
56553078ef docs(views/table): update documentation 2025-07-19 09:47:10 +03:00
Elian Doran
5584a06cb3 chore(deps): update nx monorepo to v21.3.0 (#6398) 2025-07-19 09:28:55 +03:00
renovate[bot]
cfeb69ace6 chore(deps): update nx monorepo to v21.3.0 2025-07-19 06:03:54 +00:00
Elian Doran
b0c8f110de chore(deps): update dependency @types/node to v22.16.5 (#6392) 2025-07-19 09:00:33 +03:00
Elian Doran
aba1266c45 chore(deps): update svelte monorepo (#6395) 2025-07-19 08:59:56 +03:00
Elian Doran
c331e0103d chore(deps): update dependency eslint-config-prettier to v10.1.8 (#6394) 2025-07-19 08:56:51 +03:00
Elian Doran
13978574e0 chore(deps): update dependency stylelint to v16.22.0 (#6397) 2025-07-19 08:56:25 +03:00
renovate[bot]
be85963558 chore(deps): update dependency stylelint to v16.22.0 2025-07-19 05:55:03 +00:00
Elian Doran
8c19261ced fix(deps): update dependency marked to v16.1.1 (#6396) 2025-07-19 08:53:07 +03:00
Elian Doran
7ca17fa609 chore(deps): update dependency esbuild to v0.25.7 (#6393) 2025-07-19 08:52:36 +03:00
renovate[bot]
3d107572df fix(deps): update dependency marked to v16.1.1 2025-07-19 01:52:34 +00:00
renovate[bot]
f7488655a7 chore(deps): update svelte monorepo 2025-07-19 01:51:41 +00:00
renovate[bot]
876e0a29d4 chore(deps): update dependency eslint-config-prettier to v10.1.8 2025-07-19 01:50:50 +00:00
renovate[bot]
af74375695 chore(deps): update dependency esbuild to v0.25.7 2025-07-19 01:50:00 +00:00
renovate[bot]
896965fec5 chore(deps): update dependency @types/node to v22.16.5 2025-07-19 01:49:07 +00:00
Elian Doran
ba5ef93c1a fix(views/table): wrong type when renaming relations 2025-07-18 21:07:29 +03:00
Elian Doran
ef1153d336 fix(views/table): insert direction no longer working 2025-07-18 20:37:16 +03:00
Elian Doran
0d347f8823 feat(views/table): allow creating relations 2025-07-18 16:52:13 +03:00
Elian Doran
897cdc26ae chore(deps): update dependency express-session to v1.18.2 (#6372) 2025-07-18 11:53:17 +03:00
Elian Doran
aba621c099 fix(deps): update dependency mermaid to v11.9.0 (#6384) 2025-07-18 11:53:03 +03:00
renovate[bot]
839813ebde fix(deps): update dependency mermaid to v11.9.0 2025-07-18 08:35:57 +00:00
renovate[bot]
545e2ddbfc chore(deps): update dependency express-session to v1.18.2 2025-07-18 08:35:17 +00:00
Elian Doran
1d63a5903a fix(deps): update dependency marked to v16.1.0 (#6383) 2025-07-18 11:31:35 +03:00
Elian Doran
2b34c00a0c chore(deps): update dependency openai to v5.10.1 (#6378) 2025-07-18 11:31:17 +03:00
Elian Doran
123068062a chore(deps): update dependency @stylistic/eslint-plugin to v5.2.0 (#6376) 2025-07-18 11:31:01 +03:00
Elian Doran
9a668e8709 chore(deps): update node.js to v22.17.1 (#6374) 2025-07-18 11:30:51 +03:00
Elian Doran
f6f8937d64 chore(deps): update dependency express-rate-limit to v8.0.1 (#6371) 2025-07-18 11:30:24 +03:00
Elian Doran
c9f53a2880 chore(deps): update dependency compression to v1.8.1 (#6369) 2025-07-18 11:30:17 +03:00
Elian Doran
2887e712c3 chore(deps): update dependency multer to v2.0.2 [security] (#6368) 2025-07-18 11:29:21 +03:00
renovate[bot]
5d3a0ed1b4 fix(deps): update dependency marked to v16.1.0 2025-07-18 08:06:03 +00:00
renovate[bot]
334b6319de chore(deps): update dependency openai to v5.10.1 2025-07-18 08:05:15 +00:00
renovate[bot]
4c118c0fd4 chore(deps): update dependency @stylistic/eslint-plugin to v5.2.0 2025-07-18 08:04:29 +00:00
renovate[bot]
db00d60684 chore(deps): update node.js to v22.17.1 2025-07-18 08:04:25 +00:00
renovate[bot]
25b74af363 chore(deps): update dependency express-rate-limit to v8.0.1 2025-07-18 08:03:31 +00:00
renovate[bot]
eb57cf97ad chore(deps): update dependency compression to v1.8.1 2025-07-18 08:02:36 +00:00
renovate[bot]
c92e24363f chore(deps): update dependency multer to v2.0.2 [security] 2025-07-18 08:01:44 +00:00
Elian Doran
8d5d00ac0f chore(deps): update nx monorepo to v21.2.4 (#6375) 2025-07-18 10:59:33 +03:00
renovate[bot]
8b457384ba chore(deps): update nx monorepo to v21.2.4 2025-07-18 07:27:59 +00:00
Elian Doran
fab2d53ece chore(deps): update dependency vite to v7.0.5 (#6373) 2025-07-18 10:25:50 +03:00
renovate[bot]
774f27d8d2 chore(deps): update dependency vite to v7.0.5 2025-07-18 07:10:21 +00:00
Elian Doran
d7f02ef1b3 chore(deps): update dependency webdriverio to v9.18.1 (#6380) 2025-07-18 10:07:52 +03:00
renovate[bot]
97eaa6294c chore(deps): update dependency webdriverio to v9.18.1 2025-07-18 06:44:57 +00:00
Elian Doran
dc02bb0850 chore(deps): update svelte monorepo (#6382) 2025-07-18 09:41:49 +03:00
renovate[bot]
2c8c041e1c chore(deps): update svelte monorepo 2025-07-18 06:04:00 +00:00
Elian Doran
874b1c6654 chore(deps): update dependency electron to v37.2.3 (#6370) 2025-07-18 09:02:15 +03:00
Elian Doran
fb982c7097 fix(views/table): regression in restoring column width 2025-07-18 09:01:24 +03:00
Elian Doran
b7f5ce600e chore(renovate): add more packages to svelte monorepo 2025-07-18 08:58:35 +03:00
renovate[bot]
91604c9e26 chore(deps): update dependency electron to v37.2.3 2025-07-18 01:55:54 +00:00
Elian Doran
c874333a37 chore(client): fix type errors 2025-07-17 22:38:00 +03:00
Elian Doran
1298b968f2 fix(views/table): relation display sometimes not showing up 2025-07-17 22:31:54 +03:00
Elian Doran
6fe5a854a7 feat(views/table): allow deleting relations 2025-07-17 21:44:09 +03:00
Elian Doran
aba3b5cb19 feat(views/table): hide all buttons in relation editor 2025-07-17 21:07:44 +03:00
Elian Doran
282aed22b5 feat(views/geomap): support recursive notes 2025-07-17 20:51:15 +03:00
Elian Doran
669a3d9dcf feat(views/table): automatic index col width 2025-07-17 20:44:31 +03:00
Elian Doran
9d7455d28a fix(views/table): expander style affecting row number 2025-07-17 20:44:00 +03:00
Elian Doran
4f0c8b081c feat(views): improve style in collections properties 2025-07-17 19:56:14 +03:00
Elian Doran
a5db5298a0 feat(views/table): integrate depth limit into collection properties 2025-07-17 19:44:34 +03:00
Elian Doran
876c6e9252 feat(views/table): allow limiting depth 2025-07-17 19:34:29 +03:00
Elian Doran
aef824d262 feat(views/table): add a context menu for the header outside columns 2025-07-17 15:36:33 +03:00
Elian Doran
a25ce42490 feat(views/table): allow hiding number row & title 2025-07-17 15:00:19 +03:00
Elian Doran
8b0fdaccf4 feat(views/table): improve alignment for first level + increase indentation 2025-07-17 14:45:38 +03:00
Elian Doran
bd840a2421 feat(views/table): align items expanders 2025-07-17 14:40:44 +03:00
Elian Doran
27d515f289 refactor(views/table): use builtin way of disabling branch elements 2025-07-17 14:34:40 +03:00
Elian Doran
df3b9faf8d fix(client): tree operations no longer working due to loss of focus 2025-07-17 14:05:19 +03:00
Elian Doran
0f129734ae fix(link): popup triggering with bare right click 2025-07-17 11:19:29 +03:00
Elian Doran
275aacfba9 chore(vscode): add search excludes 2025-07-16 21:40:14 +03:00
Elian Doran
e7f47a0663 feat(views/table): delete column definition as well 2025-07-16 21:40:01 +03:00
Elian Doran
66486541fe feat(client): batch delete column values 2025-07-16 21:30:16 +03:00
Elian Doran
34f1a84769 fix(views/table): wrong position when renaming column 2025-07-16 09:23:06 +03:00
Elian Doran
2244f0368f fix(views/table): index column ends up in the wrong position 2025-07-16 09:16:47 +03:00
Elian Doran
9d85005255 chore(deps): update dependency express-rate-limit to v8 (#6362) 2025-07-16 08:32:51 +03:00
Elian Doran
ad8629dca6 chore(deps): update typescript-eslint monorepo to v8.37.0 (#6361) 2025-07-16 08:31:56 +03:00
Elian Doran
cccfe0e05a chore(deps): update svelte monorepo (#6360) 2025-07-16 08:31:36 +03:00
Elian Doran
a8874257e8 fix(deps): update dependency @codemirror/view to v6.38.1 (#6359) 2025-07-16 08:29:40 +03:00
Elian Doran
f689c55f56 chore(deps): update node.js to v22.17.1 (#6358) 2025-07-16 08:28:48 +03:00
Elian Doran
853c7be8b8 chore(deps): update dependency openai to v5.9.2 (#6357) 2025-07-16 08:28:39 +03:00
Elian Doran
823df1e12d chore(deps): update dependency electron to v37.2.2 (#6356) 2025-07-16 08:14:56 +03:00
renovate[bot]
7570f818e9 chore(deps): update dependency express-rate-limit to v8 2025-07-16 02:39:01 +00:00
renovate[bot]
03aa5aea2c chore(deps): update typescript-eslint monorepo to v8.37.0 2025-07-16 02:38:09 +00:00
renovate[bot]
a4e86ac353 chore(deps): update svelte monorepo 2025-07-16 02:36:37 +00:00
renovate[bot]
cf6efc050a fix(deps): update dependency @codemirror/view to v6.38.1 2025-07-16 02:35:51 +00:00
renovate[bot]
3e0802176b chore(deps): update node.js to v22.17.1 2025-07-16 02:35:04 +00:00
renovate[bot]
697954d4d9 chore(deps): update dependency openai to v5.9.2 2025-07-16 02:34:06 +00:00
renovate[bot]
741f6c1114 chore(deps): update dependency electron to v37.2.2 2025-07-16 02:33:19 +00:00
Adorian Doran
b2237ffa51 style/collections/tables: tweak nested rows 2025-07-16 05:23:13 +03:00
Adorian Doran
7b6d11bffa style/collections/tables: fix frozen cells overlapping with the outline of the left-side cells 2025-07-16 05:04:06 +03:00
Adorian Doran
97565e8f36 style(next)/collection/tables: improve the color scheme 2025-07-16 04:22:43 +03:00
perf3ct
c0dfee8439 fix(metrics): don't assign a timestamp to Prometheus metrics, let the scraper assign the timestamp to the time series 2025-07-15 20:39:36 +00:00
Elian Doran
fc98240614 chore(client): fix type error 2025-07-15 22:36:30 +03:00
Elian Doran
169d1203c2 fix(views/table): some context menu items active when they shouldn't 2025-07-15 22:30:52 +03:00
Elian Doran
f3350bc8f5 refactor(views/table): better cleanup 2025-07-15 22:06:32 +03:00
Elian Doran
504a19275c feat(views/table): basic renaming of fields 2025-07-15 21:48:16 +03:00
Elian Doran
14cdc52670 feat(views/table): support renaming columns 2025-07-15 20:42:47 +03:00
Elian Doran
cf8063f311 feat(views/table): format note ID as monospace 2025-07-15 19:32:13 +03:00
Elian Doran
aa8902f5b9 fix(client): popup not displayed for existing attributes (closes #5718) 2025-07-15 18:55:29 +03:00
Elian Doran
7cd0e664ac feat(views/table): basic editing of columns (rename not supported) 2025-07-15 18:51:51 +03:00
Elian Doran
a04804d3fa fix(views/table): wrong specs when restoring columns 2025-07-15 18:39:20 +03:00
Elian Doran
86f90e6685 fix(api): also rate limit etapi docs endpoint (#6352) 2025-07-15 17:06:52 +03:00
Elian Doran
8131a4b3d2 fix(views/table): events/commands not well sent 2025-07-15 15:49:32 +03:00
Elian Doran
b91a3e13b0 refactor(views/table): move row editing to own component 2025-07-15 15:32:30 +03:00
Elian Doran
5a7a0d32d1 refactor(views/table): move col editing to own component 2025-07-15 14:53:18 +03:00
perf3ct
3f5df18d6c fix(api): also rate limit etapi docs endpoint 2025-07-14 21:12:00 +00:00
Elian Doran
df2cede075 fix(views/calendar): nested entries in calendar view 2025-07-14 23:12:55 +03:00
Elian Doran
4321c161ac fix(views/calendar): duplicate entries in calendar view 2025-07-14 23:07:26 +03:00
Elian Doran
b1f0c64ef2 chore(views/geo): typing issue 2025-07-14 22:52:37 +03:00
Elian Doran
c9b37dcc77 refactor(views/table): rename event 2025-07-14 21:06:44 +03:00
Elian Doran
ab093ed9a0 chore(views/table): add translations 2025-07-14 20:59:29 +03:00
Elian Doran
cf31367acd feat(views/table): insert column to the right 2025-07-14 20:42:37 +03:00
Elian Doran
e3d306cac3 fix(views/table): wrong insert position for insert left 2025-07-14 20:34:05 +03:00
Elian Doran
960d321019 fix(views/table): position not restored after new columns (closes #6285) 2025-07-14 20:32:24 +03:00
Elian Doran
2d4ac93221 feat(views/table): basic implementation for inserting columns at position 2025-07-14 19:14:10 +03:00
Elian Doran
d4a4f15416 refactor(views/table): move attribute detail widget to view 2025-07-14 17:29:20 +03:00
Elian Doran
504a842d37 feat(views/table): force a refresh if #sorted is changed 2025-07-14 17:02:07 +03:00
Elian Doran
ded5b1f5d2 feat(views/table): expand child notes by default 2025-07-14 17:00:01 +03:00
Elian Doran
fcbbc21a80 feat(views/table): force a refresh if data tree changes 2025-07-14 16:58:14 +03:00
Elian Doran
38fce25b86 fix(views/table): show/hide columns not always updated properly 2025-07-14 16:51:20 +03:00
Elian Doran
4cc2fa5300 fix(snippets): warning about missing note IDs when deleting 2025-07-14 16:49:42 +03:00
Elian Doran
4a82c3f65a fix(views/table): insert above/below not working in nested trees 2025-07-14 16:49:29 +03:00
Elian Doran
b255d70e18 fix(views/table): context menu remains active while clicking on an expand/collapse button 2025-07-14 16:24:54 +03:00
Elian Doran
caa842cd55 fix(views/table): unable to update state for newly created rows 2025-07-14 16:16:55 +03:00
Elian Doran
cd338085fb refactor(views/table): clean up 2025-07-14 15:52:21 +03:00
Elian Doran
e703ce92a8 refactor(views/table): simplify context menu handling 2025-07-14 15:46:22 +03:00
Elian Doran
84479a2c2a feat(views/table): focus if creating child note 2025-07-14 15:38:57 +03:00
Elian Doran
c13969217c feat(views/table): insert child note 2025-07-14 13:37:18 +03:00
Elian Doran
402540f483 feat(views/table): support recursive children update 2025-07-14 13:15:41 +03:00
Elian Doran
8c56315313 refactor(views): move full height detection to rendererer 2025-07-14 12:56:17 +03:00
Elian Doran
b29c3eff6e refactor(views): prepare for supporting subtrees 2025-07-14 12:53:11 +03:00
Elian Doran
ec7dacfc9b feat(views/table): improve expand/collapse button 2025-07-14 12:04:13 +03:00
Elian Doran
5f9a6a9f76 feat(views/table): integrate expander into note title section 2025-07-14 11:39:12 +03:00
Elian Doran
28f4aea3d5 refactor(views/table): use slightly more performant formatter for row number 2025-07-14 11:30:46 +03:00
Elian Doran
8d29c5fe1b feat(views/table): hide draggable rows if not supported 2025-07-14 11:29:14 +03:00
Elian Doran
ccd935b562 refactor(views/table): don't configure reordering rows if not available 2025-07-14 11:22:32 +03:00
Elian Doran
d77a49857b feat(views/table): basic nested tree support 2025-07-14 11:11:08 +03:00
Elian Doran
e30478e5d4 chore(views/table): disable menu module since it's no longer necessary 2025-07-14 10:45:01 +03:00
Elian Doran
71863752cd feat(views/table): display both promoted and non-promoted attributes 2025-07-14 10:38:45 +03:00
Elian Doran
e4a2a8e56d fix(text): selection and cursor not maintained properly when switching tabs 2025-07-14 09:58:58 +03:00
Elian Doran
0f1c505823 fix(tab): editor not focused after switching tabs 2025-07-14 09:58:58 +03:00
Elian Doran
1ecce11113 chore(deps): update dependency vite-plugin-static-copy to v3.1.1 (#6345) 2025-07-14 08:10:18 +03:00
renovate[bot]
2287d67fb5 chore(deps): update dependency vite-plugin-static-copy to v3.1.1 2025-07-13 19:16:04 +00:00
Elian Doran
5b4f17ef3d Update README.md (#6344) 2025-07-13 22:14:33 +03:00
Elian Doran
3720ab6df6 fix(views/table): not reacting to title changes 2025-07-13 21:38:23 +03:00
diyoyo
3c893d69e5 Update README.md
updating from `Notes` to `Trilium` in the `Contribute`section.
2025-07-13 20:29:02 +02:00
Elian Doran
b93a4a3e42 fix(views/table): booleans not working 2025-07-13 21:06:41 +03:00
Elian Doran
23cef0ab94 chore(views/table): translate row menu 2025-07-13 16:56:03 +03:00
Elian Doran
c8ffb8d694 chore(views/table): translate column menu 2025-07-13 16:52:29 +03:00
Elian Doran
08e08d8920 feat(views/table): improve column context menu 2025-07-13 16:45:04 +03:00
Elian Doran
7acd300163 feat(views/table): add option to clear sorting 2025-07-13 16:41:43 +03:00
Elian Doran
d8d95db4ec feat(views/table): add sort by 2025-07-13 16:37:45 +03:00
Elian Doran
af97d3ef1d feat(views/table): add back show/hide columns 2025-07-13 16:22:57 +03:00
Elian Doran
c65ec14943 feat(views/table): hide column in contetx menu 2025-07-13 14:37:13 +03:00
Elian Doran
adfdc7edb4 feat(views/table): drag handle to avoid editing issues 2025-07-13 14:24:12 +03:00
Elian Doran
8cced607eb feat(views/table): insert row before 2025-07-13 14:10:37 +03:00
Elian Doran
5dd5af90c2 feat(views/table): insert row below 2025-07-13 13:06:53 +03:00
Elian Doran
7a48333b4f chore(deps): update dependency @sveltejs/vite-plugin-svelte to v6 (#6341) 2025-07-13 08:22:24 +03:00
Elian Doran
7044533398 fix(deps): update dependency mind-elixir to v5.0.2 (#6340) 2025-07-13 08:21:06 +03:00
renovate[bot]
560aad8df6 chore(deps): update dependency @sveltejs/vite-plugin-svelte to v6 2025-07-13 01:34:38 +00:00
renovate[bot]
36c2099b2e fix(deps): update dependency mind-elixir to v5.0.2 2025-07-13 01:33:47 +00:00
Elian Doran
6c157675d7 feat(views/table): open in new tab/quick edit 2025-07-13 00:44:44 +03:00
Elian Doran
458d66cb21 feat(views/table): delete row from context menu (closes #6288) 2025-07-13 00:36:34 +03:00
Elian Doran
201e8911c5 chore: prefer short name 2025-07-12 23:48:42 +03:00
Elian Doran
1b1ed2408f feat(funding): add Buy Me a Coffee 2025-07-12 23:28:07 +03:00
Elian Doran
62487d21d8 feat(funding): add LiberaPay 2025-07-12 23:20:55 +03:00
Elian Doran
bc752bdb0b fix(popup_editor): note icon overlapping with classic editor 2025-07-12 22:38:20 +03:00
Elian Doran
9e00d421fb fix(ckeditor): color and font mismatch after update 2025-07-12 22:34:27 +03:00
Elian Doran
e7f02fe22b fix(deps): update ckeditor monorepo (major) (#6283) 2025-07-12 22:03:15 +03:00
Elian Doran
6d694f8e53 chore(client): update types 2025-07-12 20:20:41 +03:00
Elian Doran
977befd0a7 chore(ckeditor5): update ckeditor theme variable names 2025-07-12 20:00:01 +03:00
Elian Doran
1566ae4fbd chore(ckeditor5): fix references: DocumentSelection -> ModelDocumentSelection 2025-07-12 19:45:00 +03:00
Elian Doran
4e97490cc6 chore(ckeditor5): fix references: Selectable -> ModelSelectable 2025-07-12 19:44:38 +03:00
Elian Doran
446d5a0fcc chore(ckeditor5): fix references: Item -> ModelItem 2025-07-12 19:44:12 +03:00
Elian Doran
1fd6465012 chore(ckeditor5): fix references: NodeAttributes -> ModelNodeAttributes 2025-07-12 19:43:48 +03:00
Elian Doran
6cea8e3b87 chore(ckeditor5): fix references: Range -> ModelRange 2025-07-12 19:43:18 +03:00
Elian Doran
28a63e0326 chore(ckeditor5): fix references: DocumentFragment -> ModelDocumentFragment 2025-07-12 19:42:54 +03:00
Elian Doran
b73da46111 chore(ckeditor5): fix references: Writer -> ModelWriter 2025-07-12 19:42:26 +03:00
Elian Doran
abafa8c2d2 chore(ckeditor5): fix references: Position -> ModelPosition 2025-07-12 19:41:30 +03:00
Elian Doran
4ae3272cdf chore(ckeditor5): fix references: Element -> ModelElement 2025-07-12 19:40:24 +03:00
Elian Doran
6aa3b8dbd7 chore(ckeditor5-admonition): fix references 2025-07-12 19:38:36 +03:00
Elian Doran
395e9b2228 chore(ckeditor5-admonition): fix references: DocumentFragment -> ViewDocumentFragment 2025-07-12 19:29:51 +03:00
Elian Doran
be33f68c52 chore(ckeditor5-math): fix references: DowncastWriter -> ViewDowncastWriter 2025-07-12 19:28:28 +03:00
Elian Doran
29d96381fa chore(ckeditor5-math): fix references: LiveRange -> ModelLiveRange 2025-07-12 19:27:46 +03:00
Elian Doran
da8eecf774 chore(ckeditor5-math): fix references: LivePosition -> ModelLivePosition 2025-07-12 19:27:22 +03:00
Elian Doran
de91326c12 chore(ckeditor5-math): fix references: Element -> ModelElement 2025-07-12 19:26:52 +03:00
Elian Doran
ee1c3c35d7 chore(ckeditor5-mermaid): fix references: {Item,Node} -> Model{Item,Node} 2025-07-12 19:25:40 +03:00
Elian Doran
70eece1429 chore(ckeditor5-mermaid): fix references: Element -> ModelElement 2025-07-12 19:24:39 +03:00
Elian Doran
b4f2be332b chore(ckeditor5-footnotes): fix references: Schema -> ModelSchema 2025-07-12 19:23:44 +03:00
Elian Doran
23fe76989b chore(ckeditor5-footnotes): fix references: Writer -> ModelWriter 2025-07-12 19:23:40 +03:00
Elian Doran
275d07659d chore(ckeditor5-footnotes): fix references: Range -> ModelRange 2025-07-12 19:23:37 +03:00
Elian Doran
a901e92573 chore(ckeditor5-footnotes): fix references: Element -> ModelElement 2025-07-12 19:23:33 +03:00
Elian Doran
6ead31b45f chore(ckeditor5-footnotes): fix references: RootElement -> ModelRootElement 2025-07-12 19:23:30 +03:00
Elian Doran
d4ce12dca9 chore(ckeditor5-footnotes): fix references: TextProxy -> ModelTextProxy 2025-07-12 19:23:25 +03:00
Elian Doran
bb6e22cdb7 chore(ckeditor5-footnotes): fix references: Text -> ModelText 2025-07-12 19:23:13 +03:00
Elian Doran
2c9fc4812e chore(deps): update dependency electron to v37.2.1 (#6303) 2025-07-12 19:05:18 +03:00
Elian Doran
60f4554afa Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-12 19:04:43 +03:00
Elian Doran
3c486bfd1b chore(ci): set personal access token for conflict checker 2025-07-12 19:04:41 +03:00
Elian Doran
26b9a95bb2 chore(deps): update svelte monorepo (#6316) 2025-07-12 19:00:30 +03:00
Elian Doran
f7c9217cea Table Collections: restyle (#6298) 2025-07-12 18:59:56 +03:00
Elian Doran
e92022b73c Tree Context Menu: relocate the "Duplicate subtree" menu item (#6299) 2025-07-12 18:52:35 +03:00
Elian Doran
61ff2353c8 docs(help): update documentation on duplicate subtree 2025-07-12 18:52:12 +03:00
Elian Doran
c8cca26ca4 Merge remote-tracking branch 'origin/main' into feat/tree-context-menu/relocate-duplicate-note-command 2025-07-12 18:41:58 +03:00
Elian Doran
aa556ed4d5 Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-12 18:40:28 +03:00
Elian Doran
5d694a7bdf chore(ci): permissions issue with merge checker 2025-07-12 18:40:25 +03:00
renovate[bot]
c4787dae23 fix(deps): update ckeditor monorepo 2025-07-12 10:57:11 +00:00
renovate[bot]
9f5f329c53 chore(deps): update dependency electron to v37.2.1 2025-07-12 10:55:36 +00:00
Elian Doran
f82b96fcc4 chore(deps): update dependency @types/node to v22.16.3 (#6302) 2025-07-12 13:53:21 +03:00
renovate[bot]
d4b24fa427 chore(deps): update svelte monorepo 2025-07-12 10:41:27 +00:00
renovate[bot]
c852f67c59 chore(deps): update dependency @types/node to v22.16.3 2025-07-12 10:39:43 +00:00
Elian Doran
92c228a3c9 chore(deps): update nx monorepo to v21.2.3 (#6306) 2025-07-12 13:17:34 +03:00
renovate[bot]
42f948e2b3 chore(deps): update nx monorepo to v21.2.3 2025-07-12 08:59:08 +00:00
Elian Doran
13e8932117 chore(deps): update dependency @playwright/test to v1.54.1 (#6308) 2025-07-12 11:55:42 +03:00
renovate[bot]
910d34bd42 chore(deps): update dependency @playwright/test to v1.54.1 2025-07-12 07:57:58 +00:00
Elian Doran
b204ba29e7 fix(deps): update eslint monorepo to v9.31.0 (#6311) 2025-07-12 10:54:00 +03:00
Elian Doran
d49244cbc8 Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-12 10:08:35 +03:00
Elian Doran
ef2f2f17b4 feat(ci): mark PRs with merge conflicts 2025-07-12 10:08:33 +03:00
renovate[bot]
b9f21dcf4c fix(deps): update eslint monorepo to v9.31.0 2025-07-12 07:03:21 +00:00
Elian Doran
808fe690cc chore(deps): update dependency openai to v5.9.0 (#6309) 2025-07-12 10:01:18 +03:00
Elian Doran
901eec04e5 chore(deps): update dependency vite to v7.0.4 (#6305) 2025-07-12 10:01:02 +03:00
Elian Doran
9272394ada fix(deps): update dependency eslint-linter-browserify to v9.31.0 (#6310) 2025-07-12 09:59:19 +03:00
Elian Doran
4457982fae chore(deps): update ckeditor5 config packages to v12 (major) (#6312) 2025-07-12 09:59:05 +03:00
Elian Doran
7f67b2b461 chore(deps): update dependency dotenv to v17.2.0 (#6279) 2025-07-12 09:58:05 +03:00
Elian Doran
7f3934f4c3 chore(renovate): group svelte as monorepo 2025-07-12 09:57:29 +03:00
renovate[bot]
a3b80a2cc4 chore(deps): update ckeditor5 config packages to v12 2025-07-12 01:37:21 +00:00
renovate[bot]
6d967e5e51 fix(deps): update dependency eslint-linter-browserify to v9.31.0 2025-07-12 01:34:54 +00:00
renovate[bot]
b674ca90d1 chore(deps): update dependency openai to v5.9.0 2025-07-12 01:34:03 +00:00
renovate[bot]
95edb60a84 chore(deps): update dependency vite to v7.0.4 2025-07-12 01:29:46 +00:00
Adorian Doran
40add78ccb client/tree context menu: update translations 2025-07-12 02:22:00 +03:00
Adorian Doran
1029c24c06 client/tree context menu: relocate the "Duplicate subtree" menu item 2025-07-12 02:21:22 +03:00
Adorian Doran
94d94fe8fb Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/style/collections/table 2025-07-12 01:56:04 +03:00
Adorian Doran
49489c0f45 style/table collections: refactor 2025-07-12 01:55:07 +03:00
Adorian Doran
215833a2c9 style/table collections: tweak table footer 2025-07-12 01:40:18 +03:00
Adorian Doran
a7471a3d47 style/table collections: tweak checkbox cells 2025-07-12 01:34:22 +03:00
Adorian Doran
909aaefbd7 style/table collections: restyle context menus 2025-07-12 01:17:09 +03:00
Elian Doran
15c2f56bf2 fix(options): display a less ambiguous/scary message after performing… (#6284) 2025-07-12 00:34:23 +03:00
Elian Doran
84cdfec415 Popup editor (#6292) 2025-07-12 00:30:33 +03:00
Elian Doran
91572ab8b9 fix(popup_editor): use cmd on macos 2025-07-11 22:53:14 +03:00
Adorian Doran
ed758f4c92 style/table collections: tweak appearance 2025-07-11 22:39:49 +03:00
Elian Doran
f1fc15e115 fix(link): popup menu no longer triggering 2025-07-11 22:34:41 +03:00
Adorian Doran
22300e8151 style/table collections: tweak appearance 2025-07-11 21:52:35 +03:00
Elian Doran
292646e14a fix(popup_editor): styles showing up when classic toolbar is shown 2025-07-11 20:46:48 +03:00
Elian Doran
b4921a20d8 fix(client): type errors 2025-07-11 20:08:35 +03:00
Elian Doran
54be79a725 feat(in-app-help): link grid/list book types 2025-07-11 19:43:12 +03:00
Elian Doran
4fc47370fe docs(help): fix some old references to books 2025-07-11 19:42:19 +03:00
Elian Doran
9e30bcf233 docs(help): improve documentation on collections 2025-07-11 19:40:54 +03:00
Elian Doran
e5712c54e6 docs(help): add a section on feature highlights 2025-07-11 19:09:42 +03:00
Elian Doran
2a4fe21a39 docs(help): document keyboard shortcuts for note tree 2025-07-11 18:50:56 +03:00
Elian Doran
b259558f0f docs(help): document note tooltip 2025-07-11 18:33:16 +03:00
Elian Doran
e2f6d9e0d6 docs(help): document quick edit 2025-07-11 18:27:55 +03:00
Elian Doran
4fc2b0fa5e feat(popup_editor): focus on editor automatically for easier editing 2025-07-11 16:52:13 +03:00
Elian Doran
8dca79ecf2 fix(popup_editor): block toolbar from behind modal interfering 2025-07-11 16:52:13 +03:00
Elian Doran
c7f49f0e21 chore(popup_editor): switch keyboard combo to Ctrl+right click 2025-07-11 16:52:13 +03:00
Elian Doran
bce2094fb2 fix(tree): middle click triggering paste 2025-07-11 16:52:13 +03:00
renovate[bot]
65c33e1aa0 chore(deps): update dependency webdriverio to v9.17.0 2025-07-11 16:52:13 +03:00
renovate[bot]
8e108bc5e2 chore(deps): update dependency svelte to v5.35.5 2025-07-11 16:52:13 +03:00
renovate[bot]
4e75ce7fdb chore(deps): update pnpm to v10.13.1 2025-07-11 16:52:13 +03:00
renovate[bot]
1e42574d28 fix(deps): update dependency i18next to v25.3.2 2025-07-11 16:52:13 +03:00
renovate[bot]
85ebaf6afa chore(deps): update dependency dotenv to v17.2.0 2025-07-11 16:52:13 +03:00
renovate[bot]
661c7e4056 chore(deps): update dependency @sveltejs/kit to v2.22.4 2025-07-11 16:52:13 +03:00
Elian Doran
1e8ea54dbc feat(popup_editor): smoother operation 2025-07-11 12:16:56 +03:00
Elian Doran
ddbe7e9936 chore(popup_editor): clean up after closing modal 2025-07-11 12:00:32 +03:00
Elian Doran
cab86175ef fix(file): pdf having a 10px margin at the bottom 2025-07-11 11:28:10 +03:00
Elian Doran
ec7414b174 fix(popup_editor): collections being displayed under a full empty screen 2025-07-11 10:47:06 +03:00
Elian Doran
8343a5d1dd feat(popup_editor): add mobile support 2025-07-11 09:06:06 +03:00
Adorian Doran
18c55784c7 style/table collections: add a placeholder style for rows and cells 2025-07-11 00:16:15 +03:00
Elian Doran
39eac83d38 fix(popup_editor): mermaid not rendering properly 2025-07-10 23:21:37 +03:00
Elian Doran
55bd6fb57d feat(popup_editor): properly support file note types 2025-07-10 22:55:16 +03:00
Elian Doran
6fdec52332 fix(popup_editor): mind map not rendering properly 2025-07-10 22:48:33 +03:00
Adorian Doran
824a3c5fcc style/table collections: fix an issue with column headers 2025-07-10 21:54:32 +03:00
Adorian Doran
87da644027 style/table collections: add a placeholder style for column headers 2025-07-10 21:52:09 +03:00
Adorian Doran
4f42f543d8 style/table collections: create a stylesheet dedicated to the table view 2025-07-10 21:20:48 +03:00
Elian Doran
97ea3ac3fc fix(popup_editor): block popup not working 2025-07-10 20:54:50 +03:00
Elian Doran
f04b75fd36 feat(popup_editor): add shortcut in links 2025-07-10 19:56:13 +03:00
Elian Doran
f5bffc38f1 feat(popup_editor): add shortcut in note tree 2025-07-10 19:54:51 +03:00
Elian Doran
27738acefc feat(popup_editor): support collections 2025-07-10 19:39:08 +03:00
Elian Doran
59ce2072c5 feat(popup_editor): display promoted attributes 2025-07-10 19:19:44 +03:00
Elian Doran
ed68dda70b feat(popup_editor): integrate with note tooltip 2025-07-10 18:57:13 +03:00
Elian Doran
892ab02f06 feat(popup_editor): integrate with geomap 2025-07-10 18:21:12 +03:00
Elian Doran
7d9196d5e1 feat(popup_editor): integrate with calendar for day notes 2025-07-10 18:14:23 +03:00
Elian Doran
dccdb5ceb7 feat(popup_editor): integrate with calendar for existing notes 2025-07-10 17:54:27 +03:00
Elian Doran
f961698e44 feat(popup_editor): improve fit for wider notes 2025-07-10 17:40:57 +03:00
Elian Doran
278fe3262e feat(popup_editor): improve fit for full-height note types 2025-07-10 17:33:00 +03:00
Elian Doran
1fc860b052 feat(popup_editor): integrate with tree context menu 2025-07-10 17:26:40 +03:00
Elian Doran
88a8311173 feat(popup_editor): integrate with note link context menu 2025-07-10 17:19:10 +03:00
Elian Doran
63dc5697dd fix(popup_editor): classic editor toolbar displayed when it shouldn't 2025-07-10 16:37:34 +03:00
Elian Doran
b595d1fade fix(popup_editor): ckeditor modals not showing 2025-07-10 16:35:44 +03:00
Elian Doran
d91c59b7d0 feat(popup_editor): floating classic toolbar 2025-07-10 16:16:09 +03:00
Elian Doran
aa2ab0da31 feat(popup_editor): limit max height & reduce padding 2025-07-10 16:12:38 +03:00
Elian Doran
91f94106fb feat(popup_editor): integrate classic editor toolbar 2025-07-10 16:09:34 +03:00
Elian Doran
308f319138 feat(popup_editor): normalize paddings 2025-07-10 15:28:55 +03:00
Elian Doran
fa0c01591a feat(popup_editor): integrate note title + icon into modal header 2025-07-10 15:25:07 +03:00
Elian Doran
cb5a771490 feat(popup_editor): add editable note title and icon 2025-07-10 15:07:48 +03:00
Elian Doran
0c17a13462 fix(popup_editor): current tab events interfering 2025-07-10 14:57:32 +03:00
Romain DEP.
04593cb2d7 fix(options): display a less ambiguous/scary message after performing a consistency check.
The current message could easily be misinterpreted as an instruction for the user to go fix issues
2025-07-10 13:15:41 +02:00
Elian Doran
b6f50b6af0 chore(deps): update dependency webdriverio to v9.17.0 (#6281) 2025-07-10 13:57:25 +03:00
renovate[bot]
fc454cba03 chore(deps): update dependency webdriverio to v9.17.0 2025-07-10 09:54:03 +00:00
Elian Doran
6f165df29e chore(deps): update dependency svelte to v5.35.5 (#6277) 2025-07-10 12:49:50 +03:00
Elian Doran
d16468071d chore(deps): update pnpm to v10.13.1 (#6282) 2025-07-10 12:49:38 +03:00
renovate[bot]
20a492523f chore(deps): update dependency svelte to v5.35.5 2025-07-10 07:43:08 +00:00
Elian Doran
1216f51c78 fix(deps): update dependency i18next to v25.3.2 (#6278) 2025-07-10 10:42:16 +03:00
Elian Doran
ea3ac1041b chore(deps): update dependency dotenv to v17.2.0 (#6280) 2025-07-10 10:41:45 +03:00
Elian Doran
d838e8baf0 chore(deps): update dependency @sveltejs/kit to v2.22.4 (#6276) 2025-07-10 10:41:01 +03:00
renovate[bot]
60a7347d7d chore(deps): update pnpm to v10.13.1 2025-07-10 02:40:45 +00:00
renovate[bot]
4e05e79426 chore(deps): update dependency dotenv to v17.2.0 2025-07-10 02:39:34 +00:00
renovate[bot]
aa872f47f2 chore(deps): update dependency dotenv to v17.2.0 2025-07-10 02:38:39 +00:00
renovate[bot]
fbd833ad86 fix(deps): update dependency i18next to v25.3.2 2025-07-10 02:37:42 +00:00
renovate[bot]
bee65ed32c chore(deps): update dependency @sveltejs/kit to v2.22.4 2025-07-10 02:35:48 +00:00
Elian Doran
5adca76a9a refactor(popup_editor): better error handling 2025-07-09 21:56:11 +03:00
Elian Doran
e7467f6446 feat(popup_editor): get editor to show up if note is open somewhere else 2025-07-09 21:44:42 +03:00
Elian Doran
e49473fbd3 refactor(client): unused import 2025-07-09 21:20:24 +03:00
Elian Doran
bfec44aa5a refactor(popup_editor): inject note detail widget 2025-07-09 21:20:16 +03:00
Elian Doran
55b3bf6036 feat(popup_editor): create an empty modal with auto-trigger 2025-07-09 21:12:18 +03:00
Elian Doran
c9c07f0cb0 chore(book_properties): add config for all note types 2025-07-09 20:53:35 +03:00
Elian Doran
e25727441d chore(book_properties): add translations 2025-07-09 20:40:04 +03:00
Elian Doran
51b7955ccd refactor(book_properties): move rendering to book_properties 2025-07-09 20:37:05 +03:00
Elian Doran
196bba9cda refactor(book_properties): list buttons are now declarative 2025-07-09 20:29:58 +03:00
Elian Doran
430ed78d85 feat(book_properties): improve layout & accessibility 2025-07-09 20:14:42 +03:00
Elian Doran
2d11ed805d feat(book_properties): react to external changes 2025-07-09 20:13:08 +03:00
Elian Doran
f55426bdb0 feat(collections): basic properties for calendar 2025-07-09 20:10:25 +03:00
Elian Doran
87b5068fec chore(collections): rename references to book 2025-07-09 19:40:35 +03:00
Elian Doran
9ddd1a4ae2 feat(collections): add i18n 2025-07-09 19:37:10 +03:00
Elian Doran
736bc9c9bd chore(insert_note): improve layout slightly 2025-07-09 19:32:29 +03:00
Elian Doran
5a2da62992 feat(collections): hide book default note type 2025-07-09 19:28:44 +03:00
Elian Doran
1a72eb91ee feat(collections): display grid/view in collections list 2025-07-09 19:22:12 +03:00
Elian Doran
0d3c5b06e2 feat(collections): add calendar as a standalone template 2025-07-09 19:05:05 +03:00
Elian Doran
035b72a08d feat(insert_note): add dedicated section for collections 2025-07-09 18:55:08 +03:00
Elian Doran
fc4a595725 feat(insert_note): display on two columns 2025-07-09 18:37:09 +03:00
Elian Doran
444969bcf4 chore(deps): update dependency @types/node to v22.16.2 (#6267) 2025-07-09 08:47:37 +03:00
renovate[bot]
2cb6b14eca chore(deps): update dependency @types/node to v22.16.2 2025-07-09 05:23:39 +00:00
Elian Doran
468b5022a4 chore(deps): update dependency @types/express-serve-static-core to v5.0.7 (#6265) 2025-07-09 08:20:41 +03:00
Elian Doran
c1897563ca chore(deps): update dependency @types/leaflet to v1.9.20 (#6266) 2025-07-09 08:20:28 +03:00
Elian Doran
5e533896b9 chore(deps): update dependency openai to v5.8.3 (#6268) 2025-07-09 08:20:11 +03:00
Elian Doran
d3ceb7cfc1 chore(deps): update dependency vite to v7.0.3 (#6269) 2025-07-09 08:19:59 +03:00
Elian Doran
731f74f421 chore(deps): update dependency is-svg to v6.1.0 (#6270) 2025-07-09 08:19:40 +03:00
renovate[bot]
46d82651a3 chore(deps): update dependency is-svg to v6.1.0 2025-07-09 02:08:18 +00:00
renovate[bot]
b3108c7e2b chore(deps): update dependency vite to v7.0.3 2025-07-09 02:07:23 +00:00
renovate[bot]
0cb988470e chore(deps): update dependency openai to v5.8.3 2025-07-09 02:06:29 +00:00
renovate[bot]
5a030014b0 chore(deps): update dependency @types/leaflet to v1.9.20 2025-07-09 02:04:53 +00:00
renovate[bot]
2a43ef4dae chore(deps): update dependency @types/express-serve-static-core to v5.0.7 2025-07-09 02:04:03 +00:00
Elian Doran
6b5f9fc6ff Merge branch 'main' of https://github.com/TriliumNext/trilium 2025-07-08 22:33:44 +03:00
Elian Doran
b3a156c20d Update docker-compose.yml with new container image (#6263) 2025-07-08 21:33:51 +03:00
Elian Doran
24340d3a8e refactor(next): remove use of :has for performance reasons 2025-07-08 21:24:18 +03:00
Elian Doran
2fac2a8c5e chore(next): improve performance of a specific selector 2025-07-08 21:09:13 +03:00
Elian Doran
decb0c702d chore(nx): disable cache for rebuild-deps 2025-07-08 20:54:14 +03:00
Elian Doran
d45ff6cca5 fix(next): another non-performant CSS selector 2025-07-08 20:53:06 +03:00
DeveloperCrocodiles
83833e668c Update docker-compose.yml with new container image
Update docker-compose.yml to use the new container image triliumnext/trilium rather than triliumnext/notes.
2025-07-08 16:51:10 +01:00
Elian Doran
2cc181d1ac fix(tree): pasting in editor on middle click (closes #5812) 2025-07-08 15:51:19 +03:00
Elian Doran
a946ce3534 chore(deps): update dependency dotenv to v17.1.0 (#6255) 2025-07-08 09:05:40 +03:00
renovate[bot]
3e9f476b37 chore(deps): update dependency dotenv to v17.1.0 2025-07-08 05:21:18 +00:00
Elian Doran
de65c748a4 chore(deps): update dependency esbuild to v0.25.6 (#6249) 2025-07-08 08:18:57 +03:00
Elian Doran
8a2bfb9d7b chore(deps): update dependency supertest to v7.1.3 (#6250) 2025-07-08 08:18:20 +03:00
Elian Doran
a1ced31fea chore(deps): update dependency svelte to v5.35.4 (#6251) 2025-07-08 08:18:08 +03:00
Elian Doran
989a9f506e fix(deps): update dependency @electron/remote to v2.1.3 (#6252) 2025-07-08 08:17:58 +03:00
Elian Doran
59d55e2489 fix(deps): update dependency mermaid to v11.8.1 (#6253) 2025-07-08 08:17:47 +03:00
Elian Doran
2b312a9234 chore(deps): update typescript-eslint monorepo to v8.36.0 (#6256) 2025-07-08 08:17:31 +03:00
Elian Doran
16d9b982c2 chore(deps): update dependency dotenv to v17.1.0 (#6254) 2025-07-08 08:17:19 +03:00
renovate[bot]
a5600e75f5 chore(deps): update typescript-eslint monorepo to v8.36.0 2025-07-08 02:01:43 +00:00
renovate[bot]
f91dea62b6 chore(deps): update dependency dotenv to v17.1.0 2025-07-08 01:59:19 +00:00
renovate[bot]
4915ffcf2a fix(deps): update dependency mermaid to v11.8.1 2025-07-08 01:58:31 +00:00
renovate[bot]
9dbea2aa18 fix(deps): update dependency @electron/remote to v2.1.3 2025-07-08 01:57:38 +00:00
renovate[bot]
45f6a70fb8 chore(deps): update dependency svelte to v5.35.4 2025-07-08 01:56:42 +00:00
renovate[bot]
96b4c611cc chore(deps): update dependency supertest to v7.1.3 2025-07-08 01:55:48 +00:00
renovate[bot]
4e559d6594 chore(deps): update dependency esbuild to v0.25.6 2025-07-08 01:54:54 +00:00
Elian Doran
db1a599f95 fix(deps): update dependency mind-elixir to v5 (#6239) 2025-07-07 22:12:14 +03:00
Elian Doran
040964bbb7 chore(env): add variable for inspector 2025-07-07 22:10:07 +03:00
Elian Doran
dc6a303154 fix(mindmap): update deps to reintegrate features 2025-07-07 20:59:31 +03:00
Elian Doran
f88f14c983 Merge remote-tracking branch 'origin/main' into renovate/mind-elixir-5.x 2025-07-07 20:50:23 +03:00
Elian Doran
f870649256 Geomap improvements & conversion to book type (#6241) 2025-07-07 20:27:07 +03:00
Elian Doran
ed4dc30a6e refator(client): type error 2025-07-07 20:07:04 +03:00
Elian Doran
ce9010ff13 chore(client): add some documentation 2025-07-07 20:04:17 +03:00
Elian Doran
994e9fa852 docs(views/geomap): update user manual to reflect newly added features 2025-07-07 19:54:06 +03:00
Elian Doran
9df7d6227e Merge branch 'feature/geomap_collection' of https://github.com/TriliumNext/trilium into feature/geomap_collection 2025-07-07 19:20:24 +03:00
Elian Doran
242a576548 refactor(views/geomap): solve type errors 2025-07-07 19:15:38 +03:00
Elian Doran
c1a5808f37 feat(views/geomap): allow disabling editing 2025-07-07 19:04:47 +03:00
Elian Doran
5c6bb99d78 refactor(views/geomap): integrate drag into editing 2025-07-07 18:04:17 +03:00
Elian Doran
63c408c45b feat(views/geomap): dragging notes that are not children 2025-07-07 18:02:32 +03:00
Elian Doran
2a665dffbc feat(views/geomap): dragging notes that are children 2025-07-07 17:55:16 +03:00
Elian Doran
6509acd6ee feat(views/geomap): add open location to blank item as well 2025-07-07 17:24:42 +03:00
Elian Doran
4853d45609 chore(nix): switch to master for electron 37 (closes #6217) 2025-07-07 14:28:50 +03:00
Jon Fuller
fe78c1fee3 fix(api): implement better rate limiting controls for pre-auth endpoints (#6243) 2025-07-06 14:15:24 -07:00
Elian Doran
8102172557 feat(views/geomap): display geolocation in both context menus 2025-07-06 23:48:51 +03:00
Elian Doran
a1341e6036 feat(views/geomap): display geolocation in empty menu 2025-07-06 23:48:29 +03:00
Elian Doran
d31af2ddc2 feat(views/geomap): add a context menu for empty area 2025-07-06 23:34:07 +03:00
Elian Doran
a563330136 feat(import/zip): improve geomap compatibility 2025-07-06 23:05:21 +03:00
Elian Doran
a58e5789bc feat(import/zip): backward compatibility 2025-07-06 22:33:19 +03:00
Elian Doran
68e258f23b fix(views/geomap): unable to change note type to geomap 2025-07-06 22:32:24 +03:00
Elian Doran
dd18866156 refactor(server): convert to switch 2025-07-06 22:22:19 +03:00
Elian Doran
1b1f1957c3 chore(views/help): reintroduce help button 2025-07-06 22:11:03 +03:00
Elian Doran
ff6b4effbd fix(views/geomap): tooltip not showing 2025-07-06 21:29:42 +03:00
Elian Doran
06fa59239c chore(views/geomap): remove old files 2025-07-06 21:12:05 +03:00
Elian Doran
557bfbd1d6 feat(views/geomap): remove old geo map type 2025-07-06 20:23:50 +03:00
Elian Doran
f5a6dfa629 feat(views/geomap): migrate db to new collection type 2025-07-06 20:17:15 +03:00
Elian Doran
ce33dfb003 feat(views): delete duplicate attachments 2025-07-06 20:16:47 +03:00
Elian Doran
7b1c058d29 feat(views/geomap): add template 2025-07-06 19:21:53 +03:00
perf3ct
04c8f8a123 fix(api): implement better rate limiting controls for pre-auth endpoints 2025-07-06 16:19:08 +00:00
Elian Doran
d15fccb1d8 chore(views/geomap): integrate context menu options 2025-07-06 18:36:36 +03:00
Elian Doran
229dd9cd18 chore(views/geomap): integrate touchbar 2025-07-06 18:25:53 +03:00
Elian Doran
a4faaa406b fix(views/geomap): proper refresh 2025-07-06 17:54:13 +03:00
Elian Doran
b6d2de54b2 chore(views/geomap): reintroduce adding notes 2025-07-06 17:51:57 +03:00
Elian Doran
d5e81d77a2 refactor(views/geomap): move context menu 2025-07-06 12:28:24 +03:00
Elian Doran
939e99637f chore(views/geomap): reintroduce moving markers 2025-07-06 12:22:51 +03:00
Elian Doran
579a261612 chore(views/geomap): reintroduce display of tracks 2025-07-06 12:19:09 +03:00
Elian Doran
6d03304cbb chore(views/geomap): reintroduce display of markers 2025-07-06 12:12:59 +03:00
Elian Doran
b8d41b3421 chore(views/geomap): reintroduce viewport storage 2025-07-06 11:47:37 +03:00
Elian Doran
6a5bb1f5c8 chore(views/geomap): reintroduce map init 2025-07-06 11:36:59 +03:00
Elian Doran
cd742a4617 chore(views/geomap): basic initialization 2025-07-06 11:30:24 +03:00
Elian Doran
54063b97ad feat(views/geomap): display even if empty 2025-07-06 11:22:49 +03:00
Elian Doran
7abb67e737 feat(views/geomap): display in view type 2025-07-06 11:21:25 +03:00
Elian Doran
00fd1ba137 feat(views/geomap): make full-height 2025-07-06 11:15:28 +03:00
Elian Doran
7ea37b9eb9 feat(views/geomap): create empty view type 2025-07-06 11:13:17 +03:00
Elian Doran
b749de8fe1 fix(mind_map): styles no longer loading after major update 2025-07-06 10:08:45 +03:00
Elian Doran
8efef6842d Note Type Badges (#6229) 2025-07-06 09:54:27 +03:00
renovate[bot]
dc206f38d5 fix(deps): update dependency mind-elixir to v5 2025-07-06 01:15:10 +00:00
Elian Doran
29a00a6c0e Merge branch 'main' of github.com:TriliumNext/trilium 2025-07-05 22:31:08 +03:00
Elian Doran
fe678230a8 feat(webview): allow popups (closes #5698) 2025-07-05 22:31:04 +03:00
Adorian Doran
9cdbeb061f Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/context-menu/menu-items-badge-support 2025-07-05 22:06:39 +03:00
Adorian Doran
6c308f35c1 style(next)/badges: tweak appearance 2025-07-05 21:35:00 +03:00
Adorian Doran
34b89cf2e8 style/badges: tweak appearance 2025-07-05 20:46:45 +03:00
Elian Doran
b566a188dc chore(client): crash during serialization for CKEditor errors 2025-07-05 20:31:48 +03:00
Elian Doran
998432e236 chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.1 (#6178) 2025-07-05 20:20:02 +03:00
Adorian Doran
1af8edfe4d client/note type chooser: add support for badges 2025-07-05 19:31:59 +03:00
Adorian Doran
5bf01106c5 client/context menus/insert note: refactor 2025-07-05 19:13:34 +03:00
Adorian Doran
a45289e385 client/context menus/insert note: fix a typo again 2025-07-05 18:54:22 +03:00
Adorian Doran
4ffd005b09 client/context menus/insert note: fix a typo 2025-07-05 18:53:40 +03:00
Adorian Doran
e6ca89fea8 client/context menus/insert note: fix a comment 2025-07-05 18:53:03 +03:00
Adorian Doran
2225aea756 client/context menus/insert note: fix a console log 2025-07-05 18:49:22 +03:00
Adorian Doran
bfc4a84020 client/context menus/insert note: avoid highlighting predefined templates as "new" 2025-07-05 18:46:52 +03:00
Elian Doran
5390bfdcab fix(help): ligatures in code causing confusion (closes #6224) 2025-07-05 18:32:55 +03:00
Elian Doran
301211ff41 chore(client): remove comment 2025-07-05 18:25:52 +03:00
renovate[bot]
64139e4e08 chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.1 2025-07-05 15:22:56 +00:00
Elian Doran
e6485cde92 chore(deps): update dependency webdriverio to v9.16.2 (#6193) 2025-07-05 18:20:30 +03:00
Adorian Doran
891f6ba66f client/context menus/insert note: highlight new templates based on their creation date 2025-07-05 18:10:36 +03:00
Elian Doran
5d3c1e3fec chore(client,website): bypass issue with plugins 2025-07-05 17:58:36 +03:00
Elian Doran
087e755390 Revert "chore(client): bypass issue with plugins"
This reverts commit 025dc1ce75.
2025-07-05 17:48:53 +03:00
Elian Doran
025dc1ce75 chore(client): bypass issue with plugins 2025-07-05 17:44:54 +03:00
Elian Doran
703200338d Merge remote-tracking branch 'origin/main' into renovate/webdriverio-monorepo 2025-07-05 17:15:39 +03:00
Elian Doran
377c93ca0b chore(deps): update dependency @anthropic-ai/sdk to v0.56.0 (#6211) 2025-07-05 14:50:20 +03:00
renovate[bot]
69394ffe29 chore(deps): update dependency webdriverio to v9.16.2 2025-07-05 11:38:36 +00:00
renovate[bot]
f85231d74a chore(deps): update dependency @anthropic-ai/sdk to v0.56.0 2025-07-05 11:37:47 +00:00
Elian Doran
b93d8b0159 chore(deps): update dependency @types/tabulator-tables to v6.2.7 (#6220) 2025-07-05 10:16:45 +03:00
Elian Doran
67b9329903 chore(deps): update dependency vite to v7.0.2 (#6221) 2025-07-05 10:16:10 +03:00
Elian Doran
c0edd4ea4f fix(deps): update dependency i18next to v25.3.1 (#6222) 2025-07-05 10:15:41 +03:00
renovate[bot]
8eaf2786e8 fix(deps): update dependency i18next to v25.3.1 2025-07-05 02:42:27 +00:00
renovate[bot]
25622df464 chore(deps): update dependency vite to v7.0.2 2025-07-05 02:41:34 +00:00
renovate[bot]
a48900e178 chore(deps): update dependency @types/tabulator-tables to v6.2.7 2025-07-05 02:40:46 +00:00
Elian Doran
ac8b0535d2 Introduce the table view (#6097) 2025-07-04 23:34:34 +03:00
Elian Doran
6ce25a825b feat(help): link table note type 2025-07-04 23:19:33 +03:00
Elian Doran
b3f56851b8 docs(guide): document table functionality 2025-07-04 23:03:50 +03:00
Elian Doran
4b86fedce1 Merge remote-tracking branch 'origin/main' into feature/table_view
; Conflicts:
;	pnpm-lock.yaml
2025-07-04 21:47:26 +03:00
Elian Doran
1ebb70c4d2 fix(views/table): refresh when relation changes 2025-07-04 21:43:56 +03:00
Elian Doran
3de7b81be8 refactor(views/table): fix some type errors 2025-07-04 21:31:43 +03:00
Elian Doran
d08225339c style(views/table): reintroduce horizontal padding 2025-07-04 21:27:45 +03:00
Elian Doran
ba22d0706f chore(views/table): unnecessary code 2025-07-04 21:25:27 +03:00
Elian Doran
ef80f104c0 fix(views/table): scroll lost when updating data 2025-07-04 21:22:13 +03:00
Elian Doran
af296a1e4e refactor(views/table): split column & rows into separate file 2025-07-04 21:18:52 +03:00
Elian Doran
28a755306a feat(views/table): deduplicate columns 2025-07-04 21:15:10 +03:00
Elian Doran
461e085eff fix(views/table): column width or visibility lost after adding new column 2025-07-04 21:11:50 +03:00
Elian Doran
fbda049c32 fix(views/table): column context menu disappearing after update 2025-07-04 20:57:39 +03:00
Elian Doran
4ded5e2b98 feat(views/table): hide footer in search 2025-07-04 20:56:10 +03:00
Elian Doran
63537aff20 feat(views/table): disable reordering in search 2025-07-04 20:43:16 +03:00
Elian Doran
0f7a2adf15 feat(views/table): improve layout 2025-07-04 20:38:48 +03:00
Elian Doran
60963abe2c refactor(views/table): reduce duplication 2025-07-04 20:33:48 +03:00
Elian Doran
08cf95aa38 feat(views/table): merge open note and icon into title 2025-07-04 20:22:55 +03:00
Elian Doran
e5b10ab16a feat(views/table): set up relations not as a link 2025-07-04 20:08:41 +03:00
Elian Doran
7f5a1ee45a feat(ribbon): stop focusing book tab by default 2025-07-04 19:47:52 +03:00
Elian Doran
15c593f68e feat(views/table): automatically focus on title when creating new row 2025-07-04 19:46:37 +03:00
Elian Doran
5f8ef0395b feat(views/table): improve default layout 2025-07-04 19:37:05 +03:00
Elian Doran
513636e1e0 feat(views/table): hide column titles for small ones 2025-07-04 19:32:22 +03:00
Elian Doran
ae9b2c08a9 feat(views/table): hide context menu for small columns 2025-07-04 19:29:33 +03:00
Elian Doran
d5327b3b4a feat(views/table): get rid of note position column 2025-07-04 19:26:06 +03:00
Elian Doran
323e3d3cac feat(views/table): hide note ID by default 2025-07-04 19:25:08 +03:00
Elian Doran
01b2257063 feat(views/table): relocate new row/column buttons 2025-07-04 19:23:26 +03:00
Elian Doran
c69ef611a0 feat(views/table): basic reordering mechanism 2025-07-04 18:53:31 +03:00
Elian Doran
dcad23316d style(views/table): improve autocomplete styling 2025-07-04 18:26:24 +03:00
Elian Doran
e411f9932f feat(views/table): display note title when editing relation 2025-07-04 18:13:07 +03:00
Elian Doran
854969e1b8 feat(views/table): react to external attribute changes 2025-07-04 18:05:24 +03:00
Elian Doran
4ac7b6e9e8 feat(views/table): allow creation of new notes 2025-07-04 17:17:39 +03:00
Elian Doran
ac70908c5a feat(views/table): integrate reference-like for relations 2025-07-04 16:14:14 +03:00
Elian Doran
45ac70b78f feat(views/table): proper storage of relations 2025-07-04 15:07:40 +03:00
Elian Doran
a4664576fe feat(views/table): separate data model for relations 2025-07-04 15:05:00 +03:00
Elian Doran
b293643398 feat(views/table): basic formatter for relations 2025-07-04 15:02:10 +03:00
Elian Doran
a2e197facd feat(views/table): set up relation editor 2025-07-04 14:50:07 +03:00
Elian Doran
8614d39ef4 chore(views/table): remove unnecessary log 2025-07-04 14:42:04 +03:00
Elian Doran
6456bb34ae chore(views/table): start implementing a relation editor 2025-07-04 14:29:40 +03:00
Elian Doran
f5dc4de1c1 feat(views/table): parse relations 2025-07-04 14:12:36 +03:00
Adorian Doran
d869056910 client/note types mapping: add a way to highlight newly introduced types 2025-07-04 13:47:50 +03:00
Adorian Doran
821e4b17cb client/refactor: de-duplicate the note type mapping 2025-07-04 13:38:02 +03:00
Elian Doran
d8cb5efd2d Merge remote-tracking branch 'origin/main' into feature/table_view 2025-07-04 12:59:03 +03:00
Adorian Doran
f90e2fb484 client/tree context menu/insert note: mark the beta note types with a badge 2025-07-04 11:46:10 +03:00
Adorian Doran
2c9a7144da Merge branch 'main' of https://github.com/TriliumNext/Trilium into feat/context-menu/menu-items-badge-support 2025-07-04 11:36:43 +03:00
Adorian Doran
88d1af7210 style/refactor: make the current badge style to be global 2025-07-04 11:33:29 +03:00
Elian Doran
300e5a5528 chore(deps): update dependency svelte to v5.35.2 (#6209) 2025-07-04 09:36:34 +03:00
renovate[bot]
4418fefe4b chore(deps): update dependency svelte to v5.35.2 2025-07-04 06:15:18 +00:00
Elian Doran
fe5d1cac9a Feature/note map filter (#6104) 2025-07-04 09:12:20 +03:00
Elian Doran
49d17fff9b chore(deps): update dependency globals to v16.3.0 (#6190) 2025-07-04 09:11:53 +03:00
Elian Doran
557c6d2d8b chore(deps): update dependency stylelint to v16.21.1 (#6208) 2025-07-04 09:11:40 +03:00
Elian Doran
45fc62357d chore(deps): update dependency vite to v7.0.1 (#6210) 2025-07-04 09:11:26 +03:00
Elian Doran
840e3cc22f fix(deps): update dependency force-graph to v1.50.1 (#6212) 2025-07-04 09:11:16 +03:00
Elian Doran
c158c7fc88 fix(deps): update dependency mermaid to v11.8.0 (#6213) 2025-07-04 09:09:37 +03:00
renovate[bot]
bc6f8fc2dd fix(deps): update dependency mermaid to v11.8.0 2025-07-04 01:40:34 +00:00
renovate[bot]
117730acb2 fix(deps): update dependency force-graph to v1.50.1 2025-07-04 01:39:40 +00:00
renovate[bot]
595a7dac83 chore(deps): update dependency vite to v7.0.1 2025-07-04 01:38:08 +00:00
renovate[bot]
64a4d70df4 chore(deps): update dependency stylelint to v16.21.1 2025-07-04 01:36:34 +00:00
renovate[bot]
be36199fe1 chore(deps): update dependency globals to v16.3.0 2025-07-03 21:26:15 +00:00
Elian Doran
e46ad25677 Merge branch 'main' into feat/note-map-filter 2025-07-04 00:20:44 +03:00
Elian Doran
d5ee663922 Tree context menu: reorder the note types of "Insert (child) note..." (#6206) 2025-07-04 00:16:52 +03:00
Elian Doran
a7ab4be055 chore(deps): update vitest monorepo to v3.2.4 (#6186) 2025-07-03 23:39:37 +03:00
Elian Doran
6bbf29e75a Merge branch 'main' into renovate/vitest-monorepo 2025-07-03 23:39:08 +03:00
Adorian Doran
0a06c60cb7 client/context menus: add support to display badges for menu items 2025-07-03 23:27:02 +03:00
Adorian Doran
03658575eb client/context menu/insert note: reorder note types 2025-07-03 22:45:49 +03:00
Adorian Doran
38114bddb9 style(next)/ribbon/file & image properties: tweak the vertical alignment of properties 2025-07-03 21:36:37 +03:00
Adorian Doran
0711a197db style(next): tweak the "Delete notes preview" dialog width 2025-07-03 20:52:38 +03:00
Adorian Doran
f8f818b211 style(next): fix a performance-heavy selector 2025-07-03 20:00:46 +03:00
Elian Doran
988932209c chore(deps): update dependency svelte to v5.35.1 (#6192) 2025-07-03 11:53:59 +03:00
Elian Doran
2aa56cec30 chore(deps): update typescript-eslint monorepo to v8.35.1 (#6194) 2025-07-03 11:53:45 +03:00
renovate[bot]
93d493650c chore(deps): update typescript-eslint monorepo to v8.35.1 2025-07-03 08:25:21 +00:00
renovate[bot]
c6162ddcb4 chore(deps): update dependency svelte to v5.35.1 2025-07-03 08:24:09 +00:00
renovate[bot]
038517eda4 chore(deps): update vitest monorepo to v3.2.4 2025-07-03 08:23:31 +00:00
Elian Doran
30a9db73ab chore(deps): update dependency stylelint to v16.21.0 (#6191) 2025-07-03 11:21:09 +03:00
Elian Doran
a50aa41bdb fix(deps): update eslint monorepo to v9.30.1 (#6198) 2025-07-03 11:20:43 +03:00
renovate[bot]
cbb322fdb8 fix(deps): update eslint monorepo to v9.30.1 2025-07-03 06:23:39 +00:00
renovate[bot]
026e2a020d chore(deps): update dependency stylelint to v16.21.0 2025-07-03 06:20:49 +00:00
Elian Doran
07aab1d005 chore(deps): update ckeditor5 config packages to v11 (major) (#6199) 2025-07-03 09:14:38 +03:00
Elian Doran
26f0f7b188 fix(deps): update dependency tsx to v4.20.3 (#6197) 2025-07-03 09:13:57 +03:00
Elian Doran
1efde3b86b fix(deps): update dependency better-sqlite3 to v12.2.0 (#6196) 2025-07-03 09:13:23 +03:00
Elian Doran
8c1318f379 fix(deps): update dependency @inlang/paraglide-js to v2.2.0 (#6195) 2025-07-03 09:13:10 +03:00
Elian Doran
40e67e8e17 chore(deps): update dependency eslint-plugin-svelte to v3.10.1 (#6189) 2025-07-03 09:12:33 +03:00
Elian Doran
04466f52fd chore(deps): update dependency electron to v37.2.0 (#6188) 2025-07-03 09:11:59 +03:00
Elian Doran
06baa5fb57 chore(deps): update dependency @sveltejs/kit to v2.22.2 (#6187) 2025-07-03 09:11:23 +03:00
Elian Doran
04e1657628 chore(deps): update tailwindcss monorepo to v4.1.11 (#6185) 2025-07-03 09:10:31 +03:00
Elian Doran
7816c8cab0 chore(deps): update nx monorepo to v21.2.2 (#6184) 2025-07-03 09:10:18 +03:00
Elian Doran
6636e658a4 chore(deps): update dependency svelte-check to v4.2.2 (#6183) 2025-07-03 09:09:59 +03:00
Elian Doran
2a06f0daef chore(deps): update dependency lint-staged to v16.1.2 (#6182) 2025-07-03 09:09:43 +03:00
Elian Doran
883cfa588c chore(deps): update dependency dotenv to v17.0.1 (#6181) 2025-07-03 09:09:23 +03:00
Elian Doran
68011a0b5a Merge pull request #6180 from TriliumNext/renovate/mime-types-3.x-lockfile
chore(deps): update dependency @types/mime-types to v3.0.1
2025-07-03 09:08:59 +03:00
Elian Doran
5247d1a371 Merge pull request #6179 from TriliumNext/renovate/eslint-compat-1.x-lockfile
chore(deps): update dependency @eslint/compat to v1.3.1
2025-07-03 09:08:20 +03:00
renovate[bot]
cabdd528d4 chore(deps): update ckeditor5 config packages to v11 2025-07-03 00:19:09 +00:00
renovate[bot]
2bacbb796b fix(deps): update dependency tsx to v4.20.3 2025-07-03 00:17:08 +00:00
renovate[bot]
aa0ed6434a fix(deps): update dependency better-sqlite3 to v12.2.0 2025-07-03 00:16:25 +00:00
renovate[bot]
5b2215d646 fix(deps): update dependency @inlang/paraglide-js to v2.2.0 2025-07-03 00:15:46 +00:00
renovate[bot]
0e760e25f2 chore(deps): update dependency eslint-plugin-svelte to v3.10.1 2025-07-03 00:10:18 +00:00
renovate[bot]
acbb85b409 chore(deps): update dependency electron to v37.2.0 2025-07-03 00:09:29 +00:00
renovate[bot]
ea1d4b97ad chore(deps): update dependency @sveltejs/kit to v2.22.2 2025-07-03 00:08:38 +00:00
renovate[bot]
a81839c13f chore(deps): update tailwindcss monorepo to v4.1.11 2025-07-03 00:06:48 +00:00
renovate[bot]
b9d4668d4d chore(deps): update nx monorepo to v21.2.2 2025-07-03 00:05:55 +00:00
renovate[bot]
42b27f5965 chore(deps): update dependency svelte-check to v4.2.2 2025-07-03 00:05:03 +00:00
renovate[bot]
9cc8222b1c chore(deps): update dependency lint-staged to v16.1.2 2025-07-03 00:04:08 +00:00
renovate[bot]
e8479338df chore(deps): update dependency dotenv to v17.0.1 2025-07-03 00:03:19 +00:00
renovate[bot]
fa9e6c9fc0 chore(deps): update dependency @types/mime-types to v3.0.1 2025-07-03 00:02:32 +00:00
renovate[bot]
5366173b52 chore(deps): update dependency @eslint/compat to v1.3.1 2025-07-03 00:01:45 +00:00
Elian Doran
63520c55b3 Merge branch 'main' into feat/note-map-filter 2025-07-02 23:37:56 +03:00
Elian Doran
86f6d9b14a feat(export/zip): handle empty title for archive 2025-07-02 22:28:19 +03:00
Elian Doran
5270cf6284 feat(export/zip): handle empty notes for subtree 2025-07-02 22:28:03 +03:00
Elian Doran
4f46d81e1b fix(export/zip): missing note meta for empty file (closes #6146) 2025-07-02 21:59:58 +03:00
Elian Doran
294a2e6fdb feat(export/zip): improve error handling 2025-07-02 21:56:22 +03:00
Elian Doran
b20a8bc90b chore(backend_api): document week note behaviour 2025-07-02 21:41:12 +03:00
Elian Doran
68bdd1336f Merge pull request #6168 from TriliumNext/renovate/node-22.x
chore(deps): update dependency @types/node to v22.16.0
2025-07-02 20:35:02 +03:00
renovate[bot]
e62ccd932d chore(deps): update dependency @types/node to v22.16.0 2025-07-02 13:54:50 +00:00
Elian Doran
d6c188df6e Merge pull request #6170 from TriliumNext/renovate/i18next-25.x 2025-07-02 16:53:00 +03:00
Elian Doran
004000b5d2 Merge pull request #6171 from TriliumNext/renovate/eslint-monorepo 2025-07-02 16:52:25 +03:00
renovate[bot]
633c8a3444 fix(deps): update eslint monorepo to v9.30.1 2025-07-02 08:28:18 +00:00
renovate[bot]
2f59a20b6b fix(deps): update dependency i18next to v25.3.0 2025-07-02 08:27:21 +00:00
Elian Doran
593c435f75 Merge pull request #6172 from TriliumNext/renovate/multer-2.x
chore(deps): update dependency @types/multer to v2
2025-07-02 11:24:18 +03:00
Elian Doran
20ec45be57 Merge pull request #6169 from TriliumNext/renovate/globals-16.x
fix(deps): update dependency globals to v16.3.0
2025-07-02 11:23:17 +03:00
Elian Doran
d2a0e12409 Merge pull request #6167 from TriliumNext/renovate/fullcalendar-monorepo
fix(deps): update fullcalendar monorepo to v6.1.18
2025-07-02 11:22:51 +03:00
Elian Doran
33eebe117b Merge pull request #6166 from TriliumNext/renovate/eslint-linter-browserify-9.x
fix(deps): update dependency eslint-linter-browserify to v9.30.1
2025-07-02 11:22:29 +03:00
Elian Doran
ef0cfc2e7c Merge pull request #6165 from TriliumNext/renovate/dotenv-17.x
chore(deps): update dependency dotenv to v17.0.1
2025-07-02 11:22:07 +03:00
Elian Doran
b6e17ae543 Merge pull request #6164 from TriliumNext/renovate/playwright-monorepo
chore(deps): update dependency @playwright/test to v1.53.2
2025-07-02 11:21:51 +03:00
Elian Doran
8a33e2be89 Merge pull request #6163 from TriliumNext/renovate/anthropic-ai-sdk-0.x
chore(deps): update dependency @anthropic-ai/sdk to v0.55.1
2025-07-02 11:21:36 +03:00
renovate[bot]
5f91097987 chore(deps): update dependency @types/multer to v2 2025-07-02 02:56:29 +00:00
renovate[bot]
0fd4f02951 fix(deps): update dependency globals to v16.3.0 2025-07-02 02:52:17 +00:00
renovate[bot]
106e78ed62 fix(deps): update fullcalendar monorepo to v6.1.18 2025-07-02 02:47:34 +00:00
renovate[bot]
8855868b27 fix(deps): update dependency eslint-linter-browserify to v9.30.1 2025-07-02 02:46:36 +00:00
renovate[bot]
bfc3e8a907 chore(deps): update dependency dotenv to v17.0.1 2025-07-02 02:44:43 +00:00
renovate[bot]
154371e052 chore(deps): update dependency @playwright/test to v1.53.2 2025-07-02 02:43:47 +00:00
renovate[bot]
ab4a4d3d72 chore(deps): update dependency @anthropic-ai/sdk to v0.55.1 2025-07-02 02:42:29 +00:00
Elian Doran
5a4de02db7 Merge pull request #6105 from TriliumNext/renovate/ckeditor-monorepo
chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.1
2025-07-01 22:22:05 +03:00
renovate[bot]
43cbc8c6e8 chore(deps): update dependency @ckeditor/ckeditor5-package-tools to v4.0.1 2025-07-01 19:09:31 +00:00
Elian Doran
5938aa7b50 Merge pull request #6110 from TriliumNext/renovate/openai-5.x
chore(deps): update dependency openai to v5.8.2
2025-07-01 22:06:06 +03:00
Elian Doran
a49252b2f5 Merge pull request #6121 from TriliumNext/renovate/codemirror
fix(deps): update dependency @codemirror/view to v6.38.0
2025-07-01 22:05:55 +03:00
Elian Doran
0be885d9bf Merge pull request #6123 from TriliumNext/renovate/eslint-linter-browserify-9.x
fix(deps): update dependency eslint-linter-browserify to v9.30.0
2025-07-01 22:05:14 +03:00
renovate[bot]
ae1e8353f2 fix(deps): update dependency eslint-linter-browserify to v9.30.0 2025-07-01 18:50:46 +00:00
renovate[bot]
98fe88581f fix(deps): update dependency @codemirror/view to v6.38.0 2025-07-01 18:49:50 +00:00
renovate[bot]
d66475576f chore(deps): update dependency openai to v5.8.2 2025-07-01 18:48:54 +00:00
Elian Doran
65ff7be776 Merge pull request #6093 from TriliumNext/renovate/vite-7.x
chore(deps): update dependency vite to v7
2025-07-01 21:31:57 +03:00
Elian Doran
190b079494 fix(website): typecheck dependency not well set 2025-07-01 21:30:59 +03:00
renovate[bot]
b020a30bd4 chore(deps): update dependency vite to v7 2025-07-01 18:08:46 +00:00
Elian Doran
81f8453c38 fix(client): type error due to use of intersection 2025-07-01 21:01:54 +03:00
Elian Doran
533e3cf42d Merge pull request #6125 from TriliumNext/renovate/eslint-monorepo
fix(deps): update eslint monorepo to v9.30.0
2025-07-01 20:55:26 +03:00
Elian Doran
69ee73492d Merge pull request #6142 from TriliumNext/renovate/rollup-plugin-webpack-stats-2.x
chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.0
2025-07-01 20:55:16 +03:00
renovate[bot]
4a902d04b2 fix(deps): update eslint monorepo to v9.30.0 2025-07-01 17:18:00 +00:00
renovate[bot]
2e48e316c2 chore(deps): update dependency rollup-plugin-webpack-stats to v2.1.0 2025-07-01 17:16:31 +00:00
Elian Doran
bbe5dddb83 Merge pull request #6131 from TriliumNext/renovate/node-22.x
chore(deps): update dependency @types/node to v22.15.34
2025-07-01 20:14:23 +03:00
Elian Doran
7c943fe4ac chore(view/table): leftover files 2025-07-01 12:10:01 +03:00
Elian Doran
2cbb49681a fix(view/table): most type errors 2025-07-01 12:09:13 +03:00
Elian Doran
84db4ed57c docs(release): fix link 2025-07-01 11:56:05 +03:00
Elian Doran
e155642ce4 Merge remote-tracking branch 'origin/main' into feature/table_view 2025-07-01 11:55:45 +03:00
Elian Doran
87c4df60d3 Merge pull request #6126 from TriliumNext/renovate/major-ckeditor5-config-packages
chore(deps): update ckeditor5 config packages to v11 (major)
2025-07-01 11:45:01 +03:00
Elian Doran
ff412835e4 Merge pull request #6119 from TriliumNext/renovate/webdriverio-monorepo
chore(deps): update dependency webdriverio to v9.16.2
2025-07-01 11:44:39 +03:00
Elian Doran
ad15828157 Merge pull request #6111 from TriliumNext/renovate/svelte-5.x-lockfile
chore(deps): update dependency svelte to v5.34.9
2025-07-01 11:43:34 +03:00
renovate[bot]
b2fc7f934e chore(deps): update dependency @types/node to v22.15.34 2025-07-01 08:43:30 +00:00
Elian Doran
2fac4d91d6 Merge pull request #6112 from TriliumNext/renovate/svelte-check-4.x-lockfile
chore(deps): update dependency svelte-check to v4.2.2
2025-07-01 11:43:22 +03:00
Elian Doran
125cd96354 Merge pull request #6113 from TriliumNext/renovate/typedoc-0.x
chore(deps): update dependency typedoc to v0.28.7
2025-07-01 11:43:03 +03:00
Elian Doran
af02e6b714 Merge pull request #6116 from TriliumNext/renovate/dotenv-16.x-lockfile
chore(deps): update dependency dotenv to v16.6.1
2025-07-01 11:42:55 +03:00
Elian Doran
0c87b25244 Merge pull request #6117 from TriliumNext/renovate/eslint-plugin-svelte-3.x-lockfile
chore(deps): update dependency eslint-plugin-svelte to v3.10.1
2025-07-01 11:42:41 +03:00
Elian Doran
e87ada6e79 Merge pull request #6118 from TriliumNext/renovate/stylelint-16.x-lockfile
chore(deps): update dependency stylelint to v16.21.0
2025-07-01 11:42:33 +03:00
Elian Doran
282c8e58bd Merge pull request #6120 from TriliumNext/renovate/typescript-eslint-monorepo
chore(deps): update typescript-eslint monorepo to v8.35.1
2025-07-01 11:42:17 +03:00
Elian Doran
475b66b115 Merge pull request #6122 from TriliumNext/renovate/better-sqlite3-12.x-lockfile
fix(deps): update dependency better-sqlite3 to v12.2.0
2025-07-01 11:42:02 +03:00
Elian Doran
5bb971e61a Merge pull request #6124 from TriliumNext/renovate/tsx-4.x-lockfile
fix(deps): update dependency tsx to v4.20.3
2025-07-01 11:41:36 +03:00
Elian Doran
ebad9ba723 Merge pull request #6127 from TriliumNext/renovate/dotenv-17.x
chore(deps): update dependency dotenv to v17
2025-07-01 11:41:20 +03:00
Elian Doran
6ece2a839e Merge pull request #6128 from TriliumNext/renovate/marked-16.x
fix(deps): update dependency marked to v16
2025-07-01 11:40:55 +03:00
Elian Doran
8d6527fb75 Merge pull request #6132 from TriliumNext/renovate/ws-8.x
chore(deps): update dependency ws to v8.18.3
2025-07-01 11:40:27 +03:00
Elian Doran
6bfff38182 Merge pull request #6115 from TriliumNext/renovate/sveltejs-kit-2.x-lockfile
chore(deps): update dependency @sveltejs/kit to v2.22.2
2025-07-01 11:39:54 +03:00
Elian Doran
9e446717fa Merge pull request #6114 from TriliumNext/renovate/tailwindcss-monorepo
chore(deps): update tailwindcss monorepo to v4.1.11
2025-07-01 11:39:42 +03:00
Elian Doran
408b48f606 Merge pull request #6133 from TriliumNext/renovate/better-sqlite3-12.x
fix(deps): update dependency better-sqlite3 to v12.2.0
2025-07-01 11:39:32 +03:00
renovate[bot]
8d077ad46d chore(deps): update dependency dotenv to v17 2025-07-01 08:39:18 +00:00
Elian Doran
db72465e0b Merge pull request #6140 from TriliumNext/renovate/mind-elixir-4.x
fix(deps): update dependency mind-elixir to v4.6.2
2025-07-01 11:38:22 +03:00
Elian Doran
ba9f5e1688 Merge pull request #6141 from TriliumNext/renovate/eslint-stylistic-monorepo
chore(deps): update dependency @stylistic/eslint-plugin to v5.1.0
2025-07-01 11:37:57 +03:00
Elian Doran
caf40cd272 Merge pull request #6109 from TriliumNext/renovate/lint-staged-16.x-lockfile
chore(deps): update dependency lint-staged to v16.1.2
2025-07-01 11:37:02 +03:00
Elian Doran
3edccd224a Merge pull request #6107 from TriliumNext/renovate/mime-types-3.x-lockfile
chore(deps): update dependency @types/mime-types to v3.0.1
2025-07-01 11:36:28 +03:00
Elian Doran
f48931a969 Merge pull request #6106 from TriliumNext/renovate/eslint-compat-1.x-lockfile
chore(deps): update dependency @eslint/compat to v1.3.1
2025-07-01 11:36:12 +03:00
Elian Doran
84f23aa997 Merge pull request #6092 from TriliumNext/renovate/electron-37.x
chore(deps): update dependency electron to v37
2025-07-01 11:35:46 +03:00
Elian Doran
1965da6a85 docs(release): mention docker image changing 2025-07-01 10:15:10 +03:00
renovate[bot]
441ae3e25b chore(deps): update typescript-eslint monorepo to v8.35.1 2025-06-30 18:35:19 +00:00
renovate[bot]
7f612711a0 chore(deps): update dependency typedoc to v0.28.7 2025-06-30 04:54:36 +00:00
renovate[bot]
92eb4aa822 chore(deps): update dependency @stylistic/eslint-plugin to v5.1.0 2025-06-30 01:36:17 +00:00
renovate[bot]
08ec522ae7 fix(deps): update dependency mind-elixir to v4.6.2 2025-06-30 01:36:11 +00:00
Elian Doran
c5cc1fcc1e feat(views/table): introduce hiding of columns 2025-06-29 22:26:25 +03:00
Elian Doran
cedf91ea1a chore(views/table): reintroduce column reordering 2025-06-29 16:56:34 +03:00
Elian Doran
51b462f043 chore(views/table): bring back restore state 2025-06-29 16:16:15 +03:00
Elian Doran
727eeb6c74 chore(views/table): bring back persistence 2025-06-29 16:08:27 +03:00
Elian Doran
a114fba062 chore(views/table): set up frozen columns 2025-06-29 15:11:09 +03:00
Elian Doran
cf322b5c2a chore(views/table): back to bootstrap5 theme 2025-06-29 10:09:39 +03:00
renovate[bot]
92116f1671 fix(deps): update dependency better-sqlite3 to v12.2.0 2025-06-29 00:37:40 +00:00
renovate[bot]
bc479248d7 chore(deps): update dependency ws to v8.18.3 2025-06-29 00:35:22 +00:00
Elian Doran
8ee12f2950 chore(views/table): bring back resizing columns 2025-06-28 23:50:54 +03:00
Elian Doran
dcea4c30ef chore(views/table): improve editing for date types 2025-06-28 23:33:52 +03:00
Elian Doran
e7ca56e061 chore(views/table): support more data types 2025-06-28 23:29:31 +03:00
Elian Doran
09b800b9ad chore(views/table): bring back editing attributes 2025-06-28 23:23:29 +03:00
Elian Doran
9a6a8580de chore(views/table): bring back editing title 2025-06-28 23:19:54 +03:00
Elian Doran
a31ac17792 chore(views/table): set row ID as index 2025-06-28 22:49:40 +03:00
Elian Doran
0e27cd0801 feat(views/table): add row number 2025-06-28 22:47:49 +03:00
Elian Doran
bc36676fa1 chore(views/table): disable sorting for note action button 2025-06-28 17:45:11 +03:00
Elian Doran
3d2db23f33 fix(views/table): use a more stable loading mechanism 2025-06-28 17:24:18 +03:00
Elian Doran
56d366a286 feat(views/table): add column to open note 2025-06-28 17:23:42 +03:00
Elian Doran
4a26f30d65 feat(views/table): render note icon 2025-06-28 17:07:11 +03:00
Elian Doran
8e51469de5 chore(views/table): re-enable sorting 2025-06-28 16:56:36 +03:00
Elian Doran
50ebcd552c fix(views/table): error when adding a new column 2025-06-28 16:51:24 +03:00
Elian Doran
ada39cd3c7 fix(views/table): error when adding a new row 2025-06-28 16:48:01 +03:00
Elian Doran
b2d20af51a fix(views/table): refreshing of columns 2025-06-28 16:39:24 +03:00
Elian Doran
f528fa25d1 feat(views/table): switch to bootstrap theme 2025-06-28 12:51:19 +03:00
Elian Doran
e09a7fb6e0 chore(views/table): reintroduce rows 2025-06-28 12:24:40 +03:00
Elian Doran
30f7939616 chore(views/table): reintroduce column definitions 2025-06-28 12:18:24 +03:00
Elian Doran
16b9375b9d chore(views/table): add types 2025-06-28 12:18:17 +03:00
Elian Doran
4ef93569a1 refactor(views/table): start switching to tabulator 2025-06-28 12:00:50 +03:00
renovate[bot]
1ce2aaeaf1 fix(deps): update dependency marked to v16 2025-06-28 02:47:26 +00:00
renovate[bot]
6bfe8dfcf0 chore(deps): update ckeditor5 config packages to v11 2025-06-28 02:45:32 +00:00
renovate[bot]
8d8f4795e2 fix(deps): update dependency tsx to v4.20.3 2025-06-28 02:42:36 +00:00
renovate[bot]
6f6d06377b fix(deps): update dependency better-sqlite3 to v12.1.1 2025-06-28 02:40:39 +00:00
renovate[bot]
f22823fcf6 chore(deps): update dependency webdriverio to v9.16.2 2025-06-28 02:37:40 +00:00
renovate[bot]
93ce57ee1a chore(deps): update dependency stylelint to v16.21.0 2025-06-28 02:36:39 +00:00
renovate[bot]
97dd747252 chore(deps): update dependency eslint-plugin-svelte to v3.10.1 2025-06-28 02:35:40 +00:00
renovate[bot]
bc8c136458 chore(deps): update dependency dotenv to v16.6.1 2025-06-28 02:34:42 +00:00
renovate[bot]
0774252dc1 chore(deps): update dependency @sveltejs/kit to v2.22.2 2025-06-28 02:33:38 +00:00
renovate[bot]
ae30ae4be6 chore(deps): update tailwindcss monorepo to v4.1.11 2025-06-28 02:32:27 +00:00
renovate[bot]
a2b8935763 chore(deps): update dependency svelte-check to v4.2.2 2025-06-28 02:31:19 +00:00
renovate[bot]
703efb74d3 chore(deps): update dependency svelte to v5.34.8 2025-06-28 02:30:18 +00:00
renovate[bot]
b2c6062e9a chore(deps): update dependency lint-staged to v16.1.2 2025-06-28 02:28:22 +00:00
renovate[bot]
c9e7e461b1 chore(deps): update dependency @types/mime-types to v3.0.1 2025-06-28 02:26:47 +00:00
renovate[bot]
6aaddfc5a4 chore(deps): update dependency @eslint/compat to v1.3.1 2025-06-28 02:25:51 +00:00
Elian Doran
7f2c41940d feat(views/table): add basic row creation mechanism 2025-06-28 00:07:14 +03:00
Elian Doran
d31ba39a91 feat(views/table): basic dark mode support 2025-06-27 23:40:00 +03:00
Elian Doran
c058673e33 feat(views/table): smooth column update 2025-06-27 23:01:15 +03:00
Elian Doran
44ce6a5169 feat(views/table): refresh on attribute change 2025-06-27 22:50:27 +03:00
Elian Doran
0fb0be4ffc feat(views/table): actually add attributes 2025-06-27 22:43:29 +03:00
Kieran
e70ba00929 docs(map): document relation filtering 2025-06-27 20:25:09 +01:00
Elian Doran
fe1dbb4cbf feat(views/table): display a dialog to add a new column 2025-06-27 22:19:09 +03:00
Kieran
31df2341c3 feat(map): add mapIncludeRelation and mapExcludeRelation to builtin_attributes 2025-06-27 20:18:28 +01:00
Kieran
9d99da14e1 feat(map): add mapIncludeRelation and mapExcludeRelation to include only or exclude specific relation types 2025-06-27 20:15:59 +01:00
Elian Doran
f8e10f36db refactor(note_list): use object for constructor arg 2025-06-27 21:51:38 +03:00
Elian Doran
bb0f384a39 feat(views/table): disable drag if sorted 2025-06-27 20:30:36 +03:00
Elian Doran
6a0b24f032 chore(views/table): remove logs 2025-06-27 20:08:41 +03:00
Elian Doran
80d5536503 feat(views/table): basic drag support 2025-06-27 19:53:40 +03:00
Elian Doran
9dcd79bd94 feat(views/table): add debouncing 2025-06-27 17:58:25 +03:00
Elian Doran
c5020b8884 refactor(views/table): move table view into its own folder 2025-06-27 17:44:29 +03:00
Elian Doran
0b74de275c refactor(views/table): integrate parser into data 2025-06-27 17:43:19 +03:00
Elian Doran
e66aef17df refactor(views/table): merge storage into table view 2025-06-27 17:40:56 +03:00
Elian Doran
19eff5e6d6 refactor(views/table): merge renderer into table view 2025-06-27 17:39:57 +03:00
Elian Doran
88b4fc73de chore(views/table): remove placeholder text 2025-06-27 17:22:47 +03:00
Elian Doran
70694542eb feat(views/table): allow in search 2025-06-27 17:18:52 +03:00
Elian Doran
360e5e3102 Merge remote-tracking branch 'origin/main' into feature/table_view 2025-06-27 17:08:23 +03:00
renovate[bot]
6e89a232e6 chore(deps): update dependency electron to v37 2025-06-27 06:48:15 +00:00
Elian Doran
ecd3b7039f feat(book/table): add template 2025-06-25 19:31:25 +03:00
Elian Doran
4a22e3d2d4 feat(book/table): hide promoted attributes 2025-06-25 19:25:01 +03:00
Elian Doran
dcb4ebe5d9 feat(book/table): display even if empty 2025-06-25 18:31:45 +03:00
Elian Doran
dd379bf18d refactor(book/table): fix some lack of generics 2025-06-25 18:30:44 +03:00
Elian Doran
c9b556160f feat(book/table): support changing note title 2025-06-25 17:56:47 +03:00
Elian Doran
168e224d3e refactor(book/table): make clear what kind of attribute is being changed 2025-06-25 17:54:00 +03:00
Elian Doran
9e57c14130 feat(attachments): add pretty formatting to JSON 2025-06-25 17:45:11 +03:00
Elian Doran
9c137a1c48 feat(book/table): display attachment JSON 2025-06-25 17:43:58 +03:00
Elian Doran
ccb9b7e5fb feat(book/table): store hidden columns 2025-06-25 16:18:34 +03:00
Elian Doran
c7b16cd043 feat(book/table): allow show/hide columns 2025-06-25 13:52:53 +03:00
Elian Doran
7e20e41521 feat(book/table): allow editing cell values 2025-06-25 13:06:38 +03:00
Elian Doran
66761a69d3 refactor(book/table): clean up 2025-06-25 12:10:08 +03:00
Elian Doran
fb32d26479 feat(book/table): support boolean type 2025-06-25 12:05:10 +03:00
Elian Doran
b6398fdb5d refactor(book/table): extract gathering definitions 2025-06-25 12:03:17 +03:00
Elian Doran
d9443527ee feat(book/table): support date type 2025-06-25 11:56:30 +03:00
Elian Doran
7c175da9f1 chore(book/table): ignore multi attributes 2025-06-25 11:45:46 +03:00
Elian Doran
05aa087851 feat(book/table): support basic text columns 2025-06-25 11:23:34 +03:00
Elian Doran
592e968f9f feat(book/table): display note titles 2025-06-25 11:06:49 +03:00
Elian Doran
894a26cc67 feat(book/table): set up sample grid 2025-06-25 10:49:33 +03:00
Elian Doran
1b5dd4638d chore(book/table): install ag-grid 2025-06-25 10:40:11 +03:00
Elian Doran
a19186c508 feat(book/table): set full height 2025-06-25 10:40:04 +03:00
Elian Doran
5450bdeae9 feat(book/table): hide no children warning 2025-06-25 10:34:03 +03:00
Elian Doran
fcd71957ff feat(book/table): create new view type 2025-06-25 10:31:41 +03:00
454 changed files with 109452 additions and 10057 deletions

2
.github/FUNDING.yml vendored
View File

@@ -2,3 +2,5 @@
github: [eliandoran]
custom: ["https://paypal.me/eliandoran"]
liberapay: ElianDoran
buy_me_a_coffee: eliandoran

40
.github/instructions/nx.instructions.md vendored Normal file
View File

@@ -0,0 +1,40 @@
---
applyTo: '**'
---
// This file is automatically generated by Nx Console
You are in an nx workspace using Nx 21.3.7 and pnpm as the package manager.
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
# General Guidelines
- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture
- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors
- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool
# Generation Guidelines
If the user wants to generate something, use the following flow:
- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable
- get the available generators using the 'nx_generators' tool
- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them
- get generator details using the 'nx_generator_schema' tool
- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure
- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic
- open the generator UI using the 'nx_open_generate_ui' tool
- wait for the user to finish the generator
- read the generator log file using the 'nx_read_generator_log' tool
- use the information provided in the log file to answer the user's question or continue with what they were doing
# Running Tasks Guidelines
If the user wants help with tasks or commands (which include keywords like "test", "build", "lint", or other similar actions), use the following flow:
- Use the 'nx_current_running_tasks_details' tool to get the list of tasks (this can include tasks that were completed, stopped or failed).
- If there are any tasks, ask the user if they would like help with a specific task then use the 'nx_current_running_task_output' tool to get the terminal output for that task/command
- Use the terminal output from 'nx_current_running_task_output' to see what's wrong and help the user fix their problem. Use the appropriate tools if necessary
- If the user would like to rerun the task or command, always use `nx run <taskId>` to rerun in the terminal. This will ensure that the task will run in the nx context and will be run the same way it originally executed
- If the task was marked as "continuous" do not offer to rerun the task. This task is already running and the user can see the output in the terminal. You can use 'nx_current_running_task_output' to get the output of the task to verify the output.

17
.github/workflows/checks.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Checks
on:
push:
pull_request_target:
types: [synchronize]
jobs:
main:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Check if PRs have conflicts
uses: eps1lon/actions-label-merge-conflict@v3
with:
dirtyLabel: "merge-conflicts"
repoToken: "${{ secrets.MERGE_CONFLICT_LABEL_PAT }}"

View File

@@ -1,2 +1,2 @@
Adam Zivner <adam.zivner@gmail.com>
Adam Zivner <zadam.apps@gmail.com>
zadam <adam.zivner@gmail.com>
zadam <zadam.apps@gmail.com>

2
.nvmrc
View File

@@ -1 +1 @@
22.17.0
22.17.1

8
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"servers": {
"nx-mcp": {
"type": "http",
"url": "http://localhost:9461/mcp"
}
}
}

10
.vscode/settings.json vendored
View File

@@ -28,5 +28,13 @@
"typescript.validate.enable": true,
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
"typescript.enablePromptUseWorkspaceTsdk": true,
"search.exclude": {
"**/node_modules": true,
"docs/**/*.html": true,
"docs/**/*.png": true,
"apps/server/src/assets/doc_notes/**": true,
"apps/edit-docs/demo/**": true
},
"nxConsole.generateAiAgentRules": true
}

161
CLAUDE.md Normal file
View File

@@ -0,0 +1,161 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using NX, with multiple applications and shared packages.
## Development Commands
### Setup
- `pnpm install` - Install all dependencies
- `corepack enable` - Enable pnpm if not available
### Running Applications
- `pnpm run server:start` - Start development server (http://localhost:8080)
- `pnpm nx run server:serve` - Alternative server start command
- `pnpm nx run desktop:serve` - Run desktop Electron app
- `pnpm run server:start-prod` - Run server in production mode
### Building
- `pnpm nx build <project>` - Build specific project (server, client, desktop, etc.)
- `pnpm run client:build` - Build client application
- `pnpm run server:build` - Build server application
- `pnpm run electron:build` - Build desktop application
### Testing
- `pnpm test:all` - Run all tests (parallel + sequential)
- `pnpm test:parallel` - Run tests that can run in parallel
- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math)
- `pnpm nx test <project>` - Run tests for specific project
- `pnpm coverage` - Generate coverage reports
### Linting & Type Checking
- `pnpm nx run <project>:lint` - Lint specific project
- `pnpm nx run <project>:typecheck` - Type check specific project
## Architecture Overview
### Monorepo Structure
- **apps/**: Runnable applications
- `client/` - Frontend application (shared by server and desktop)
- `server/` - Node.js server with web interface
- `desktop/` - Electron desktop application
- `web-clipper/` - Browser extension for saving web content
- Additional tools: `db-compare`, `dump-db`, `edit-docs`
- **packages/**: Shared libraries
- `commons/` - Shared interfaces and utilities
- `ckeditor5/` - Custom rich text editor with Trilium-specific plugins
- `codemirror/` - Code editor customizations
- `highlightjs/` - Syntax highlighting
- Custom CKEditor plugins: `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid`
### Core Architecture Patterns
#### Three-Layer Cache System
- **Becca** (Backend Cache): Server-side entity cache (`apps/server/src/becca/`)
- **Froca** (Frontend Cache): Client-side mirror of backend data (`apps/client/src/services/froca.ts`)
- **Shaca** (Share Cache): Optimized cache for shared/published notes (`apps/server/src/share/`)
#### Entity System
Core entities are defined in `apps/server/src/becca/entities/`:
- `BNote` - Notes with content and metadata
- `BBranch` - Hierarchical relationships between notes (allows multiple parents)
- `BAttribute` - Key-value metadata attached to notes
- `BRevision` - Note version history
- `BOption` - Application configuration
#### Widget-Based UI
Frontend uses a widget system (`apps/client/src/widgets/`):
- `BasicWidget` - Base class for all UI components
- `NoteContextAwareWidget` - Widgets that respond to note changes
- `RightPanelWidget` - Widgets displayed in the right panel
- Type-specific widgets in `type_widgets/` directory
#### API Architecture
- **Internal API**: REST endpoints in `apps/server/src/routes/api/`
- **ETAPI**: External API for third-party integrations (`apps/server/src/etapi/`)
- **WebSocket**: Real-time synchronization (`apps/server/src/services/ws.ts`)
### Key Files for Understanding Architecture
1. **Application Entry Points**:
- `apps/server/src/main.ts` - Server startup
- `apps/client/src/desktop.ts` - Client initialization
2. **Core Services**:
- `apps/server/src/becca/becca.ts` - Backend data management
- `apps/client/src/services/froca.ts` - Frontend data synchronization
- `apps/server/src/services/backend_script_api.ts` - Scripting API
3. **Database Schema**:
- `apps/server/src/assets/db/schema.sql` - Core database structure
4. **Configuration**:
- `nx.json` - NX workspace configuration
- `package.json` - Project dependencies and scripts
## Note Types and Features
Trilium supports multiple note types, each with specialized widgets:
- **Text**: Rich text with CKEditor5 (markdown import/export)
- **Code**: Syntax-highlighted code editing with CodeMirror
- **File**: Binary file attachments
- **Image**: Image display with editing capabilities
- **Canvas**: Drawing/diagramming with Excalidraw
- **Mermaid**: Diagram generation
- **Relation Map**: Visual note relationship mapping
- **Web View**: Embedded web pages
- **Doc/Book**: Hierarchical documentation structure
## Development Guidelines
### Testing Strategy
- Server tests run sequentially due to shared database
- Client tests can run in parallel
- E2E tests use Playwright for both server and desktop apps
- Build validation tests check artifact integrity
### Scripting System
Trilium provides powerful user scripting capabilities:
- Frontend scripts run in browser context
- Backend scripts run in Node.js context with full API access
- Script API documentation available in `docs/Script API/`
### Internationalization
- Translation files in `apps/client/src/translations/`
- Supported languages: English, German, Spanish, French, Romanian, Chinese
### Security Considerations
- Per-note encryption with granular protected sessions
- CSRF protection for API endpoints
- OpenID and TOTP authentication support
- Sanitization of user-generated content
## Common Development Tasks
### Adding New Note Types
1. Create widget in `apps/client/src/widgets/type_widgets/`
2. Register in `apps/client/src/services/note_types.ts`
3. Add backend handling in `apps/server/src/services/notes.ts`
### Extending Search
- Search expressions handled in `apps/server/src/services/search/`
- Add new search operators in search context files
### Custom CKEditor Plugins
- Create new package in `packages/` following existing plugin structure
- Register in `packages/ckeditor5/src/plugins.ts`
### Database Migrations
- Add migration scripts in `apps/server/src/migrations/`
- Update schema in `apps/server/src/assets/db/schema.sql`
## Build System Notes
- Uses NX for monorepo management with build caching
- Vite for fast development builds
- ESBuild for production optimization
- pnpm workspaces for dependency management
- Docker support with multi-stage builds

View File

@@ -1,6 +1,7 @@
# Trilium Notes
![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran?style=flat-square)
Donate: ![GitHub Sponsors](https://img.shields.io/github/sponsors/eliandoran?style=flat-square) ![LiberaPay patrons](https://img.shields.io/liberapay/patrons/ElianDoran?style=flat-square)
![Docker Pulls](https://img.shields.io/docker/pulls/triliumnext/notes?style=flat-square)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/triliumnext/notes/total?style=flat-square)
[![RelativeCI](https://badges.relative-ci.com/badges/Di5q7dz9daNDZ9UXi0Bp?branch=develop&style=flat-square)](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
@@ -119,8 +120,8 @@ To install TriliumNext on your own server (including via Docker from [Dockerhub]
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
```shell
git clone https://github.com/TriliumNext/Notes.git
cd Notes
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm run server:start
```
@@ -129,8 +130,8 @@ pnpm run server:start
Download the repository, install dependencies using `pnpm` and then run the environment required to edit the documentation:
```shell
git clone https://github.com/TriliumNext/Notes.git
cd Notes
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm nx run edit-docs:edit-docs
```
@@ -138,8 +139,8 @@ pnpm nx run edit-docs:edit-docs
### Building the Executable
Download the repository, install dependencies using `pnpm` and then build the desktop app for Windows:
```shell
git clone https://github.com/TriliumNext/Notes.git
cd Notes
git clone https://github.com/TriliumNext/Trilium.git
cd Trilium
pnpm install
pnpm nx --project=desktop electron-forge:make -- --arch=x64 --platform=win32
```

View File

@@ -35,13 +35,13 @@
"chore:generate-openapi": "tsx bin/generate-openapi.js"
},
"devDependencies": {
"@playwright/test": "1.53.1",
"@stylistic/eslint-plugin": "5.0.0",
"@playwright/test": "1.54.1",
"@stylistic/eslint-plugin": "5.2.2",
"@types/express": "5.0.3",
"@types/node": "22.15.33",
"@types/node": "22.16.5",
"@types/yargs": "17.0.33",
"@vitest/coverage-v8": "3.2.4",
"eslint": "9.29.0",
"eslint": "9.32.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"esm": "3.2.25",
"jsdoc": "4.0.4",
@@ -49,7 +49,7 @@
"rcedit": "4.0.1",
"rimraf": "6.0.1",
"tslib": "2.8.1",
"typedoc": "0.28.5",
"typedoc": "0.28.7",
"typedoc-plugin-missing-exports": "4.0.0"
},
"optionalDependencies": {

View File

@@ -1,4 +1,5 @@
# The development license key for premium CKEditor features.
# Note: This key must only be used for the Trilium Notes project.
# Expires on: 2025-09-13
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w
VITE_CKEDITOR_KEY=eyJhbGciOiJFUzI1NiJ9.eyJleHAiOjE3NTc3MjE1OTksImp0aSI6ImFiN2E0NjZmLWJlZGMtNDNiYy1iMzU4LTk0NGQ0YWJhY2I3ZiIsImRpc3RyaWJ1dGlvbkNoYW5uZWwiOlsic2giLCJkcnVwYWwiXSwid2hpdGVMYWJlbCI6dHJ1ZSwiZmVhdHVyZXMiOlsiRFJVUCIsIkNNVCIsIkRPIiwiRlAiLCJTQyIsIlRPQyIsIlRQTCIsIlBPRSIsIkNDIiwiTUYiLCJTRUUiLCJFQ0giLCJFSVMiXSwidmMiOiI1MzlkOWY5YyJ9.2rvKPql4hmukyXhEtWPZ8MLxKvzPIwzCdykO653g7IxRRZy2QJpeRszElZx9DakKYZKXekVRAwQKgHxwkgbE_w
VITE_CKEDITOR_ENABLE_INSPECTOR=false

View File

@@ -1,6 +1,6 @@
{
"name": "@triliumnext/client",
"version": "0.96.0",
"version": "0.97.1",
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
"private": true,
"license": "AGPL-3.0-only",
@@ -10,16 +10,17 @@
"url": "https://github.com/TriliumNext/Notes"
},
"dependencies": {
"@eslint/js": "9.29.0",
"@eslint/js": "9.32.0",
"@excalidraw/excalidraw": "0.18.0",
"@fullcalendar/core": "6.1.17",
"@fullcalendar/daygrid": "6.1.17",
"@fullcalendar/interaction": "6.1.17",
"@fullcalendar/list": "6.1.17",
"@fullcalendar/multimonth": "6.1.17",
"@fullcalendar/timegrid": "6.1.17",
"@fullcalendar/core": "6.1.18",
"@fullcalendar/daygrid": "6.1.18",
"@fullcalendar/interaction": "6.1.18",
"@fullcalendar/list": "6.1.18",
"@fullcalendar/multimonth": "6.1.18",
"@fullcalendar/timegrid": "6.1.18",
"@maplibre/maplibre-gl-leaflet": "0.1.2",
"@mermaid-js/layout-elk": "0.1.8",
"@mind-elixir/node-menu": "1.0.5",
"@mind-elixir/node-menu": "5.0.0",
"@popperjs/core": "2.11.8",
"@triliumnext/ckeditor5": "workspace:*",
"@triliumnext/codemirror": "workspace:*",
@@ -33,9 +34,9 @@
"dayjs-plugin-utc": "0.1.2",
"debounce": "2.2.0",
"draggabilly": "3.0.0",
"force-graph": "1.49.6",
"globals": "16.2.0",
"i18next": "25.2.1",
"force-graph": "1.50.1",
"globals": "16.3.0",
"i18next": "25.3.2",
"i18next-http-backend": "3.0.2",
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
@@ -46,27 +47,29 @@
"leaflet": "1.9.4",
"leaflet-gpx": "2.2.0",
"mark.js": "8.11.1",
"marked": "15.0.12",
"mermaid": "11.7.0",
"mind-elixir": "4.6.1",
"marked": "16.1.1",
"mermaid": "11.9.0",
"mind-elixir": "5.0.4",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"preact": "10.26.9",
"split.js": "1.6.5",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",
"vanilla-js-wheel-zoom": "9.0.4"
},
"devDependencies": {
"@ckeditor/ckeditor5-inspector": "4.1.0",
"@ckeditor/ckeditor5-inspector": "5.0.0",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.32",
"@types/leaflet": "1.9.19",
"@types/leaflet": "1.9.20",
"@types/leaflet-gpx": "1.3.7",
"@types/mark.js": "8.11.12",
"@types/tabulator-tables": "6.2.8",
"copy-webpack-plugin": "13.0.0",
"happy-dom": "18.0.1",
"script-loader": "0.7.2",
"vite-plugin-static-copy": "3.1.0"
"vite-plugin-static-copy": "3.1.1"
},
"nx": {
"name": "client",

View File

@@ -28,6 +28,8 @@ 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";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
@@ -122,6 +124,7 @@ export type CommandMappings = {
showImportDialog: CommandData & { noteId: string };
openNewNoteSplit: NoteCommandData;
openInWindow: NoteCommandData;
openInPopup: CommandData & { noteIdOrPath: string; };
openNoteInNewTab: CommandData;
openNoteInNewSplit: CommandData;
openNoteInNewWindow: CommandData;
@@ -140,6 +143,7 @@ export type CommandMappings = {
};
openInTab: ContextMenuCommandData;
openNoteInSplit: ContextMenuCommandData;
openNoteInPopup: ContextMenuCommandData;
toggleNoteHoisting: ContextMenuCommandData;
insertNoteAfter: ContextMenuCommandData;
insertChildNote: ContextMenuCommandData;
@@ -261,7 +265,6 @@ export type CommandMappings = {
// Geomap
deleteFromMap: { noteId: string };
openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent };
toggleZenMode: CommandData;
@@ -275,6 +278,21 @@ export type CommandMappings = {
geoMapCreateChildNote: CommandData;
// Table view
addNewRow: CommandData & {
customOpts: CreateNoteOpts;
parentNotePath?: string;
};
addNewTableColumn: CommandData & {
columnToEdit?: ColumnComponent;
referenceColumn?: ColumnComponent;
direction?: "before" | "after";
type?: "label" | "relation";
};
deleteTableColumn: CommandData & {
columnToDelete?: ColumnComponent;
};
buildTouchBar: CommandData & {
TouchBar: typeof TouchBar;
buildIcon(name: string): NativeImage;

View File

@@ -93,11 +93,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
if (fun) {
return this.callMethod(fun, data);
} else {
if (!this.parent) {
throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`);
}
} else if (this.parent) {
return this.parent.triggerCommand(name, data);
}
}

View File

@@ -315,14 +315,39 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
}
hasNoteList() {
return (
this.note &&
["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "") &&
(this.note.hasChildren() || this.note.getLabelValue("viewType") === "calendar") &&
["book", "text", "code"].includes(this.note.type) &&
this.note.mime !== "text/x-sqlite;schema=trilium" &&
!this.note.isLabelTruthy("hideChildrenOverview")
);
const note = this.note;
if (!note) {
return false;
}
if (!["default", "contextual-help"].includes(this.viewScope?.viewMode ?? "")) {
return false;
}
// Collections must always display a note list, even if no children.
const viewType = note.getLabelValue("viewType") ?? "grid";
if (!["list", "grid"].includes(viewType)) {
return true;
}
if (!note.hasChildren()) {
return false;
}
if (!["book", "text", "code"].includes(note.type)) {
return false;
}
if (note.mime === "text/x-sqlite;schema=trilium") {
return false;
}
if (note.isLabelTruthy("hideChildrenOverview")) {
return false;
}
return true;
}
async getTextEditor(callback?: GetTextEditorCallback) {

View File

@@ -27,7 +27,6 @@ const NOTE_TYPE_ICONS = {
doc: "bx bxs-file-doc",
contentWidget: "bx bxs-widget",
mindMap: "bx bx-sitemap",
geoMap: "bx bx-map-alt",
aiChat: "bx bx-bot"
};
@@ -36,7 +35,7 @@ const NOTE_TYPE_ICONS = {
* end user. Those types should be used only for checking against, they are
* not for direct use.
*/
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap" | "aiChat";
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "aiChat";
export interface NotePathRecord {
isArchived: boolean;
@@ -257,6 +256,20 @@ class FNote {
return this.children;
}
async getSubtreeNoteIds() {
let noteIds: (string | string[])[] = [];
for (const child of await this.getChildNotes()) {
noteIds.push(child.noteId);
noteIds.push(await child.getSubtreeNoteIds());
}
return noteIds.flat();
}
async getSubtreeNotes() {
const noteIds = await this.getSubtreeNoteIds();
return this.froca.getNotes(noteIds);
}
async getChildNotes() {
return await this.froca.getNotes(this.children);
}

View File

@@ -46,28 +46,7 @@ import SharedInfoWidget from "../widgets/shared_info.js";
import FindWidget from "../widgets/find.js";
import TocWidget from "../widgets/toc.js";
import HighlightsListWidget from "../widgets/highlights_list.js";
import BulkActionsDialog from "../widgets/dialogs/bulk_actions.js";
import AboutDialog from "../widgets/dialogs/about.js";
import HelpDialog from "../widgets/dialogs/help.js";
import RecentChangesDialog from "../widgets/dialogs/recent_changes.js";
import BranchPrefixDialog from "../widgets/dialogs/branch_prefix.js";
import SortChildNotesDialog from "../widgets/dialogs/sort_child_notes.js";
import PasswordNoteSetDialog from "../widgets/dialogs/password_not_set.js";
import IncludeNoteDialog from "../widgets/dialogs/include_note.js";
import NoteTypeChooserDialog from "../widgets/dialogs/note_type_chooser.js";
import JumpToNoteDialog from "../widgets/dialogs/jump_to_note.js";
import AddLinkDialog from "../widgets/dialogs/add_link.js";
import CloneToDialog from "../widgets/dialogs/clone_to.js";
import MoveToDialog from "../widgets/dialogs/move_to.js";
import ImportDialog from "../widgets/dialogs/import.js";
import ExportDialog from "../widgets/dialogs/export.js";
import MarkdownImportDialog from "../widgets/dialogs/markdown_import.js";
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
import RevisionsDialog from "../widgets/dialogs/revisions.js";
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
import InfoDialog from "../widgets/dialogs/info.js";
import ConfirmDialog from "../widgets/dialogs/confirm.js";
import PromptDialog from "../widgets/dialogs/prompt.js";
import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
import SvgExportButton from "../widgets/floating_buttons/svg_export_button.js";
@@ -83,7 +62,7 @@ import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_ref
import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import options from "../services/options.js";
import utils, { hasTouchBar } from "../services/utils.js";
import utils from "../services/utils.js";
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
import ContextualHelpButton from "../widgets/floating_buttons/help_button.js";
import CloseZenButton from "../widgets/close_zen_button.js";
@@ -229,7 +208,7 @@ export default class DesktopLayout {
.child(new PromotedAttributesWidget())
.child(new SqlTableSchemasWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget())
.child(new NoteListWidget(false))
.child(new SearchResultWidget())
.child(new SqlResultWidget())
.child(new ScrollPaddingWidget())

View File

@@ -22,6 +22,14 @@ import RevisionsDialog from "../widgets/dialogs/revisions.js";
import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js";
import InfoDialog from "../widgets/dialogs/info.js";
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
import PopupEditorDialog from "../widgets/dialogs/popup_editor.js";
import FlexContainer from "../widgets/containers/flex_container.js";
import NoteIconWidget from "../widgets/note_icon.js";
import NoteTitleWidget from "../widgets/note_title.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js";
import NoteDetailWidget from "../widgets/note_detail.js";
import NoteListWidget from "../widgets/note_list.js";
export function applyModals(rootContainer: RootContainer) {
rootContainer
@@ -47,4 +55,15 @@ export function applyModals(rootContainer: RootContainer) {
.child(new ConfirmDialog())
.child(new PromptDialog())
.child(new IncorrectCpuArchDialog())
.child(new PopupEditorDialog()
.child(new FlexContainer("row")
.class("title-row")
.css("align-items", "center")
.cssBlock(".title-row > * { margin: 5px; }")
.child(new NoteIconWidget())
.child(new NoteTitleWidget()))
.child(new ClassicEditorToolbar())
.child(new PromotedAttributesWidget())
.child(new NoteDetailWidget())
.child(new NoteListWidget(true)))
}

View File

@@ -162,7 +162,7 @@ export default class MobileLayout {
.filling()
.contentSized()
.child(new NoteDetailWidget())
.child(new NoteListWidget())
.child(new NoteListWidget(false))
.child(new FilePropertiesWidget().css("font-size", "smaller"))
)
.child(new MobileEditorToolbar())

View File

@@ -2,7 +2,7 @@ import keyboardActionService from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js";
interface ContextMenuOptions<T> {
export interface ContextMenuOptions<T> {
x: number;
y: number;
orientation?: "left";
@@ -17,17 +17,30 @@ interface MenuSeparatorItem {
title: "----";
}
export interface MenuItemBadge {
title: string;
className?: string;
}
export interface MenuCommandItem<T> {
title: string;
command?: T;
type?: string;
/**
* The icon to display in the menu item.
*
* If not set, no icon is displayed and the item will appear shifted slightly to the left if there are other items with icons. To avoid this, use `bx bx-empty`.
*/
uiIcon?: string;
badges?: MenuItemBadge[];
templateNoteId?: string;
enabled?: boolean;
handler?: MenuHandler<T>;
items?: MenuItem<T>[] | null;
shortcut?: string;
spellingSuggestion?: string;
checked?: boolean;
columns?: number;
}
export type MenuItem<T> = MenuCommandItem<T> | MenuSeparatorItem;
@@ -146,10 +159,13 @@ class ContextMenu {
} else {
const $icon = $("<span>");
if ("uiIcon" in item && item.uiIcon) {
$icon.addClass(item.uiIcon);
} else {
$icon.append("&nbsp;");
if ("uiIcon" in item || "checked" in item) {
const icon = (item.checked ? "bx bx-check" : item.uiIcon);
if (icon) {
$icon.addClass(icon);
} else {
$icon.append("&nbsp;");
}
}
const $link = $("<span>")
@@ -157,6 +173,18 @@ class ContextMenu {
.append(" &nbsp; ") // some space between icon and text
.append(item.title);
if ("badges" in item && item.badges) {
for (let badge of item.badges) {
const badgeElement = $(`<span class="badge">`).text(badge.title);
if (badge.className) {
badgeElement.addClass(badge.className);
}
$link.append(badgeElement);
}
}
if ("shortcut" in item && item.shortcut) {
$link.append($("<kbd>").text(item.shortcut));
}
@@ -213,6 +241,9 @@ class ContextMenu {
$link.addClass("dropdown-toggle");
const $subMenu = $("<ul>").addClass("dropdown-menu");
if (!this.isMobile && item.columns) {
$subMenu.css("column-count", item.columns);
}
this.addItems($subMenu, item.items);

View File

@@ -16,7 +16,8 @@ function getItems(): MenuItem<CommandNames>[] {
return [
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" }
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" },
{ title: t("link_context_menu.open_note_in_popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit" }
];
}
@@ -40,6 +41,8 @@ function handleLinkContextMenuItem(command: string | undefined, notePath: string
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
} else if (command === "openNoteInNewWindow") {
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
} else if (command === "openNoteInPopup") {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
}
}

View File

@@ -70,8 +70,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const items: (MenuItem<TreeCommandNames> | null)[] = [
{ title: `${t("tree-context-menu.open-in-a-new-tab")}`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
isHoisted
? null
@@ -92,7 +92,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
command: "insertNoteAfter",
uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptionsOrHelp,
columns: 2
},
{
@@ -100,7 +101,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
command: "insertChildNote",
uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptionsOrHelp
enabled: notSearch && noSelectedNotes && notOptionsOrHelp,
columns: 2
},
{ title: "----" },
@@ -127,13 +129,7 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptionsOrHelp
},
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptionsOrHelp },
{
title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`,
command: "duplicateSubtree",
uiIcon: "bx bx-outline",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
},
{ title: "----" },
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
@@ -186,6 +182,13 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", enabled: isNotRoot && !isHoisted },
{
title: `${t("tree-context-menu.duplicate")} <kbd data-command="duplicateSubtree">`,
command: "duplicateSubtree",
uiIcon: "bx bx-outline",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptionsOrHelp
},
{
title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`,
command: "deleteNotes",
@@ -244,6 +247,8 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
} else if (command === "openNoteInPopup") {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
} else if (command === "convertNoteToAttachment") {
if (!(await dialogService.confirm(t("tree-context-menu.convert-to-attachment-confirm")))) {
return;

View File

@@ -3,19 +3,21 @@ import froca from "./froca.js";
import type FNote from "../entities/fnote.js";
import type { AttributeRow } from "./load_results.js";
async function addLabel(noteId: string, name: string, value: string = "") {
async function addLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/attribute`, {
type: "label",
name: name,
value: value
value: value,
isInheritable
});
}
async function setLabel(noteId: string, name: string, value: string = "") {
export async function setLabel(noteId: string, name: string, value: string = "", isInheritable = false) {
await server.put(`notes/${noteId}/set-attribute`, {
type: "label",
name: name,
value: value
value: value,
isInheritable
});
}
@@ -49,7 +51,7 @@ function removeOwnedLabelByName(note: FNote, labelName: string) {
* @param name the name of the attribute to set.
* @param value the value of the attribute to set.
*/
async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
export async function setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) {
if (value) {
// Create or update the attribute.
await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value });

View File

@@ -95,7 +95,15 @@ async function moveToParentNote(branchIdsToMove: string[], newParentBranchId: st
}
}
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false) {
/**
* Shows the delete confirmation screen
*
* @param branchIdsToDelete the list of branch IDs to delete.
* @param forceDeleteAllClones whether to check by default the "Delete also all clones" checkbox.
* @param moveToParent whether to automatically go to the parent note path after a succesful delete. Usually makes sense if deleting the active note(s).
* @returns promise that returns false if the operation was cancelled or there was nothing to delete, true if the operation succeeded.
*/
async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = false, moveToParent = true) {
branchIdsToDelete = filterRootNote(branchIdsToDelete);
if (branchIdsToDelete.length === 0) {
@@ -110,10 +118,12 @@ async function deleteNotes(branchIdsToDelete: string[], forceDeleteAllClones = f
return false;
}
try {
await activateParentNotePath();
} catch (e) {
console.error(e);
if (moveToParent) {
try {
await activateParentNotePath();
} catch (e) {
console.error(e);
}
}
const taskId = utils.randomString(10);

View File

@@ -15,6 +15,8 @@ import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation
import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js";
import { t } from "./i18n.js";
import type FNote from "../entities/fnote.js";
import toast from "./toast.js";
import { BulkAction } from "@triliumnext/commons";
const ACTION_GROUPS = [
{
@@ -89,6 +91,17 @@ function parseActions(note: FNote) {
.filter((action) => !!action);
}
export async function executeBulkActions(targetNoteIds: string[], actions: BulkAction[], includeDescendants = false) {
await server.post("bulk-action/execute", {
noteIds: targetNoteIds,
includeDescendants,
actions
});
await ws.waitForMaxKnownEntityChangeId();
toast.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
}
export default {
addAction,
parseActions,

View File

@@ -118,8 +118,17 @@ async function renderText(note: FNote | FAttachment, $renderedContent: JQuery<HT
async function renderCode(note: FNote | FAttachment, $renderedContent: JQuery<HTMLElement>) {
const blob = await note.getBlob();
let content = blob?.content || "";
if (note.mime === "application/json") {
try {
content = JSON.stringify(JSON.parse(content), null, 4);
} catch (e) {
// Ignore JSON parsing errors.
}
}
const $codeBlock = $("<code>");
$codeBlock.text(blob?.content || "");
$codeBlock.text(content);
$renderedContent.append($("<pre>").append($codeBlock));
await applySingleBlockSyntaxHighlight($codeBlock, normalizeMimeTypeForCKEditor(note.mime));
}
@@ -301,7 +310,7 @@ function getRenderingType(entity: FNote | FAttachment) {
if (type === "file" && mime === "application/pdf") {
type = "pdf";
} else if (type === "file" && mime && CODE_MIME_TYPES.has(mime)) {
} else if ((type === "file" || type === "viewConfig") && mime && CODE_MIME_TYPES.has(mime)) {
type = "code";
} else if (type === "file" && mime && mime.startsWith("audio/")) {
type = "audio";

View File

@@ -4,14 +4,14 @@ import type { ConfirmDialogOptions, ConfirmDialogResult, ConfirmWithMessageOptio
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import { focusSavedElement, saveFocusedElement } from "./focus.js";
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true) {
export async function openDialog($dialog: JQuery<HTMLElement>, closeActDialog = true, config?: Partial<Modal.Options>) {
if (closeActDialog) {
closeActiveDialog();
glob.activeDialog = $dialog;
}
saveFocusedElement();
Modal.getOrCreateInstance($dialog[0]).show();
Modal.getOrCreateInstance($dialog[0], config).show();
$dialog.on("hidden.bs.modal", () => {
const $autocompleteEl = $(".aa-input");
@@ -41,8 +41,14 @@ async function info(message: string) {
return new Promise((res) => appContext.triggerCommand("showInfoDialog", { message, callback: res }));
}
/**
* Displays a confirmation dialog with the given message.
*
* @param message the message to display in the dialog.
* @returns A promise that resolves to true if the user confirmed, false otherwise.
*/
async function confirm(message: string) {
return new Promise((res) =>
return new Promise<boolean>((res) =>
appContext.triggerCommand("showConfirmDialog", <ConfirmWithMessageOptions>{
message,
callback: (x: false | ConfirmDialogOptions) => res(x && x.confirmed)

View File

@@ -35,7 +35,7 @@ async function processEntityChanges(entityChanges: EntityChange[]) {
loadResults.addOption(attributeEntity.name);
} else if (ec.entityName === "attachments") {
processAttachment(loadResults, ec);
} else if (ec.entityName === "blobs" || ec.entityName === "etapi_tokens") {
} else if (ec.entityName === "blobs" || ec.entityName === "etapi_tokens" || ec.entityName === "file_note_mappings" || ec.entityName === "file_system_mappings") {
// NOOP - these entities are handled at the backend level and don't require frontend processing
} else {
throw new Error(`Unknown entityName '${ec.entityName}'`);

View File

@@ -49,6 +49,13 @@ function setupGlobs() {
const string = e?.reason?.message?.toLowerCase();
let message = "Uncaught error: ";
let errorObjectString;
try {
errorObjectString = JSON.stringify(e.reason)
} catch (error: any) {
errorObjectString = error.toString();
}
if (string?.includes("script error")) {
message += "No details available";
@@ -57,7 +64,7 @@ function setupGlobs() {
`Message: ${e.reason.message}`,
`Line: ${e.reason.lineNumber}`,
`Column: ${e.reason.columnNumber}`,
`Error object: ${JSON.stringify(e.reason)}`,
`Error object: ${errorObjectString}`,
`Stack: ${e.reason && e.reason.stack}`
].join(", ");
}

View File

@@ -231,6 +231,7 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
let ntxId: string | null = null;
let hoistedNoteId: string | null = null;
let searchString: string | null = null;
let openInPopup = false;
if (paramString) {
for (const pair of paramString.split("&")) {
@@ -246,6 +247,8 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
searchString = value; // supports triggering search from URL, e.g. #?searchString=blabla
} else if (["viewMode", "attachmentId"].includes(name)) {
(viewScope as any)[name] = value;
} else if (name === "popup") {
openInPopup = true;
} else {
console.warn(`Unrecognized hash parameter '${name}'.`);
}
@@ -266,7 +269,8 @@ export function parseNavigationStateFromUrl(url: string | undefined) {
ntxId,
hoistedNoteId,
viewScope,
searchString
searchString,
openInPopup
};
}
@@ -277,13 +281,21 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent) {
return goToLinkExt(evt, hrefLink, $link);
}
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
/**
* Handles navigation to a link, which can be an internal note path (e.g., `#root/1234`) or an external URL (e.g., `https://example.com`).
*
* @param evt the event that triggered the link navigation, or `null` if the link was clicked programmatically. Used to determine if the link should be opened in a new tab/window, based on the button presses.
* @param hrefLink the link to navigate to, which can be a note path (e.g., `#root/1234`) or an external URL with any supported protocol (e.g., `https://example.com`).
* @param $link the jQuery element of the link that was clicked, used to determine if the link is an anchor link (e.g., `#fn1` or `#fnref1`) and to handle it accordingly.
* @returns `true` if the link was handled (i.e., the element was found and scrolled to), or a falsy value otherwise.
*/
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
if (hrefLink?.startsWith("data:")) {
return true;
}
evt.preventDefault();
evt.stopPropagation();
evt?.preventDefault();
evt?.stopPropagation();
if (hrefLink && hrefLink.startsWith("#") && !hrefLink.startsWith("#root/") && $link) {
if (handleAnchor(hrefLink, $link)) {
@@ -291,19 +303,22 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
}
}
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
const { notePath, viewScope, openInPopup } = parseNavigationStateFromUrl(hrefLink);
const ctrlKey = utils.isCtrlKey(evt);
const shiftKey = evt.shiftKey;
const isLeftClick = "which" in evt && evt.which === 1;
const isMiddleClick = "which" in evt && evt.which === 2;
const ctrlKey = evt && utils.isCtrlKey(evt);
const shiftKey = evt?.shiftKey;
const isLeftClick = !evt || ("which" in evt && evt.which === 1);
// Right click is handled separately.
const isMiddleClick = evt && "which" in evt && evt.which === 2;
const targetIsBlank = ($link?.attr("target") === "_blank");
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
const openInNewWindow = isLeftClick && evt.shiftKey && !ctrlKey;
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
if (notePath) {
if (openInNewWindow) {
if (isLeftClick && openInPopup) {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
} else if (openInNewWindow) {
appContext.triggerCommand("openInWindow", { notePath, viewScope });
} else if (openInNewTab) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
@@ -311,7 +326,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
viewScope
});
} else if (isLeftClick) {
const ntxId = $(evt.target as any)
const ntxId = $(evt?.target as any)
.closest("[data-ntx-id]")
.attr("data-ntx-id");
@@ -379,6 +394,12 @@ function linkContextMenu(e: PointerEvent) {
return;
}
if (utils.isCtrlKey(e) && e.button === 2) {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
e.preventDefault();
return;
}
e.preventDefault();
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);

View File

@@ -40,7 +40,10 @@ interface Options {
allowCreatingNotes?: boolean;
allowJumpToSearchNotes?: boolean;
allowExternalLinks?: boolean;
/** If set, hides the right-side button corresponding to go to selected note. */
hideGoToSelectedNoteButton?: boolean;
/** If set, hides all right-side buttons in the autocomplete dropdown */
hideAllButtons?: boolean;
}
async function autocompleteSourceForCKEditor(queryText: string) {
@@ -190,9 +193,11 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
if (!options.hideAllButtons) {
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
}
if (!options.hideGoToSelectedNoteButton) {
if (!options.hideGoToSelectedNoteButton && !options.hideAllButtons) {
$el.after($goToSelectedNoteButton);
}

View File

@@ -11,7 +11,7 @@ import type FBranch from "../entities/fbranch.js";
import type { ChooseNoteTypeResponse } from "../widgets/dialogs/note_type_chooser.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
interface CreateNoteOpts {
export interface CreateNoteOpts {
isProtected?: boolean;
saveSelection?: boolean;
title?: string | null;

View File

@@ -1,38 +1,31 @@
import type FNote from "../entities/fnote.js";
import BoardView from "../widgets/view_widgets/board_view/index.js";
import CalendarView from "../widgets/view_widgets/calendar_view.js";
import GeoView from "../widgets/view_widgets/geo_view/index.js";
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
import TableView from "../widgets/view_widgets/table_view/index.js";
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
import type ViewMode from "../widgets/view_widgets/view_mode.js";
export type ViewTypeOptions = "list" | "grid" | "calendar";
const allViewTypes = ["list", "grid", "calendar", "table", "geoMap", "board"] as const;
export type ArgsWithoutNoteId = Omit<ViewModeArgs, "noteIds">;
export type ViewTypeOptions = typeof allViewTypes[number];
export default class NoteListRenderer {
private viewType: ViewTypeOptions;
public viewMode: ViewMode | null;
private args: ArgsWithoutNoteId;
public viewMode?: ViewMode<any>;
constructor($parent: JQuery<HTMLElement>, parentNote: FNote, noteIds: string[], showNotePath: boolean = false) {
this.viewType = this.#getViewType(parentNote);
const args: ViewModeArgs = {
$parent,
parentNote,
noteIds,
showNotePath
};
if (this.viewType === "list" || this.viewType === "grid") {
this.viewMode = new ListOrGridView(this.viewType, args);
} else if (this.viewType === "calendar") {
this.viewMode = new CalendarView(args);
} else {
this.viewMode = null;
}
constructor(args: ArgsWithoutNoteId) {
this.args = args;
this.viewType = this.#getViewType(args.parentNote);
}
#getViewType(parentNote: FNote): ViewTypeOptions {
const viewType = parentNote.getLabelValue("viewType");
if (!["list", "grid", "calendar"].includes(viewType || "")) {
if (!(allViewTypes as readonly string[]).includes(viewType || "")) {
// when not explicitly set, decide based on the note type
return parentNote.type === "search" ? "list" : "grid";
} else {
@@ -41,15 +34,38 @@ export default class NoteListRenderer {
}
get isFullHeight() {
return this.viewMode?.isFullHeight;
switch (this.viewType) {
case "list":
case "grid":
return false;
default:
return true;
}
}
async renderList() {
if (!this.viewMode) {
return null;
}
const args = this.args;
const viewMode = this.#buildViewMode(args);
this.viewMode = viewMode;
await viewMode.beforeRender();
return await viewMode.renderList();
}
return await this.viewMode.renderList();
#buildViewMode(args: ViewModeArgs) {
switch (this.viewType) {
case "calendar":
return new CalendarView(args);
case "table":
return new TableView(args);
case "geoMap":
return new GeoView(args);
case "board":
return new BoardView(args);
case "list":
case "grid":
default:
return new ListOrGridView(this.viewType, args);
}
}
}

View File

@@ -14,6 +14,7 @@ let dismissTimer: ReturnType<typeof setTimeout>;
function setupGlobalTooltip() {
$(document).on("mouseenter", "a", mouseEnterHandler);
$(document).on("mouseenter", "[data-href]", mouseEnterHandler);
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
$(document).on("click", (e) => {
@@ -167,7 +168,10 @@ async function renderTooltip(note: FNote | null) {
if (isContentEmpty) {
classes.push("note-no-content");
}
content = `<h5 class="${classes.join(" ")}"><a href="#${note.noteId}" data-no-context-menu="true">${noteTitleWithPathAsSuffix.prop("outerHTML")}</a></h5>`;
content = `\
<h5 class="${classes.join(" ")}">
<a href="#${note.noteId}" data-no-context-menu="true">${noteTitleWithPathAsSuffix.prop("outerHTML")}</a>
</h5>`;
}
content = `${content}<div class="note-tooltip-attributes">${$renderedAttributes[0].outerHTML}</div>`;
@@ -175,6 +179,7 @@ async function renderTooltip(note: FNote | null) {
content += $renderedContent[0].outerHTML;
}
content += `<a class="open-popup-button" title="${t("note_tooltip.quick-edit")}" href="#${note.noteId}?popup"><span class="bx bx-edit" /></a>`;
return content;
}

View File

@@ -1,32 +1,118 @@
import server from "./server.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
import type { MenuItem } from "../menus/context_menu.js";
import froca from "./froca.js";
import server from "./server.js";
import type { MenuCommandItem, MenuItem, MenuItemBadge } from "../menus/context_menu.js";
import type { NoteType } from "../entities/fnote.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
export interface NoteTypeMapping {
type: NoteType;
mime?: string;
title: string;
icon?: string;
/** Indicates whether this type should be marked as a newly introduced feature. */
isNew?: boolean;
/** Indicates that this note type is part of a beta feature. */
isBeta?: boolean;
/** Indicates that this note type cannot be created by the user. */
reserved?: boolean;
/** Indicates that once a note of this type is created, its type can no longer be changed. */
static?: boolean;
}
export const NOTE_TYPES: NoteTypeMapping[] = [
// The suggested note type ordering method: insert the item into the corresponding group,
// then ensure the items within the group are ordered alphabetically.
// The default note type (always the first item)
{ type: "text", mime: "text/html", title: t("note_types.text"), icon: "bx-note" },
// Text notes group
{ type: "book", mime: "", title: t("note_types.book"), icon: "bx-book" },
// Graphic notes
{ type: "canvas", mime: "application/json", title: t("note_types.canvas"), icon: "bx-pen" },
{ type: "mermaid", mime: "text/mermaid", title: t("note_types.mermaid-diagram"), icon: "bx-selection" },
// Map notes
{ type: "mindMap", mime: "application/json", title: t("note_types.mind-map"), icon: "bx-sitemap" },
{ type: "noteMap", mime: "", title: t("note_types.note-map"), icon: "bxs-network-chart", static: true },
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), icon: "bxs-network-chart" },
// Misc note types
{ type: "render", mime: "", title: t("note_types.render-note"), icon: "bx-extension" },
{ type: "search", title: t("note_types.saved-search"), icon: "bx-file-find", static: true },
{ type: "webView", mime: "", title: t("note_types.web-view"), icon: "bx-globe-alt" },
// Code notes
{ type: "code", mime: "text/plain", title: t("note_types.code"), icon: "bx-code" },
// Reserved types (cannot be created by the user)
{ type: "contentWidget", mime: "", title: t("note_types.widget"), reserved: true },
{ type: "doc", mime: "", title: t("note_types.doc"), reserved: true },
{ type: "file", title: t("note_types.file"), reserved: true },
{ type: "image", title: t("note_types.image"), reserved: true },
{ type: "launcher", mime: "", title: t("note_types.launcher"), reserved: true },
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), reserved: true }
];
/** The maximum age in days for a template to be marked with the "New" badge */
const NEW_TEMPLATE_MAX_AGE = 3;
/** The length of a day in milliseconds. */
const DAY_LENGTH = 1000 * 60 * 60 * 24;
/** The menu item badge used to mark new note types and templates */
const NEW_BADGE: MenuItemBadge = {
title: t("note_types.new-feature"),
className: "new-note-type-badge"
};
/** The menu item badge used to mark note types that are part of a beta feature */
const BETA_BADGE = {
title: t("note_types.beta-feature")
};
const SEPARATOR = { title: "----" };
const creationDateCache = new Map<string, Date>();
let rootCreationDate: Date | undefined;
async function getNoteTypeItems(command?: TreeCommandNames) {
const items: MenuItem<TreeCommandNames>[] = [
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
{ title: t("note_types.relation-map"), command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.note-map"), command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.render-note"), command, type: "render", uiIcon: "bx bx-extension" },
{ title: t("note_types.book"), command, type: "book", uiIcon: "bx bx-book" },
{ title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: t("note_types.canvas"), command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" },
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" },
{ title: t("note_types.geo-map"), command, type: "geoMap", uiIcon: "bx bx-map-alt" },
...await getBuiltInTemplates(command),
...getBlankNoteTypes(command),
...await getBuiltInTemplates(t("note_types.collections"), command, true),
...await getBuiltInTemplates(null, command, false),
...await getUserTemplates(command)
];
return items;
}
function getBlankNoteTypes(command?: TreeCommandNames): MenuItem<TreeCommandNames>[] {
return NOTE_TYPES
.filter((nt) => !nt.reserved && nt.type !== "book")
.map((nt) => {
const menuItem: MenuCommandItem<TreeCommandNames> = {
title: nt.title,
command,
type: nt.type,
uiIcon: "bx " + nt.icon,
badges: []
}
if (nt.isNew) {
menuItem.badges?.push(NEW_BADGE);
}
if (nt.isBeta) {
menuItem.badges?.push(BETA_BADGE);
}
return menuItem;
});
}
async function getUserTemplates(command?: TreeCommandNames) {
const templateNoteIds = await server.get<string[]>("search-templates");
const templateNotes = await froca.getNotes(templateNoteIds);
@@ -37,19 +123,26 @@ async function getUserTemplates(command?: TreeCommandNames) {
const items: MenuItem<TreeCommandNames>[] = [
SEPARATOR
];
for (const templateNote of templateNotes) {
items.push({
const item: MenuItem<TreeCommandNames> = {
title: templateNote.title,
uiIcon: templateNote.getIcon(),
command: command,
type: templateNote.type,
templateNoteId: templateNote.noteId
});
};
if (await isNewTemplate(templateNote.noteId)) {
item.badges = [NEW_BADGE];
}
items.push(item);
}
return items;
}
async function getBuiltInTemplates(command?: TreeCommandNames) {
async function getBuiltInTemplates(title: string | null, command: TreeCommandNames | undefined, filterCollections: boolean) {
const templatesRoot = await froca.getNote("_templates");
if (!templatesRoot) {
console.warn("Unable to find template root.");
@@ -61,21 +154,85 @@ async function getBuiltInTemplates(command?: TreeCommandNames) {
return [];
}
const items: MenuItem<TreeCommandNames>[] = [
SEPARATOR
];
for (const templateNote of childNotes) {
const items: MenuItem<TreeCommandNames>[] = [];
if (title) {
items.push({
title: title,
enabled: false,
uiIcon: "bx bx-empty"
});
} else {
items.push(SEPARATOR);
}
for (const templateNote of childNotes) {
if (templateNote.hasLabel("collection") !== filterCollections) {
continue;
}
const item: MenuItem<TreeCommandNames> = {
title: templateNote.title,
uiIcon: templateNote.getIcon(),
command: command,
type: templateNote.type,
templateNoteId: templateNote.noteId
});
};
if (await isNewTemplate(templateNote.noteId)) {
item.badges = [NEW_BADGE];
}
items.push(item);
}
return items;
}
async function isNewTemplate(templateNoteId) {
if (rootCreationDate === undefined) {
// Retrieve the root note creation date
try {
let rootNoteInfo: any = await server.get("notes/root");
if ("dateCreated" in rootNoteInfo) {
rootCreationDate = new Date(rootNoteInfo.dateCreated);
}
} catch (ex) {
console.error(ex);
}
}
// Try to retrieve the template's creation date from the cache
let creationDate: Date | undefined = creationDateCache.get(templateNoteId);
if (creationDate === undefined) {
// The creation date isn't available in the cache, try to retrieve it from the server
try {
const noteInfo: any = await server.get("notes/" + templateNoteId);
if ("dateCreated" in noteInfo) {
creationDate = new Date(noteInfo.dateCreated);
creationDateCache.set(templateNoteId, creationDate);
}
} catch (ex) {
console.error(ex);
}
}
if (creationDate) {
if (rootCreationDate && creationDate.getTime() - rootCreationDate.getTime() < 30000) {
// Ignore templates created within 30 seconds after the root note is created.
// This is useful to prevent predefined templates from being marked
// as 'New' after setting up a new database.
return false;
}
// Determine the difference in days between now and the template's creation date
const age = (new Date().getTime() - creationDate.getTime()) / DAY_LENGTH;
// Return true if the template is at most NEW_TEMPLATE_MAX_AGE days old
return (age <= NEW_TEMPLATE_MAX_AGE);
} else {
return false;
}
}
export default {
getNoteTypeItems
};

View File

@@ -1,4 +1,4 @@
type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url";
export type LabelType = "text" | "number" | "boolean" | "date" | "datetime" | "time" | "url" | "color";
type Multiplicity = "single" | "multi";
export interface DefinitionObject {
@@ -17,7 +17,7 @@ function parse(value: string) {
for (const token of tokens) {
if (token === "promoted") {
defObj.isPromoted = true;
} else if (["text", "number", "boolean", "date", "datetime", "time", "url"].includes(token)) {
} else if (["text", "number", "boolean", "date", "datetime", "time", "url", "color"].includes(token)) {
defObj.labelType = token as LabelType;
} else if (["single", "multi"].includes(token)) {
defObj.multiplicity = token as Multiplicity;

View File

@@ -51,6 +51,14 @@ export default class SpacedUpdate {
this.lastUpdated = Date.now();
}
/**
* Sets the update interval for the spaced update.
* @param interval The update interval in milliseconds.
*/
setUpdateInterval(interval: number) {
this.updateInterval = interval;
}
triggerUpdate() {
if (!this.changed) {
return;

View File

@@ -29,6 +29,14 @@ async function formatCodeBlocks() {
await formatCodeBlocks($("#content"));
}
async function setupTextNote() {
formatCodeBlocks();
applyMath();
const setupMermaid = (await import("./share/mermaid.js")).default;
setupMermaid();
}
/**
* Fetch note with given ID from backend
*
@@ -47,8 +55,11 @@ async function fetchNote(noteId: string | null = null) {
document.addEventListener(
"DOMContentLoaded",
() => {
formatCodeBlocks();
applyMath();
const noteType = determineNoteType();
if (noteType === "text") {
setupTextNote();
}
const toggleMenuButton = document.getElementById("toggleMenuButton");
const layout = document.getElementById("layout");
@@ -60,6 +71,12 @@ document.addEventListener(
false
);
function determineNoteType() {
const bodyClass = document.body.className;
const match = bodyClass.match(/type-([^\s]+)/);
return match ? match[1] : null;
}
// workaround to prevent webpack from removing "fetchNote" as dead code:
// add fetchNote as property to the window object
Object.defineProperty(window, "fetchNote", {

View File

@@ -0,0 +1,17 @@
import mermaid from "mermaid";
export default function setupMermaid() {
for (const codeBlock of document.querySelectorAll("#content pre code.language-mermaid")) {
const parentPre = codeBlock.parentElement;
if (!parentPre) {
continue;
}
const mermaidDiv = document.createElement("div");
mermaidDiv.classList.add("mermaid");
mermaidDiv.innerHTML = codeBlock.innerHTML;
parentPre.replaceWith(mermaidDiv);
}
mermaid.init();
}

View File

@@ -81,8 +81,8 @@ body {
/* -- Overrides the default colors used by the ckeditor5-image package. --------------------- */
--ck-color-image-caption-background: var(--main-background-color);
--ck-color-image-caption-text: var(--main-text-color);
--ck-content-color-image-caption-background: var(--main-background-color);
--ck-content-color-image-caption-text: var(--main-text-color);
/* -- Overrides the default colors used by the ckeditor5-widget package. -------------------- */

View File

@@ -139,12 +139,6 @@ textarea,
color: var(--muted-text-color);
}
/* Restore default apperance */
input[type="number"],
input[type="checkbox"] {
appearance: auto !important;
}
/* Add a gap between consecutive radios / check boxes */
label.tn-radio + label.tn-radio,
label.tn-checkbox + label.tn-checkbox {
@@ -192,6 +186,13 @@ samp {
font-family: var(--monospace-font-family) !important;
}
.badge {
--bs-badge-color: var(--muted-text-color);
margin-left: 8px;
background: var(--accented-background-color);
}
.input-group-text {
background-color: var(--accented-background-color) !important;
color: var(--muted-text-color) !important;
@@ -320,7 +321,8 @@ button kbd {
}
}
.dropdown-menu {
.dropdown-menu,
.tabulator-popup-container {
color: var(--menu-text-color) !important;
font-size: inherit;
background-color: var(--menu-background-color) !important;
@@ -330,7 +332,13 @@ button kbd {
--bs-dropdown-link-active-bg: var(--active-item-background-color) !important;
}
body.desktop .dropdown-menu {
.dropdown-menu .dropdown-divider {
break-before: avoid;
break-after: avoid;
}
body.desktop .dropdown-menu,
body.desktop .tabulator-popup-container {
border: 1px solid var(--dropdown-border-color);
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
animation: dropdown-menu-opening 100ms ease-in;
@@ -373,7 +381,8 @@ body.desktop .dropdown-menu {
}
.dropdown-menu a:hover:not(.disabled),
.dropdown-item:hover:not(.disabled, .dropdown-item-container) {
.dropdown-item:hover:not(.disabled, .dropdown-item-container),
.tabulator-menu-item:hover {
color: var(--hover-item-text-color) !important;
background-color: var(--hover-item-background-color) !important;
border-color: var(--hover-item-border-color) !important;
@@ -528,6 +537,7 @@ button.btn-sm {
/* Making this narrower because https://github.com/zadam/trilium/issues/502 (problem only in smaller font sizes) */
min-width: 0;
padding: 0;
z-index: 1000;
}
pre:not(.hljs) {
@@ -759,6 +769,14 @@ table.promoted-attributes-in-tooltip th {
font-size: small;
}
.note-tooltip-content .open-popup-button {
position: absolute;
right: 15px;
bottom: 8px;
font-size: 1.2em;
color: inherit;
}
.note-tooltip-attributes {
display: -webkit-box;
-webkit-box-orient: vertical;
@@ -900,6 +918,13 @@ div[data-notify="container"] {
font-family: var(--monospace-font-family);
}
.ck-content {
--ck-content-font-family: var(--detail-font-family);
--ck-content-font-size: 1.1em;
--ck-content-font-color: var(--main-text-color);
--ck-content-line-height: var(--bs-body-line-height);
}
.ck-content .table table th {
background-color: var(--accented-background-color);
}
@@ -1186,12 +1211,14 @@ body.mobile .dropdown-submenu > .dropdown-menu {
}
#context-menu-container,
#context-menu-container .dropdown-menu {
padding: 3px 0 0;
#context-menu-container .dropdown-menu,
.tabulator-popup-container {
padding: 3px 0;
z-index: 2000;
}
#context-menu-container .dropdown-item {
#context-menu-container .dropdown-item,
.tabulator-menu .tabulator-menu-item {
padding: 0 7px 0 10px;
cursor: pointer;
user-select: none;
@@ -2174,3 +2201,189 @@ footer.file-footer button {
content: "\ec24";
transform: rotate(180deg);
}
/* File System Sync Modal Styles */
.mapping-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1050;
display: flex;
align-items: center;
justify-content: center;
}
.mapping-modal .modal-backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1051;
}
.mapping-modal .modal-content {
position: relative;
background: var(--main-background-color);
border: 1px solid var(--main-border-color);
border-radius: 5px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
width: 90%;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
z-index: 1052;
}
.mapping-modal .modal-header {
display: flex;
justify-content: between;
align-items: center;
padding: 1rem;
border-bottom: 1px solid var(--main-border-color);
}
.mapping-modal .modal-title {
margin: 0;
font-size: 1.25rem;
flex: 1;
}
.mapping-modal .modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--muted-text-color);
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.mapping-modal .modal-close:hover {
color: var(--main-text-color);
}
.mapping-modal .modal-body {
padding: 1rem;
}
.mapping-modal .modal-footer {
padding: 1rem;
border-top: 1px solid var(--main-border-color);
display: flex;
justify-content: flex-end;
gap: 0.5rem;
}
/* File System Sync Mapping Cards */
.mapping-item.card {
border: 1px solid var(--main-border-color);
border-radius: 5px;
transition: box-shadow 0.2s ease;
}
.mapping-item.card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.mapping-item .mapping-path {
font-family: monospace;
font-size: 0.9rem;
word-break: break-all;
}
.mapping-item .mapping-details {
font-size: 0.85rem;
margin-top: 0.25rem;
}
.mapping-item .mapping-status {
margin-top: 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.mapping-item .mapping-actions {
display: flex;
gap: 0.25rem;
}
.mapping-item .mapping-actions .btn {
padding: 0.25rem 0.5rem;
}
/* Status Badges */
.status-badge.badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 3px;
}
.status-badge.badge-success {
background-color: #28a745;
color: white;
}
.status-badge.badge-danger {
background-color: #dc3545;
color: white;
}
.status-badge.badge-secondary {
background-color: #6c757d;
color: white;
}
/* Path Validation Styles */
.path-validation-result {
margin-top: 0.5rem;
font-size: 0.875rem;
}
.path-validation-result .text-success {
color: #28a745;
}
.path-validation-result .text-warning {
color: #ffc107;
}
.path-validation-result .text-danger {
color: #dc3545;
}
/* Sync Status Section */
.sync-status-container {
margin: 1rem 0;
padding: 1rem;
background: var(--accented-background-color);
border-radius: 5px;
}
.sync-status-info .status-item,
.sync-status-info .active-mappings-count {
margin-bottom: 0.5rem;
}
/* Form Enhancements */
.mapping-form .form-group {
margin-bottom: 1rem;
}
.mapping-form .subtree-options {
margin-left: 1.5rem;
}
.mapping-form .help-block {
font-size: 0.875rem;
color: var(--muted-text-color);
margin-top: 0.25rem;
}

View File

@@ -0,0 +1,199 @@
.tabulator {
--table-background-color: var(--main-background-color);
--col-header-background-color: var(--main-background-color);
--col-header-hover-background-color: var(--accented-background-color);
--col-header-text-color: var(--main-text-color);
--col-header-arrow-active-color: var(--main-text-color);
--col-header-arrow-inactive-color: var(--more-accented-background-color);
--col-header-separator-border: none;
--col-header-bottom-border: 2px solid var(--main-border-color);
--row-background-color: var(--main-background-color);
--row-alternate-background-color: var(--main-background-color);
--row-moving-background-color: var(--accented-background-color);
--row-text-color: var(--main-text-color);
--row-delimiter-color: var(--more-accented-background-color);
--cell-horiz-padding-size: 8px;
--cell-vert-padding-size: 8px;
--cell-editable-hover-outline-color: var(--main-border-color);
--cell-read-only-text-color: var(--muted-text-color);
--cell-editing-border-color: var(--main-border-color);
--cell-editing-border-width: 2px;
--cell-editing-background-color: var(--ck-color-selector-focused-cell-background);
--cell-editing-text-color: initial;
background: unset;
border: unset;
}
.tabulator .tabulator-tableholder .tabulator-table {
background: var(--table-background-color);
}
/* Column headers */
.tabulator div.tabulator-header {
border-bottom: var(--col-header-bottom-border);
background: var(--col-header-background-color);
color: var(--col-header-text-color);
}
.tabulator .tabulator-col-content {
padding: 8px 4px !important;
}
@media (hover: hover) and (pointer: fine) {
.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover {
background-color: var(--col-header-hover-background-color);
}
}
.tabulator div.tabulator-header .tabulator-col.tabulator-moving {
border: none;
background: var(--col-header-hover-background-color);
}
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
border-bottom-color: var(--col-header-arrow-active-color);
border-top-color: var(--col-header-arrow-active-color);
}
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort="none"] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow {
border-bottom-color: var(--col-header-arrow-inactive-color);
}
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
margin-left: var(--cell-editing-border-width);
}
.tabulator div.tabulator-header .tabulator-col,
.tabulator div.tabulator-header .tabulator-frozen.tabulator-frozen-left {
background: var(--col-header-background-color);
border-right: var(--col-header-separator-border);
}
/* Table body */
.tabulator-tableholder {
padding-top: 10px;
height: unset !important; /* Don't extend on the full height */
}
/* Rows */
.tabulator-row .tabulator-cell {
padding: var(--cell-vert-padding-size) var(--cell-horiz-padding-size);
}
.tabulator-row .tabulator-cell input {
padding-left: var(--cell-horiz-padding-size) !important;
padding-right: var(--cell-horiz-padding-size) !important;
}
.tabulator-row {
background: transparent;
border-top: none;
border-bottom: 1px solid var(--row-delimiter-color);
color: var(--row-text-color);
}
.tabulator-row.tabulator-row-odd {
background: var(--row-background-color);
}
.tabulator-row.tabulator-row-even {
background: var(--row-alternate-background-color);
}
.tabulator-row.tabulator-moving {
border-color: transparent;
background-color: var(--row-moving-background-color);
}
/* Cell */
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left {
margin-right: var(--cell-editing-border-width);
}
.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left,
.tabulator-row .tabulator-cell {
border-right-color: transparent;
}
.tabulator-row .tabulator-cell:not(.tabulator-editable) {
color: var(--cell-read-only-text-color);
}
.tabulator:not(.tabulator-editing) .tabulator-row .tabulator-cell.tabulator-editable:hover {
outline: 2px solid var(--cell-editable-hover-outline-color);
outline-offset: -1px;
}
.tabulator-row .tabulator-cell.tabulator-editing {
border-color: transparent;
}
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing {
outline: calc(var(--cell-editing-border-width) - 1px) solid var(--cell-editing-border-color);
border-color: var(--cell-editing-border-color);
background: var(--cell-editing-background-color);
}
.tabulator-row:not(.tabulator-moving) .tabulator-cell.tabulator-editing > * {
color: var(--cell-editing-text-color);
}
.tabulator .tree-collapse,
.tabulator .tree-expand {
color: var(--row-text-color);
}
/* Align items without children/expander to the ones with. */
.tabulator-cell[tabulator-field="title"] > span:first-child, /* 1st level */
.tabulator-cell[tabulator-field="title"] > div:first-child + span { /* sub-level */
padding-left: 21px;
}
/* Checkbox cells */
.tabulator .tabulator-cell:has(svg),
.tabulator .tabulator-cell:has(input[type="checkbox"]) {
padding-left: 8px;
display: inline-flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.tabulator .tabulator-cell input[type="checkbox"] {
margin: 0;
}
.tabulator .tabulator-footer {
color: var(--main-text-color);
}
/* Context menus */
.tabulator-popup-container {
min-width: 10em;
border-radius: var(--bs-border-radius);
}
.tabulator-menu .tabulator-menu-item {
border: 1px solid transparent;
color: var(--menu-text-color);
font-size: 16px;
}
/* Footer */
:root .tabulator .tabulator-footer {
border-top: unset;
padding: 10px 0;
}

View File

@@ -178,6 +178,9 @@
--alert-bar-background: #6b6b6b3b;
--badge-background-color: #ffffff1a;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #000000b3;

View File

@@ -171,6 +171,9 @@
--alert-bar-background: #32637b29;
--badge-background-color: #00000011;
--badge-text-color: var(--muted-text-color);
--promoted-attribute-card-background-color: var(--card-background-color);
--promoted-attribute-card-shadow-color: #00000033;

View File

@@ -4,6 +4,7 @@
@import url(./pages.css);
@import url(./ribbon.css);
@import url(./notes/text.css);
@import url(./notes/collections/table.css);
@font-face {
font-family: "Inter";
@@ -171,9 +172,19 @@ html body .dropdown-item[disabled] {
opacity: var(--menu-item-disabled-opacity);
}
/* Badges */
:root .badge {
--bs-badge-color: var(--badge-text-color);
--bs-badge-font-weight: 500;
background: var(--badge-background-color);
text-transform: uppercase;
letter-spacing: .2pt;
}
/* Menu item icon */
.dropdown-item .bx {
transform: translateY(var(--menu-item-icon-vert-offset));
translate: 0 var(--menu-item-icon-vert-offset);
color: var(--menu-item-icon-color) !important;
font-size: 1.1em;
}

View File

@@ -382,6 +382,10 @@ div.tn-tool-dialog {
/* DELETE NOTE PREVIEW DIALOG */
.delete-notes-dialog .modal-dialog {
--bs-modal-width: fit-content;
}
.delete-notes-list .note-path {
padding-left: 8px;
}

View File

@@ -0,0 +1,13 @@
:root .tabulator {
--col-header-hover-background-color: var(--hover-item-background-color);
--col-header-arrow-active-color: var(--active-item-text-color);
--col-header-arrow-inactive-color: var(--main-border-color);
--row-moving-background-color: var(--more-accented-background-color);
--cell-editable-hover-outline-color: var(--input-focus-outline-color);
--cell-editing-border-color: var(--input-focus-outline-color);
--cell-editing-background-color: var(--input-background-color);
--cell-editing-text-color: var(--input-text-color);
}

View File

@@ -46,6 +46,12 @@ div.promoted-attributes-container {
.image-properties > div:first-child > span > strong {
opacity: 0.65;
font-weight: 500;
vertical-align: top;
}
.note-info-widget-table td,
.file-properties-widget .file-table td {
vertical-align: top;
}
.file-properties-widget {

View File

@@ -71,12 +71,13 @@ body.background-effects.platform-win32.layout-vertical #vertical-main-container
/* #endregion */
/* Matches when the left pane is collapsed */
:has(.layout-vertical #left-pane.hidden-int) {
#horizontal-main-container.left-pane-hidden {
--center-pane-border-radius: 0;
--tab-first-item-horiz-offset: 5px;
}
:has(#left-pane.hidden-int) #launcher-pane.vertical {
/* Add a border to the vertical launch bar if collapsed. */
body.layout-vertical #horizontal-main-container.left-pane-hidden #launcher-pane.vertical {
border-right: 2px solid var(--left-pane-collapsed-border-color);
}
@@ -1300,9 +1301,9 @@ div.promoted-attribute-cell .tn-checkbox {
height: 1cap;
}
/* The <div> containing the checkbox for a promoted boolean attribute */
div.promoted-attribute-cell div:has(input[type="checkbox"]) {
order: -1; /* Relocate the checkbox before the label */
/* Relocate the checkbox before the label */
div.promoted-attribute-cell.promoted-attribute-label-boolean > div:first-of-type {
order: -1;
margin-right: 1.5em;
}
@@ -1677,4 +1678,42 @@ div.find-replace-widget div.find-widget-found-wrapper > span {
#right-pane .highlights-list li:active {
background: transparent;
transition: none;
}
/** Canvas **/
.excalidraw {
--border-radius-lg: 6px;
}
.excalidraw .Island {
backdrop-filter: var(--dropdown-backdrop-filter);
}
.excalidraw .Island.App-toolbar {
--island-bg-color: var(--floating-button-background-color);
--shadow-island: 1px 1px 1px var(--floating-button-shadow-color);
}
.excalidraw .dropdown-menu {
border: unset !important;
box-shadow: unset !important;
background-color: transparent !important;
--island-bg-color: var(--menu-background-color);
--shadow-island: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
--default-border-color: var(--bs-dropdown-divider-bg);
--button-hover-bg: var(--hover-item-background-color);
}
.excalidraw .dropdown-menu .dropdown-menu-container {
border-radius: var(--dropdown-border-radius);
}
.excalidraw .dropdown-menu .dropdown-menu-container > div:not([class]):not(:last-child) {
margin-left: calc(var(--padding) * var(--space-factor) * -1) !important;
margin-right: calc(var(--padding) * var(--space-factor) * -1) !important;
}
.excalidraw .dropdown-menu:before {
content: unset !important;
}

View File

@@ -754,7 +754,7 @@
"expand_all_children": "展开所有子项",
"collapse": "折叠",
"expand": "展开",
"book_properties": "书籍属性",
"book_properties": "",
"invalid_view_type": "无效的查看类型 '{{type}}'",
"calendar": "日历"
},
@@ -1431,7 +1431,6 @@
"move-to": "移动到...",
"paste-into": "粘贴到里面",
"paste-after": "粘贴到后面",
"duplicate-subtree": "复制子树",
"export": "导出",
"import-into-note": "导入到笔记",
"apply-bulk-actions": "应用批量操作",
@@ -1450,7 +1449,7 @@
"relation-map": "关系图",
"note-map": "笔记地图",
"render-note": "渲染笔记",
"book": "",
"book": "",
"mermaid-diagram": "Mermaid 图",
"canvas": "画布",
"web-view": "网页视图",

View File

@@ -750,7 +750,7 @@
"expand_all_children": "Unternotizen ausklappen",
"collapse": "Einklappen",
"expand": "Ausklappen",
"book_properties": "Bucheigenschaften",
"book_properties": "",
"invalid_view_type": "Ungültiger Ansichtstyp „{{type}}“",
"calendar": "Kalender"
},
@@ -1384,7 +1384,7 @@
"move-to": "Verschieben nach...",
"paste-into": "Als Unternotiz einfügen",
"paste-after": "Danach einfügen",
"duplicate-subtree": "Notizbaum duplizieren",
"duplicate": "Duplizieren",
"export": "Exportieren",
"import-into-note": "In Notiz importieren",
"apply-bulk-actions": "Massenaktionen ausführen",
@@ -1403,7 +1403,7 @@
"relation-map": "Beziehungskarte",
"note-map": "Notizkarte",
"render-note": "Render Notiz",
"book": "Buch",
"book": "",
"mermaid-diagram": "Mermaid Diagram",
"canvas": "Canvas",
"web-view": "Webansicht",

View File

@@ -443,7 +443,8 @@
"other_notes_with_name": "Other notes with {{attributeType}} name \"{{attributeName}}\"",
"and_more": "... and {{count}} more.",
"print_landscape": "When exporting to PDF, changes the orientation of the page to landscape instead of portrait.",
"print_page_size": "When exporting to PDF, changes the size of the page. Supported values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>."
"print_page_size": "When exporting to PDF, changes the size of the page. Supported values: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
"color_type": "Color"
},
"attribute_editor": {
"help_text_body1": "To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code>",
@@ -758,9 +759,12 @@
"expand_all_children": "Expand all children",
"collapse": "Collapse",
"expand": "Expand",
"book_properties": "Book Properties",
"book_properties": "Collection Properties",
"invalid_view_type": "Invalid view type '{{type}}'",
"calendar": "Calendar"
"calendar": "Calendar",
"table": "Table",
"geo-map": "Geo Map",
"board": "Board"
},
"edited_notes": {
"no_edited_notes_found": "No edited notes on this day yet...",
@@ -837,7 +841,8 @@
"unknown_label_type": "Unknown label type '{{type}}'",
"unknown_attribute_type": "Unknown attribute type '{{type}}'",
"add_new_attribute": "Add new attribute",
"remove_this_attribute": "Remove this attribute"
"remove_this_attribute": "Remove this attribute",
"remove_color": "Remove the color label"
},
"script_executor": {
"query": "Query",
@@ -960,7 +965,7 @@
"no_attachments": "This note has no attachments."
},
"book": {
"no_children_help": "This note of type Book doesn't have any child notes so there's nothing to display. See <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> for details."
"no_children_help": "This collection doesn't have any child notes so there's nothing to display. See <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> for details."
},
"editable_code": {
"placeholder": "Type the content of your code note here..."
@@ -1023,7 +1028,7 @@
"title": "Consistency Checks",
"find_and_fix_button": "Find and fix consistency issues",
"finding_and_fixing_message": "Finding and fixing consistency issues...",
"issues_fixed_message": "Consistency issues should be fixed."
"issues_fixed_message": "Any consistency issue which may have been found is now fixed."
},
"database_anonymization": {
"title": "Database Anonymization",
@@ -1593,12 +1598,13 @@
"move-to": "Move to...",
"paste-into": "Paste into",
"paste-after": "Paste after",
"duplicate-subtree": "Duplicate subtree",
"duplicate": "Duplicate",
"export": "Export",
"import-into-note": "Import into note",
"apply-bulk-actions": "Apply bulk actions",
"converted-to-attachments": "{{count}} notes have been converted to attachments.",
"convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?"
"convert-to-attachment-confirm": "Are you sure you want to convert note selected notes into attachments of their parent notes?",
"open-in-popup": "Quick edit"
},
"shared_info": {
"shared_publicly": "This note is shared publicly on",
@@ -1612,7 +1618,7 @@
"relation-map": "Relation Map",
"note-map": "Note Map",
"render-note": "Render Note",
"book": "Book",
"book": "Collection",
"mermaid-diagram": "Mermaid Diagram",
"canvas": "Canvas",
"web-view": "Web View",
@@ -1626,7 +1632,9 @@
"geo-map": "Geo Map",
"beta-feature": "Beta",
"ai-chat": "AI Chat",
"task-list": "Task List"
"task-list": "Task List",
"new-feature": "New",
"collections": "Collections"
},
"protect_note": {
"toggle-on": "Protect the note",
@@ -1828,7 +1836,8 @@
"link_context_menu": {
"open_note_in_new_tab": "Open note in a new tab",
"open_note_in_new_split": "Open note in a new split",
"open_note_in_new_window": "Open note in a new window"
"open_note_in_new_window": "Open note in a new window",
"open_note_in_popup": "Quick edit"
},
"electron_integration": {
"desktop-application": "Desktop Application",
@@ -1848,7 +1857,8 @@
"full-text-search": "Full text search"
},
"note_tooltip": {
"note-has-been-deleted": "Note has been deleted."
"note-has-been-deleted": "Note has been deleted.",
"quick-edit": "Quick edit"
},
"geo-map": {
"create-child-note-title": "Create a new child note and add it to the map",
@@ -1857,7 +1867,8 @@
},
"geo-map-context": {
"open-location": "Open location",
"remove-from-map": "Remove from map"
"remove-from-map": "Remove from map",
"add-note": "Add a marker at this location"
},
"help-button": {
"title": "Open the relevant help page"
@@ -1933,5 +1944,48 @@
"title": "Features",
"emoji_completion_enabled": "Enable Emoji auto-completion",
"note_completion_enabled": "Enable note auto-completion"
},
"table_view": {
"new-row": "New row",
"new-column": "New column",
"sort-column-by": "Sort by \"{{title}}\"",
"sort-column-ascending": "Ascending",
"sort-column-descending": "Descending",
"sort-column-clear": "Clear sorting",
"hide-column": "Hide column \"{{title}}\"",
"show-hide-columns": "Show/hide columns",
"row-insert-above": "Insert row above",
"row-insert-below": "Insert row below",
"row-insert-child": "Insert child note",
"add-column-to-the-left": "Add column to the left",
"add-column-to-the-right": "Add column to the right",
"edit-column": "Edit column",
"delete_column_confirmation": "Are you sure you want to delete this column? The corresponding attribute will be removed from all notes.",
"delete-column": "Delete column",
"new-column-label": "Label",
"new-column-relation": "Relation"
},
"book_properties_config": {
"hide-weekends": "Hide weekends",
"display-week-numbers": "Display week numbers",
"map-style": "Map style:",
"max-nesting-depth": "Max nesting depth:",
"raster": "Raster",
"vector_light": "Vector (Light)",
"vector_dark": "Vector (Dark)",
"show-scale": "Show scale"
},
"table_context_menu": {
"delete_row": "Delete row"
},
"board_view": {
"delete-note": "Delete Note",
"move-to": "Move to",
"insert-above": "Insert above",
"insert-below": "Insert below",
"delete-column": "Delete column",
"delete-column-confirmation": "Are you sure you want to delete this column? The corresponding attribute will be deleted in the notes under this column as well.",
"new-item": "New item",
"add-column": "Add Column"
}
}

View File

@@ -758,7 +758,7 @@
"expand_all_children": "Ampliar todas las subnotas",
"collapse": "Colapsar",
"expand": "Expandir",
"book_properties": "Propiedades del libro",
"book_properties": "",
"invalid_view_type": "Tipo de vista inválida '{{type}}'",
"calendar": "Calendario"
},
@@ -1593,7 +1593,7 @@
"move-to": "Mover a...",
"paste-into": "Pegar en",
"paste-after": "Pegar después de",
"duplicate-subtree": "Duplicar subárbol",
"duplicate": "Duplicar",
"export": "Exportar",
"import-into-note": "Importar a nota",
"apply-bulk-actions": "Aplicar acciones en lote",
@@ -1612,7 +1612,7 @@
"relation-map": "Mapa de Relaciones",
"note-map": "Mapa de Notas",
"render-note": "Nota de Renderizado",
"book": "Libro",
"book": "",
"mermaid-diagram": "Diagrama Mermaid",
"canvas": "Lienzo",
"web-view": "Vista Web",

View File

@@ -753,7 +753,7 @@
"expand_all_children": "Développer tous les enfants",
"collapse": "Réduire",
"expand": "Développer",
"book_properties": "Propriétés du livre",
"book_properties": "",
"invalid_view_type": "Type de vue non valide '{{type}}'",
"calendar": "Calendrier"
},
@@ -1389,7 +1389,7 @@
"move-to": "Déplacer vers...",
"paste-into": "Coller dans",
"paste-after": "Coller après",
"duplicate-subtree": "Dupliquer le sous-arbre",
"duplicate": "Dupliquer",
"export": "Exporter",
"import-into-note": "Importer dans la note",
"apply-bulk-actions": "Appliquer des Actions groupées",
@@ -1408,7 +1408,7 @@
"relation-map": "Carte des relations",
"note-map": "Carte de notes",
"render-note": "Rendu Html",
"book": "Livre",
"book": "",
"mermaid-diagram": "Diagramme Mermaid",
"canvas": "Canevas",
"web-view": "Affichage Web",

View File

@@ -274,7 +274,7 @@
"no_children_help": "Această notiță de tip Carte nu are nicio subnotiță așadar nu este nimic de afișat. Vedeți <a href=\"https://triliumnext.github.io/Docs/Wiki/book-note.html\">wiki</a> pentru detalii."
},
"book_properties": {
"book_properties": "Proprietăți carte",
"book_properties": "",
"collapse": "Minimizează",
"collapse_all_notes": "Minimizează toate notițele",
"expand": "Expandează",
@@ -1349,7 +1349,7 @@
"copy-note-path-to-clipboard": "Copiază calea notiței în clipboard",
"cut": "Decupează",
"delete": "Șterge",
"duplicate-subtree": "Dublifică ierarhia",
"duplicate": "Dublifică",
"edit-branch-prefix": "Editează prefixul ramurii",
"expand-subtree": "Expandează subnotițele",
"export": "Exportă",
@@ -1377,7 +1377,7 @@
"shared_publicly": "Această notiță este partajată public la"
},
"note_types": {
"book": "Carte",
"book": "Colecție",
"canvas": "Schiță",
"code": "Cod sursă",
"mermaid-diagram": "Diagramă Mermaid",

View File

@@ -718,7 +718,7 @@
"expand_all_children": "展開所有子項",
"collapse": "折疊",
"expand": "展開",
"book_properties": "書籍屬性",
"book_properties": "",
"invalid_view_type": "無效的查看類型 '{{type}}'"
},
"edited_notes": {
@@ -1336,7 +1336,6 @@
"move-to": "移動到...",
"paste-into": "貼上到裡面",
"paste-after": "貼上到後面",
"duplicate-subtree": "複製子樹",
"export": "匯出",
"import-into-note": "匯入到筆記",
"apply-bulk-actions": "應用批量操作",
@@ -1355,7 +1354,7 @@
"relation-map": "關係圖",
"note-map": "筆記地圖",
"render-note": "渲染筆記",
"book": "",
"book": "",
"mermaid-diagram": "美人魚圖Mermaid",
"canvas": "畫布",
"web-view": "網頁視圖",

View File

@@ -3,6 +3,11 @@ declare module "*.png" {
export default path;
}
declare module "*.json" {
var content: any;
export default content;
}
declare module "*?url" {
var path: string;
export default path;

View File

@@ -78,7 +78,7 @@ const TPL = /*html*/`
}
</style>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 8px;">
<h5 class="attr-detail-title">${t("attribute_detail.attr_detail_title")}</h5>
<span class="bx bx-x close-attr-detail-button tn-tool-button" title="${t("attribute_detail.close_button_title")}"></span>
@@ -142,6 +142,7 @@ const TPL = /*html*/`
<option value="datetime">${t("attribute_detail.date_time")}</option>
<option value="time">${t("attribute_detail.time")}</option>
<option value="url">${t("attribute_detail.url")}</option>
<option value="color">${t("attribute_detail.color_type")}</option>
</select>
</td>
</tr>
@@ -295,6 +296,8 @@ interface AttributeDetailOpts {
x: number;
y: number;
focus?: "name";
parent?: HTMLElement;
hideMultiplicity?: boolean;
}
interface SearchRelatedResponse {
@@ -477,7 +480,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
});
}
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }: AttributeDetailOpts) {
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus, hideMultiplicity }: AttributeDetailOpts) {
if (!attribute) {
this.hide();
@@ -528,7 +531,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowPromotedAlias.toggle(!!definition.isPromoted);
this.$inputPromotedAlias.val(definition.promotedAlias || "").attr("disabled", disabledFn);
this.$rowMultiplicity.toggle(["label-definition", "relation-definition"].includes(this.attrType || ""));
this.$rowMultiplicity.toggle(["label-definition", "relation-definition"].includes(this.attrType || "") && !hideMultiplicity);
this.$inputMultiplicity.val(definition.multiplicity || "").attr("disabled", disabledFn);
this.$rowLabelType.toggle(this.attrType === "label-definition");
@@ -560,19 +563,22 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.toggleInt(true);
const offset = this.parent?.$widget.offset() || { top: 0, left: 0 };
const offset = this.parent?.$widget?.offset() || { top: 0, left: 0 };
const detPosition = this.getDetailPosition(x, offset);
const outerHeight = this.$widget.outerHeight();
const height = $(window).height();
if (detPosition && outerHeight && height) {
this.$widget
.css("left", detPosition.left)
.css("right", detPosition.right)
.css("top", y - offset.top + 70)
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
if (!detPosition || !outerHeight || !height) {
console.warn("Can't position popup, is it attached?");
return;
}
this.$widget
.css("left", detPosition.left)
.css("right", detPosition.right)
.css("top", y - offset.top + 70)
.css("max-height", outerHeight + y > height - 50 ? height - y - 50 : 10000);
if (focus === "name") {
this.$inputName.trigger("focus").trigger("select");
}

View File

@@ -4,7 +4,7 @@ import noteAutocompleteService, { type Suggestion } from "../../services/note_au
import server from "../../services/server.js";
import contextMenuService from "../../menus/context_menu.js";
import attributeParser, { type Attribute } from "../../services/attribute_parser.js";
import { AttributeEditor, type EditorConfig, type Element, type MentionFeed, type Node, type Position } from "@triliumnext/ckeditor5";
import { AttributeEditor, type EditorConfig, type ModelElement, type MentionFeed, type ModelNode, type ModelPosition } from "@triliumnext/ckeditor5";
import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js";
@@ -417,16 +417,16 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
this.$editor.tooltip("show");
}
getClickIndex(pos: Position) {
getClickIndex(pos: ModelPosition) {
let clickIndex = pos.offset - (pos.textNode?.startOffset ?? 0);
let curNode: Node | Text | Element | null = pos.textNode;
let curNode: ModelNode | Text | ModelElement | null = pos.textNode;
while (curNode?.previousSibling) {
curNode = curNode.previousSibling;
if ((curNode as Element).name === "reference") {
clickIndex += (curNode.getAttribute("notePath") as string).length + 1;
if ((curNode as ModelElement).name === "reference") {
clickIndex += (curNode.getAttribute("href") as string).length + 1;
} else if ("data" in curNode) {
clickIndex += (curNode.data as string).length;
}

View File

@@ -189,7 +189,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap"].includes(note.type));
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
const canPrint = ["text", "code"].includes(note.type);
this.toggleDisabled(this.$printActiveNoteButton, canPrint);

View File

@@ -1,5 +1,5 @@
import type { default as Component, TypedComponent } from "../../components/component.js";
import BasicWidget, { TypedBasicWidget } from "../basic_widget.js";
import type { TypedComponent } from "../../components/component.js";
import { TypedBasicWidget } from "../basic_widget.js";
export default class Container<T extends TypedComponent<any>> extends TypedBasicWidget<T> {
doRender() {

View File

@@ -5,7 +5,7 @@ import type Component from "../../components/component.js";
export default class LeftPaneContainer extends FlexContainer<Component> {
private currentLeftPaneVisible: boolean;
constructor() {
super("column");
@@ -24,6 +24,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
this.currentLeftPaneVisible = leftPaneVisible ?? !this.currentLeftPaneVisible;
const visible = this.isEnabled();
this.toggleInt(visible);
this.parent?.$widget.toggleClass("left-pane-hidden", !visible);
if (visible) {
this.triggerEvent("focusTree", {});

View File

@@ -154,13 +154,21 @@ export default class NoteTypeChooserDialog extends BasicWidget {
this.$noteTypeDropdown.append($('<h6 class="dropdown-header">').append(t("note_type_chooser.templates")));
} else {
const commandItem = noteType as MenuCommandItem<CommandNames>;
this.$noteTypeDropdown.append(
$('<a class="dropdown-item" tabindex="0">')
.attr("data-note-type", commandItem.type || "")
.attr("data-template-note-id", commandItem.templateNoteId || "")
.append($("<span>").addClass(commandItem.uiIcon || ""))
.append(` ${noteType.title}`)
);
const listItem = $('<a class="dropdown-item" tabindex="0">')
.attr("data-note-type", commandItem.type || "")
.attr("data-template-note-id", commandItem.templateNoteId || "")
.append($("<span>").addClass(commandItem.uiIcon || ""))
.append(` ${noteType.title}`);
if (commandItem.badges) {
for (let badge of commandItem.badges) {
listItem.append($(`<span class="badge">`)
.addClass(badge.className || "")
.text(badge.title));
}
}
this.$noteTypeDropdown.append(listItem);
}
}

View File

@@ -0,0 +1,161 @@
import type { EventNames, EventData } from "../../components/app_context.js";
import NoteContext from "../../components/note_context.js";
import { openDialog } from "../../services/dialog.js";
import BasicWidget from "../basic_widget.js";
import Container from "../containers/container.js";
import TypeWidget from "../type_widgets/type_widget.js";
const TPL = /*html*/`\
<div class="popup-editor-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<style>
body.desktop .modal.popup-editor-dialog .modal-dialog {
max-width: 75vw;
}
.modal.popup-editor-dialog .modal-header .modal-title {
font-size: 1.1em;
}
.modal.popup-editor-dialog .modal-body {
padding: 0;
height: 75vh;
overflow: auto;
}
.modal.popup-editor-dialog .note-detail-editable-text {
padding: 0 1em;
}
.modal.popup-editor-dialog .title-row,
.modal.popup-editor-dialog .modal-title,
.modal.popup-editor-dialog .note-icon-widget {
height: 32px;
}
.modal.popup-editor-dialog .note-icon-widget {
width: 32px;
margin: 0;
padding: 0;
}
.modal.popup-editor-dialog .note-icon-widget button.note-icon,
.modal.popup-editor-dialog .note-title-widget input.note-title {
font-size: 1em;
}
.modal.popup-editor-dialog .classic-toolbar-widget {
position: sticky;
top: 0;
left: 0;
right: 0;
background: var(--modal-background-color);
z-index: 998;
}
.modal.popup-editor-dialog .note-detail-file {
padding: 0;
}
</style>
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title">
<!-- This is where the first child will be injected -->
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- This is where all but the first child will be injected. -->
</div>
</div>
</div>
</div>
`;
export default class PopupEditorDialog extends Container<BasicWidget> {
private noteContext: NoteContext;
private $modalHeader!: JQuery<HTMLElement>;
private $modalBody!: JQuery<HTMLElement>;
constructor() {
super();
this.noteContext = new NoteContext("_popup-editor");
}
doRender() {
// This will populate this.$widget with the content of the children.
super.doRender();
// Now we wrap it in the modal.
const $newWidget = $(TPL);
this.$modalHeader = $newWidget.find(".modal-title");
this.$modalBody = $newWidget.find(".modal-body");
const children = this.$widget.children();
this.$modalHeader.append(children[0]);
this.$modalBody.append(children.slice(1));
this.$widget = $newWidget;
this.setVisibility(false);
}
async openInPopupEvent({ noteIdOrPath }: EventData<"openInPopup">) {
const $dialog = await openDialog(this.$widget, false, {
focus: false
});
await this.noteContext.setNote(noteIdOrPath, {
viewScope: {
readOnlyTemporarilyDisabled: true
}
});
const activeEl = document.activeElement;
if (activeEl && "blur" in activeEl) {
(activeEl as HTMLElement).blur();
}
$dialog.on("shown.bs.modal", async () => {
// Reduce the z-index of modals so that ckeditor popups are properly shown on top of it.
// The backdrop instance is not shared so it's OK to make a one-off modification.
$("body > .modal-backdrop").css("z-index", "998");
$dialog.css("z-index", "999");
await this.handleEventInChildren("activeContextChanged", { noteContext: this.noteContext });
this.setVisibility(true);
await this.handleEventInChildren("focusOnDetail", { ntxId: this.noteContext.ntxId });
});
$dialog.on("hidden.bs.modal", () => {
const $typeWidgetEl = $dialog.find(".note-detail-printable");
if ($typeWidgetEl.length) {
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as TypeWidget;
typeWidget.cleanup();
}
this.setVisibility(false);
});
}
setVisibility(visible: boolean) {
const $bodyItems = this.$modalBody.find("> div");
if (visible) {
$bodyItems.fadeIn();
this.$modalHeader.children().show();
} else {
$bodyItems.hide();
this.$modalHeader.children().hide();
}
}
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
// Avoid events related to the current tab interfere with our popup.
if (["noteSwitched", "noteSwitchedAndActivated"].includes(name)) {
return Promise.resolve();
}
return super.handleEventInChildren(name, data);
}
}

View File

@@ -23,7 +23,9 @@ const TPL = /*html*/`\
export default class GeoMapButtons extends NoteContextAwareWidget {
isEnabled() {
return super.isEnabled() && this.note?.type === "geoMap";
return super.isEnabled()
&& this.note?.getLabelValue("viewType") === "geoMap"
&& !this.note.hasLabel("readOnly");
}
doRender() {

View File

@@ -17,7 +17,6 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
contentWidget: null,
doc: null,
file: null,
geoMap: "81SGnPGMk7Xc",
image: null,
launcher: null,
mermaid: null,
@@ -32,9 +31,12 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
};
export const byBookType: Record<ViewTypeOptions, string | null> = {
list: null,
grid: null,
calendar: "xWbu3jpNWapp"
list: "mULW0Q3VojwY",
grid: "8QqnMzx393bx",
calendar: "xWbu3jpNWapp",
table: "2FvYrpmOXm29",
geoMap: "81SGnPGMk7Xc",
board: "CtBQqbwXDx1w"
};
export default class ContextualHelpButton extends NoteContextAwareWidget {

View File

@@ -39,10 +39,20 @@ export default class ToggleReadOnlyButton extends OnClickButtonWidget {
}
isEnabled() {
return super.isEnabled()
&& this.note?.type === "mermaid"
&& this.note?.isContentAvailable()
&& this.noteContext?.viewScope?.viewMode === "default";
if (!super.isEnabled()) {
return false;
}
if (!this?.note?.isContentAvailable()) {
return false;
}
if (this.noteContext?.viewScope?.viewMode !== "default") {
return false;
}
return this.note.type === "mermaid" ||
(this.note.getLabelValue("viewType") === "geoMap");
}
}

View File

@@ -1,58 +0,0 @@
import type { Map } from "leaflet";
import L from "leaflet";
import "leaflet/dist/leaflet.css";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = /*html*/`\
<div class="geo-map-widget">
<style>
.note-detail-geo-map,
.geo-map-widget,
.geo-map-container {
height: 100%;
overflow: hidden;
}
.leaflet-top,
.leaflet-bottom {
z-index: 900;
}
</style>
<div class="geo-map-container"></div>
</div>`;
export type Leaflet = typeof L;
export type InitCallback = (L: Leaflet) => void;
export default class GeoMapWidget extends NoteContextAwareWidget {
map?: Map;
$container!: JQuery<HTMLElement>;
private initCallback?: InitCallback;
constructor(widgetMode: "type", initCallback?: InitCallback) {
super();
this.initCallback = initCallback;
}
doRender() {
this.$widget = $(TPL);
this.$container = this.$widget.find(".geo-map-container");
const map = L.map(this.$container[0], {
worldCopyJump: true
});
this.map = map;
if (this.initCallback) {
this.initCallback(L);
}
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
detectRetina: true
}).addTo(map);
}
}

View File

@@ -28,7 +28,6 @@ import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
import MindMapWidget from "./type_widgets/mind_map.js";
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
import utils from "../services/utils.js";
import type { NoteType } from "../entities/fnote.js";
import type TypeWidget from "./type_widgets/type_widget.js";
@@ -71,7 +70,6 @@ const typeWidgetClasses = {
attachmentDetail: AttachmentDetailTypeWidget,
attachmentList: AttachmentListTypeWidget,
mindMap: MindMapWidget,
geoMap: GeoMapTypeWidget,
aiChat: AiChatTypeWidget,
// Split type editors
@@ -197,7 +195,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
// https://github.com/zadam/trilium/issues/2522
const isBackendNote = this.noteContext?.noteId === "_backendLog";
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap", "mermaid"].includes(this.type ?? "");
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid", "file"].includes(this.type ?? "");
const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|| this.noteContext?.viewScope?.viewMode === "attachments"
|| isBackendNote;

View File

@@ -1,7 +1,7 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import NoteListRenderer from "../services/note_list_renderer.js";
import type FNote from "../entities/fnote.js";
import type { CommandListener, CommandListenerData, EventData } from "../components/app_context.js";
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData, EventNames } from "../components/app_context.js";
import type ViewMode from "./view_widgets/view_mode.js";
const TPL = /*html*/`
@@ -36,10 +36,31 @@ export default class NoteListWidget extends NoteContextAwareWidget {
private isIntersecting?: boolean;
private noteIdRefreshed?: string;
private shownNoteId?: string | null;
private viewMode?: ViewMode | null;
private viewMode?: ViewMode<any> | null;
private displayOnlyCollections: boolean;
/**
* @param displayOnlyCollections if set to `true` then only collection-type views are displayed such as geo-map and the calendar. The original book types grid and list will be ignored.
*/
constructor(displayOnlyCollections: boolean) {
super();
this.displayOnlyCollections = displayOnlyCollections;
}
isEnabled() {
return super.isEnabled() && this.noteContext?.hasNoteList();
if (!super.isEnabled()) {
return false;
}
if (this.displayOnlyCollections && this.note?.type !== "book") {
const viewType = this.note?.getLabelValue("viewType");
if (!viewType || ["grid", "list"].includes(viewType)) {
return false;
}
}
return this.noteContext?.hasNoteList();
}
doRender() {
@@ -76,7 +97,11 @@ export default class NoteListWidget extends NoteContextAwareWidget {
}
async renderNoteList(note: FNote) {
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds());
const noteListRenderer = new NoteListRenderer({
$parent: this.$content,
parentNote: note,
parentNotePath: this.notePath
});
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
await noteListRenderer.renderList();
this.viewMode = noteListRenderer.viewMode;
@@ -120,12 +145,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
this.refresh();
this.checkRenderStatus();
}
// Inform the view mode of changes and refresh if needed.
if (this.viewMode && this.viewMode.onEntitiesReloaded(e)) {
this.refresh();
this.checkRenderStatus();
}
}
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
@@ -134,4 +153,26 @@ export default class NoteListWidget extends NoteContextAwareWidget {
}
}
triggerCommand<K extends CommandNames>(name: K, data?: CommandMappings[K]): Promise<unknown> | undefined | null {
// Pass the commands to the view mode, which is not actually attached to the hierarchy.
if (this.viewMode?.triggerCommand(name, data)) {
return;
}
return super.triggerCommand(name, data);
}
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
super.handleEventInChildren(name, data);
if (this.viewMode) {
const ret = this.viewMode.handleEvent(name, data);
if (ret) {
return ret;
}
}
return null;
}
}

View File

@@ -324,7 +324,13 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
}
const mapRootNoteId = this.getMapRootNoteId();
const data = await this.loadNotesAndRelations(mapRootNoteId);
const labelValues = (name: string) => this.note?.getLabels(name).map(l => l.value) ?? [];
const excludeRelations = labelValues("mapExcludeRelation");
const includeRelations = labelValues("mapIncludeRelation");
const data = await this.loadNotesAndRelations(mapRootNoteId, excludeRelations, includeRelations);
const nodeLinkRatio = data.nodes.length / data.links.length;
const magnifiedRatio = Math.pow(nodeLinkRatio, 1.5);
@@ -473,8 +479,10 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
ctx.restore();
}
async loadNotesAndRelations(mapRootNoteId: string): Promise<NotesAndRelationsData> {
const resp = await server.post<PostNotesMapResponse>(`note-map/${mapRootNoteId}/${this.mapType}`);
async loadNotesAndRelations(mapRootNoteId: string, excludeRelations: string[], includeRelations: string[]): Promise<NotesAndRelationsData> {
const resp = await server.post<PostNotesMapResponse>(`note-map/${mapRootNoteId}/${this.mapType}`, {
excludeRelations, includeRelations
});
this.calculateNodeSizes(resp);

View File

@@ -186,6 +186,15 @@ interface RefreshContext {
noteIdsToReload: Set<string>;
}
/**
* The information contained within a drag event.
*/
export interface DragData {
noteId: string;
branchId: string;
title: string;
}
export default class NoteTreeWidget extends NoteContextAwareWidget {
private $tree!: JQuery<HTMLElement>;
private $treeActions!: JQuery<HTMLElement>;
@@ -231,15 +240,21 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
this.$tree.on("mousedown", ".fancytree-title", (e) => {
if (e.which === 2) {
const node = $.ui.fancytree.getNode(e as unknown as Event);
const notePath = treeService.getNotePath(node);
if (notePath) {
e.stopPropagation();
e.preventDefault();
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: e.shiftKey ? true : false
});
}
}
});
this.$tree.on("mouseup", ".fancytree-title", (e) => {
// Prevent middle click from pasting in the editor.
if (e.which === 2) {
e.stopPropagation();
e.preventDefault();
}
@@ -698,7 +713,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
});
} else {
this.$tree.on("contextmenu", ".fancytree-node", (e) => {
this.showContextMenu(e);
if (!utils.isCtrlKey(e)) {
this.showContextMenu(e);
} else {
const node = $.ui.fancytree.getNode(e as unknown as Event);
const notePath = treeService.getNotePath(node);
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath });
}
return false; // blocks default browser right click menu
});

View File

@@ -1,59 +1,15 @@
import server from "../services/server.js";
import { Dropdown } from "bootstrap";
import { NOTE_TYPES } from "../services/note_types.js";
import { t } from "../services/i18n.js";
import dialogService from "../services/dialog.js";
import mimeTypesService from "../services/mime_types.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import dialogService from "../services/dialog.js";
import { t } from "../services/i18n.js";
import type FNote from "../entities/fnote.js";
import type { NoteType } from "../entities/fnote.js";
import server from "../services/server.js";
import type { EventData } from "../components/app_context.js";
import { Dropdown } from "bootstrap";
import type { NoteType } from "../entities/fnote.js";
import type FNote from "../entities/fnote.js";
interface NoteTypeMapping {
type: NoteType;
mime?: string;
title: string;
isBeta?: boolean;
selectable: boolean;
}
const NOTE_TYPES: NoteTypeMapping[] = [
// The suggested note type ordering method: insert the item into the corresponding group,
// then ensure the items within the group are ordered alphabetically.
// The default note type (always the first item)
{ type: "text", mime: "text/html", title: t("note_types.text"), selectable: true },
// Text notes group
{ type: "book", mime: "", title: t("note_types.book"), selectable: true },
// Graphic notes
{ type: "canvas", mime: "application/json", title: t("note_types.canvas"), selectable: true },
{ type: "mermaid", mime: "text/mermaid", title: t("note_types.mermaid-diagram"), selectable: true },
// Map notes
{ type: "geoMap", mime: "application/json", title: t("note_types.geo-map"), isBeta: true, selectable: true },
{ type: "mindMap", mime: "application/json", title: t("note_types.mind-map"), selectable: true },
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), selectable: true },
// Misc note types
{ type: "render", mime: "", title: t("note_types.render-note"), selectable: true },
{ type: "webView", mime: "", title: t("note_types.web-view"), selectable: true },
// Code notes
{ type: "code", mime: "text/plain", title: t("note_types.code"), selectable: true },
// Reserved types (cannot be created by the user)
{ type: "contentWidget", mime: "", title: t("note_types.widget"), selectable: false },
{ type: "doc", mime: "", title: t("note_types.doc"), selectable: false },
{ type: "file", title: t("note_types.file"), selectable: false },
{ type: "image", title: t("note_types.image"), selectable: false },
{ type: "launcher", mime: "", title: t("note_types.launcher"), selectable: false },
{ type: "noteMap", mime: "", title: t("note_types.note-map"), selectable: false },
{ type: "search", title: t("note_types.saved-search"), selectable: false },
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), selectable: false }
];
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => !nt.selectable).map((nt) => nt.type);
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type);
const TPL = /*html*/`
<div class="dropdown note-type-widget">
@@ -63,13 +19,6 @@ const TPL = /*html*/`
overflow-y: auto;
overflow-x: hidden;
}
.note-type-dropdown .badge {
margin-left: 8px;
background: var(--accented-background-color);
font-weight: normal;
color: var(--menu-text-color);
}
</style>
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-type-button">
<span class="note-type-desc"></span>
@@ -116,10 +65,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
return;
}
for (const noteType of NOTE_TYPES.filter((nt) => nt.selectable)) {
for (const noteType of NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static)) {
let $typeLink: JQuery<HTMLElement>;
const $title = $("<span>").text(noteType.title);
if (noteType.isNew) {
$title.append($(`<span class="badge new-note-type-badge">`).text(t("note_types.new-feature")));
}
if (noteType.isBeta) {
$title.append($(`<span class="badge">`).text(t("note_types.beta-feature")));
}

View File

@@ -64,7 +64,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
}
#isFullWidthNote(note: FNote) {
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap", "geoMap"].includes(note.type)) {
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
return true;
}

View File

@@ -3,6 +3,18 @@ import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js";
import { bookPropertiesConfig, BookProperty } from "./book_properties_config.js";
import attributes from "../../services/attributes.js";
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
const VIEW_TYPE_MAPPINGS: Record<ViewTypeOptions, string> = {
grid: t("book_properties.grid"),
list: t("book_properties.list"),
calendar: t("book_properties.calendar"),
table: t("book_properties.table"),
geoMap: t("book_properties.geo-map"),
board: t("book_properties.board")
};
const TPL = /*html*/`
<div class="book-properties-widget">
@@ -15,42 +27,56 @@ const TPL = /*html*/`
.book-properties-widget > * {
margin-right: 15px;
}
.book-properties-container {
display: flex;
align-items: center;
}
.book-properties-container > div {
margin-right: 15px;
}
.book-properties-container > .type-number > label {
display: flex;
align-items: baseline;
}
.book-properties-container input[type="checkbox"] {
margin-right: 5px;
}
.book-properties-container label {
display: flex;
justify-content: center;
align-items: center;
text-overflow: clip;
white-space: nowrap;
}
</style>
<div style="display: flex; align-items: baseline">
<span style="white-space: nowrap">${t("book_properties.view_type")}:&nbsp; &nbsp;</span>
<select class="view-type-select form-select form-select-sm">
<option value="grid">${t("book_properties.grid")}</option>
<option value="list">${t("book_properties.list")}</option>
<option value="calendar">${t("book_properties.calendar")}</option>
${Object.entries(VIEW_TYPE_MAPPINGS)
.filter(([type]) => type !== "raster")
.map(([type, label]) => `
<option value="${type}">${label}</option>
`).join("")}
</select>
</div>
<button type="button"
class="collapse-all-button btn btn-sm"
title="${t("book_properties.collapse_all_notes")}">
<span class="bx bx-layer-minus"></span>
${t("book_properties.collapse")}
</button>
<button type="button"
class="expand-children-button btn btn-sm"
title="${t("book_properties.expand_all_children")}">
<span class="bx bx-move-vertical"></span>
${t("book_properties.expand")}
</button>
<div class="book-properties-container">
</div>
</div>
`;
export default class BookPropertiesWidget extends NoteContextAwareWidget {
private $viewTypeSelect!: JQuery<HTMLElement>;
private $expandChildrenButton!: JQuery<HTMLElement>;
private $collapseAllButton!: JQuery<HTMLElement>;
private $propertiesContainer!: JQuery<HTMLElement>;
private labelsToWatch: string[] = [];
get name() {
return "bookProperties";
@@ -67,7 +93,6 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: this.isEnabled(),
activate: true,
title: t("book_properties.book_properties"),
icon: "bx bx-book"
};
@@ -80,32 +105,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
this.$viewTypeSelect = this.$widget.find(".view-type-select");
this.$viewTypeSelect.on("change", () => this.toggleViewType(String(this.$viewTypeSelect.val())));
this.$expandChildrenButton = this.$widget.find(".expand-children-button");
this.$expandChildrenButton.on("click", async () => {
if (!this.noteId || !this.note) {
return;
}
if (!this.note?.isLabelTruthy("expanded")) {
await attributeService.addLabel(this.noteId, "expanded");
}
this.triggerCommand("refreshNoteList", { noteId: this.noteId });
});
this.$collapseAllButton = this.$widget.find(".collapse-all-button");
this.$collapseAllButton.on("click", async () => {
if (!this.noteId || !this.note) {
return;
}
// owned is important - we shouldn't remove inherited expanded labels
for (const expandedAttr of this.note.getOwnedLabels("expanded")) {
await attributeService.removeAttributeById(this.noteId, expandedAttr.attributeId);
}
this.triggerCommand("refreshNoteList", { noteId: this.noteId });
});
this.$propertiesContainer = this.$widget.find(".book-properties-container");
}
async refreshWithNote(note: FNote) {
@@ -117,8 +117,15 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
this.$viewTypeSelect.val(viewType);
this.$expandChildrenButton.toggle(viewType === "list");
this.$collapseAllButton.toggle(viewType === "list");
this.$propertiesContainer.empty();
const bookPropertiesData = bookPropertiesConfig[viewType];
if (bookPropertiesData) {
for (const property of bookPropertiesData.properties) {
this.$propertiesContainer.append(this.renderBookProperty(property));
this.labelsToWatch.push(property.bindToLabel);
}
}
}
async toggleViewType(type: string) {
@@ -126,7 +133,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
return;
}
if (!["list", "grid", "calendar"].includes(type)) {
if (!VIEW_TYPE_MAPPINGS.hasOwnProperty(type)) {
throw new Error(t("book_properties.invalid_view_type", { type }));
}
@@ -134,8 +141,122 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
if (loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId && attr.name === "viewType")) {
if (loadResults.getAttributeRows().find((attr) =>
attr.noteId === this.noteId
&& (attr.name === "viewType" || this.labelsToWatch.includes(attr.name ?? "")))) {
this.refresh();
}
}
renderBookProperty(property: BookProperty) {
const $container = $("<div>");
$container.addClass(`type-${property.type}`);
const note = this.note;
if (!note) {
return $container;
}
switch (property.type) {
case "checkbox":
const $label = $("<label>").text(property.label);
const $checkbox = $("<input>", {
type: "checkbox",
class: "form-check-input",
});
$checkbox.on("change", () => {
if ($checkbox.prop("checked")) {
attributes.setLabel(note.noteId, property.bindToLabel);
} else {
attributes.removeOwnedLabelByName(note, property.bindToLabel);
}
});
$checkbox.prop("checked", note.hasOwnedLabel(property.bindToLabel));
$label.prepend($checkbox);
$container.append($label);
break;
case "button":
const $button = $("<button>", {
type: "button",
class: "btn btn-sm"
}).text(property.label);
if (property.title) {
$button.attr("title", property.title);
}
if (property.icon) {
$button.prepend($("<span>", { class: property.icon }));
}
$button.on("click", () => {
property.onClick({
note,
triggerCommand: this.triggerCommand.bind(this)
});
});
$container.append($button);
break;
case "number":
const $numberInput = $("<input>", {
type: "number",
class: "form-control form-control-sm",
value: note.getLabelValue(property.bindToLabel) || "",
width: property.width ?? 100,
min: property.min ?? 0
});
$numberInput.on("change", () => {
const value = $numberInput.val();
if (value === "") {
attributes.removeOwnedLabelByName(note, property.bindToLabel);
} else {
attributes.setLabel(note.noteId, property.bindToLabel, String(value));
}
});
$container.append($("<label>")
.text(property.label)
.append("&nbsp;".repeat(2))
.append($numberInput));
break;
case "combobox":
const $select = $("<select>", {
class: "form-select form-select-sm"
});
const actualValue = note.getLabelValue(property.bindToLabel) ?? property.defaultValue ?? "";
for (const option of property.options) {
if ("items" in option) {
const $optGroup = $("<optgroup>", { label: option.name });
for (const item of option.items) {
buildComboBoxItem(item, actualValue).appendTo($optGroup);
}
$optGroup.appendTo($select);
} else {
buildComboBoxItem(option, actualValue).appendTo($select);
}
}
$select.on("change", () => {
const value = $select.val();
if (value === null || value === "") {
attributes.removeOwnedLabelByName(note, property.bindToLabel);
} else {
attributes.setLabel(note.noteId, property.bindToLabel, String(value));
}
});
$container.append($("<label>")
.text(property.label)
.append("&nbsp;".repeat(2))
.append($select));
break;
}
return $container;
}
}
function buildComboBoxItem({ value, label }: { value: string, label: string }, actualValue: string) {
const $option = $("<option>", {
value,
text: label
});
if (actualValue === value) {
$option.prop("selected", true);
}
return $option;
}

View File

@@ -0,0 +1,169 @@
import { t } from "i18next";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import { ViewTypeOptions } from "../../services/note_list_renderer"
import NoteContextAwareWidget from "../note_context_aware_widget";
import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, type MapLayer } from "../view_widgets/geo_view/map_layer";
interface BookConfig {
properties: BookProperty[];
}
interface CheckBoxProperty {
type: "checkbox",
label: string;
bindToLabel: string
}
interface ButtonProperty {
type: "button",
label: string;
title?: string;
icon?: string;
onClick: (context: BookContext) => void;
}
interface NumberProperty {
type: "number",
label: string;
bindToLabel: string;
width?: number;
min?: number;
}
interface ComboBoxItem {
value: string;
label: string;
}
interface ComboBoxGroup {
name: string;
items: ComboBoxItem[];
}
interface ComboBoxProperty {
type: "combobox",
label: string;
bindToLabel: string;
/**
* The default value is used when the label is not set.
*/
defaultValue?: string;
options: (ComboBoxItem | ComboBoxGroup)[];
}
export type BookProperty = CheckBoxProperty | ButtonProperty | NumberProperty | ComboBoxProperty;
interface BookContext {
note: FNote;
triggerCommand: NoteContextAwareWidget["triggerCommand"];
}
export const bookPropertiesConfig: Record<ViewTypeOptions, BookConfig> = {
grid: {
properties: []
},
list: {
properties: [
{
label: t("book_properties.collapse"),
title: t("book_properties.collapse_all_notes"),
type: "button",
icon: "bx bx-layer-minus",
async onClick({ note, triggerCommand }) {
const { noteId } = note;
// owned is important - we shouldn't remove inherited expanded labels
for (const expandedAttr of note.getOwnedLabels("expanded")) {
await attributes.removeAttributeById(noteId, expandedAttr.attributeId);
}
triggerCommand("refreshNoteList", { noteId: noteId });
},
},
{
label: t("book_properties.expand"),
title: t("book_properties.expand_all_children"),
type: "button",
icon: "bx bx-move-vertical",
async onClick({ note, triggerCommand }) {
const { noteId } = note;
if (!note.isLabelTruthy("expanded")) {
await attributes.addLabel(noteId, "expanded");
}
triggerCommand("refreshNoteList", { noteId });
},
}
]
},
calendar: {
properties: [
{
label: t("book_properties_config.hide-weekends"),
type: "checkbox",
bindToLabel: "calendar:hideWeekends"
},
{
label: t("book_properties_config.display-week-numbers"),
type: "checkbox",
bindToLabel: "calendar:weekNumbers"
}
]
},
geoMap: {
properties: [
{
label: t("book_properties_config.map-style"),
type: "combobox",
bindToLabel: "map:style",
defaultValue: DEFAULT_MAP_LAYER_NAME,
options: [
{
name: t("book_properties_config.raster"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "raster")
.map(buildMapLayer)
},
{
name: t("book_properties_config.vector_light"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "vector" && !layer.isDarkTheme)
.map(buildMapLayer)
},
{
name: t("book_properties_config.vector_dark"),
items: Object.entries(MAP_LAYERS)
.filter(([_, layer]) => layer.type === "vector" && layer.isDarkTheme)
.map(buildMapLayer)
}
]
},
{
label: t("book_properties_config.show-scale"),
type: "checkbox",
bindToLabel: "map:scale"
}
]
},
table: {
properties: [
{
label: t("book_properties_config.max-nesting-depth"),
type: "number",
bindToLabel: "maxNestingDepth",
width: 65
}
]
},
board: {
properties: []
}
};
function buildMapLayer([ id, layer ]: [ string, MapLayer ]): ComboBoxItem {
return {
value: id,
label: layer.name
};
}

View File

@@ -48,6 +48,18 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
this.contentSized();
}
isEnabled(): boolean | null | undefined {
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
return false;
}
if (!this.note || this.note.type !== "text") {
return false;
}
return true;
}
async getTitle() {
return {
show: await this.#shouldDisplay(),
@@ -58,11 +70,7 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget {
}
async #shouldDisplay() {
if (options.get("textNoteEditorType") !== "ckeditor-classic") {
return false;
}
if (!this.note || this.note.type !== "text") {
if (!this.isEnabled()) {
return false;
}

View File

@@ -53,12 +53,56 @@ const TPL = /*html*/`
word-break:keep-all;
white-space: nowrap;
}
.promoted-attribute-cell input[type="checkbox"] {
width: 22px !important;
flex-grow: 0;
width: unset;
}
/* Restore default apperance */
.promoted-attribute-cell input[type="number"],
.promoted-attribute-cell input[type="checkbox"] {
appearance: auto;
}
.promoted-attribute-cell input[type="color"] {
width: 24px;
height: 24px;
margin-top: 2px;
appearance: none;
padding: 0;
border: 0;
outline: none;
border-radius: 25% !important;
}
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.promoted-attribute-cell input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 25%;
}
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"] {
position: relative;
opacity: 0.5;
}
.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"]:after {
content: "";
position: absolute;
top: 10px;
left: 0px;
right: 0;
height: 2px;
background: rgba(0, 0, 0, 0.5);
transform: rotate(45deg);
pointer-events: none;
}
</style>
<div class="promoted-attributes-container"></div>
@@ -69,11 +113,6 @@ interface AttributeResult {
attributeId: string;
}
/**
* This widget is quite special because it's used in the desktop ribbon, but in mobile outside of ribbon.
* This works without many issues (apart from autocomplete), but it should be kept in mind when changing things
* and testing.
*/
export default class PromotedAttributesWidget extends NoteContextAwareWidget {
private $container!: JQuery<HTMLElement>;
@@ -117,7 +156,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
// the order of attributes is important as well
ownedAttributes.sort((a, b) => a.position - b.position);
if (promotedDefAttrs.length === 0) {
if (promotedDefAttrs.length === 0 || note.getLabelValue("viewType") === "table") {
this.toggleInt(false);
return;
}
@@ -188,6 +227,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.append($multiplicityCell);
if (valueAttr.type === "label") {
$wrapper.addClass(`promoted-attribute-label-${definition.labelType}`);
if (definition.labelType === "text") {
$input.prop("type", "text");
@@ -262,6 +302,35 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.on("click", () => window.open($input.val() as string, "_blank"));
$input.after($openButton);
} else if (definition.labelType === "color") {
const defaultColor = "#ffffff";
$input.prop("type", "hidden");
$input.val(valueAttr.value ?? "");
// We insert a separate input since the color input does not support empty value.
// This is a workaround to allow clearing the color input.
const $colorInput = $("<input>")
.prop("type", "color")
.prop("value", valueAttr.value || defaultColor)
.addClass("form-control promoted-attribute-input")
.on("change", e => setValue((e.target as HTMLInputElement).value, e));
$input.after($colorInput);
const $clearButton = $("<span>")
.addClass("input-group-text bx bxs-tag-x")
.prop("title", t("promoted_attributes.remove_color"))
.on("click", e => setValue("", e));
const setValue = (color: string, event: JQuery.TriggeredEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => {
$input.val(color);
if (!color) {
$colorInput.val(defaultColor);
}
event.target = $input[0]; // Set the event target to the main input
this.promotedAttributeChanged(event);
};
$colorInput.after($clearButton);
} else {
ws.logError(t("promoted_attributes.unknown_label_type", { type: definition.labelType }));
}

View File

@@ -65,7 +65,11 @@ export default class SearchResultWidget extends NoteContextAwareWidget {
return;
}
const noteListRenderer = new NoteListRenderer(this.$content, note, note.getChildNoteIds(), true);
const noteListRenderer = new NoteListRenderer({
$parent: this.$content,
parentNote: note,
showNotePath: true
});
await noteListRenderer.renderList();
}

View File

@@ -8,6 +8,7 @@ import appContext, { type CommandNames, type CommandListenerData, type EventData
import froca from "../services/froca.js";
import attributeService from "../services/attributes.js";
import type NoteContext from "../components/note_context.js";
import { setupHorizontalScrollViaWheel } from "./widget_utils.js";
const isDesktop = utils.isDesktop();
@@ -386,15 +387,7 @@ export default class TabRowWidget extends BasicWidget {
};
setupScrollEvents() {
this.$tabScrollingContainer.on('wheel', (event) => {
const wheelEvent = event.originalEvent as WheelEvent;
if (utils.isCtrlKey(event) || event.altKey || event.shiftKey) {
return;
}
event.preventDefault();
event.stopImmediatePropagation();
event.currentTarget.scrollLeft += wheelEvent.deltaY + wheelEvent.deltaX;
});
setupHorizontalScrollViaWheel(this.$tabScrollingContainer);
this.$scrollButtonLeft[0].addEventListener('click', () => this.scrollTabContainer(-210));
this.$scrollButtonRight[0].addEventListener('click', () => this.scrollTabContainer(210));

View File

@@ -130,7 +130,8 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
constructor() {
super();
this.editorTypeWidget = new EditableCodeTypeWidget();
this.editorTypeWidget = new EditableCodeTypeWidget(true);
this.editorTypeWidget.updateBackgroundColor = () => {};
this.editorTypeWidget.isEnabled = () => true;
@@ -146,6 +147,8 @@ export default abstract class AbstractSplitTypeWidget extends TypeWidget {
doRender(): void {
this.$widget = $(TPL);
this.spacedUpdate.setUpdateInterval(750);
// Preview pane
this.$previewCol = this.$widget.find(".note-detail-split-preview-col");
this.$preview = this.$widget.find(".note-detail-split-preview");

View File

@@ -36,7 +36,21 @@ export default class BookTypeWidget extends TypeWidget {
}
async doRefresh(note: FNote) {
this.$helpNoChildren.toggle(!this.note?.hasChildren() && this.note?.getAttributeValue("label", "viewType") !== "calendar");
this.$helpNoChildren.toggle(this.shouldDisplayNoChildrenWarning());
}
shouldDisplayNoChildrenWarning() {
if (this.note?.hasChildren()) {
return false;
}
switch (this.note?.getAttributeValue("label", "viewType")) {
case "list":
case "grid":
return true;
default:
return false;
}
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {

View File

@@ -166,7 +166,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
onChange: () => this.onChangeHandler(),
viewModeEnabled: options.is("databaseReadonly"),
zenModeEnabled: false,
gridModeEnabled: false,
isCollaborating: false,
detectScroll: false,
handleKeyboardGlobally: false,

View File

@@ -153,7 +153,8 @@ export default class Canvas {
appState: {
scrollX: appState.scrollX,
scrollY: appState.scrollY,
zoom: appState.zoom
zoom: appState.zoom,
gridModeEnabled: appState.gridModeEnabled
}
};

View File

@@ -59,7 +59,7 @@ async function handleContentUpdate(affectedNoteIds: string[]) {
const templateNoteIds = new Set(templateCache.keys());
const affectedTemplateNoteIds = templateNoteIds.intersection(updatedNoteIds);
await froca.getNotes(affectedNoteIds);
await froca.getNotes(affectedNoteIds, true);
let fullReloadNeeded = false;
for (const affectedTemplateNoteId of affectedTemplateNoteIds) {

View File

@@ -27,6 +27,7 @@ import RevisionSnapshotsLimitOptions from "./options/other/revision_snapshots_li
import NetworkConnectionsOptions from "./options/other/network_connections.js";
import HtmlImportTagsOptions from "./options/other/html_import_tags.js";
import AdvancedSyncOptions from "./options/advanced/sync.js";
import FileSystemSyncOptions from "./options/advanced/file_system_sync.js";
import DatabaseIntegrityCheckOptions from "./options/advanced/database_integrity_check.js";
import VacuumDatabaseOptions from "./options/advanced/vacuum_database.js";
import DatabaseAnonymizationOptions from "./options/advanced/database_anonymization.js";
@@ -138,6 +139,7 @@ const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", (typeof NoteContextAw
],
_optionsAdvanced: [
AdvancedSyncOptions,
FileSystemSyncOptions,
DatabaseIntegrityCheckOptions,
DatabaseAnonymizationOptions,
VacuumDatabaseOptions

View File

@@ -16,6 +16,10 @@ const TPL = /*html*/`<div class="note-detail-doc note-detail-printable">
border-radius: 5px;
}
.note-detail-doc-content code {
font-variant: none;
}
.note-detail-doc-content pre:not(.hljs) {
background-color: var(--accented-background-color);
border: 1px solid var(--main-border-color);

View File

@@ -28,6 +28,16 @@ const TPL = /*html*/`
export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
private debounceUpdate: boolean;
/**
* @param debounceUpdate if true, the update will be debounced to prevent excessive updates. Especially useful if the editor is linked to a live preview.
*/
constructor(debounceUpdate: boolean = false) {
super();
this.debounceUpdate = debounceUpdate;
}
static getType() {
return "editableCode";
}
@@ -46,7 +56,13 @@ export default class EditableCodeTypeWidget extends AbstractCodeTypeWidget {
return {
placeholder: t("editable_code.placeholder"),
vimKeybindings: options.is("vimKeymapEnabled"),
onContentChanged: () => this.spacedUpdate.scheduleUpdate(),
onContentChanged: () => {
if (this.debounceUpdate) {
this.spacedUpdate.resetUpdateTimer();
}
this.spacedUpdate.scheduleUpdate();
},
tabIndex: 300
}
}

View File

@@ -178,13 +178,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
});
if (isClassicEditor) {
let $classicToolbarWidget;
if (!utils.isMobile()) {
const $parentSplit = this.$widget.parents(".note-split.type-text");
$classicToolbarWidget = $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
} else {
$classicToolbarWidget = $("body").find(".classic-toolbar-widget");
}
const $classicToolbarWidget = this.findClassicToolbar();
$classicToolbarWidget.empty();
if ($classicToolbarWidget.length) {
@@ -271,7 +265,12 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
}
focus() {
this.$editor.trigger("focus");
const editor = this.watchdog.editor;
if (editor) {
editor.editing.view.focus();
} else {
this.$editor.trigger("focus");
}
}
scrollToEnd() {
@@ -515,6 +514,22 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
}
}
findClassicToolbar(): JQuery<HTMLElement> {
if (!utils.isMobile()) {
const $parentSplit = this.$widget.parents(".note-split.type-text");
if ($parentSplit.length) {
// The editor is in a normal tab.
return $parentSplit.find("> .ribbon-container .classic-toolbar-widget");
} else {
// The editor is in a popup.
return this.$widget.closest(".modal-body").find(".classic-toolbar-widget");
}
} else {
return $("body").find(".classic-toolbar-widget");
}
}
buildTouchBarCommand(data: CommandListenerData<"buildTouchBar">) {
const { TouchBar, buildIcon } = data;
const { TouchBarSegmentedControl, TouchBarGroup, TouchBarButton } = TouchBar;

View File

@@ -3,7 +3,6 @@ import TypeWidget from "./type_widget.js";
import appContext from "../../components/app_context.js";
import searchService from "../../services/search.js";
import { t } from "../../services/i18n.js";
import type FNote from "../../entities/fnote.js";
const TPL = /*html*/`
<div class="note-detail-empty note-detail-printable">

View File

@@ -22,7 +22,8 @@ const TPL = /*html*/`
padding: 0;
}
.note-split.full-content-width .note-detail-file[data-preview-type="video"] {
.note-detail.full-height .note-detail-file[data-preview-type="pdf"],
.note-detail.full-height .note-detail-file[data-preview-type="video"] {
overflow: hidden;
}

View File

@@ -1,447 +0,0 @@
import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet";
import type FNote from "../../entities/fnote.js";
import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js";
import TypeWidget from "./type_widget.js";
import server from "../../services/server.js";
import toastService from "../../services/toast.js";
import dialogService from "../../services/dialog.js";
import type { CommandListenerData, EventData } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import attributes from "../../services/attributes.js";
import openContextMenu from "./geo_map_context_menu.js";
import link from "../../services/link.js";
import note_tooltip from "../../services/note_tooltip.js";
import appContext from "../../components/app_context.js";
import markerIcon from "leaflet/dist/images/marker-icon.png";
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
import { hasTouchBar } from "../../services/utils.js";
const TPL = /*html*/`\
<div class="note-detail-geo-map note-detail-printable">
<style>
.leaflet-pane {
z-index: 1;
}
.geo-map-container.placing-note {
cursor: crosshair;
}
.geo-map-container .marker-pin {
position: relative;
}
.geo-map-container .leaflet-div-icon {
position: relative;
background: transparent;
border: 0;
overflow: visible;
}
.geo-map-container .leaflet-div-icon .icon-shadow {
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
.geo-map-container .leaflet-div-icon .bx {
position: absolute;
top: 3px;
left: 2px;
background-color: white;
color: black;
padding: 2px;
border-radius: 50%;
font-size: 17px;
}
.geo-map-container .leaflet-div-icon .title-label {
display: block;
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 0.75rem;
height: 1rem;
color: black;
width: 100px;
text-align: center;
text-overflow: ellipsis;
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
white-space: no-wrap;
overflow: hidden;
}
</style>
</div>`;
const LOCATION_ATTRIBUTE = "geolocation";
const CHILD_NOTE_ICON = "bx bx-pin";
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
interface MapData {
view?: {
center?: LatLng | [number, number];
zoom?: number;
};
}
// TODO: Deduplicate
interface CreateChildResponse {
note: {
noteId: string;
};
}
enum State {
Normal,
NewNote
}
export default class GeoMapTypeWidget extends TypeWidget {
private geoMapWidget: GeoMapWidget;
private _state: State;
private L!: Leaflet;
private currentMarkerData: Record<string, Marker>;
private currentTrackData: Record<string, GPX>;
private gpxLoaded?: boolean;
private ignoreNextZoomEvent?: boolean;
static getType() {
return "geoMap";
}
constructor() {
super();
this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L));
this.currentMarkerData = {};
this.currentTrackData = {};
this._state = State.Normal;
this.child(this.geoMapWidget);
}
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$widget.append(this.geoMapWidget.render());
}
async #onMapInitialized(L: Leaflet) {
this.L = L;
const map = this.geoMapWidget.map;
if (!map) {
throw new Error(t("geo-map.unable-to-load-map"));
}
this.#restoreViewportAndZoom();
// Restore markers.
await this.#reloadMarkers();
// This fixes an issue with the map appearing cut off at the beginning, due to the container not being properly attached
setTimeout(() => {
map.invalidateSize();
}, 100);
const updateFn = () => this.spacedUpdate.scheduleUpdate();
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
map.on("click", (e) => this.#onMapClicked(e));
if (hasTouchBar) {
map.on("zoom", () => {
if (!this.ignoreNextZoomEvent) {
this.triggerCommand("refreshTouchBar");
}
this.ignoreNextZoomEvent = false;
});
}
}
async #restoreViewportAndZoom() {
const map = this.geoMapWidget.map;
if (!map || !this.note) {
return;
}
const blob = await this.note.getBlob();
let parsedContent: MapData = {};
if (blob && blob.content) {
parsedContent = JSON.parse(blob.content);
}
// Restore viewport position & zoom
const center = parsedContent.view?.center ?? DEFAULT_COORDINATES;
const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM;
map.setView(center, zoom);
}
async #reloadMarkers() {
if (!this.note) {
return;
}
// Delete all existing markers
for (const marker of Object.values(this.currentMarkerData)) {
marker.remove();
}
// Delete all existing tracks
for (const track of Object.values(this.currentTrackData)) {
track.remove();
}
// Add the new markers.
this.currentMarkerData = {};
const childNotes = await this.note.getChildNotes();
for (const childNote of childNotes) {
if (childNote.mime === "application/gpx+xml") {
this.#processNoteWithGpxTrack(childNote);
continue;
}
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
if (latLng) {
this.#processNoteWithMarker(childNote, latLng);
}
}
}
async #processNoteWithGpxTrack(note: FNote) {
if (!this.L || !this.geoMapWidget.map) {
return;
}
if (!this.gpxLoaded) {
await import("leaflet-gpx");
this.gpxLoaded = true;
}
const xmlResponse = await server.get<string | Uint8Array>(`notes/${note.noteId}/open`, undefined, true);
let stringResponse: string;
if (xmlResponse instanceof Uint8Array) {
stringResponse = new TextDecoder().decode(xmlResponse);
} else {
stringResponse = xmlResponse;
}
const track = new this.L.GPX(stringResponse, {
markers: {
startIcon: this.#buildIcon(note.getIcon(), note.getColorClass(), note.title),
endIcon: this.#buildIcon("bxs-flag-checkered"),
wptIcons: {
"": this.#buildIcon("bx bx-pin")
}
},
polyline_options: {
color: note.getLabelValue("color") ?? "blue"
}
});
track.addTo(this.geoMapWidget.map);
this.currentTrackData[note.noteId] = track;
}
#processNoteWithMarker(note: FNote, latLng: string) {
const map = this.geoMapWidget.map;
if (!map) {
return;
}
const [lat, lng] = latLng.split(",", 2).map((el) => parseFloat(el));
const L = this.L;
const icon = this.#buildIcon(note.getIcon(), note.getColorClass(), note.title);
const marker = L.marker(L.latLng(lat, lng), {
icon,
draggable: true,
autoPan: true,
autoPanSpeed: 5
})
.addTo(map)
.on("moveend", (e) => {
this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
});
marker.on("mousedown", ({ originalEvent }) => {
// Middle click to open in new tab
if (originalEvent.button === 1) {
const hoistedNoteId = this.hoistedNoteId;
//@ts-ignore, fix once tab manager is ported.
appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId);
return true;
}
});
marker.on("contextmenu", (e) => {
openContextMenu(note.noteId, e.originalEvent);
});
const el = marker.getElement();
if (el) {
const $el = $(el);
$el.attr("data-href", `#${note.noteId}`);
note_tooltip.setupElementTooltip($($el));
}
this.currentMarkerData[note.noteId] = marker;
}
#buildIcon(bxIconClass: string, colorClass?: string, title?: string) {
return this.L.divIcon({
html: /*html*/`\
<img class="icon" src="${markerIcon}" />
<img class="icon-shadow" src="${markerIconShadow}" />
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
<span class="title-label">${title ?? ""}</span>`,
iconSize: [25, 41],
iconAnchor: [12, 41]
});
}
#changeState(newState: State) {
this._state = newState;
this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote);
if (hasTouchBar) {
this.triggerCommand("refreshTouchBar");
}
}
async #onMapClicked(e: LeafletMouseEvent) {
if (this._state !== State.NewNote) {
return;
}
toastService.closePersistent("geo-new-note");
const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
if (title?.trim()) {
const { note } = await server.post<CreateChildResponse>(`notes/${this.noteId}/children?target=into`, {
title,
content: "",
type: "text"
});
attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON);
this.moveMarker(note.noteId, e.latlng);
}
this.#changeState(State.Normal);
}
async moveMarker(noteId: string, latLng: LatLng | null) {
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
}
getData(): any {
const map = this.geoMapWidget.map;
if (!map) {
return;
}
const data: MapData = {
view: {
center: map.getBounds().getCenter(),
zoom: map.getZoom()
}
};
return {
content: JSON.stringify(data)
};
}
async geoMapCreateChildNoteEvent({ ntxId }: EventData<"geoMapCreateChildNote">) {
if (!this.isNoteContext(ntxId)) {
return;
}
toastService.showPersistent({
icon: "plus",
id: "geo-new-note",
title: "New note",
message: t("geo-map.create-child-note-instruction")
});
this.#changeState(State.NewNote);
const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => {
if (e.key !== "Escape") {
return;
}
this.#changeState(State.Normal);
window.removeEventListener("keydown", globalKeyListener);
toastService.closePersistent("geo-new-note");
};
window.addEventListener("keydown", globalKeyListener);
}
async doRefresh(note: FNote) {
await this.geoMapWidget.refresh();
this.#restoreViewportAndZoom();
await this.#reloadMarkers();
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
// If any of the children branches are altered.
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.noteId)) {
this.#reloadMarkers();
return;
}
// If any of note has its location attribute changed.
// TODO: Should probably filter by parent here as well.
const attributeRows = loadResults.getAttributeRows();
if (attributeRows.find((at) => [LOCATION_ATTRIBUTE, "color"].includes(at.name ?? ""))) {
this.#reloadMarkers();
}
}
openGeoLocationEvent({ noteId, event }: EventData<"openGeoLocation">) {
const marker = this.currentMarkerData[noteId];
if (!marker) {
return;
}
const latLng = this.currentMarkerData[noteId].getLatLng();
const url = `geo:${latLng.lat},${latLng.lng}`;
link.goToLinkExt(event, url);
}
deleteFromMapEvent({ noteId }: EventData<"deleteFromMap">) {
this.moveMarker(noteId, null);
}
buildTouchBarCommand({ TouchBar }: CommandListenerData<"buildTouchBar">) {
const map = this.geoMapWidget.map;
const that = this;
if (!map) {
return;
}
return [
new TouchBar.TouchBarSlider({
label: "Zoom",
value: map.getZoom(),
minValue: map.getMinZoom(),
maxValue: map.getMaxZoom(),
change(newValue) {
that.ignoreNextZoomEvent = true;
map.setZoom(newValue);
},
}),
new TouchBar.TouchBarButton({
label: "New geo note",
click: () => this.triggerCommand("geoMapCreateChildNote", { ntxId: this.ntxId }),
enabled: (this._state === State.Normal)
})
];
}
}

View File

@@ -1,32 +0,0 @@
import appContext from "../../components/app_context.js";
import type { ContextMenuEvent } from "../../menus/context_menu.js";
import contextMenu from "../../menus/context_menu.js";
import linkContextMenu from "../../menus/link_context_menu.js";
import { t } from "../../services/i18n.js";
export default function openContextMenu(noteId: string, e: ContextMenuEvent) {
contextMenu.show({
x: e.pageX,
y: e.pageY,
items: [
...linkContextMenu.getItems(),
{ title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" },
{ title: "----" },
{ title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" }
],
selectMenuItemHandler: ({ command }, e) => {
if (command === "deleteFromMap") {
appContext.triggerCommand(command, { noteId });
return;
}
if (command === "openGeoLocation") {
appContext.triggerCommand(command, { noteId, event: e });
return;
}
// Pass the events to the link context menu
linkContextMenu.handleLinkContextMenuItem(command, noteId);
}
});
}

View File

@@ -1,11 +1,12 @@
import TypeWidget from "./type_widget.js";
import utils from "../../services/utils.js";
import type { MindElixirCtor, MindElixirInstance } from "mind-elixir";
import type { MindElixirInstance } from "mind-elixir";
import nodeMenu from "@mind-elixir/node-menu";
import type FNote from "../../entities/fnote.js";
import type { EventData } from "../../components/app_context.js";
// allow node-menu plugin css to be bundled by webpack
import "mind-elixir/style";
import "@mind-elixir/node-menu/dist/style.css";
const NEW_TOPIC_NAME = "";

View File

@@ -0,0 +1,659 @@
import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js";
import toastService from "../../../../services/toast.js";
import noteAutocompleteService from "../../../../services/note_autocomplete.js";
import type { OptionMap } from "@triliumnext/commons";
import type { Suggestion } from "../../../../services/note_autocomplete.js";
interface FileSystemMapping {
mappingId: string;
noteId: string;
filePath: string;
syncDirection: 'bidirectional' | 'trilium_to_disk' | 'disk_to_trilium';
isActive: boolean;
includeSubtree: boolean;
preserveHierarchy: boolean;
contentFormat: 'auto' | 'markdown' | 'html' | 'raw';
excludePatterns: string[] | null;
lastSyncTime: string | null;
syncErrors: string[] | null;
dateCreated: string;
dateModified: string;
}
interface SyncStatus {
enabled: boolean;
initialized: boolean;
status?: Record<string, any>;
}
// API Request/Response interfaces
interface PathValidationRequest {
filePath: string;
}
interface PathValidationResponse {
exists: boolean;
stats?: {
isDirectory: boolean;
size: number;
modified: string;
};
}
interface CreateMappingRequest {
noteId: string;
filePath: string;
syncDirection: 'bidirectional' | 'trilium_to_disk' | 'disk_to_trilium';
contentFormat: 'auto' | 'markdown' | 'html' | 'raw';
includeSubtree: boolean;
preserveHierarchy: boolean;
excludePatterns: string[] | null;
}
interface UpdateMappingRequest extends CreateMappingRequest {}
interface SyncMappingResponse {
success: boolean;
message?: string;
}
interface ApiResponse {
success?: boolean;
message?: string;
}
const TPL = /*html*/`
<style>
.modal-hidden {
display: none !important;
}
.modal-visible {
display: flex !important;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1050;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 0.5rem;
max-width: 600px;
width: 90%;
max-height: 90%;
overflow-y: auto;
}
</style>
<div class="options-section">
<h4>File System Sync</h4>
<div class="form-group">
<label>
<input type="checkbox" class="file-sync-enabled-checkbox">
Enable file system synchronization
</label>
<div class="help-block">
Allows bidirectional synchronization between Trilium notes and files on your local file system.
</div>
</div>
<div class="file-sync-controls" style="display: none;">
<div class="alert alert-info">
<strong>Note:</strong> File system sync creates mappings between notes and files/directories.
Changes in either location will be synchronized automatically when enabled.
</div>
<div class="sync-status-container">
<h5>Sync Status</h5>
<div class="sync-status-info">
<div class="status-item">
<strong>Status:</strong> <span class="sync-status-text">Loading...</span>
</div>
<div class="active-mappings-count">
<strong>Active Mappings:</strong> <span class="mappings-count">0</span>
</div>
</div>
</div>
<div class="mappings-section">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5>File System Mappings</h5>
<button class="btn btn-primary btn-sm create-mapping-button">
<i class="bx bx-plus"></i> Create Mapping
</button>
</div>
<div class="mappings-list">
<!-- Mappings will be populated here -->
</div>
</div>
<div class="sync-actions mt-3">
<button class="btn btn-secondary refresh-status-button">
<i class="bx bx-refresh"></i> Refresh Status
</button>
</div>
</div>
</div>
<!-- Create/Edit Mapping Modal -->
<div class="mapping-modal modal-hidden">
<div class="modal-backdrop"></div>
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Create File System Mapping</h5>
<button type="button" class="modal-close" aria-label="Close">
<i class="bx bx-x"></i>
</button>
</div>
<div class="modal-body">
<form class="mapping-form">
<div class="form-group">
<label for="note-selector">Note:</label>
<div class="input-group">
<input type="text" id="note-selector" class="form-control note-selector"
placeholder="Search for a note...">
</div>
<div class="help-block">Select the note to map to the file system.</div>
</div>
<div class="form-group">
<label for="file-path">File/Directory Path:</label>
<div class="input-group">
<input type="text" id="file-path" class="form-control file-path-input"
placeholder="/path/to/file/or/directory">
<div class="input-group-append">
<button type="button" class="btn btn-secondary validate-path-button">
<i class="bx bx-search"></i> Validate
</button>
</div>
</div>
<div class="path-validation-result"></div>
</div>
<div class="form-group">
<label for="sync-direction">Sync Direction:</label>
<select id="sync-direction" class="form-control sync-direction-select">
<option value="bidirectional">Bidirectional (default)</option>
<option value="trilium_to_disk">Trilium → Disk only</option>
<option value="disk_to_trilium">Disk → Trilium only</option>
</select>
</div>
<div class="form-group">
<label for="content-format">Content Format:</label>
<select id="content-format" class="form-control content-format-select">
<option value="auto">Auto-detect (default)</option>
<option value="markdown">Markdown</option>
<option value="html">HTML</option>
<option value="raw">Raw/Binary</option>
</select>
</div>
<div class="form-group">
<label>
<input type="checkbox" class="include-subtree-checkbox">
Include subtree
</label>
<div class="help-block">Map entire note subtree to directory structure.</div>
</div>
<div class="form-group subtree-options" style="display: none;">
<label>
<input type="checkbox" class="preserve-hierarchy-checkbox" checked>
Preserve directory hierarchy
</label>
<div class="help-block">Create subdirectories matching note hierarchy.</div>
</div>
<div class="form-group">
<label for="exclude-patterns">Exclude Patterns (one per line):</label>
<textarea id="exclude-patterns" class="form-control exclude-patterns-textarea"
rows="3" placeholder="*.tmp&#10;node_modules&#10;.git"></textarea>
<div class="help-block">Files/directories matching these patterns will be ignored.</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel-mapping-button">Cancel</button>
<button type="button" class="btn btn-primary save-mapping-button">Save Mapping</button>
</div>
</div>
</div>`;
const MAPPING_ITEM_TPL = /*html*/`
<div class="mapping-item card mb-2" data-mapping-id="">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div class="mapping-info">
<div class="mapping-path">
<strong class="file-path"></strong>
</div>
<div class="mapping-details text-muted">
<span class="note-title"></span> •
<span class="sync-direction-text"></span> •
<span class="content-format-text"></span>
</div>
<div class="mapping-status">
<span class="status-badge"></span>
<span class="last-sync"></span>
</div>
</div>
<div class="mapping-actions">
<button class="btn btn-sm btn-secondary sync-mapping-button" title="Sync now">
<i class="bx bx-refresh"></i>
</button>
<button class="btn btn-sm btn-secondary edit-mapping-button" title="Edit">
<i class="bx bx-edit"></i>
</button>
<button class="btn btn-sm btn-danger delete-mapping-button" title="Delete">
<i class="bx bx-trash"></i>
</button>
</div>
</div>
<div class="sync-errors" style="display: none;">
<div class="alert alert-warning mt-2">
<strong>Sync Errors:</strong>
<ul class="error-list mb-0"></ul>
</div>
</div>
</div>
</div>`;
export default class FileSystemSyncOptions extends OptionsWidget {
private $fileSyncEnabledCheckbox!: JQuery<HTMLElement>;
private $fileSyncControls!: JQuery<HTMLElement>;
private $syncStatusText!: JQuery<HTMLElement>;
private $mappingsCount!: JQuery<HTMLElement>;
private $mappingsList!: JQuery<HTMLElement>;
private $createMappingButton!: JQuery<HTMLElement>;
private $refreshStatusButton!: JQuery<HTMLElement>;
// Modal elements
private $mappingModal!: JQuery<HTMLElement>;
private $modalTitle!: JQuery<HTMLElement>;
private $noteSelector!: JQuery<HTMLElement>;
private $filePathInput!: JQuery<HTMLElement>;
private $validatePathButton!: JQuery<HTMLElement>;
private $pathValidationResult!: JQuery<HTMLElement>;
private $syncDirectionSelect!: JQuery<HTMLElement>;
private $contentFormatSelect!: JQuery<HTMLElement>;
private $includeSubtreeCheckbox!: JQuery<HTMLElement>;
private $preserveHierarchyCheckbox!: JQuery<HTMLElement>;
private $subtreeOptions!: JQuery<HTMLElement>;
private $excludePatternsTextarea!: JQuery<HTMLElement>;
private $saveMappingButton!: JQuery<HTMLElement>;
private $cancelMappingButton!: JQuery<HTMLElement>;
private $modalClose!: JQuery<HTMLElement>;
private currentEditingMappingId: string | null = null;
private mappings: FileSystemMapping[] = [];
doRender() {
this.$widget = $(TPL);
this.initializeElements();
// Ensure modal is hidden on initialization
this.$mappingModal.addClass('modal-hidden').removeClass('modal-visible');
this.setupEventHandlers();
}
private initializeElements() {
this.$fileSyncEnabledCheckbox = this.$widget.find(".file-sync-enabled-checkbox");
this.$fileSyncControls = this.$widget.find(".file-sync-controls");
this.$syncStatusText = this.$widget.find(".sync-status-text");
this.$mappingsCount = this.$widget.find(".mappings-count");
this.$mappingsList = this.$widget.find(".mappings-list");
this.$createMappingButton = this.$widget.find(".create-mapping-button");
this.$refreshStatusButton = this.$widget.find(".refresh-status-button");
// Modal elements
this.$mappingModal = this.$widget.closest(".mapping-modal");
this.$modalTitle = this.$mappingModal.find(".modal-title");
this.$noteSelector = this.$mappingModal.find(".note-selector");
this.$filePathInput = this.$mappingModal.find(".file-path-input");
this.$validatePathButton = this.$mappingModal.find(".validate-path-button");
this.$pathValidationResult = this.$mappingModal.find(".path-validation-result");
this.$syncDirectionSelect = this.$mappingModal.find(".sync-direction-select");
this.$contentFormatSelect = this.$mappingModal.find(".content-format-select");
this.$includeSubtreeCheckbox = this.$mappingModal.find(".include-subtree-checkbox");
this.$preserveHierarchyCheckbox = this.$mappingModal.find(".preserve-hierarchy-checkbox");
this.$subtreeOptions = this.$mappingModal.find(".subtree-options");
this.$excludePatternsTextarea = this.$mappingModal.find(".exclude-patterns-textarea");
this.$saveMappingButton = this.$mappingModal.find(".save-mapping-button");
this.$cancelMappingButton = this.$mappingModal.find(".cancel-mapping-button");
this.$modalClose = this.$mappingModal.find(".modal-close");
}
private setupEventHandlers() {
this.$fileSyncEnabledCheckbox.on("change", async () => {
const isEnabled = this.$fileSyncEnabledCheckbox.prop("checked");
try {
if (isEnabled) {
await server.post<ApiResponse>("file-system-sync/enable");
} else {
await server.post<ApiResponse>("file-system-sync/disable");
}
this.toggleControls(isEnabled);
if (isEnabled) {
await this.refreshStatus();
}
toastService.showMessage(`File system sync ${isEnabled ? 'enabled' : 'disabled'}`);
} catch (error) {
toastService.showError(`Failed to ${isEnabled ? 'enable' : 'disable'} file system sync`);
// Revert checkbox state
this.$fileSyncEnabledCheckbox.prop("checked", !isEnabled);
}
});
this.$createMappingButton.on("click", () => {
this.showMappingModal();
});
this.$refreshStatusButton.on("click", () => {
this.refreshStatus();
});
this.$validatePathButton.on("click", () => {
this.validatePath();
});
this.$includeSubtreeCheckbox.on("change", () => {
const isChecked = this.$includeSubtreeCheckbox.prop("checked");
this.$subtreeOptions.toggle(isChecked);
});
// Modal handlers
this.$saveMappingButton.on("click", () => {
this.saveMapping();
});
this.$cancelMappingButton.on("click", () => {
this.hideMappingModal();
});
this.$modalClose.on("click", () => {
this.hideMappingModal();
});
this.$mappingModal.find(".modal-backdrop").on("click", () => {
this.hideMappingModal();
});
// Note selector autocomplete will be initialized in showMappingModal
}
private toggleControls(enabled: boolean) {
this.$fileSyncControls.toggle(enabled);
}
private async refreshStatus() {
try {
const status = await server.get<SyncStatus>("file-system-sync/status");
this.$syncStatusText.text(status.initialized ? "Active" : "Inactive");
if (status.initialized) {
await this.loadMappings();
}
} catch (error) {
this.$syncStatusText.text("Error");
toastService.showError("Failed to get sync status");
}
}
private async loadMappings() {
try {
this.mappings = await server.get<FileSystemMapping[]>("file-system-sync/mappings");
this.renderMappings();
this.$mappingsCount.text(this.mappings.length.toString());
} catch (error) {
toastService.showError("Failed to load mappings");
}
}
private renderMappings() {
this.$mappingsList.empty();
for (const mapping of this.mappings) {
const $item = $(MAPPING_ITEM_TPL);
$item.attr("data-mapping-id", mapping.mappingId);
$item.find(".file-path").text(mapping.filePath);
$item.find(".note-title").text(`Note: ${mapping.noteId}`); // TODO: Get actual note title
$item.find(".sync-direction-text").text(this.formatSyncDirection(mapping.syncDirection));
$item.find(".content-format-text").text(mapping.contentFormat);
// Status badge
const $statusBadge = $item.find(".status-badge");
if (mapping.syncErrors && mapping.syncErrors.length > 0) {
$statusBadge.addClass("badge badge-danger").text("Error");
const $errorsDiv = $item.find(".sync-errors");
const $errorList = $errorsDiv.find(".error-list");
mapping.syncErrors.forEach(error => {
$errorList.append(`<li>${error}</li>`);
});
$errorsDiv.show();
} else if (mapping.isActive) {
$statusBadge.addClass("badge badge-success").text("Active");
} else {
$statusBadge.addClass("badge badge-secondary").text("Inactive");
}
// Last sync time
if (mapping.lastSyncTime) {
const lastSync = new Date(mapping.lastSyncTime).toLocaleString();
$item.find(".last-sync").text(`Last sync: ${lastSync}`);
} else {
$item.find(".last-sync").text("Never synced");
}
// Action handlers
$item.find(".sync-mapping-button").on("click", () => {
this.syncMapping(mapping.mappingId);
});
$item.find(".edit-mapping-button").on("click", () => {
this.editMapping(mapping);
});
$item.find(".delete-mapping-button").on("click", () => {
this.deleteMapping(mapping.mappingId);
});
this.$mappingsList.append($item);
}
}
private formatSyncDirection(direction: string): string {
switch (direction) {
case 'bidirectional': return 'Bidirectional';
case 'trilium_to_disk': return 'Trilium → Disk';
case 'disk_to_trilium': return 'Disk → Trilium';
default: return direction;
}
}
private showMappingModal(mapping?: FileSystemMapping) {
this.currentEditingMappingId = mapping?.mappingId || null;
console.log("Showing mapping modal", this.currentEditingMappingId, this.$mappingModal);
if (mapping) {
this.$modalTitle.text("Edit File System Mapping");
this.populateMappingForm(mapping);
} else {
this.$modalTitle.text("Create File System Mapping");
this.clearMappingForm();
}
// Initialize note autocomplete
noteAutocompleteService.initNoteAutocomplete(this.$noteSelector, {
allowCreatingNotes: true,
});
// Handle note selection
this.$noteSelector.off("autocomplete:noteselected").on("autocomplete:noteselected", (event: JQuery.Event, suggestion: Suggestion) => {
// The note autocomplete service will automatically set the selected note path
// which we can retrieve using getSelectedNoteId()
});
this.$mappingModal.removeClass('modal-hidden').addClass('modal-visible');
}
private hideMappingModal() {
this.$mappingModal.removeClass('modal-visible').addClass('modal-hidden');
this.clearMappingForm();
this.currentEditingMappingId = null;
}
private async populateMappingForm(mapping: FileSystemMapping) {
// Set the note using the autocomplete service's setNote method
await this.$noteSelector.setNote(mapping.noteId);
this.$filePathInput.val(mapping.filePath);
this.$syncDirectionSelect.val(mapping.syncDirection);
this.$contentFormatSelect.val(mapping.contentFormat);
this.$includeSubtreeCheckbox.prop("checked", mapping.includeSubtree);
this.$preserveHierarchyCheckbox.prop("checked", mapping.preserveHierarchy);
this.$subtreeOptions.toggle(mapping.includeSubtree);
if (mapping.excludePatterns) {
this.$excludePatternsTextarea.val(mapping.excludePatterns.join('\n'));
}
}
private clearMappingForm() {
// Clear the note selector using autocomplete service
this.$noteSelector.val('').setSelectedNotePath('');
this.$filePathInput.val('');
this.$syncDirectionSelect.val('bidirectional');
this.$contentFormatSelect.val('auto');
this.$includeSubtreeCheckbox.prop("checked", false);
this.$preserveHierarchyCheckbox.prop("checked", true);
this.$subtreeOptions.hide();
this.$excludePatternsTextarea.val('');
this.$pathValidationResult.empty();
}
private async validatePath() {
const filePath = this.$filePathInput.val() as string;
if (!filePath) {
this.$pathValidationResult.html('<div class="text-danger">Please enter a file path</div>');
return;
}
try {
const result = await server.post<PathValidationResponse>("file-system-sync/validate-path", { filePath } as PathValidationRequest);
if (result.exists && result.stats) {
const type = result.stats.isDirectory ? 'directory' : 'file';
this.$pathValidationResult.html(
`<div class="text-success">✓ Valid ${type} (${result.stats.size} bytes, modified ${new Date(result.stats.modified).toLocaleString()})</div>`
);
} else {
this.$pathValidationResult.html('<div class="text-warning">⚠ Path does not exist</div>');
}
} catch (error) {
this.$pathValidationResult.html('<div class="text-danger">✗ Invalid path</div>');
}
}
private async saveMapping() {
const noteId = this.$noteSelector.getSelectedNoteId();
const filePath = this.$filePathInput.val() as string;
const syncDirection = this.$syncDirectionSelect.val() as string;
const contentFormat = this.$contentFormatSelect.val() as string;
const includeSubtree = this.$includeSubtreeCheckbox.prop("checked");
const preserveHierarchy = this.$preserveHierarchyCheckbox.prop("checked");
const excludePatternsText = this.$excludePatternsTextarea.val() as string;
// Validation
if (!noteId) {
toastService.showError("Please select a note");
return;
}
if (!filePath) {
toastService.showError("Please enter a file path");
return;
}
const excludePatterns = excludePatternsText.trim()
? excludePatternsText.split('\n').map(p => p.trim()).filter(p => p)
: null;
const mappingData: CreateMappingRequest = {
noteId,
filePath,
syncDirection: syncDirection as 'bidirectional' | 'trilium_to_disk' | 'disk_to_trilium',
contentFormat: contentFormat as 'auto' | 'markdown' | 'html' | 'raw',
includeSubtree,
preserveHierarchy,
excludePatterns
};
try {
if (this.currentEditingMappingId) {
await server.put<ApiResponse>(`file-system-sync/mappings/${this.currentEditingMappingId}`, mappingData as UpdateMappingRequest);
toastService.showMessage("Mapping updated successfully");
} else {
await server.post<ApiResponse>("file-system-sync/mappings", mappingData);
toastService.showMessage("Mapping created successfully");
}
this.hideMappingModal();
await this.loadMappings();
} catch (error) {
toastService.showError("Failed to save mapping");
}
}
private async syncMapping(mappingId: string) {
try {
const result = await server.post<SyncMappingResponse>(`file-system-sync/mappings/${mappingId}/sync`);
if (result.success) {
toastService.showMessage("Sync completed successfully");
} else {
toastService.showError(`Sync failed: ${result.message}`);
}
await this.loadMappings();
} catch (error) {
toastService.showError("Failed to trigger sync");
}
}
private editMapping(mapping: FileSystemMapping) {
this.showMappingModal(mapping);
}
private async deleteMapping(mappingId: string) {
if (!confirm("Are you sure you want to delete this mapping?")) {
return;
}
try {
await server.delete<ApiResponse>(`file-system-sync/mappings/${mappingId}`);
toastService.showMessage("Mapping deleted successfully");
await this.loadMappings();
} catch (error) {
toastService.showError("Failed to delete mapping");
}
}
async optionsLoaded(options: OptionMap) {
const isEnabled = options.fileSystemSyncEnabled === "true";
this.$fileSyncEnabledCheckbox.prop("checked", isEnabled);
this.toggleControls(isEnabled);
if (isEnabled) {
await this.refreshStatus();
}
}
}

View File

@@ -71,6 +71,17 @@ export default abstract class TypeWidget extends NoteContextAwareWidget {
}
}
activeNoteChangedEvent() {
if (!this.isActiveNoteContext()) {
return;
}
// Restore focus to the editor when switching tabs, but only if the note tree is not already focused.
if (!document.activeElement?.classList.contains("fancytree-title")) {
this.focus();
}
}
/**
* {@inheritdoc}
*

View File

@@ -20,7 +20,7 @@ const TPL = /*html*/`
function buildElement() {
if (!utils.isElectron()) {
return `<iframe class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts"></iframe>`;
return `<iframe class="note-detail-web-view-content" sandbox="allow-same-origin allow-scripts allow-popups"></iframe>`;
} else {
return `<webview class="note-detail-web-view-content"></webview>`;
}

View File

@@ -0,0 +1,180 @@
import appContext from "../../../components/app_context";
import FNote from "../../../entities/fnote";
import attributes from "../../../services/attributes";
import { executeBulkActions } from "../../../services/bulk_action";
import note_create from "../../../services/note_create";
import ViewModeStorage from "../view_mode_storage";
import { BoardData } from "./config";
import { ColumnMap, getBoardData } from "./data";
export default class BoardApi {
private constructor(
private _columns: string[],
private _parentNoteId: string,
private viewStorage: ViewModeStorage<BoardData>,
private byColumn: ColumnMap,
private persistedData: BoardData,
private _statusAttribute: string) {}
get columns() {
return this._columns;
}
get statusAttribute() {
return this._statusAttribute;
}
getColumn(column: string) {
return this.byColumn.get(column);
}
async changeColumn(noteId: string, newColumn: string) {
await attributes.setLabel(noteId, this._statusAttribute, newColumn);
}
openNote(noteId: string) {
appContext.triggerCommand("openInPopup", { noteIdOrPath: noteId });
}
async insertRowAtPosition(
column: string,
relativeToBranchId: string,
direction: "before" | "after",
open: boolean = true) {
const { note } = await note_create.createNote(this._parentNoteId, {
activate: false,
targetBranchId: relativeToBranchId,
target: direction,
title: "New item"
});
if (!note) {
throw new Error("Failed to create note");
}
const { noteId } = note;
await this.changeColumn(noteId, column);
if (open) {
this.openNote(noteId);
}
return note;
}
async renameColumn(oldValue: string, newValue: string, noteIds: string[]) {
// Change the value in the notes.
await executeBulkActions(noteIds, [
{
name: "updateLabelValue",
labelName: this._statusAttribute,
labelValue: newValue
}
]);
// Rename the column in the persisted data.
for (const column of this.persistedData.columns || []) {
if (column.value === oldValue) {
column.value = newValue;
}
}
await this.viewStorage.store(this.persistedData);
}
async removeColumn(column: string) {
// Remove the value from the notes.
const noteIds = this.byColumn.get(column)?.map(item => item.note.noteId) || [];
await executeBulkActions(noteIds, [
{
name: "deleteLabel",
labelName: this._statusAttribute
}
]);
this.persistedData.columns = (this.persistedData.columns ?? []).filter(col => col.value !== column);
this.viewStorage.store(this.persistedData);
}
async createColumn(columnValue: string) {
// Add the new column to persisted data if it doesn't exist
if (!this.persistedData.columns) {
this.persistedData.columns = [];
}
const existingColumn = this.persistedData.columns.find(col => col.value === columnValue);
if (!existingColumn) {
this.persistedData.columns.push({ value: columnValue });
await this.viewStorage.store(this.persistedData);
}
return columnValue;
}
async reorderColumns(newColumnOrder: string[]) {
// Update the column order in persisted data
if (!this.persistedData.columns) {
this.persistedData.columns = [];
}
// Create a map of existing column data
const columnDataMap = new Map();
this.persistedData.columns.forEach(col => {
columnDataMap.set(col.value, col);
});
// Reorder columns based on new order
this.persistedData.columns = newColumnOrder.map(columnValue => {
return columnDataMap.get(columnValue) || { value: columnValue };
});
// Update internal columns array
this._columns = newColumnOrder;
await this.viewStorage.store(this.persistedData);
}
async refresh(parentNote: FNote) {
// Refresh the API data by re-fetching from the parent note
const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status";
this._statusAttribute = statusAttribute;
// Use the current in-memory persisted data instead of restoring from storage
// This ensures we don't lose recent updates like column renames
const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, this.persistedData);
// Update internal state
this.byColumn = byColumn;
if (newPersistedData) {
this.persistedData = newPersistedData;
this.viewStorage.store(this.persistedData);
}
// Use the order from persistedData.columns, then add any new columns found
const orderedColumns = this.persistedData.columns?.map(col => col.value) || [];
const allColumns = Array.from(byColumn.keys());
const newColumns = allColumns.filter(col => !orderedColumns.includes(col));
this._columns = [...orderedColumns, ...newColumns];
}
static async build(parentNote: FNote, viewStorage: ViewModeStorage<BoardData>) {
const statusAttribute = parentNote.getLabelValue("board:groupBy") ?? "status";
let persistedData = await viewStorage.restore() ?? {};
const { byColumn, newPersistedData } = await getBoardData(parentNote, statusAttribute, persistedData);
// Use the order from persistedData.columns, then add any new columns found
const orderedColumns = persistedData.columns?.map(col => col.value) || [];
const allColumns = Array.from(byColumn.keys());
const newColumns = allColumns.filter(col => !orderedColumns.includes(col));
const columns = [...orderedColumns, ...newColumns];
if (newPersistedData) {
persistedData = newPersistedData;
viewStorage.store(persistedData);
}
return new BoardApi(columns, parentNote.noteId, viewStorage, byColumn, persistedData, statusAttribute);
}
}

View File

@@ -0,0 +1,278 @@
import BoardApi from "./api";
import { DragContext, BaseDragHandler } from "./drag_types";
export class ColumnDragHandler implements BaseDragHandler {
private $container: JQuery<HTMLElement>;
private api: BoardApi;
private context: DragContext;
constructor(
$container: JQuery<HTMLElement>,
api: BoardApi,
context: DragContext,
) {
this.$container = $container;
this.api = api;
this.context = context;
}
setupColumnDrag($columnEl: JQuery<HTMLElement>, columnValue: string) {
const $titleEl = $columnEl.find('h3[data-column-value]');
$titleEl.attr("draggable", "true");
// Delay drag start to allow click detection
let dragStartTimer: number | null = null;
$titleEl.on("mousedown", (e) => {
// Don't interfere with editing mode or input field interactions
if ($titleEl.hasClass('editing') || $(e.target).is('input')) {
return;
}
// Clear any existing timer
if (dragStartTimer) {
clearTimeout(dragStartTimer);
dragStartTimer = null;
}
// Set a short delay before enabling dragging
dragStartTimer = window.setTimeout(() => {
$titleEl.attr("draggable", "true");
dragStartTimer = null;
}, 150);
});
$titleEl.on("mouseup mouseleave", (e) => {
// Don't interfere with editing mode
if ($titleEl.hasClass('editing') || $(e.target).is('input')) {
return;
}
// Cancel drag start timer on mouse up or leave
if (dragStartTimer) {
clearTimeout(dragStartTimer);
dragStartTimer = null;
}
});
$titleEl.on("dragstart", (e) => {
// Only start dragging if the target is not an input (for inline editing)
if ($(e.target).is('input') || $titleEl.hasClass('editing')) {
e.preventDefault();
return false;
}
this.context.draggedColumn = columnValue;
this.context.draggedColumnElement = $columnEl;
$columnEl.addClass("column-dragging");
const originalEvent = e.originalEvent as DragEvent;
if (originalEvent.dataTransfer) {
originalEvent.dataTransfer.effectAllowed = "move";
originalEvent.dataTransfer.setData("text/plain", columnValue);
}
// Prevent note dragging when column is being dragged
e.stopPropagation();
// Setup global drag tracking for better drop indicator positioning
this.setupGlobalColumnDragTracking();
});
$titleEl.on("dragend", () => {
$columnEl.removeClass("column-dragging");
this.context.draggedColumn = null;
this.context.draggedColumnElement = null;
this.cleanupColumnDropIndicators();
this.cleanupGlobalColumnDragTracking();
// Re-enable draggable
$titleEl.attr("draggable", "true");
});
}
setupColumnDropZone($columnEl: JQuery<HTMLElement>) {
$columnEl.on("dragover", (e) => {
// Only handle column drops when a column is being dragged
if (this.context.draggedColumn && !this.context.draggedNote) {
e.preventDefault();
const originalEvent = e.originalEvent as DragEvent;
if (originalEvent.dataTransfer) {
originalEvent.dataTransfer.dropEffect = "move";
}
// Don't highlight columns - we only care about the drop indicator position
}
});
$columnEl.on("drop", async (e) => {
if (this.context.draggedColumn && !this.context.draggedNote) {
e.preventDefault();
console.log("Column drop event triggered for column:", this.context.draggedColumn);
// Use the drop indicator position to determine where to place the column
await this.handleColumnDrop();
}
});
}
cleanup() {
this.cleanupColumnDropIndicators();
this.context.draggedColumn = null;
this.context.draggedColumnElement = null;
this.cleanupGlobalColumnDragTracking();
}
private setupGlobalColumnDragTracking() {
// Add container-level drag tracking for better indicator positioning
this.$container.on("dragover.columnDrag", (e) => {
if (this.context.draggedColumn) {
e.preventDefault();
const originalEvent = e.originalEvent as DragEvent;
this.showColumnDropIndicator(originalEvent.clientX);
}
});
// Add container-level drop handler for column reordering
this.$container.on("drop.columnDrag", async (e) => {
if (this.context.draggedColumn) {
e.preventDefault();
console.log("Container drop event triggered for column:", this.context.draggedColumn);
await this.handleColumnDrop();
}
});
}
private cleanupGlobalColumnDragTracking() {
this.$container.off("dragover.columnDrag");
this.$container.off("drop.columnDrag");
}
private cleanupColumnDropIndicators() {
// Remove column drop indicators
this.$container.find(".column-drop-indicator").remove();
}
private showColumnDropIndicator(mouseX: number) {
// Clean up existing indicators
this.cleanupColumnDropIndicators();
// Get all columns (excluding the dragged one if it exists)
let $allColumns = this.$container.find('.board-column');
if (this.context.draggedColumnElement) {
$allColumns = $allColumns.not(this.context.draggedColumnElement);
}
let $targetColumn: JQuery<HTMLElement> = $();
let insertBefore = false;
// Find which column the mouse is closest to
$allColumns.each((_, columnEl) => {
const $column = $(columnEl);
const rect = columnEl.getBoundingClientRect();
const columnMiddle = rect.left + rect.width / 2;
if (mouseX >= rect.left && mouseX <= rect.right) {
// Mouse is over this column
$targetColumn = $column;
insertBefore = mouseX < columnMiddle;
return false; // Break the loop
}
});
// If no column found under mouse, find the closest one
if ($targetColumn.length === 0) {
let closestDistance = Infinity;
$allColumns.each((_, columnEl) => {
const $column = $(columnEl);
const rect = columnEl.getBoundingClientRect();
const columnCenter = rect.left + rect.width / 2;
const distance = Math.abs(mouseX - columnCenter);
if (distance < closestDistance) {
closestDistance = distance;
$targetColumn = $column;
insertBefore = mouseX < columnCenter;
}
});
}
if ($targetColumn.length > 0) {
const $dropIndicator = $("<div>").addClass("column-drop-indicator");
if (insertBefore) {
$targetColumn.before($dropIndicator);
} else {
$targetColumn.after($dropIndicator);
}
$dropIndicator.addClass("show");
}
}
private async handleColumnDrop() {
console.log("handleColumnDrop called for:", this.context.draggedColumn);
if (!this.context.draggedColumn || !this.context.draggedColumnElement) {
console.log("No dragged column or element found");
return;
}
try {
// Find the drop indicator to determine insert position
const $dropIndicator = this.$container.find(".column-drop-indicator.show");
console.log("Drop indicator found:", $dropIndicator.length > 0);
if ($dropIndicator.length > 0) {
// Get current column order from the API (source of truth)
const currentOrder = [...this.api.columns];
let newOrder = [...currentOrder];
// Remove dragged column from current position
newOrder = newOrder.filter(col => col !== this.context.draggedColumn);
// Determine insertion position based on drop indicator position
const $nextColumn = $dropIndicator.next('.board-column');
const $prevColumn = $dropIndicator.prev('.board-column');
let insertIndex = -1;
if ($nextColumn.length > 0) {
// Insert before the next column
const nextColumnValue = $nextColumn.attr('data-column');
if (nextColumnValue) {
insertIndex = newOrder.indexOf(nextColumnValue);
}
} else if ($prevColumn.length > 0) {
// Insert after the previous column
const prevColumnValue = $prevColumn.attr('data-column');
if (prevColumnValue) {
insertIndex = newOrder.indexOf(prevColumnValue) + 1;
}
} else {
// Insert at the beginning
insertIndex = 0;
}
// Insert the dragged column at the determined position
if (insertIndex >= 0 && insertIndex <= newOrder.length) {
newOrder.splice(insertIndex, 0, this.context.draggedColumn);
} else {
// Fallback: insert at the end
newOrder.push(this.context.draggedColumn);
}
// Update column order in API
await this.api.reorderColumns(newOrder);
} else {
console.warn("No drop indicator found for column drop");
}
} catch (error) {
console.error("Failed to reorder columns:", error);
} finally {
this.cleanupColumnDropIndicators();
}
}
}

View File

@@ -0,0 +1,7 @@
export interface BoardColumnData {
value: string;
}
export interface BoardData {
columns?: BoardColumnData[];
}

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