Compare commits

..

178 Commits

Author SHA1 Message Date
Elian Doran
6e64cab2d4 Merge pull request #327 from TriliumNext/JYC333-patch-1
Create nightly release action
2024-10-11 21:56:04 +03:00
Elian Doran
9c8cf0bc09 Merge pull request #452 from TriliumNext/fix-image-copy
Use the electron Clipboard module when using "Copy image to clipboard"
2024-10-11 19:48:18 +03:00
Elian Doran
bafc556b00 server: Fix build errors 2024-10-11 19:45:54 +03:00
Elian Doran
0ec3232c81 Merge pull request #451 from jaimeferj/bugfix/parseDatesOnSort
Fixes issue #441: Dates were not parsed on sorting
2024-10-10 23:31:47 +03:00
Elian Doran
469c1ceb07 Merge pull request #458 from TriliumNext/zip-import-image-fix
Resolve MIME type not being used when creating new note correctly, upon zip import
2024-10-10 23:26:48 +03:00
Elian Doran
5e80f120c9 server: Refactor variable usage 2024-10-10 20:21:06 +03:00
Elian Doran
8faa26b663 Merge pull request #391 from SiriusXT/revisions_number_limit
Revisions number limit
2024-10-10 20:13:24 +03:00
Elian Doran
201e2fcfdb Merge pull request #472 from dousha/bugfix-math-preview
Override the z-index of Bootstrap tooltips
2024-10-10 20:04:25 +03:00
Elian Doran
5678487a16 Merge pull request #473 from TriliumNext/feature/i18n-part5
Feature/i18n part5
2024-10-10 19:13:31 +03:00
Nriver
6646e8c311 update Chinese translation and synchronize with English 2024-10-10 15:42:10 +08:00
dousha
96fc4d3280 Override the z-index of Bootstrap tooltips, making it conform to CKEditor's z-index ordering.
This change overrides the z-index of Bootstrap tooltips (calculates to 1080 in the current version) to the value assigned to CKEditor Panel Balloons (calculates to 1000 in the current version) minus 1. It also removes the original override to the CKEditor Panel Balloons which forces the autocomplete box to appear above tooltips, which is no longer needed and would interfere with math preview boxes.

This change fixes the regression to the [original issue][1] and the [math preview issue][2]. One notable effect is that tooltips will not cover menus when overlapped with one, including menus opened from the top-right corner. Further discussion and more extensive testing are encouraged.

[1]: https://github.com/zadam/trilium/issues/1272
[2]: https://github.com/TriliumNext/Notes/issues/297
2024-10-10 13:19:08 +08:00
Elian Doran
cb3ea3fb75 Merge pull request #449 from hasecilu/i18n/Spanish_pt_3_4
i18n: Continue Spanish translation, parts 3 and 4
2024-10-09 21:58:38 +03:00
Elian Doran
787a2d1957 Merge pull request #450 from hasecilu/docs/fix_links
docs: Use relative link to other README files
2024-10-09 21:56:38 +03:00
Elian Doran
89e1275dda i18n: Tweak a single message 2024-10-09 20:06:20 +03:00
Elian Doran
ddc927d617 server: Bump sync version (closes #467) 2024-10-09 20:06:13 +03:00
perf3ct
c37a51c6d0 use type assertions to make TS happy? 2024-10-03 22:48:47 -07:00
perf3ct
91095e8d4e detectFileTypeAndMime also returns the type, fixes #456 2024-10-03 22:24:43 -07:00
MeIchthys
d4fa7e3fd2 Add link to docs repo under contributing section 2024-10-03 23:23:43 -04:00
perf3ct
9204f0735c use fewer const 2024-09-30 19:43:44 +00:00
perf3ct
cfc32a14e0 Use the electron Clipboard module when using "Copy image to clipboard" 2024-09-28 17:41:59 -07:00
Jaime Fernández
270aa52591 Fixes issue #441: Dates were not parsed on sorting 2024-09-28 02:33:36 +02:00
hasecilu
99a5913391 docs: Use relative link to other README files
`master` branch was hardcoded despite working on `develop` branch
2024-09-27 14:38:45 -06:00
hasecilu
c3d6165aff i18n: Continue Spanish translation, parts 3 and 4
Some fixes were done thanks by using PO file with Poedit. It enhances a
lot the workflow for translators

---

Step 1: Create PO files from English and localized JSON files as source
$ json2po -t en es po-es

Step 2: Make the translation

Step 3: Recover translation from PO files to JSON file. Remember to set
the indentation to 2 spaces
$ po2json -t en po-es es

Step 4: Update localized JSON file with missing tags from English JSON
$ jq -s '.[0] * .[1]' en/translation.json \
    es/translation.json > merged.json
$ mv merged.json es/translation.json

Step 5: Remove PO files
$ rm -r po-es
2024-09-25 17:54:46 -06:00
Elian Doran
9220616bb5 Merge pull request #448 from Nriver/feature/i18n-part4
Feature/i18n part4
2024-09-25 18:54:12 +03:00
Nriver
eb9b4aee21 add translation for 6 widgets 2024-09-24 09:57:16 +08:00
Nriver
d9a7671830 update Chinese translation and synchronize with English 2024-09-24 09:28:46 +08:00
SiriusXT
353c410f0e Merge branch 'develop' into revisions_number_limit 2024-09-17 19:58:16 +08:00
Elian Doran
7ca4cddc58 Merge pull request #431 from TriliumNext/perfectra1n-patch-2
Print Docker container log, regardless of success or failure of healthcheck
2024-09-15 23:29:06 +03:00
Elian Doran
12543f762b Merge pull request #432 from TriliumNext/hide_float_button
Floating buttons can be displayed again after being closed
2024-09-15 23:21:44 +03:00
SiriusXT
ec7fabcb58 Floating buttons can be displayed again after being closed 2024-09-15 22:22:38 +08:00
Jin
5d08f2bc5a fix revision list ESC behaviour 2024-09-15 12:41:45 +02:00
SiriusXT
a557b82c83 Fixed the bug that notes in different formats cannot be restoredRevisioned 2024-09-15 12:48:01 +08:00
SiriusXT
62a884cb5a fix potential bugs that may exist in getRevisions 2024-09-15 10:51:55 +08:00
perfectra1n
88875a3375 Print Docker container log, if success or failure healthcheck 2024-09-14 12:50:25 -07:00
SiriusXT
cd547ebdaf Fix svg not previewing in revisions 2024-09-14 21:09:52 +08:00
Elian Doran
e511d6aecf client: Fix strange margins when ribbon has hidden components 2024-09-14 14:47:48 +03:00
Elian Doran
9b6148dec3 client: Translate attribute editor placeholder 2024-09-14 14:44:32 +03:00
Elian Doran
29a1aad45a Merge pull request #430 from TriliumNext/fix
Fix global menu advanced command can't click
2024-09-14 14:40:03 +03:00
SiriusXT
a9fce727d4 Fix svg not previewing in revisions 2024-09-14 19:37:25 +08:00
Elian Doran
9e85d2cdbd server: Fix missing translations in keyboard actions 2024-09-14 14:34:24 +03:00
Jin
c4f430276a fix global menu advanced command can't click 2024-09-14 11:02:23 +02:00
SiriusXT
fdf57b6cfa revisions_number_limit 2024-09-14 16:00:18 +08:00
SiriusXT
0a9e76abb7 Merge branch 'develop' into revisions_number_limit 2024-09-14 14:58:47 +08:00
SiriusXT
3e085e5cae Show revision information 2024-09-14 14:32:43 +08:00
Elian Doran
73cd54e75c client: Fix shortcuts section crashing when no translations 2024-09-13 22:58:39 +03:00
Elian Doran
28b8e59b4d electron: Translate title bar button 2024-09-13 22:56:30 +03:00
Elian Doran
51b6725b91 server,electron: Downgrade incompatible libraries for now 2024-09-13 22:54:06 +03:00
Elian Doran
93ca30fda0 build: Fix type not in dev dependencies 2024-09-13 22:46:15 +03:00
Elian Doran
8c222368de i18n: Translate some more messages in Romanian 2024-09-13 22:26:56 +03:00
Elian Doran
a3aa6b0628 client: Translate note tree 2024-09-13 22:22:16 +03:00
Elian Doran
d0061794dd client: Translate note icon 2024-09-13 22:17:22 +03:00
Elian Doran
17c8708b54 client: Translate quick search 2024-09-13 22:08:52 +03:00
Elian Doran
da0ddbb80d i18n: Translate the rest of Romanian messages 2024-09-13 21:59:19 +03:00
Elian Doran
138b4b1a95 i18n: Remove unused key 2024-09-13 21:57:23 +03:00
Elian Doran
1299072ea9 server: Update dependencies 2024-09-13 21:51:46 +03:00
Elian Doran
bb5893f989 electron: Update squirrel startup 2024-09-13 21:49:36 +03:00
Elian Doran
7b764fd7d8 client: Update mermaid, mind elixir 2024-09-13 21:48:38 +03:00
Elian Doran
ac2fc49a48 build: Update tooling dependencies 2024-09-13 21:44:57 +03:00
Elian Doran
c0abf6e2c5 Use fixed versions for all dependencies 2024-09-13 21:43:58 +03:00
Elian Doran
85ff0bac55 electron: Set up StartupWMClass to avoid missing icon 2024-09-13 21:21:29 +03:00
Elian Doran
3671b83a9c server: Disable debug logs for i18n 2024-09-13 21:16:20 +03:00
Elian Doran
6970bf4fc1 Merge pull request #428 from TriliumNext/icon_improvement
Fix strange underline for zoom button
2024-09-13 17:50:44 +03:00
SiriusXT
e5cce1b1bc Fix strange underline for zoom button 2024-09-13 22:24:19 +08:00
Elian Doran
faa8eca810 i18n: Translate a few Romanian messages 2024-09-13 14:57:05 +03:00
SiriusXT
84bf0cbae5 Merge branch 'develop' into revisions_number_limit 2024-09-13 19:46:07 +08:00
SiriusXT
a77264208e Merge branch 'develop' into icon_improvement 2024-09-13 19:15:33 +08:00
SiriusXT
ee06db8c8f Fix strange underline for zoom button 2024-09-13 19:10:44 +08:00
Elian Doran
e330b43750 Merge pull request #426 from TriliumNext/feature/i18n-part3
Feature/i18n part3
2024-09-13 10:28:26 +03:00
Nriver
f5b21498bf add translation for mermaid.js 2024-09-13 11:24:42 +08:00
Nriver
83388ecf1c add translation for 2 widgets 2024-09-13 11:10:59 +08:00
Nriver
87de631af4 sync Chinese translation with English 2024-09-13 10:53:50 +08:00
Nriver
e4271d3945 update missing translation 2024-09-13 10:29:44 +08:00
Elian Doran
65b8a2f97c Merge pull request #425 from TriliumNext/dropped-backslash
Picking up the backslash that I dropped
2024-09-13 00:04:08 +03:00
perfectra1n
0656154c35 Picking up the backslash that I dropped
I wiped it off and cheered it up, so it should be ready to go now.
2024-09-12 13:45:13 -07:00
Elian Doran
b3f682144b server: Update better-sqlite3 11.1.2 → 11.3.0 2024-09-12 23:08:22 +03:00
Elian Doran
d3c3e157c7 server: Update express 4.19.2 -> 4.21.0 2024-09-12 22:49:21 +03:00
Elian Doran
2534402157 client,server: Update i18next 23.14.0 -> 23.15.1 2024-09-12 22:47:46 +03:00
Elian Doran
ec80ba5caf build: Update jasmine from 5.1.0 to 5.3.0 2024-09-12 22:43:21 +03:00
Elian Doran
26621b6336 build: Fix test-jasmine on Windows 2024-09-12 22:42:22 +03:00
Elian Doran
d961e3cdf1 build: tsx 4.19.0 -> 4.19.1 2024-09-12 22:39:38 +03:00
Elian Doran
7ef399912b Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop 2024-09-12 22:23:51 +03:00
Elian Doran
9fc789676f electron: Fix window drag not working 2024-09-12 22:23:49 +03:00
Elian Doran
f01dc3d102 Merge pull request #424 from TriliumNext/stop-sha-images
Stop tagging images with `sha*`
2024-09-12 22:23:36 +03:00
perfectra1n
501bf624cc Stop tagging images with sha* 2024-09-12 12:20:54 -07:00
Elian Doran
f8605688ab Merge pull request #423 from TriliumNext/perfectra1n-patch-1
Add explicit permissions for GITHUB_TOKEN for package access
2024-09-12 20:54:15 +03:00
Elian Doran
590a9bef2d client: Fix calendar double open on existing note 2024-09-12 20:53:31 +03:00
perfectra1n
419756d19e Add explicit permissions for GITHUB_TOKEN for package access 2024-09-12 10:42:47 -07:00
Elian Doran
93ce81b355 Merge pull request #416 from SiriusXT/highlightslits
Toc and Highlightslist improvement
2024-09-12 20:24:30 +03:00
Elian Doran
8bfa4461b0 Merge pull request #381 from TriliumNext/bootstrap
Upgrade bootstrap from v4 to v5
2024-09-12 20:09:19 +03:00
Elian Doran
648feb82f8 client: Fix close button on dark theme 2024-09-12 20:06:40 +03:00
Elian Doran
74645e12fc Merge remote-tracking branch 'origin/develop' into bootstrap 2024-09-12 20:06:31 +03:00
Elian Doran
71de1a0b55 Merge pull request #421 from TriliumNext/perfectra1n-patch-1
Force `github.repository_owner` to lowercase
2024-09-12 19:59:23 +03:00
perfectra1n
694f3cb174 Force github.repository_owner to lowercase
Ahhhh, the joys of Docker....
2024-09-12 09:54:00 -07:00
Elian Doran
a2bc5073d0 client: Fix note hint not working in calendar 2024-09-12 19:37:15 +03:00
Elian Doran
3134ef7c03 Merge pull request #402 from perfectra1n/develop
Use matrix strategy for CI/CD
2024-09-12 19:24:56 +03:00
Elian Doran
8e665e27e7 client: Fix calendar opening notes twice (closes #417) 2024-09-12 19:22:58 +03:00
Elian Doran
e6fbf62cf9 Merge pull request #414 from SiriusXT/i18n_falls_back_en
i18n Fallback to English for null characters
2024-09-12 19:03:50 +03:00
Jin
ce8b5e33da fix regressions 2024-09-12 13:55:07 +02:00
SiriusXT
b63b603d64 complete toc/highlightslist improvements 2024-09-12 19:34:23 +08:00
SiriusXT
31aa6feb0c Make toc/hightlightslist reopen after it's closed 2024-09-12 19:22:41 +08:00
SiriusXT
98c9e25124 Merge branch 'develop' into highlightslits 2024-09-12 09:37:01 +08:00
SiriusXT
bb97e1a661 Let toc and highlights_list display formulas 2024-09-12 09:36:08 +08:00
Elian Doran
a4341a5cac Merge pull request #392 from SiriusXT/Icon_improvements
Icon improvements
2024-09-11 23:56:10 +03:00
Elian Doran
fe844d4f8c client: Fix math not loading (closes #297) 2024-09-11 23:50:17 +03:00
SiriusXT
447cf60afb i18n Fallback to English for null characters 2024-09-11 15:17:21 +08:00
Elian Doran
7a11f9aaff Merge pull request #389 from SiriusXT/open_in_file_manager_of_data
Open in file manager of Trilium's data directory
2024-09-10 21:04:56 +03:00
Jin
527591f651 fix package-lock conflict 2024-09-10 00:48:52 +02:00
Jin
fca6f99870 fix checkbox problem 2024-09-10 00:45:00 +02:00
Jin
8c97f0bec7 fix some styles, add some cn translations 2024-09-10 00:45:00 +02:00
Jin
58ee801e57 other fixes 2024-09-10 00:45:00 +02:00
Jin
c1ce578018 fix all dialogs 2024-09-10 00:45:00 +02:00
Jin
464d8417f5 fix all dropdowns and tooltips, fix some translations 2024-09-10 00:45:00 +02:00
Jin
44bd008829 fix note revisions layout 2024-09-10 00:45:00 +02:00
Jin
f3b7261748 fix all launcher bar components 2024-09-10 00:45:00 +02:00
Jin
5d579fee68 Upgrade bootstrap from v4 to v5, fix global menu 2024-09-10 00:44:54 +02:00
Elian Doran
d7ab99013c client: Fix zoom interfering with mind map (closes #387) 2024-09-09 23:59:25 +03:00
Elian Doran
a3a5339048 client: Fix relative path for loading languages 2024-09-09 20:42:47 +03:00
Elian Doran
d95a23de28 Merge pull request #392 from SiriusXT/Icon_improvements
Icon improvements
2024-09-09 20:30:35 +03:00
SiriusXT
744d953822 icon improvement 2024-09-09 20:15:35 +08:00
SiriusXT
f8e5717b80 Merge branch 'develop' into revisions_number_limit 2024-09-09 14:52:31 +08:00
SiriusXT
3ad2d1a309 Merge branch 'develop' into open_in_file_manager_of_data 2024-09-09 14:50:01 +08:00
SiriusXT
7186222393 Merge branch 'develop' into Icon_improvements 2024-09-09 14:30:22 +08:00
SiriusXT
d66c07717c Merge branch 'develop' into Icon_improvements 2024-09-09 14:08:27 +08:00
Elian Doran
132dd7514a client: Update axios 1.7.2 to 1.7.7 2024-09-09 00:22:49 +03:00
Elian Doran
aca9d0f1ef server: Update ini 4.1.3 -> 5.0.0 2024-09-09 00:20:11 +03:00
Elian Doran
eabceae6f1 server: Update Marked 13.0.2 → 14.1.2 2024-09-09 00:18:15 +03:00
Elian Doran
3a5b05e5c6 client: Bump eslint to 9.10.0 2024-09-09 00:14:29 +03:00
Elian Doran
11e5f6a1c1 client: Update dayjs 1.11.12 -> 1.11.13 2024-09-09 00:10:52 +03:00
Elian Doran
bec9acdc3f client: Update jsdom 24.1.0 -> 25.0.0 2024-09-09 00:07:53 +03:00
Elian Doran
37079b7388 client: Update Mermaid to 11.1.1 2024-09-09 00:03:02 +03:00
Elian Doran
22b7bf826b electron: Bump electron-dl 2024-09-08 23:59:55 +03:00
Elian Doran
7cc71a4fdb electron: Bump electron-debug 2024-09-08 23:51:01 +03:00
Elian Doran
fb28b9d36d client: Improve max content widget fit on other languages 2024-09-08 22:20:38 +03:00
Elian Doran
720087f082 client: Translate open help page 2024-09-08 22:18:54 +03:00
Elian Doran
3bdb87d3aa client: Translate template switch 2024-09-08 22:14:51 +03:00
Elian Doran
e4066ba164 client: Translate shared switch 2024-09-08 22:11:52 +03:00
Elian Doran
a0fdaabb1f client: Translate protected switch 2024-09-08 22:04:22 +03:00
Elian Doran
38ac4318b9 client: Translate rest of note types 2024-09-08 21:54:30 +03:00
Elian Doran
e382a32ebd client: Translate note types 2024-09-08 21:48:24 +03:00
Elian Doran
8074245758 client: Translate shared info 2024-09-08 21:19:13 +03:00
Elian Doran
f26a04216a server: Translate share page 2024-09-08 20:36:25 +03:00
Elian Doran
e565d29490 server: Translate share 404 2024-09-08 20:25:38 +03:00
Elian Doran
947c50353d server: Translate here link in setup 2024-09-08 20:16:57 +03:00
Elian Doran
83851a61d5 i18n: Translate setup screen into Romanian 2024-09-08 20:02:29 +03:00
Elian Doran
893a1e3723 i18n: Translate setup screen 2024-09-08 20:00:55 +03:00
Elian Doran
6bf18b6837 i18n: Configure i18n-ally for .ejs templates 2024-09-08 19:27:19 +03:00
Elian Doran
94857d8802 server: Translate JavaScript required 2024-09-08 18:55:11 +03:00
Elian Doran
58d1e77ac1 server: Translate set password page 2024-09-08 18:28:29 +03:00
Elian Doran
1fbc65b007 electron: Fix crash on first start due to i18n 2024-09-08 18:23:06 +03:00
Elian Doran
c25c8c753d server: Translate login page 2024-09-08 18:12:16 +03:00
Elian Doran
317b7b4c59 server: Implement basic restore of language 2024-09-08 18:01:08 +03:00
Elian Doran
68042994e0 electron: Fix loading of i18n 2024-09-08 17:40:05 +03:00
Elian Doran
d00c028a0c client: Fix note actions not fitting on all languages 2024-09-08 16:38:12 +03:00
Elian Doran
a9e6c887f2 i18n: Configure extensions & nodemon 2024-09-08 16:04:14 +03:00
Elian Doran
69f9457bda i18n: Translate keyboard actions into Romanian 2024-09-08 16:04:00 +03:00
Elian Doran
0fed54f1c3 server: Translate keyboard actions 2024-09-08 15:33:06 +03:00
Elian Doran
b45fde2e5f server: Set up early initialization of i18n 2024-09-08 15:31:47 +03:00
Elian Doran
c1010a79f9 server: Initialize backend translations 2024-09-08 14:37:40 +03:00
Elian Doran
c33154d128 i18n: Translate tree context menu 2024-09-08 12:32:22 +03:00
Elian Doran
c0d613c46d i18n: Translate note map buttons 2024-09-08 11:59:24 +03:00
Elian Doran
4f7bbee769 i18n: Fix erase attachments button translation 2024-09-08 11:53:36 +03:00
Elian Doran
7a567583f2 i18n: Improve keybindings phrasing 2024-09-08 11:49:23 +03:00
Elian Doran
0ac1f071cd i18n: Tweak two Romanian translations 2024-09-08 11:48:01 +03:00
perf3ct
114e7809fe Fix CI/CD to use Matrix strategy
fix the digests missing

tweak dockerhub digests 1

Revert "tweak dockerhub digests 1"

This reverts commit 3542125d6035d2330fe1075682b046133568137d.

Revert "fix the digests missing"

This reverts commit 01954e5687549586233d73339b74e03e2182a339.

give this a shot then

add short sha

fix sha tag names
2024-09-07 22:34:57 -07:00
Elian Doran
4106cc61b5 release 0.90.6-beta 2024-09-07 21:36:34 +03:00
Elian Doran
7c0d6930fa build: Revert alpine build 2024-09-07 21:35:51 +03:00
SiriusXT
89e073bf28 bx-map-alt->bxl-graphql 2024-09-05 15:24:37 +08:00
SiriusXT
490ae1c9fd icon improvements 2024-09-05 15:09:31 +08:00
SiriusXT
2a27383682 revision snapshots number limit 2024-09-04 14:27:33 +00:00
SiriusXT
00f8a43b0f test 2024-09-04 12:10:13 +00:00
SiriusXT
759ac4a4c8 test 2024-09-04 11:52:05 +00:00
SiriusXT
e66b865cd6 open directory 2024-09-04 11:08:04 +00:00
SiriusXT
78bfc3341b Add versioningLimit label support 2024-09-04 09:04:40 +00:00
SiriusXT
898afb7ed7 Add revision number limit 2024-09-04 08:41:17 +00:00
SiriusXT
bc4444d132 open in file manager of Trilium's data directory 2024-09-03 10:37:28 +00:00
SiriusXT
56259616cb open in file manager of Trilium's data directory 2024-09-03 10:06:01 +00:00
Jin
a50e5cc733 fix typo 2024-08-10 22:52:41 +02:00
JYC333
b790dabc8c Create nighyly release action 2024-08-10 22:38:32 +02:00
164 changed files with 3716 additions and 2285 deletions

View File

@@ -1,153 +0,0 @@
on:
push:
branches:
- "develop"
- "feature/update**"
- "feature/server_esm**"
paths-ignore:
- "docs/**"
- "bin/**"
tags:
- "v*"
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/amd64
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Build and export to Docker
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run the container in the background
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v1
with:
container: trilium_local
wait-time: 50
require-status: running
require-healthy: true
build_docker:
name: Build Docker images
runs-on: ubuntu-latest
needs:
- test_docker
permissions:
contents: read
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- run: npm ci
- name: Run the TypeScript build
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Log in to the GHCR container registry
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
file: ./Dockerfile.alpine
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true

View File

@@ -9,22 +9,35 @@ on:
- "bin/**"
tags:
- "v*"
workflow_dispatch:
workflow_dispatch:
env:
GHCR_REGISTRY: ghcr.io
DOCKERHUB_REGISTRY: docker.io
IMAGE_NAME: ${{ github.repository }}
TEST_TAG: triliumnext/notes:test
PLATFORMS: linux/arm64,linux/arm/v7
IMAGE_NAME: ${{ github.repository_owner }}/notes
TEST_TAG: ${{ github.repository_owner }}/notes:test
permissions:
contents: read
packages: write
jobs:
test_docker:
name: Check Docker build
runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
- dockerfile: Dockerfile
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -47,14 +60,17 @@ jobs:
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
load: true
tags: ${{ env.TEST_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Run the container in the background
run: docker run -d --rm --name trilium_local ${{ env.TEST_TAG }}
- name: Validate container run output
run: |
CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }})
echo "Container ID: $CONTAINER_ID"
- name: Wait for the healthchecks to pass
uses: stringbean/docker-healthcheck-action@v1
with:
@@ -63,7 +79,13 @@ jobs:
require-status: running
require-healthy: true
build_docker:
# Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded
- name: Print entire log
if: always()
run: |
journalctl -u docker CONTAINER_NAME=trilium_local --no-pager
build:
name: Build Docker images
runs-on: ubuntu-latest
needs:
@@ -73,26 +95,48 @@ jobs:
packages: write
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Extract metadata (tags, labels) for GHCR image
id: ghcr-meta
strategy:
fail-fast: false
matrix:
include:
- dockerfile: Dockerfile.alpine
platform: linux/amd64
- dockerfile: Dockerfile
platform: linux/arm64
- dockerfile: Dockerfile
platform: linux/arm/v7
steps:
- name: Prepare
run: |
platform=${{ matrix.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Extract metadata (tags, labels) for DockerHub image
id: dh-meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
@@ -103,50 +147,103 @@ jobs:
run: npx tsc
- name: Create server-package.json
run: cat package.json | grep -v electron > server-package.json
- name: Log in to the GHCR container registry
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v3
- name: Build and push container image to GHCR
uses: docker/build-push-action@v6
id: ghcr-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.ghcr-meta.outputs.tags }}
labels: ${{ steps.ghcr-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to GHCR
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.ghcr-push.outputs.digest }}
push-to-registry: true
- name: Log in to the DockerHub container registry
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image to DockerHub
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
id: dh-push
with:
context: .
platforms: ${{ env.PLATFORMS }}
push: true
tags: ${{ steps.dh-meta.outputs.tags }}
labels: ${{ steps.dh-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate and push artifact attestation to DockerHub
uses: actions/attest-build-provenance@v1
file: ${{ matrix.dockerfile }}
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
subject-name: ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.dh-push.outputs.digest }}
push-to-registry: true
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
merge:
name: Merge manifest lists
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true
- name: Set IMAGE_NAME to lowercase
run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV
- name: Set TEST_TAG to lowercase
run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Login to GHCR
uses: docker/login-action@v2
with:
registry: ${{ env.GHCR_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKERHUB_REGISTRY }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
# Extract the branch or tag name from the ref
REF_NAME=$(echo "${GITHUB_REF}" | sed 's/refs\/heads\///' | sed 's/refs\/tags\///')
# Create and push the manifest list with both the branch/tag name and the commit SHA
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
-t ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${REF_NAME} \
$(printf '${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
docker buildx imagetools inspect ${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}

122
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,122 @@
name: Nightly Release
on:
# This can be used to automatically publish nightlies at UTC nighttime
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
# This can be used to allow manually triggering nightlies from the web interface
workflow_dispatch:
jobs:
nightly-electron:
name: Deploy nightly
strategy:
fail-fast: false
matrix:
arch: [x64, arm64]
os:
- name: macos
image: macos-latest
extension: dmg
- name: linux
image: ubuntu-latest
extension: deb
- name: windows
image: windows-latest
extension: exe
runs-on: ${{ matrix.os.image }}
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
- name: Set up Python for appdmg to be installed
if: ${{ matrix.os.name == 'macos' }}
run: brew install python-setuptools
- name: Install dependencies
run: npm ci
- name: Update build info
run: npm run update-build-info
- name: Run electron-forge
run: npm run make-electron -- --arch=${{ matrix.arch }}
- name: Prepare artifacts (Unix)
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find out/make -name '*.zip' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit)
cp "$file" "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Prepare artifacts (Windows)
if: runner.os == 'windows'
run: |
mkdir upload
$file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip"
$file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1
Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}"
- name: Publish artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.zip
overwrite: true
- name: Publish installer artifacts
uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes ${{ matrix.os.name }} ${{ matrix.arch }}
path: upload/*.${{ matrix.os.extension }}
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
# upload_url: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.zip # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
- name: Deploy installer release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
# upload_url: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
asset_path: upload/TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }} # path to archive to upload
asset_name: TriliumNextNotes-${{ matrix.os.name }}-${{ matrix.arch }}-nightly.${{ matrix.os.extension }} # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API
nightly-server:
name: Deploy server nightly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up node & dependencies
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run Linux server build (x86_64)
run: |
npm run update-build-info
./bin/build-server.sh
- name: Prepare artifacts
if: runner.os != 'windows'
run: |
mkdir -p upload
file=$(find dist -name '*.tar.xz' -print -quit)
cp "$file" "upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz"
- uses: actions/upload-artifact@v4
with:
name: TriliumNextNotes linux server x64
path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz
overwrite: true
- name: Deploy release
uses: WebFreak001/deploy-nightly@v3.1.0
with:
# upload_url: # find out this value by opening https://api.github.com/repos/<owner>/<repo>/releases in your browser and copy the full "upload_url" value including the {?name,label} part
# release_id: # same as above (id can just be taken out the upload_url, it's used to find old releases)
asset_path: upload/TriliumNextNotes-linux-x64-${{ github.ref_name }}.tar.xz # path to archive to upload
asset_name: TriliumNextNotes-linux-x64-nightly.zip # name to upload the release as, use $$ to insert date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/zip # required by GitHub API

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
save-prefix = ''

View File

@@ -3,6 +3,7 @@
languageIds:
- javascript
- typescript
- html
# An array of RegExes to find the key usage. **The key should be captured in the first match group**.
# You should unescape RegEx strings in order to fit in the YAML file
@@ -25,6 +26,7 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]"
refactorTemplates:
- t("$1")
- ${t("$1")}
- <%= t("$1") %>
# If set to true, only enables this custom framework (will disable all built-in frameworks)

View File

@@ -6,7 +6,8 @@
"i18n-ally.sourceLanguage": "en",
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [
"./src/public/translations"
"./src/public/translations",
"./translations"
],
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)以快速了解:

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes es una aplicación de toma de notas jerárquicas multi-plataforma y de código libre con un enfoque en la construcción de grandes bases de conocimiento personal.

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://triliumnext.github.io/Docs/Wiki/screenshot-tour)をご覧ください:

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
TriliumNext Notes is an open-source, cross-platform hierarchical note taking application with focus on building large personal knowledge bases.
@@ -82,13 +82,19 @@ You can also read [Patterns of personal knowledge base](https://triliumnext.gith
## 💻 Contribute
Clone locally and run
### Code
```shell
git clone https://github.com/TriliumNext/Notes.git
cd Notes
npm install
npm run start-server
```
### Documentation
Head on over to our [Docs repo](https://github.com/TriliumNext/Docs)
## 👏 Shoutouts
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team

View File

@@ -1,6 +1,6 @@
# TriliumNext Notes
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md) | [Spanish](https://github.com/TriliumNext/Notes/blob/master/README.es.md)
[English](./README.md) | [Chinese](./README-ZH_CN.md) | [Russian](./README.ru.md) | [Japanese](./README.ja.md) | [Italian](./README.it.md) | [Spanish](./README.es.md)
Trilium Notes это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://triliumnext.github.io/Docs/Wiki/screenshot-tour):

View File

@@ -22,7 +22,7 @@ rm -r $PKG_DIR/node/lib/node_modules/npm
rm -r $PKG_DIR/node/include/node
rm -r $PKG_DIR/node_modules/electron*
rm -r $PKG_DIR/electron.js
rm -r $PKG_DIR/electron*.js
printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh

View File

@@ -37,11 +37,11 @@ for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample
done
# Patch package.json main
sed -i 's/.\/dist\/electron.js/electron.js/g' "$DIR/package.json"
sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json"
script_dir=$(realpath $(dirname $0))
cp -R "$script_dir/../build/src" "$DIR"
cp "$script_dir/../build/electron.js" "$DIR"
cp "$script_dir/../build/electron-main.js" "$DIR"
# run in subshell (so we return to original dir)
(cd $DIR && npm install --omit=dev)

View File

@@ -42,9 +42,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@@ -570,7 +570,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

4
electron-main.ts Normal file
View File

@@ -0,0 +1,4 @@
import { initializeTranslations } from "./src/services/i18n.js";
await initializeTranslations();
await import("./electron.js")

View File

@@ -70,4 +70,4 @@ electron.app.on("will-quit", () => {
// this is to disable electron warning spam in the dev console (local development only)
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
await import('./src/www.js');
await import('./src/main.js');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,10 @@
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
"verbose": false,
"exec": "tsx",
"watch": ["src/"],
"watch": [
"src/",
"translations/"
],
"signal": "SIGTERM",
"env": {
"NODE_ENV": "development"

1727
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,9 @@
"name": "trilium",
"productName": "TriliumNext Notes",
"description": "Build your personal knowledge base with TriliumNext Notes",
"version": "0.90.5-beta",
"version": "0.90.6-beta",
"license": "AGPL-3.0-only",
"main": "./dist/electron.js",
"main": "./dist/electron-main.js",
"author": {
"name": "TriliumNext Notes Team",
"email": "contact@eliandoran.me",
@@ -12,7 +12,7 @@
},
"copyright": "",
"bin": {
"trilium": "src/www.js"
"trilium": "src/main.js"
},
"repository": {
"type": "git",
@@ -20,12 +20,12 @@
},
"type": "module",
"scripts": {
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts",
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts",
"start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/main.ts",
"qstart-server": "npm run switch-server && npm run start-server",
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .",
"start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .",
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
"qstart-electron": "npm run switch-electron && npm run start-electron",
"switch-server": "rimraf ./node_modules/better-sqlite3 && npm install",
@@ -34,7 +34,7 @@
"build-frontend-docs": "rimraf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "cross-env node --import ./loader-register.js node_modules/webpack/bin/webpack.js -c webpack.config.ts",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/.bin/jasmine",
"test-jasmine": "cross-env TRILIUM_DATA_DIR=./data-test tsx ./node_modules/jasmine/bin/jasmine.js",
"test-es6": "tsx -r esm spec-es6/attribute_parser.spec.ts",
"test": "npm run test-jasmine && npm run test-es6",
"start-electron-forge": "npm run prepare-dist && electron-forge start",
@@ -43,42 +43,41 @@
"prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts",
"update-build-info": "tsx bin/update-build-info.ts",
"errors": "tsc --watch --noEmit",
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts",
"integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts",
"generate-document": "cross-env nodemon src/tools/generate_document.ts 1000"
},
"dependencies": {
"@braintree/sanitize-url": "^7.1.0",
"@braintree/sanitize-url": "7.1.0",
"@electron/remote": "2.1.2",
"@excalidraw/excalidraw": "^0.17.6",
"@types/electron-squirrel-startup": "^1.0.2",
"archiver": "^7.0.1",
"async-mutex": "^0.5.0",
"autocomplete.js": "^0.38.1",
"axios": "^1.7.2",
"better-sqlite3": "^11.1.2",
"bootstrap": "^4.6.2",
"@excalidraw/excalidraw": "0.17.6",
"archiver": "7.0.1",
"async-mutex": "0.5.0",
"autocomplete.js": "0.38.1",
"axios": "1.7.7",
"better-sqlite3": "11.3.0",
"bootstrap": "5.3.3",
"boxicons": "2.1.4",
"chokidar": "3.6.0",
"cls-hooked": "4.2.2",
"codemirror": "^5.65.17",
"codemirror": "5.65.17",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"csurf": "1.11.0",
"dayjs": "^1.11.12",
"dayjs": "1.11.13",
"dayjs-plugin-utc": "0.1.2",
"debounce": "^2.1.0",
"ejs": "^3.1.10",
"electron-debug": "3.2.0",
"electron-dl": "3.5.2",
"electron-squirrel-startup": "^1.0.0",
"debounce": "2.1.1",
"ejs": "3.1.10",
"electron-debug": "4.0.1",
"electron-dl": "4.0.0",
"electron-squirrel-startup": "1.0.1",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"eslint": "^9.9.0",
"express": "^4.19.2",
"eslint": "9.10.0",
"express": "4.21.0",
"express-partial-content": "1.0.2",
"express-rate-limit": "^7.3.1",
"express-rate-limit": "7.4.0",
"express-session": "1.18.0",
"force-graph": "1.43.5",
"fs-extra": "11.2.0",
@@ -86,112 +85,114 @@
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "7.0.2",
"https-proxy-agent": "^7.0.5",
"i18next": "^23.14.0",
"i18next-http-backend": "^2.6.1",
"https-proxy-agent": "7.0.5",
"i18next": "23.15.1",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.1",
"image-type": "4.1.0",
"ini": "^4.1.3",
"ini": "5.0.0",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.12",
"joplin-turndown-plugin-gfm": "1.0.12",
"jquery": "3.7.1",
"jquery-hotkeys": "0.2.2",
"jquery.fancytree": "^2.38.3",
"jsdom": "^24.1.0",
"jsplumb": "^2.15.6",
"katex": "^0.16.11",
"knockout": "^3.5.1",
"mark.js": "^8.11.1",
"marked": "^13.0.2",
"mermaid": "^10.9.1",
"jquery.fancytree": "2.38.3",
"jsdom": "25.0.0",
"jsplumb": "2.15.6",
"katex": "0.16.11",
"knockout": "3.5.1",
"mark.js": "8.11.1",
"marked": "14.1.2",
"mermaid": "11.2.0",
"mime-types": "2.1.35",
"mind-elixir": "^4.0.5",
"mind-elixir": "4.1.1",
"multer": "1.4.5-lts.1",
"node-abi": "^3.65.0",
"node-abi": "3.67.0",
"normalize-strings": "1.1.1",
"normalize.css": "^8.0.1",
"normalize.css": "8.0.1",
"panzoom": "9.4.3",
"print-this": "2.0.0",
"rand-token": "1.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"request": "2.88.2",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "^2.13.0",
"sax": "^1.4.1",
"semver": "^7.6.3",
"sanitize-html": "2.13.0",
"sax": "1.4.1",
"semver": "7.6.3",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"source-map-support": "^0.5.21",
"source-map-support": "0.5.21",
"split.js": "1.6.5",
"stream-throttle": "0.1.3",
"striptags": "3.2.0",
"tmp": "0.2.3",
"tree-kill": "1.2.2",
"turndown": "^7.2.0",
"turndown": "7.2.0",
"unescape": "1.0.1",
"vanilla-js-wheel-zoom": "^9.0.2",
"ws": "^8.18.0",
"vanilla-js-wheel-zoom": "9.0.2",
"ws": "8.18.0",
"xml2js": "0.6.2",
"yauzl": "^3.1.3"
"yauzl": "3.1.3"
},
"devDependencies": {
"@electron-forge/cli": "^7.4.0",
"@electron-forge/maker-deb": "^7.4.0",
"@electron-forge/maker-dmg": "^7.4.0",
"@electron-forge/maker-squirrel": "^7.4.0",
"@electron-forge/maker-zip": "^7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
"@playwright/test": "^1.46.0",
"@types/archiver": "^6.0.2",
"@types/better-sqlite3": "^7.6.9",
"@types/cls-hooked": "^4.3.8",
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.7",
"@types/csurf": "^1.11.5",
"@types/debounce": "^1.2.4",
"@types/ejs": "^3.1.5",
"@types/escape-html": "^1.0.4",
"@types/express": "^4.17.21",
"@types/express-session": "^1.18.0",
"@types/html": "^1.0.4",
"@types/ini": "^4.1.0",
"@types/jasmine": "^5.1.4",
"@types/jsdom": "^21.1.6",
"@types/mime-types": "^2.1.4",
"@types/multer": "^1.4.12",
"@types/node": "^22.5.2",
"@types/safe-compare": "^1.1.2",
"@types/sanitize-html": "^2.13.0",
"@types/sax": "^1.2.7",
"@types/semver": "^7.5.8",
"@types/serve-favicon": "^2.5.7",
"@types/session-file-store": "^1.2.5",
"@types/source-map-support": "^0.5.10",
"@types/stream-throttle": "^0.1.4",
"@types/tmp": "^0.2.6",
"@types/turndown": "^5.0.5",
"@types/ws": "^8.5.12",
"@types/xml2js": "^0.4.14",
"@electron-forge/cli": "7.4.0",
"@electron-forge/maker-deb": "7.4.0",
"@electron-forge/maker-dmg": "7.4.0",
"@electron-forge/maker-squirrel": "7.4.0",
"@electron-forge/maker-zip": "7.4.0",
"@electron-forge/plugin-auto-unpack-natives": "7.4.0",
"@playwright/test": "1.47.1",
"@types/archiver": "6.0.2",
"@types/better-sqlite3": "7.6.11",
"@types/cls-hooked": "4.3.8",
"@types/compression": "1.7.5",
"@types/cookie-parser": "1.4.7",
"@types/csurf": "1.11.5",
"@types/debounce": "1.2.4",
"@types/ejs": "3.1.5",
"@types/electron-squirrel-startup": "1.0.2",
"@types/escape-html": "1.0.4",
"@types/express": "4.17.21",
"@types/express-session": "1.18.0",
"@types/html": "1.0.4",
"@types/ini": "4.1.1",
"@types/jasmine": "5.1.4",
"@types/jsdom": "21.1.7",
"@types/mime-types": "2.1.4",
"@types/multer": "1.4.12",
"@types/node": "22.5.4",
"@types/safe-compare": "1.1.2",
"@types/sanitize-html": "2.13.0",
"@types/sax": "1.2.7",
"@types/semver": "7.5.8",
"@types/serve-favicon": "2.5.7",
"@types/session-file-store": "1.2.5",
"@types/source-map-support": "0.5.10",
"@types/stream-throttle": "0.1.4",
"@types/tmp": "0.2.6",
"@types/turndown": "5.0.5",
"@types/ws": "8.5.12",
"@types/xml2js": "0.4.14",
"cross-env": "7.0.3",
"electron": "^31.2.1",
"electron": "31.3.1",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
"esm": "3.2.25",
"iconsur": "^1.7.0",
"jasmine": "5.1.0",
"jsdoc": "^4.0.3",
"iconsur": "1.7.0",
"jasmine": "5.3.0",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "^3.1.4",
"nodemon": "3.1.4",
"rcedit": "4.0.1",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"tslib": "^2.7.0",
"tsx": "^4.19.0",
"typescript": "^5.5.4",
"webpack": "^5.93.0",
"rimraf": "6.0.1",
"ts-node": "10.9.2",
"tslib": "2.7.0",
"tsx": "4.19.1",
"typescript": "5.6.2",
"webpack": "5.94.0",
"webpack-cli": "5.1.4"
}
}

View File

@@ -14,6 +14,7 @@ import custom from "./routes/custom.js";
import error_handlers from "./routes/error_handlers.js";
import { startScheduledCleanup } from "./services/erase.js";
import sql_init from "./services/sql_init.js";
import { t } from "i18next";
await import('./services/handlers.js');
await import('./becca/becca_loader.js');
@@ -29,6 +30,11 @@ sql_init.initializeDb();
app.set('views', path.join(scriptDir, 'views'));
app.set('view engine', 'ejs');
app.use((req, res, next) => {
res.locals.t = t;
return next();
});
if (!utils.isElectron()) {
app.use(compression()); // HTTP compression
}

View File

@@ -13,11 +13,13 @@ import cls from "../services/cls.js";
import entityConstructor from "../becca/entity_constructor.js";
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows.js';
import AbstractBeccaEntity from "./entities/abstract_becca_entity.js";
import options_init from "../services/options_init.js";
import ws from "../services/ws.js";
const beccaLoaded = new Promise<void>(async (res, rej) => {
const sqlInit = (await import("../services/sql_init.js")).default;
// We have to import async since options init requires keyboard actions which require translations.
const options_init = (await import("../services/options_init.js")).default;
sqlInit.dbReady.then(() => {
cls.init(() => {
load();

View File

@@ -3,6 +3,8 @@
import protectedSessionService from "../../services/protected_session.js";
import log from "../../services/log.js";
import sql from "../../services/sql.js";
import optionService from "../../services/options.js";
import eraseService from "../../services/erase.js";
import utils from "../../services/utils.js";
import dateUtils from "../../services/date_utils.js";
import AbstractBeccaEntity from "./abstract_becca_entity.js";
@@ -68,7 +70,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
/** set during the deletion operation, before it is completed (removed from becca completely). */
isBeingDeleted!: boolean;
isDecrypted!: boolean;
ownedAttributes!: BAttribute[];
parentBranches!: BBranch[];
parents!: BNote[];
@@ -455,8 +457,8 @@ class BNote extends AbstractBeccaEntity<BNote> {
return this.getAttributes().find(
attr => attr.name.toLowerCase() === name
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
&& (!value || attr.value.toLowerCase() === value)
&& attr.type === type);
}
getRelationTarget(name: string) {
@@ -1107,7 +1109,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
}
getRevisions(): BRevision[] {
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
return sql.getRows<RevisionRow>("SELECT * FROM revisions WHERE noteId = ? ORDER BY revisions.utcDateCreated ASC", [this.noteId])
.map(row => new BRevision(row));
}
@@ -1612,10 +1614,31 @@ class BNote extends AbstractBeccaEntity<BNote> {
revision.setContent(noteContent);
this.eraseExcessRevisionSnapshots()
return revision;
});
}
// Limit the number of Snapshots to revisionSnapshotNumberLimit
// Delete older Snapshots that exceed the limit
eraseExcessRevisionSnapshots() {
// lable has a higher priority
let revisionSnapshotNumberLimit = parseInt(this.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionSnapshotNumberLimit)) {
revisionSnapshotNumberLimit = parseInt(optionService.getOption('revisionSnapshotNumberLimit'));
}
if (revisionSnapshotNumberLimit >= 0) {
const revisions = this.getRevisions();
if (revisions.length - revisionSnapshotNumberLimit > 0) {
const revisionIds = revisions
.slice(0, revisions.length - revisionSnapshotNumberLimit)
.map(revision => revision.revisionId)
.filter((id): id is string => id !== undefined);
eraseService.eraseRevisions(revisionIds);
}
}
}
/**
* @param matchBy - choose by which property we detect if to update an existing attachment.
* Supported values are either 'attachmentId' (default) or 'title'

13
src/main.ts Normal file
View File

@@ -0,0 +1,13 @@
/*
* Make sure not to import any modules that depend on localized messages via i18next here, as the initializations
* are loaded later and will result in an empty string.
*/
import { initializeTranslations } from "./services/i18n.js";
async function startApplication() {
await import("./www.js");
}
await initializeTranslations();
await startApplication();

View File

@@ -14,9 +14,9 @@ const NOTE_TYPE_ICONS = {
"code": "bx bx-code",
"render": "bx bx-extension",
"search": "bx bx-file-find",
"relationMap": "bx bx-map-alt",
"relationMap": "bx bxs-network-chart",
"book": "bx bx-book",
"noteMap": "bx bx-map-alt",
"noteMap": "bx bxs-network-chart",
"mermaid": "bx bx-selection",
"canvas": "bx bx-pen",
"webView": "bx bx-globe-alt",
@@ -543,7 +543,7 @@ class FNote {
return workspaceIconClass;
}
else if (this.noteId === 'root') {
return "bx bx-chevrons-right";
return "bx bx-home-alt-2";
}
if (this.noteId === '_share') {
return "bx bx-share-alt";

View File

@@ -38,6 +38,8 @@ import SimilarNotesWidget from "../widgets/ribbon_widgets/similar_notes.js";
import RightPaneContainer from "../widgets/containers/right_pane_container.js";
import EditButton from "../widgets/buttons/edit_button.js";
import EditedNotesWidget from "../widgets/ribbon_widgets/edited_notes.js";
import ShowTocWidgetButton from "../widgets/buttons/show_toc_widget_button.js";
import ShowHighlightsListWidgetButton from "../widgets/buttons/show_highlights_list_widget_button.js";
import MermaidWidget from "../widgets/mermaid.js";
import NoteWrapperWidget from "../widgets/note_wrapper.js";
import BacklinksWidget from "../widgets/floating_buttons/zpetne_odkazy.js";
@@ -160,6 +162,8 @@ export default class DesktopLayout {
.child(new WatchedFileUpdateStatusWidget())
.child(new FloatingButtons()
.child(new EditButton())
.child(new ShowTocWidgetButton())
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new CopyImageReferenceButton())

View File

@@ -22,15 +22,31 @@ function setupContextMenu($image) {
command: "copyImageReferenceToClipboard",
uiIcon: "bx bx-empty"
},
{title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty"},
{ title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" },
],
selectMenuItemHandler: ({command}) => {
selectMenuItemHandler: async ({ command }) => {
if (command === 'copyImageReferenceToClipboard') {
imageService.copyImageReferenceToClipboard($image);
} else if (command === 'copyImageToClipboard') {
const webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents();
utils.dynamicRequire('electron');
webContents.copyImageAt(e.pageX, e.pageY);
try {
const nativeImage = utils.dynamicRequire('electron').nativeImage;
const clipboard = utils.dynamicRequire('electron').clipboard;
const response = await fetch(
$image.attr('src')
);
const blob = await response.blob();
clipboard.writeImage(
nativeImage.createFromBuffer(
Buffer.from(
await blob.arrayBuffer()
)
)
);
} catch (error) {
console.error('Failed to copy image to clipboard:', error);
}
} else {
throw new Error(`Unrecognized command '${command}'`);
}
@@ -41,4 +57,4 @@ function setupContextMenu($image) {
export default {
setupContextMenu
};
};

View File

@@ -8,6 +8,7 @@ import noteTypesService from "../services/note_types.js";
import server from "../services/server.js";
import toastService from "../services/toast.js";
import dialogService from "../services/dialog.js";
import { t } from "../services/i18n.js";
export default class TreeContextMenu {
/**
@@ -48,55 +49,55 @@ export default class TreeContextMenu {
const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch;
return [
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
{ title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "bx bx-plus",
{ title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-empty", 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.insert-note-after")} <kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus",
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions },
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
{ title: `${t("tree-context-menu.insert-child-note")} <kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus",
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
enabled: notSearch && noSelectedNotes && notOptions },
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
{ title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash",
enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions },
{ title: "----" },
{ title: 'Search in subtree <kbd data-command="searchInSubtree"></kbd>', command: "searchInSubtree", uiIcon: "bx bx-search",
{ title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search",
enabled: notSearch && noSelectedNotes },
isHoisted ? null : { title: 'Hoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
!isHoisted || !isNotRoot ? null : { title: 'Unhoist note <kbd data-command="toggleNoteHoisting"></kbd>', command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" },
{ title: 'Edit branch prefix <kbd data-command="editBranchPrefix"></kbd>', command: "editBranchPrefix", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-empty",
enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions },
{ title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [
{ title: 'Expand subtree <kbd data-command="expandSubtree"></kbd>', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
{ title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
{ title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [
{ title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions },
{ title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions },
{ title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
] },
{ title: "----" },
{ title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: "Unprotect subtree", command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
{ title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes },
{ title: "----" },
{ title: 'Copy / clone <kbd data-command="copyNotesToClipboard"></kbd>', command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
{ title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy",
enabled: isNotRoot && !isHoisted },
{ title: 'Clone to ... <kbd data-command="cloneNotesTo"></kbd>', command: "cloneNotesTo", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-empty",
enabled: isNotRoot && !isHoisted },
{ title: 'Cut <kbd data-command="cutNotesToClipboard"></kbd>', command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
{ title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: 'Move to ... <kbd data-command="moveNotesTo"></kbd>', command: "moveNotesTo", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-empty",
enabled: isNotRoot && !isHoisted && parentNotSearch },
{ title: 'Paste into <kbd data-command="pasteNotesFromClipboard"></kbd>', command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
{ title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes },
{ title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
{ title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste",
enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes },
{ title: `Duplicate subtree <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
{ title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty",
enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions },
{ title: "----" },
{ title: "Export", command: "exportNote", uiIcon: "bx bx-empty",
{ title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty",
enabled: notSearch && noSelectedNotes && notOptions },
{ title: "Import into note", command: "importIntoNote", uiIcon: "bx bx-empty",
{ title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty",
enabled: notSearch && noSelectedNotes && notOptions },
{ title: "Apply bulk actions", command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
{ title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus",
enabled: true }
].filter(row => row !== null);
}

View File

@@ -12,8 +12,9 @@ export async function initLocale() {
lng: locale,
fallbackLng: "en",
backend: {
loadPath: `/${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
}
loadPath: `${window.glob.assetPath}/translations/{{lng}}/{{ns}}.json`
},
returnEmptyString: false
});
}

View File

@@ -105,28 +105,23 @@ function initNoteAutocomplete($el, options) {
$el.addClass("note-autocomplete-input");
const $clearTextButton = $("<a>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $clearTextButton = $("<button>")
.addClass("input-group-text input-clearer-button bx bxs-tag-x")
.prop("title", "Clear text field");
const $showRecentNotesButton = $("<a>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $showRecentNotesButton = $("<button>")
.addClass("input-group-text show-recent-notes-button bx bx-time")
.prop("title", "Show recent notes");
const $goToSelectedNoteButton = $("<a>")
const $goToSelectedNoteButton = $("<button>")
.addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
const $sideButtons = $("<div>")
.addClass("input-group-append")
.append($clearTextButton)
.append($showRecentNotesButton);
$el.after($clearTextButton).after($showRecentNotesButton);
if (!options.hideGoToSelectedNoteButton) {
$sideButtons.append($goToSelectedNoteButton);
$el.after($goToSelectedNoteButton);
}
$el.after($sideButtons);
$clearTextButton.on('click', () => clearText($el));
$showRecentNotesButton.on('click', e => {
@@ -180,13 +175,13 @@ function initNoteAutocomplete($el, options) {
}
if (suggestion.action === 'create-note') {
const {success, noteType, templateNoteId} = await noteCreateService.chooseNoteType();
const { success, noteType, templateNoteId } = await noteCreateService.chooseNoteType();
if (!success) {
return;
}
const {note} = await noteCreateService.createNote(suggestion.parentNoteId, {
const { note } = await noteCreateService.createNote(suggestion.parentNoteId, {
title: suggestion.noteTitle,
activate: false,
type: noteType,

View File

@@ -1,19 +1,20 @@
import server from "./server.js";
import froca from "./froca.js";
import { t } from "./i18n.js";
async function getNoteTypeItems(command) {
const items = [
{ title: "Text", command: command, type: "text", uiIcon: "bx bx-note" },
{ title: "Code", command: command, type: "code", uiIcon: "bx bx-code" },
{ title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" },
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
{ title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
{ title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" },
{ title: "Book", command: command, type: "book", uiIcon: "bx bx-book" },
{ title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
{ title: "Mind Map", command, type: "mindMap", uiIcon: "bx bx-sitemap" }
{ title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" },
{ title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" },
{ title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" },
{ title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
{ title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" },
{ title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" },
{ title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: t("note_types.canvas"), command: command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: t("note_types.web-view"), command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }
];
const templateNoteIds = await server.get("search-templates");

View File

@@ -166,6 +166,23 @@ function getHost() {
return `${url.protocol}//${url.hostname}:${url.port}`;
}
async function openDirectory(directory) {
try {
if (utils.isElectron()) {
const electron = utils.dynamicRequire('electron');
const res = await electron.shell.openPath(directory);
if (res) {
console.error('Failed to open directory:', res);
}
} else {
console.error('Not running in an Electron environment.');
}
} catch (err) {
// Handle file system errors (e.g. path does not exist or is inaccessible)
console.error('Error:', err.message);
}
}
export default {
download,
downloadFileNote,
@@ -176,4 +193,5 @@ export default {
openAttachmentExternally,
openNoteCustom,
openAttachmentCustom,
openDirectory
}

View File

@@ -2,15 +2,18 @@ import ws from "./ws.js";
import utils from "./utils.js";
function toast(options) {
const $toast = $(`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto"><span class="bx bx-${options.icon}"></span> <span class="toast-title"></span></strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body"></div>
</div>`);
const $toast = $(
`<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">
<span class="bx bx-${options.icon}"></span>
<span class="toast-title"></span>
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body"></div>
</div>`
);
$toast.find('.toast-title').text(options.title);
$toast.find('.toast-body').text(options.message);

View File

@@ -201,7 +201,7 @@ function getMimeTypeClass(mime) {
function closeActiveDialog() {
if (glob.activeDialog) {
glob.activeDialog.modal('hide');
bootstrap.Modal.getOrCreateInstance(glob.activeDialog).hide();
glob.activeDialog = null;
}
}
@@ -245,8 +245,7 @@ async function openDialog($dialog, closeActDialog = true) {
}
saveFocusedElement();
$dialog.modal();
bootstrap.Modal.getOrCreateInstance($dialog).show();
$dialog.on('hidden.bs.modal', () => {
$(".aa-input").autocomplete("close");

View File

@@ -97,7 +97,7 @@ const TPL = `
<tr class="attr-row-promoted"
title="${t('attribute_detail.promoted_title')}">
<th>${t('attribute_detail.promoted')}</th>
<td><input type="checkbox" class="attr-input-promoted form-control form-control-sm" /></td>
<td><input type="checkbox" class="attr-input-promoted form-check" /></td>
</tr>
<tr class="attr-row-promoted-alias">
<th title="${t('attribute_detail.promoted_alias_title')}">${t('attribute_detail.promoted_alias')}</th>
@@ -134,9 +134,7 @@ const TPL = `
<td>
<div class="input-group">
<input type="number" class="form-control attr-input-number-precision" style="text-align: right">
<div class="input-group-append">
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
<span class="input-group-text">${t('attribute_detail.digits')}</span>
</div>
</td>
</tr>
@@ -150,7 +148,7 @@ const TPL = `
</tr>
<tr title="${t('attribute_detail.inheritable_title')}">
<th>${t('attribute_detail.inheritable')}</th>
<td><input type="checkbox" class="attr-input-inheritable form-control form-control-sm" /></td>
<td><input type="checkbox" class="attr-input-inheritable form-check" /></td>
</tr>
</table>
@@ -349,7 +347,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, {allowCreatingNotes: true})
noteAutocompleteService.initNoteAutocomplete(this.$inputTargetNote, { allowCreatingNotes: true })
.on('autocomplete:noteselected', (event, suggestion, dataset) => {
if (!suggestion.notePath) {
return false;
@@ -403,7 +401,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
});
}
async showAttributeDetail({allAttributes, attribute, isOwned, x, y, focus}) {
async showAttributeDetail({ allAttributes, attribute, isOwned, x, y, focus }) {
if (!attribute) {
this.hide();
@@ -545,7 +543,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
}
return {left, right};
return { left, right };
}
async saveAndClose() {
@@ -589,19 +587,19 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
async updateRelatedNotes() {
let {results, count} = await server.post('search-related', this.attribute);
let { results, count } = await server.post('search-related', this.attribute);
for (const res of results) {
res.noteId = res.notePathArray[res.notePathArray.length - 1];
}
results = results.filter(({noteId}) => noteId !== this.noteId);
results = results.filter(({ noteId }) => noteId !== this.noteId);
if (results.length === 0) {
this.$relatedNotesContainer.hide();
} else {
this.$relatedNotesContainer.show();
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", {attributeType: this.attribute.type, attributeName: this.attribute.name}));
this.$relatedNotesTitle.text(t("attribute_detail.other_notes_with_name", { attributeType: this.attribute.type, attributeName: this.attribute.name }));
this.$relatedNotesList.empty();
@@ -611,7 +609,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
for (const note of displayedNotes) {
const notePath = note.getBestNotePathString(hoistedNoteId);
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
const $noteLink = await linkService.createLink(notePath, { showNotePath: true });
this.$relatedNotesList.append(
$("<li>").append($noteLink)
@@ -619,7 +617,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
}
if (results.length > DISPLAYED_NOTES) {
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", {count: count - DISPLAYED_NOTES}));
this.$relatedNotesMoreNotes.show().text(t("attribute_detail.and_more", { count: count - DISPLAYED_NOTES }));
} else {
this.$relatedNotesMoreNotes.hide();
}

View File

@@ -175,7 +175,7 @@ const editorConfig = {
toolbar: {
items: []
},
placeholder: "Type the labels and relations here",
placeholder: t("attribute_editor.placeholder"),
mention: mentionSetup
};

View File

@@ -15,7 +15,7 @@ const TPL = `
<td class="button-column">
<div style="display: flex">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('execute_script.help_text')}

View File

@@ -21,7 +21,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("add_label.help_text")}</p>
@@ -30,7 +30,7 @@ const TPL = `
<li>${t("add_label.help_text_item2")}</li>
</ul>
<p>${t("add_label.help_text_note")}</p>
${t("add_label.help_text_note")}
</div>
</div>

View File

@@ -21,11 +21,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("update_label_value.help_text")}</p>
<p>${t("update_label_value.help_text_note")}</p>
${t("update_label_value.help_text_note")}
</div>
</div>

View File

@@ -10,13 +10,13 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("delete_note.delete_matched_notes_description")}</p>
<p>${t("delete_note.undelete_notes_instruction")}</p>
<p>${t("delete_note.erase_notes_instruction")}</p>
${t("delete_note.erase_notes_instruction")}
</div>
</div>

View File

@@ -9,7 +9,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
${t('delete_revisions.all_past_note_revisions')}
</div>

View File

@@ -18,11 +18,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('move_note.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<li>${t('move_note.move_note_new_parent')}</li>
<li>${t('move_note.clone_note_new_parent')}</li>
<li>${t('move_note.nothing_will_happen')}</li>

View File

@@ -16,7 +16,7 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('rename_note.evaluated_as_js_string')}</p>

View File

@@ -25,9 +25,9 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('add_relation.create_relation_on_all_matched_notes')}</p>
${t('add_relation.create_relation_on_all_matched_notes')}
</div>
</div>

View File

@@ -25,11 +25,11 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t('update_relation_target.on_all_matched_notes')}:</p>
<ul>
<ul style="margin-bottom: 0;">
<li>${t('update_relation_target.create_given_relation')}</li>
<li>${t('update_relation_target.change_target_note')}</li>
</ul>

View File

@@ -1,7 +1,7 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
const TPL = `<button class="button-widget bx"
data-toggle="tooltip"
data-bs-toggle="tooltip"
title=""></button>`;
export default class AbstractButtonWidget extends NoteContextAwareWidget {
@@ -22,10 +22,13 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.tooltip = new bootstrap.Tooltip(this.$widget, {
html: true, title: () => this.getTitle(), trigger: 'hover'
})
if (this.settings.onContextMenu) {
this.$widget.on("contextmenu", e => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.settings.onContextMenu(e);
@@ -35,12 +38,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget {
this.$widget.attr("data-placement", this.settings.titlePlacement);
this.$widget.tooltip({
html: true,
title: () => this.getTitle(),
trigger: "hover"
});
super.doRender();
}

View File

@@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"
style="position: relative; top: 3px;"></button>
@@ -61,7 +61,8 @@ export default class AttachmentActionsWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
this.$uploadNewRevisionInput.on('change', async () => {
@@ -84,7 +85,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
.addClass("disabled")
.append($('<span class="disabled-tooltip"> (?)</span>')
.attr("title", t('attachments_actions.open_externally_detail_page'))
);
);
if (isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
@@ -94,7 +95,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
);
}
}
if (!isElectron){
if (!isElectron) {
const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']");
$openAttachmentCustomButton
.addClass("disabled")
@@ -138,7 +139,7 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
const {note: newNote} = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
const { note: newNote } = await server.post(`attachments/${this.attachmentId}/convert-to-note`)
toastService.showMessage(t('attachments_actions.convert_success', { title: this.attachment.title }));
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
@@ -155,6 +156,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
return;
}
await server.put(`attachments/${this.attachmentId}/rename`, {title: attachmentTitle});
await server.put(`attachments/${this.attachmentId}/rename`, { title: attachmentTitle });
}
}

View File

@@ -74,12 +74,12 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
doRender() {
super.doRender();
this.$month = this.$dropdownContent.find('[data-calendar-area="month"]');
this.$weekHeader = this.$dropdownContent.find(".calendar-week");
this.manageFirstDayOfWeek();
// Month navigation
this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]');
this.$monthSelect.on("input", (e) => {
@@ -88,10 +88,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
});
this.$next = this.$dropdownContent.find('[data-calendar-toggle="next"]');
this.$next.on('click', () => {
this.date.setMonth(this.date.getMonth() + 1);
this.date.setMonth(this.date.getMonth() + 1);
this.createMonth();
});
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous = this.$dropdownContent.find('[data-calendar-toggle="previous"]');
this.$previous.on('click', e => {
this.date.setMonth(this.date.getMonth() - 1);
this.createMonth();
@@ -108,7 +108,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.date.setFullYear(this.date.getFullYear() + 1);
this.createMonth();
});
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]');
this.$previousYear.on('click', e => {
this.date.setFullYear(this.date.getFullYear() - 1);
this.createMonth();
@@ -123,11 +123,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (note) {
appContext.tabManager.getActiveContext().setNote(note.noteId);
this.hideDropdown();
this.dropdown.hide();
}
else {
toastService.showError(t("calendar.cannot_find_day_note"));
}
ev.stopPropagation();
});
// Prevent dismissing the calendar popup by clicking on an empty space inside it.
@@ -138,9 +140,9 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.firstDayOfWeek = options.getInt("firstDayOfWeek");
// Generate the list of days of the week taking into consideration the user's selected first day of week.
let localeDaysOfWeek = [ ...DAYS_OF_WEEK ];
let localeDaysOfWeek = [...DAYS_OF_WEEK];
const daysToBeAddedAtEnd = localeDaysOfWeek.splice(0, this.firstDayOfWeek);
localeDaysOfWeek = [ ...localeDaysOfWeek, ...daysToBeAddedAtEnd ];
localeDaysOfWeek = [...localeDaysOfWeek, ...daysToBeAddedAtEnd];
this.$weekHeader.html(localeDaysOfWeek.map((el) => `<span>${el}</span>`));
}
@@ -184,7 +186,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
if (dateNoteId) {
$newDay.addClass('calendar-date-exists');
$newDay.attr("href", `#root/${dateNoteId}`);
$newDay.attr("data-href", `#root/${dateNoteId}`);
}
if (this.isEqual(this.date, this.activeDate)) {
@@ -236,11 +238,11 @@ export default class CalendarWidget extends RightDropdownButtonWidget {
this.$yearSelect.val(this.date.getFullYear());
}
async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (!loadResults.getOptionNames().includes("firstDayOfWeek")) {
return;
}
this.manageFirstDayOfWeek();
this.createMonth();
}

View File

@@ -11,7 +11,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget {
if (this.settings.command) {
this.$widget.on("click", () => {
this.$widget.tooltip("hide");
this.tooltip.hide();
this.triggerCommand(this._command);
});

View File

@@ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js";
import options from "../../services/options.js";
const TPL = `
<div class="dropdown global-menu dropright">
<div class="dropdown global-menu dropend">
<style>
.global-menu {
width: 53px;
@@ -70,6 +70,7 @@ const TPL = `
background-color: var(--button-background-color);
padding: 3px;
margin-left: 3px;
text-decoration: none;
}
.global-menu .zoom-buttons a:hover {
@@ -102,10 +103,9 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="icon-action global-menu-button" title="${t('global_menu.menu')}">
<svg viewBox="0 0 256 256">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action global-menu-button">
<svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}">
<g>
<path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/>
<path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/>
@@ -126,7 +126,7 @@ const TPL = `
<ul class="dropdown-menu dropdown-menu-right">
<li class="dropdown-item" data-trigger-command="showOptions">
<span class="bx bx-slider"></span>
<span class="bx bx-cog"></span>
${t('global_menu.options')}
</li>
@@ -177,7 +177,7 @@ const TPL = `
<li class="dropdown-item dropdown-submenu">
<span class="dropdown-toggle">
<span class="bx bx-empty"></span>
<span class="bx bx-chip"></span>
${t('global_menu.advanced')}
</span>
@@ -195,43 +195,43 @@ const TPL = `
</li>
<li class="dropdown-item" data-trigger-command="showSQLConsoleHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-data"></span>
${t('global_menu.open_sql_console_history')}
</li>
<li class="dropdown-item" data-trigger-command="showSearchHistory">
<span class="bx bx-empty"></span>
<span class="bx bx-search-alt"></span>
${t('global_menu.open_search_history')}
</li>
<li class="dropdown-item" data-trigger-command="showBackendLog">
<span class="bx bx-empty"></span>
<span class="bx bx-detail"></span>
${t('global_menu.show_backend_log')}
<kbd data-command="showBackendLog"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="reloadFrontendApp"
title="${t('global_menu.reload_hint')}">
<span class="bx bx-empty"></span>
<span class="bx bx-refresh"></span>
${t('global_menu.reload_frontend')}
<kbd data-command="reloadFrontendApp"></kbd>
</li>
<li class="dropdown-item" data-trigger-command="showHiddenSubtree">
<span class="bx bx-empty"></span>
<span class="bx bx-hide"></span>
${t('global_menu.show_hidden_subtree')}
</li>
</ul>
</li>
<li class="dropdown-item show-help-button" data-trigger-command="showHelp">
<span class="bx bx-info-circle"></span>
<span class="bx bx-help-circle"></span>
${t('global_menu.show_help')}
<kbd data-command="showHelp"></kbd>
</li>
<li class="dropdown-item show-about-dialog-button">
<span class="bx bx-empty"></span>
<span class="bx bx-info-circle"></span>
${t('global_menu.about')}
</li>
@@ -259,10 +259,9 @@ export default class GlobalMenuWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdown = this.$widget.find("[data-toggle='dropdown']");
const $button = this.$widget.find(".global-menu-button");
$button.tooltip({ trigger: "hover" });
$button.on("click", () => $button.tooltip("hide"));
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" });
this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog"));
@@ -278,8 +277,13 @@ export default class GlobalMenuWidget extends BasicWidget {
return;
}
this.$dropdown.dropdown('toggle');
this.dropdown.toggle();
});
this.$widget.on('click', '.dropdown-submenu', e => {
if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) {
e.stopPropagation();
}
})
this.$widget.find(".global-menu-button-update-available").append(
this.updateAvailableWidget.render()
@@ -292,7 +296,12 @@ export default class GlobalMenuWidget extends BasicWidget {
}
this.$zoomState = this.$widget.find(".zoom-state");
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
this.$widget.on('show.bs.dropdown', () => {
this.updateZoomState();
this.tooltip.hide();
this.tooltip.disable();
});
this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable());
this.$widget.find(".zoom-buttons").on("click",
// delay to wait for the actual zoom change
@@ -342,10 +351,10 @@ export default class GlobalMenuWidget extends BasicWidget {
}
activeContextChangedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
noteSwitchedEvent() {
this.$dropdown.dropdown('hide');
this.dropdown.hide();
}
}

View File

@@ -17,7 +17,7 @@ const TPL = `
}
.note-actions .dropdown-menu {
width: 15em;
min-width: 15em;
}
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
@@ -27,7 +27,7 @@ const TPL = `
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true"
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button>
<div class="dropdown-menu dropdown-menu-right">
@@ -82,7 +82,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$importNoteButton = this.$widget.find('.import-files-button');
this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-bs-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");

View File

@@ -1,17 +1,19 @@
import BasicWidget from "../basic_widget.js";
const TPL = `
<div class="dropdown right-dropdown-widget dropright">
<div class="dropdown right-dropdown-widget dropend">
<style>
.right-dropdown-widget {
height: 53px;
}
</style>
<button type="button" data-toggle="dropdown" data-placement="right"
<button type="button" data-bs-toggle="dropdown" data-placement="right"
aria-haspopup="true" aria-expanded="false"
class="bx right-dropdown-button launcher-button"></button>
<div class="tooltip-trigger"></div>
<div class="dropdown-menu dropdown-menu-right"></div>
</div>
`;
@@ -28,12 +30,16 @@ export default class RightDropdownButtonWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$dropdownMenu = this.$widget.find(".dropdown-menu");
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
const $button = this.$widget.find(".right-dropdown-button")
this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title);
this.tooltip = new bootstrap.Tooltip(this.$tooltip);
this.$widget.find(".right-dropdown-button")
.addClass(this.iconClass)
.attr("title", this.title)
.tooltip({ trigger: "hover" })
.on("click", () => $button.tooltip("hide"));
.on("click", () => this.tooltip.hide())
.on('mouseenter', () => this.tooltip.show())
.on('mouseleave', () => this.tooltip.hide());
this.$widget.on('show.bs.dropdown', async () => {
await this.dropdownShown();
@@ -51,10 +57,5 @@ export default class RightDropdownButtonWidget extends BasicWidget {
}
// to be overridden
async dropdownShow() {}
hideDropdown() {
this.$widget.dropdown("hide");
this.$dropdownMenu.removeClass("show");
}
async dropdownShow() { }
}

View File

@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowHighlightsListWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-highlight")
.title(t("show_highlights_list_widget_button.show_highlights_list"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.highlightsListTemporarilyHidden = false;
appContext.triggerEvent("showHighlightsListWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.highlightsListTemporarilyHidden);
}
async reEvaluateHighlightsListWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'hideHighlightWidget')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -0,0 +1,50 @@
import OnClickButtonWidget from "./onclick_button.js";
import appContext from "../../components/app_context.js";
import attributeService from "../../services/attributes.js";
import { t } from "../../services/i18n.js";
export default class ShowTocWidgetButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled()
&& this.note
&& this.note.type === 'text'
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
super();
this.icon("bx-objects-horizontal-left")
.title(t("show_toc_widget_button.show_toc"))
.titlePlacement("bottom")
.onClick(widget => {
this.noteContext.viewScope.tocTemporarilyHidden = false;
appContext.triggerEvent("showTocWidget", { noteId: this.noteId });
this.toggleInt(false);
});
}
async refreshWithNote(note) {
this.toggleInt(this.noteContext.viewScope.tocTemporarilyHidden);
}
async reEvaluateTocWidgetVisibilityEvent({ noteId }) {
if (noteId === this.noteId) {
await this.refresh();
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'
&& (attr.name.toLowerCase().includes('readonly') || attr.name === 'toc')
&& attributeService.isAffecting(attr, this.note))) {
await this.refresh();
}
}
async noteTypeMimeChangedEvent({ noteId }) {
if (this.isNote(noteId)) {
await this.refresh();
}
}
}

View File

@@ -2,17 +2,16 @@ import server from "../../services/server.js";
import utils from "../../services/utils.js";
import { t } from "../../services/i18n.js";
import BasicWidget from "../basic_widget.js";
import openService from "../../services/open.js";
const TPL = `
<div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("about.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("about.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<table class="table table-borderless text-nowrap">
@@ -72,7 +71,18 @@ export default class AboutDialog extends BasicWidget {
this.$buildDate.text(appInfo.buildDate);
this.$buildRevision.text(appInfo.buildRevision);
this.$buildRevision.attr('href', `https://github.com/TriliumNext/Notes/commit/${appInfo.buildRevision}`);
this.$dataDirectory.text(appInfo.dataDirectory);
if (utils.isElectron()) {
this.$dataDirectory.html($('<a></a>', {
href: '#',
text: appInfo.dataDirectory,
}));
this.$dataDirectory.find("a").on('click', (event) => {
event.preventDefault();
openService.openDirectory(appInfo.dataDirectory);
})
} else {
this.$dataDirectory.text(appInfo.dataDirectory);
}
}
async openAboutDialogEvent() {

View File

@@ -9,13 +9,9 @@ const TPL = `
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('add_link.add_link')}</h5>
<h5 class="modal-title flex-grow-1">${t('add_link.add_link')}</h5>
<button type="button" class="help-button" title="${t('add_link.help_on_links')}" data-help-page="links.html">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('add_link.close')}" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('add_link.close')}"></button>
</div>
<form class="add-link-form">
<div class="modal-body">

View File

@@ -14,13 +14,9 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
<form class="branch-prefix-form">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('branch_prefix.edit_branch_prefix')}</h5>
<h5 class="modal-title flex-grow-1">${t('branch_prefix.edit_branch_prefix')}</h5>
<button class="help-button" type="button" data-help-page="tree-concepts.html#prefix" title="${t('branch_prefix.help_on_tree_prefix')}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('branch_prefix.close')}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('branch_prefix.close')}"></button>
</div>
<div class="modal-body">
<div class="form-group">
@@ -28,10 +24,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
<div class="input-group">
<input class="branch-prefix-input form-control">
<div class="input-group-append">
<div class="branch-prefix-note-title input-group-text"></div>
</div>
<div class="branch-prefix-note-title input-group-text"></div>
</div>
</div>
</div>
@@ -46,6 +39,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
export default class BranchPrefixDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".branch-prefix-form");
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
this.$noteTitle = this.$widget.find('.branch-prefix-note-title');
@@ -60,7 +54,7 @@ export default class BranchPrefixDialog extends BasicWidget {
}
async refresh(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
if (!noteId || !parentNoteId) {
return;
@@ -97,9 +91,9 @@ export default class BranchPrefixDialog extends BasicWidget {
async savePrefix() {
const prefix = this.$treePrefixInput.val();
await server.put(`branches/${branchId}/set-prefix`, {prefix: prefix});
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t('branch_prefix.branch_prefix_saved'));
}

View File

@@ -38,20 +38,15 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('bulk_actions.bulk_actions')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('bulk_actions.close')}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('bulk_actions.bulk_actions')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('bulk_actions.close')}"></button>
</div>
<div class="modal-body">
<h4>${t('bulk_actions.affected_notes')}: <span class="affected-note-count">0</span></h4>
<div class="form-check">
<label class="form-check-label">
<input class="include-descendants form-check-input" type="checkbox" value="">
${t('bulk_actions.include_descendants')}
</label>
<input class="include-descendants form-check-input" type="checkbox" value="">
<label class="form-check-label">${t('bulk_actions.include_descendants')}</label>
</div>
<h4>${t('bulk_actions.available_actions')}</h4>
@@ -72,7 +67,6 @@ const TPL = `
export default class BulkActionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.$includeDescendants = this.$widget.find(".include-descendants");
this.$includeDescendants.on("change", () => this.refresh());

View File

@@ -13,13 +13,9 @@ const TPL = `
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('clone_to.clone_notes_to')}</h5>
<h5 class="modal-title flex-grow-1">${t('clone_to.clone_notes_to')}</h5>
<button type="button" class="help-button" title="${t('clone_to.help_on_links')}" data-help-page="cloning-notes.html">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="clone-to-form">
<div class="modal-body">
@@ -81,9 +77,9 @@ export default class CloneToDialog extends BasicWidget {
});
}
async cloneNoteIdsToEvent({noteIds}) {
async cloneNoteIdsToEvent({ noteIds }) {
if (!noteIds || noteIds.length === 0) {
noteIds = [ appContext.tabManager.getActiveContextNoteId() ];
noteIds = [appContext.tabManager.getActiveContextNoteId()];
}
this.clonedNoteIds = [];
@@ -111,7 +107,7 @@ export default class CloneToDialog extends BasicWidget {
}
async cloneNotesTo(notePath) {
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
for (const cloneNoteId of this.clonedNoteIds) {
@@ -120,7 +116,7 @@ export default class CloneToDialog extends BasicWidget {
const clonedNote = await froca.getNote(cloneNoteId);
const targetNote = await froca.getBranch(targetBranchId).getNote();
toastService.showMessage(t('clone_to.note_cloned', {clonedTitle: clonedNote.title, targetTitle: targetNote.title}));
toastService.showMessage(t('clone_to.note_cloned', { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
}
}
}

View File

@@ -8,11 +8,8 @@ const TPL = `
<div class="modal-dialog modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('confirm.confirmation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('confirm.confirmation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="confirm-dialog-content"></div>
@@ -40,6 +37,7 @@ export default class ConfirmDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$confirmContent = this.$widget.find(".confirm-dialog-content");
this.$okButton = this.$widget.find(".confirm-dialog-ok-button");
this.$cancelButton = this.$widget.find(".confirm-dialog-cancel-button");
@@ -62,7 +60,7 @@ export default class ConfirmDialog extends BasicWidget {
this.$okButton.on('click', () => this.doResolve(true));
}
showConfirmDialogEvent({message, callback}) {
showConfirmDialogEvent({ message, callback }) {
this.$originallyFocused = $(':focus');
this.$custom.hide();
@@ -75,15 +73,15 @@ export default class ConfirmDialog extends BasicWidget {
this.$confirmContent.empty().append(message);
this.$widget.modal();
this.modal.show();
this.resolve = callback;
}
showConfirmDeleteNoteBoxWithNoteDialogEvent({title, callback}) {
showConfirmDeleteNoteBoxWithNoteDialogEvent({ title, callback }) {
glob.activeDialog = this.$widget;
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', {title: title})}`);
this.$confirmContent.text(`${t('confirm.are_you_sure_remove_note', { title: title })}`);
this.$custom.empty()
.append("<br/>")
@@ -104,7 +102,7 @@ export default class ConfirmDialog extends BasicWidget {
this.$custom.show();
this.$widget.modal();
this.modal.show();
this.resolve = callback;
}
@@ -117,6 +115,6 @@ export default class ConfirmDialog extends BasicWidget {
this.resolve = null;
this.$widget.modal("hide");
this.modal.hide();
}
}

View File

@@ -10,27 +10,18 @@ const TPL = `
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title mr-auto">${t('delete_notes.delete_notes_preview')}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">${t('delete_notes.delete_notes_preview')}</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="checkbox">
<label>
<input class="delete-all-clones" value="1" type="checkbox">
${t('delete_notes.delete_all_clones_description')}
</label>
<div class="form-checkbox">
<input class="delete-all-clones form-check-input" value="1" type="checkbox">
<label class="form-check-label">${t('delete_notes.delete_all_clones_description')}</label>
</div>
<div class="checkbox">
<label title="${t('delete_notes.erase_notes_description')}">
<input class="erase-notes" value="1" type="checkbox">
${t('delete_notes.erase_notes_warning')}
</label>
<div class="form-checkbox" style="margin-bottom: 1rem">
<input class="erase-notes form-check-input" value="1" type="checkbox">
<label class="form-check-label">${t('delete_notes.erase_notes_warning')}</label>
</div>
<div class="delete-notes-list-wrapper">

View File

@@ -34,9 +34,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t('export.export_note_title')} <span class="export-note-title"></span></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('export.close')}">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('export.close')}"></button>
</div>
<form class="export-form">
<div class="modal-body">
@@ -127,6 +125,7 @@ export default class ExportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".export-form");
this.$noteTitle = this.$widget.find(".export-note-title");
this.$subtreeFormats = this.$widget.find(".export-subtree-formats");
@@ -137,7 +136,7 @@ export default class ExportDialog extends BasicWidget {
this.$opmlVersions = this.$widget.find(".opml-versions");
this.$form.on('submit', () => {
this.$widget.modal('hide');
this.modal.hide();
const exportType = this.$widget.find("input[name='export-type']:checked").val();
@@ -188,7 +187,7 @@ export default class ExportDialog extends BasicWidget {
});
}
async showExportDialogEvent({notePath, defaultType}) {
async showExportDialogEvent({ notePath, defaultType }) {
this.taskId = '';
this.$exportButton.removeAttr("disabled");
@@ -208,7 +207,7 @@ export default class ExportDialog extends BasicWidget {
utils.openDialog(this.$widget);
const {noteId, parentNoteId} = treeService.getNoteIdAndParentIdFromUrl(notePath);
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
this.branchId = await froca.getBranchId(parentNoteId, noteId);
this.$noteTitle.text(await treeService.getNoteTitle(noteId));

View File

@@ -7,14 +7,11 @@ const TPL = `
<div class="modal-dialog" role="document" style="min-width: 100%; height: 100%; margin: 0;">
<div class="modal-content" style="height: auto;">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('help.fullDocumentation')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('help.close')}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t('help.fullDocumentation')}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('help.close')}"></button>
</div>
<div class="modal-body" style="overflow: auto; height: calc(100vh - 70px);">
<div class="card-columns help-cards">
<div class="help-cards row row-cols-3 g-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">${t('help.noteNavigation')}</h5>

View File

@@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t('import.importIntoNote')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t('import.close')}">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t('import.close')}"></button>
</div>
<form class="import-form">
<div class="modal-body">
@@ -29,21 +27,21 @@ const TPL = `
<strong>${t('import.options')}:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.safeImportTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.safeImportTooltip')}">
<input class="safe-import-checkbox" value="1" type="checkbox" checked>
<span>${t('import.safeImport')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.explodeArchivesTooltip')}">
<input class="explode-archives-checkbox" value="1" type="checkbox" checked>
<span>${t('import.explodeArchives')}</span>
</label>
</div>
<div class="checkbox">
<label data-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
<label data-bs-toggle="tooltip" title="${t('import.shrinkImagesTooltip')}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t('import.shrinkImages')}</span>
</label>
</div>
@@ -86,6 +84,8 @@ export default class ImportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".import-form");
this.$noteTitle = this.$widget.find(".import-note-title");
this.$fileUploadInput = this.$widget.find(".import-file-upload-input");
@@ -115,12 +115,14 @@ export default class ImportDialog extends BasicWidget {
}
});
this.$widget.find('[data-toggle="tooltip"]').tooltip({
html: true
let _ = [...this.$widget.find('[data-bs-toggle="tooltip"]')].forEach(element => {
bootstrap.Tooltip.getOrCreateInstance(element, {
html: true
});
});
}
async showImportDialogEvent({noteId}) {
async showImportDialogEvent({ noteId }) {
this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below

View File

@@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t('include_note.dialog_title')}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="include-note-form">
<div class="modal-body">
@@ -27,22 +25,16 @@ const TPL = `
${t('include_note.box_size_prompt')}
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
${t('include_note.box_size_small')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="small">
<label class="form-check-label">${t('include_note.box_size_small')}</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
${t('include_note.box_size_medium')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="medium" checked>
<label class="form-check-label">${t('include_note.box_size_medium')}</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
${t('include_note.box_size_full')}
</label>
<input class="form-check-input" type="radio" name="include-note-box-size" value="full">
<label class="form-check-label">${t('include_note.box_size_full')}</label>
</div>
</div>
<div class="modal-footer">
@@ -56,13 +48,14 @@ const TPL = `
export default class IncludeNoteDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".include-note-form");
this.$autoComplete = this.$widget.find(".include-note-autocomplete");
this.$form.on('submit', () => {
const notePath = this.$autoComplete.getSelectedNotePath();
if (notePath) {
this.$widget.modal('hide');
this.modal.hide();
this.includeNote(notePath);
} else {
logError("No noteId to include.");
@@ -72,7 +65,7 @@ export default class IncludeNoteDialog extends BasicWidget {
})
}
async showIncludeNoteDialogEvent({textTypeWidget}) {
async showIncludeNoteDialogEvent({ textTypeWidget }) {
this.textTypeWidget = textTypeWidget;
await this.refresh();
utils.openDialog(this.$widget);

View File

@@ -7,11 +7,8 @@ const TPL = `
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("info.modalTitle")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="${t("info.closeButton")}">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("info.modalTitle")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("info.closeButton")}"></button>
</div>
<div class="modal-body">
<div class="info-dialog-content"></div>
@@ -33,6 +30,7 @@ export default class InfoDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$infoContent = this.$widget.find(".info-dialog-content");
this.$okButton = this.$widget.find(".info-dialog-ok-button");
@@ -49,10 +47,10 @@ export default class InfoDialog extends BasicWidget {
}
});
this.$okButton.on('click', () => this.$widget.modal("hide"));
this.$okButton.on('click', () => this.modal.hide());
}
showInfoDialogEvent({message, callback}) {
showInfoDialogEvent({ message, callback }) {
this.$originallyFocused = $(':focus');
this.$infoContent.text(message);

View File

@@ -12,10 +12,7 @@ const TPL = `<div class="jump-to-note-dialog modal mx-auto" tabindex="-1" role="
<div class="input-group">
<input class="jump-to-note-autocomplete form-control" placeholder="${t('jump_to_note.search_placeholder')}">
</div>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="algolia-autocomplete-container jump-to-note-results"></div>
@@ -38,6 +35,8 @@ export default class JumpToNoteDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$autoComplete = this.$widget.find(".jump-to-note-autocomplete");
this.$results = this.$widget.find(".jump-to-note-results");
this.$showInFullTextButton = this.$widget.find(".show-in-full-text-button");
@@ -94,8 +93,8 @@ export default class JumpToNoteDialog extends BasicWidget {
const searchString = this.$autoComplete.val();
this.triggerCommand('searchNotes', {searchString});
this.triggerCommand('searchNotes', { searchString });
this.$widget.modal('hide');
this.modal.hide();
}
}

View File

@@ -12,9 +12,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("markdown_import.dialog_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>${t("markdown_import.modal_body_text")}</p>
@@ -37,6 +35,7 @@ export default class MarkdownImportDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$importTextarea = this.$widget.find('.markdown-import-textarea');
this.$importButton = this.$widget.find('.markdown-import-button');
@@ -48,7 +47,7 @@ export default class MarkdownImportDialog extends BasicWidget {
}
async convertMarkdownToHtml(markdownContent) {
const {htmlContent} = await server.post('other/render-markdown', { markdownContent });
const { htmlContent } = await server.post('other/render-markdown', { markdownContent });
const textEditor = await appContext.tabManager.getActiveContext().getTextEditor();
@@ -70,7 +69,7 @@ export default class MarkdownImportDialog extends BasicWidget {
}
if (utils.isElectron()) {
const {clipboard} = utils.dynamicRequire('electron');
const { clipboard } = utils.dynamicRequire('electron');
const text = clipboard.readText();
this.convertMarkdownToHtml(text);
@@ -83,7 +82,7 @@ export default class MarkdownImportDialog extends BasicWidget {
async sendForm() {
const text = this.$importTextarea.val();
this.$widget.modal('hide');
this.modal.hide();
await this.convertMarkdownToHtml(text);

View File

@@ -5,18 +5,15 @@ import froca from "../../services/froca.js";
import branchService from "../../services/branches.js";
import treeService from "../../services/tree.js";
import BasicWidget from "../basic_widget.js";
import { t } from "../../services/i18n.js"; // Added import
import { t } from "../../services/i18n.js";
const TPL = `
<div class="move-to-dialog modal mx-auto" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" style="max-width: 1000px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("move_to.dialog_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title me-auto">${t("move_to.dialog_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="move-to-form">
<div class="modal-body">

View File

@@ -23,17 +23,16 @@ const TPL = `
<div class="modal-dialog" style="max-width: 500px;" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("note_type_chooser.modal_title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("note_type_chooser.modal_title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
${t("note_type_chooser.modal_body")}
<div class="dropdown">
<button class="note-type-dropdown-trigger" type="button" style="display: none;" data-toggle="dropdown"></button>
<div class="dropdown" style="display: flex;">
<button class="note-type-dropdown-trigger" type="button" style="display: none;"
data-bs-toggle="dropdown" data-bs-display="static">
</button>
<div class="note-type-dropdown dropdown-menu"></div>
</div>
@@ -53,13 +52,14 @@ export default class NoteTypeChooserDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeDropdownTrigger = this.$widget.find(".note-type-dropdown-trigger");
this.$noteTypeDropdownTrigger.dropdown();
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find(".note-type-dropdown-trigger"));
this.$widget.on("hidden.bs.modal", () => {
if (this.resolve) {
this.resolve({success: false});
this.resolve({ success: false });
}
if (this.$originalFocused) {
@@ -94,7 +94,7 @@ export default class NoteTypeChooserDialog extends BasicWidget {
});
}
async chooseNoteTypeEvent({callback}) {
async chooseNoteTypeEvent({ callback }) {
this.$originalFocused = $(':focus');
const noteTypes = await noteTypesService.getNoteTypeItems();
@@ -116,11 +116,11 @@ export default class NoteTypeChooserDialog extends BasicWidget {
}
}
this.$noteTypeDropdownTrigger.dropdown('show');
this.dropdown.show();
this.$originalDialog = glob.activeDialog;
glob.activeDialog = this.$widget;
this.$widget.modal();
glob.activeDialog = this.modal;
this.modal.show();
this.$noteTypeDropdown.find(".dropdown-item:first").focus();
@@ -139,6 +139,6 @@ export default class NoteTypeChooserDialog extends BasicWidget {
});
this.resolve = null;
this.$widget.modal("hide");
this.modal.hide();
}
}

View File

@@ -7,11 +7,8 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("password_not_set.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("password_not_set.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
${t("password_not_set.body1")}
@@ -26,8 +23,10 @@ const TPL = `
export default class PasswordNoteSetDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$openPasswordOptionsButton = this.$widget.find(".open-password-options-button");
this.$openPasswordOptionsButton.on("click", () => {
this.modal.hide();
this.triggerCommand("showOptions", { section: '_optionsPassword' });
});
}

View File

@@ -8,13 +8,10 @@ const TPL = `
<div class="modal-content">
<form class="prompt-dialog-form">
<div class="modal-header">
<h5 class="prompt-title modal-title mr-auto">${t("prompt.title")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h5 class="prompt-title modal-title">${t("prompt.title")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button class="prompt-dialog-ok-button btn btn-primary btn-sm">${t("prompt.ok")}</button>
</div>
@@ -33,6 +30,7 @@ export default class PromptDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$dialogBody = this.$widget.find(".modal-body");
this.$form = this.$widget.find(".prompt-dialog-form");
this.$question = null;
@@ -61,7 +59,7 @@ export default class PromptDialog extends BasicWidget {
e.preventDefault();
this.resolve(this.$answer.val());
this.$widget.modal('hide');
this.modal.hide();
});
}

View File

@@ -8,22 +8,14 @@ const TPL = `
<div class="modal-dialog modal-md" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("protected_session_password.modal_title")}</h5>
<h5 class="modal-title flex-grow-1">${t("protected_session_password.modal_title")}</h5>
<button class="help-button" type="button" data-help-page="protected-notes.html" title="${t("protected_session_password.help_title")}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="${t("protected_session_password.close_label")}" style="margin-left: 0;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("protected_session_password.close_label")}"></button>
</div>
<form class="protected-session-password-form">
<div class="modal-body">
<div class="form-group">
<label>
${t("protected_session_password.form_label")}
<input class="form-control protected-session-password" type="password">
</label>
</div>
<label class="col-form-label">${t("protected_session_password.form_label")}</label>
<input class="form-control protected-session-password" type="password">
</div>
<div class="modal-footer">
<button class="btn btn-primary">${t("protected_session_password.start_button")}</button>
@@ -36,6 +28,8 @@ const TPL = `
export default class ProtectedSessionPasswordDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$passwordForm = this.$widget.find(".protected-session-password-form");
this.$passwordInput = this.$widget.find(".protected-session-password");
this.$passwordForm.on('submit', () => {
@@ -55,6 +49,6 @@ export default class ProtectedSessionPasswordDialog extends BasicWidget {
}
closeProtectedSessionPasswordDialogEvent() {
this.$widget.modal('hide');
this.modal.hide();
}
}

View File

@@ -15,14 +15,9 @@ const TPL = `
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t('recent_changes.title')}</h5>
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">
${t('recent_changes.erase_notes_button')}</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title flex-grow-1">${t('recent_changes.title')}</h5>
<button class="erase-deleted-notes-now-button btn btn-sm" style="padding: 0 10px">${t('recent_changes.erase_notes_button')}</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="recent-changes-content"></div>
@@ -34,6 +29,8 @@ const TPL = `
export default class RecentChangesDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$content = this.$widget.find(".recent-changes-content");
this.$eraseDeletedNotesNow = this.$widget.find(".erase-deleted-notes-now-button");
this.$eraseDeletedNotesNow.on("click", () => {
@@ -45,7 +42,7 @@ export default class RecentChangesDialog extends BasicWidget {
});
}
async showRecentChangesEvent({ancestorNoteId}) {
async showRecentChangesEvent({ ancestorNoteId }) {
this.ancestorNoteId = ancestorNoteId;
await this.refresh();
@@ -93,7 +90,7 @@ export default class RecentChangesDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);
this.$widget.modal('hide');
this.modal.hide();
await ws.waitForMaxKnownEntityChangeId();

View File

@@ -8,6 +8,7 @@ import openService from "../../services/open.js";
import protectedSessionHolder from "../../services/protected_session_holder.js";
import BasicWidget from "../basic_widget.js";
import dialogService from "../../services/dialog.js";
import options from "../../services/options.js";
const TPL = `
<div class="revisions-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@@ -40,21 +41,18 @@ const TPL = `
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("revisions.note_revisions")}</h5>
<h5 class="modal-title flex-grow-1">${t("revisions.note_revisions")}</h5>
<button class="revisions-erase-all-revisions-button btn btn-sm"
title="${t("revisions.delete_all_revisions")}"
style="padding: 0 10px 0 10px;" type="button">${t("revisions.delete_all_button")}</button>
<button class="help-button" type="button" data-help-page="note-revisions.html" title="${t("revisions.help_title")}">?</button>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="display: flex; height: 80vh;">
<div class="dropdown">
<button class="revision-list-dropdown" type="button" style="display: none;" data-toggle="dropdown"></button>
<button class="revision-list-dropdown" type="button" style="display: none;"
data-bs-toggle="dropdown" data-bs-display="static">
</button>
<div class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div>
</div>
@@ -69,6 +67,11 @@ const TPL = `
<div class="revision-content"></div>
</div>
</div>
<div class="modal-footer py-0">
<span class="revisions-snapshot-interval flex-grow-1 my-0 py-0"></span>
<span class="maximum-revisions-for-current-note flex-grow-1 my-0 py-0"></span>
<button class="revision-settings-button icon-action bx bx-cog my-0 py-0" title="${t("revisions.settings")}"></button>
</div>
</div>
</div>
</div>`;
@@ -84,22 +87,33 @@ export default class RevisionsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$list = this.$widget.find(".revision-list");
this.$listDropdown = this.$widget.find(".revision-list-dropdown");
this.listDropdown = bootstrap.Dropdown.getOrCreateInstance(this.$listDropdown);
this.$content = this.$widget.find(".revision-content");
this.$title = this.$widget.find(".revision-title");
this.$titleButtons = this.$widget.find(".revision-title-buttons");
this.$eraseAllRevisionsButton = this.$widget.find(".revisions-erase-all-revisions-button");
this.$listDropdown.dropdown();
this.$snapshotInterval = this.$widget.find(".revisions-snapshot-interval");
this.$maximumRevisions = this.$widget.find(".maximum-revisions-for-current-note");
this.$revisionSettingsButton = this.$widget.find(".revision-settings-button")
this.listDropdown.show();
this.$listDropdown.parent().on('hide.bs.dropdown', e => {
// prevent closing dropdown by clicking outside
if (e.clickEvent) {
e.preventDefault();
}
// Prevent closing dropdown by pressing ESC and clicking outside
e.preventDefault();
});
document.addEventListener('keydown', e => {
// Close the revision dialog when revision element is focused and ESC is pressed
if (e.key === 'Escape' ||
e.target.classList.contains(['dropdown-item', 'active'])) {
this.modal.hide();
}
}, true)
this.$widget.on('shown.bs.modal', () => {
this.$list.find(`[data-revision-id="${this.revisionId}"]`)
.trigger('focus');
@@ -111,17 +125,12 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.remove(`notes/${this.note.noteId}/revisions`);
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t("revisions.revisions_deleted"));
}
});
this.$list.on('click', '.dropdown-item', e => {
e.preventDefault();
return false;
});
this.$list.on('focus', '.dropdown-item', e => {
this.$list.find('.dropdown-item').each((i, el) => {
$(el).toggleClass('active', el === e.target);
@@ -129,9 +138,13 @@ export default class RevisionsDialog extends BasicWidget {
this.setContentPane();
});
this.$revisionSettingsButton.on('click', async () => {
appContext.tabManager.openContextWithNote('_optionsOther', { activate: true });
});
}
async showRevisionsEvent({noteId = appContext.tabManager.getActiveContextNoteId()}) {
async showRevisionsEvent({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
utils.openDialog(this.$widget);
await this.loadRevisions(noteId);
@@ -154,7 +167,7 @@ export default class RevisionsDialog extends BasicWidget {
);
}
this.$listDropdown.dropdown('show');
this.listDropdown.show();
if (this.revisionItems.length > 0) {
if (!this.revisionId) {
@@ -166,6 +179,17 @@ export default class RevisionsDialog extends BasicWidget {
}
this.$eraseAllRevisionsButton.toggle(this.revisionItems.length > 0);
// Show the footer of the revisions dialog
this.$snapshotInterval.text(t("revisions.snapshot_interval", { seconds: options.getInt('revisionSnapshotTimeInterval') }))
let revisionsNumberLimit = parseInt(this.note.getLabelValue("versioningLimit") ?? "");
if (!Number.isInteger(revisionsNumberLimit)) {
revisionsNumberLimit = parseInt(options.getInt('revisionSnapshotNumberLimit'));
}
if (revisionsNumberLimit === -1) {
revisionsNumberLimit = "∞"
}
this.$maximumRevisions.text(t("revisions.maximum_revisions", { number: revisionsNumberLimit }))
}
async setContentPane() {
@@ -191,7 +215,7 @@ export default class RevisionsDialog extends BasicWidget {
if (await dialogService.confirm(text)) {
await server.post(`revisions/${revisionItem.revisionId}/restore`);
this.$widget.modal('hide');
this.modal.hide();
toastService.showMessage(t("revisions.revision_restored"));
}
@@ -241,17 +265,25 @@ export default class RevisionsDialog extends BasicWidget {
if (this.$content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement(this.$content[0], {trust: true});
renderMathInElement(this.$content[0], { trust: true });
}
} else if (revisionItem.type === 'code') {
this.$content.html($("<pre>").text(fullRevision.content));
} else if (revisionItem.type === 'image') {
this.$content.html($("<img>")
// the reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
if (fullRevision.mime === "image/svg+xml") {
let encodedSVG = encodeURIComponent(fullRevision.content); //Base64 of other format images may be embedded in svg
this.$content.html($("<img>")
.attr("src", `data:${fullRevision.mime};utf8,${encodedSVG}`)
.css("max-width", "100%")
.css("max-height", "100%"));
} else {
this.$content.html($("<img>")
// the reason why we put this inline as base64 is that we do not want to let user copy this
// as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be uploaded as a new note
.attr("src", `data:${fullRevision.mime};base64,${fullRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
}
} else if (revisionItem.type === 'file') {
const $table = $("<table cellpadding='10'>")
.append($("<tr>").append(
@@ -274,7 +306,7 @@ export default class RevisionsDialog extends BasicWidget {
}
this.$content.html($table);
} else if ([ "canvas", "mindMap" ].includes(revisionItem.type)) {
} else if (["canvas", "mindMap"].includes(revisionItem.type)) {
const encodedTitle = encodeURIComponent(revisionItem.title);
this.$content.html($("<img>")

View File

@@ -7,61 +7,45 @@ const TPL = `<div class="sort-child-notes-dialog modal mx-auto" tabindex="-1" ro
<div class="modal-dialog modal-lg" style="max-width: 500px" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title mr-auto">${t("sort_child_notes.sort_children_by")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0 !important;">
<span aria-hidden="true">&times;</span>
</button>
<h5 class="modal-title">${t("sort_child_notes.sort_children_by")}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="sort-child-notes-form">
<div class="modal-body">
<h5>${t("sort_child_notes.sorting_criteria")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
${t("sort_child_notes.title")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="title" checked>
<label class="form-check-label">${t("sort_child_notes.title")}</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
${t("sort_child_notes.date_created")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="dateCreated">
<label class="form-check-label">${t("sort_child_notes.date_created")}</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
${t("sort_child_notes.date_modified")}
</label>
<input class="form-check-input" type="radio" name="sort-by" value="dateModified">
<label class="form-check-label">${t("sort_child_notes.date_modified")}</label>
</div>
<br/>
<h5>${t("sort_child_notes.sorting_direction")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
${t("sort_child_notes.ascending")}
</label>
<input class="form-check-input" type="radio" name="sort-direction" value="asc" checked>
<label class="form-check-label">${t("sort_child_notes.ascending")}</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
${t("sort_child_notes.descending")}
</label>
<input class="form-check-input" type="radio" name="sort-direction" value="desc">
<label class="form-check-label">${t("sort_child_notes.descending")}</label>
</div>
<br />
<h5>${t("sort_child_notes.folders")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
${t("sort_child_notes.sort_folders_at_top")}
</label>
<input class="form-check-input" type="checkbox" name="sort-folders-first" value="1">
<label class="form-check-label">${t("sort_child_notes.sort_folders_at_top")}</label>
</div>
<br />
<h5>${t("sort_child_notes.natural_sort")}</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}
</label>
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
<label class="form-check-label">${t("sort_child_notes.sort_with_respect_to_different_character_sorting")}</label>
</div>
<br />
<div class="form-check">

View File

@@ -11,9 +11,7 @@ const TPL = `
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${t("upload_attachments.upload_attachments_to_note")}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="upload-attachment-form">
<div class="modal-body">
@@ -26,8 +24,8 @@ const TPL = `
<div class="form-group">
<strong>${t("upload_attachments.options")}:</strong>
<div class="checkbox">
<label data-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
<input class="shrink-images-checkbox" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
<label data-bs-toggle="tooltip" title="${t("upload_attachments.tooltip")}">
<input class="shrink-images-checkbox form-check-input" value="1" type="checkbox" checked> <span>${t("upload_attachments.shrink_images")}</span>
</label>
</div>
</div>
@@ -49,6 +47,8 @@ export default class UploadAttachmentsDialog extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.modal = bootstrap.Modal.getOrCreateInstance(this.$widget);
this.$form = this.$widget.find(".upload-attachment-form");
this.$noteTitle = this.$widget.find(".upload-attachment-note-title");
this.$fileUploadInput = this.$widget.find(".upload-attachment-file-upload-input");
@@ -71,12 +71,12 @@ export default class UploadAttachmentsDialog extends BasicWidget {
}
});
this.$widget.find('[data-toggle="tooltip"]').tooltip({
bootstrap.Tooltip.getOrCreateInstance(this.$widget.find('[data-bs-toggle="tooltip"]'), {
html: true
});
}
async showUploadAttachmentsDialogEvent({noteId}) {
async showUploadAttachmentsDialogEvent({ noteId }) {
this.parentNoteId = noteId;
this.$fileUploadInput.val('').trigger('change'); // to trigger upload button disabling listener below
@@ -96,7 +96,7 @@ export default class UploadAttachmentsDialog extends BasicWidget {
shrinkImages: boolToString(this.$shrinkImagesCheckbox),
};
this.$widget.modal('hide');
this.modal.hide();
await importService.uploadFiles('attachments', parentNoteId, files, options);
}

View File

@@ -15,7 +15,7 @@ const TPL = `
white-space: normal;
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle editability-button">
<span class="editability-active-desc">${t("editability_select.auto")}</span>
<span class="caret"></span>
</button>
@@ -43,11 +43,13 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$editabilityActiveDesc = this.$widget.find(".editability-active-desc");
this.$widget.on('click', '.dropdown-item',
async e => {
this.$widget.find('.dropdown-toggle').dropdown('toggle');
this.dropdown.toggle();
const editability = $(e.target).closest("[data-editability]").attr("data-editability");
@@ -85,7 +87,7 @@ export default class EditabilitySelectWidget extends NoteContextAwareWidget {
this.$editabilityActiveDesc.text(labels[editability]);
}
entitiesReloadedEvent({loadResults}) {
entitiesReloadedEvent({ loadResults }) {
if (loadResults.getAttributeRows().find(attr => attr.noteId === this.noteId)) {
this.refresh();
}

View File

@@ -3,6 +3,7 @@
* https://github.com/antoniotejada/Trilium-FindWidget
*/
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import FindInText from "./find_in_text.js";
import FindInCode from "./find_in_code.js";
@@ -47,24 +48,18 @@ const TPL = `
<div class="find-widget-box">
<div class="input-group find-widget-search-term-input-group">
<input type="text" class="form-control find-widget-search-term-input">
<div class="input-group-append">
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div>
<button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button>
<button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button>
</div>
<div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
case sensitive
</label>
<input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">
<label tabIndex="-1" class="form-check-label">${t('find.case_sensitive')}</label>
</div>
<div class="form-check">
<label tabIndex="-1" class="form-check-label">
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
match words
</label>
<input type="checkbox" class="form-check-input find-widget-match-words-checkbox">
<label tabIndex="-1" class="form-check-label">${t('find.match_words')}</label>
</div>
<div class="find-widget-found-wrapper">
@@ -279,7 +274,7 @@ export default class FindWidget extends NoteContextAwareWidget {
async entitiesReloadedEvent({loadResults}) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
this.$totalFound.text("?")
}
this.$totalFound.text("?")
}
}
}

View File

@@ -1,4 +1,5 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import { t } from "../../services/i18n.js";
const TPL = `
<div class="floating-buttons no-print">
@@ -7,7 +8,7 @@ const TPL = `
position: relative;
}
.floating-buttons-children {
.floating-buttons-children,.show-floating-buttons {
position: absolute;
top: 10px;
right: 10px;
@@ -42,12 +43,44 @@ const TPL = `
border-color: var(--button-border-color);
}
.floating-buttons.temporarily-hidden {
.floating-buttons .floating-buttons-children.temporarily-hidden {
display: none;
}
</style>
<div class="floating-buttons-children"></div>
<!-- Show button that displays floating button after click on close button -->
<div class="show-floating-buttons">
<style>
.floating-buttons-children.temporarily-hidden+.show-floating-buttons {
display: block;
}
.floating-buttons-children:not(.temporarily-hidden)+.show-floating-buttons {
display: none;
}
.show-floating-buttons {
/* display: none;*/
margin-left: 5px !important;
}
.show-floating-buttons-button {
border: 1px solid transparent;
color: var(--button-text-color);
padding: 6px;
border-radius: 100px;
}
.show-floating-buttons-button:hover {
border: 1px solid var(--button-border-color);
}
</style>
<button type="button" class="show-floating-buttons-button btn bx bx-chevrons-left"
title="${t('show_floating_buttons_button.button_title')}"></button>
</div>
</div>`;
export default class FloatingButtons extends NoteContextAwareWidget {
@@ -62,10 +95,11 @@ export default class FloatingButtons extends NoteContextAwareWidget {
async refreshWithNote(note) {
this.toggle(true);
this.$widget.find(".show-floating-buttons-button").on('click', () => this.toggle(true));
}
toggle(show) {
this.$widget.toggleClass("temporarily-hidden", !show);
this.$widget.find(".floating-buttons-children").toggleClass("temporarily-hidden", !show);
}
hideFloatingButtonsCommand() {

View File

@@ -27,7 +27,7 @@ const TPL = `
</style>
<button type="button"
class="close-floating-buttons-button btn bx bx-x"
class="close-floating-buttons-button btn bx bx-chevrons-right"
title="${t('hide_floating_buttons_button.button_title')}"></button>
</div>
`;

View File

@@ -5,11 +5,13 @@
* - For example, if there is a formula in the middle of the highlighted text, the two ends of the formula will be regarded as two entries
*/
import { t } from "../services/i18n.js";
import attributeService from "../services/attributes.js";
import RightPanelWidget from "./right_panel_widget.js";
import options from "../services/options.js";
import OnClickButtonWidget from "./buttons/onclick_button.js";
import appContext from "../components/app_context.js";
import libraryLoader from "../services/library_loader.js";
const TPL = `<div class="highlights-list-widget">
<style>
@@ -28,7 +30,6 @@ const TPL = `<div class="highlights-list-widget">
cursor: pointer;
margin-bottom: 3px;
text-align: justify;
text-justify: distribute;
word-wrap: break-word;
hyphens: auto;
}
@@ -43,16 +44,16 @@ const TPL = `<div class="highlights-list-widget">
export default class HighlightsListWidget extends RightPanelWidget {
get widgetTitle() {
return "Highlights List";
return t("highlights_list_2.title");
}
get widgetButtons() {
return [
new OnClickButtonWidget()
.icon("bx-slider")
.title("Options")
.icon("bx-cog")
.title(t("highlights_list_2.options"))
.titlePlacement("left")
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', {activate: true}))
.onClick(() => appContext.tabManager.openContextWithNote('_optionsTextNotes', { activate: true }))
.class("icon-action"),
new OnClickButtonWidget()
.icon("bx-x")
@@ -97,8 +98,8 @@ export default class HighlightsListWidget extends RightPanelWidget {
let $highlightsList = "", hlLiCount = -1;
// Check for type text unconditionally in case alwaysShowWidget is set
if (this.note.type === 'text') {
const {content} = await note.getNoteComplement();
({$highlightsList, hlLiCount} = this.getHighlightList(content, optionsHighlightsList));
const { content } = await note.getNoteComplement();
({ $highlightsList, hlLiCount } = await this.getHighlightList(content, optionsHighlightsList));
}
this.$highlightsList.empty().append($highlightsList);
if (hlLiCount > 0) {
@@ -112,7 +113,79 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.triggerCommand("reEvaluateRightPaneVisibility");
}
getHighlightList(content, optionsHighlightsList) {
extractOuterTag(htmlStr) {
if (htmlStr === null) {
return null
}
// Regular expressions that match only the outermost tag
const regex = /^<([a-zA-Z]+)([^>]*)>/;
const match = htmlStr.match(regex);
if (match) {
const tagName = match[1].toLowerCase(); // Extract tag name
const attributes = match[2].trim(); // Extract label attributes
return { tagName, attributes };
}
return null;
}
areOuterTagsConsistent(str1, str2) {
const tag1 = this.extractOuterTag(str1);
const tag2 = this.extractOuterTag(str2);
// If one of them has no label, returns false
if (!tag1 || !tag2) {
return false;
}
// Compare tag names and attributes to see if they are the same
return tag1.tagName === tag2.tagName && tag1.attributes === tag2.attributes;
}
/**
* Rendering formulas in strings using katex
*
* @param {string} html Note's html content
* @returns {string} The HTML content with mathematical formulas rendered by KaTeX.
*/
async replaceMathTextWithKatax(html) {
const mathTextRegex = /<span class="math-tex">\\\(([\s\S]*?)\\\)<\/span>/g;
var matches = [...html.matchAll(mathTextRegex)];
let modifiedText = html;
if (matches.length > 0) {
// Process all matches asynchronously
for (const match of matches) {
let latexCode = match[1];
let rendered;
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (e) {
if (e instanceof ReferenceError && e.message.includes('katex is not defined')) {
// Load KaTeX if it is not already loaded
await libraryLoader.requireLibrary(libraryLoader.KATEX);
try {
rendered = katex.renderToString(latexCode, {
throwOnError: false
});
} catch (renderError) {
console.error("KaTeX rendering error after loading library:", renderError);
rendered = match[0]; // Fall back to original if error persists
}
} else {
console.error("KaTeX rendering error:", e);
rendered = match[0]; // Fall back to original on error
}
}
// Replace the matched formula in the modified text
modifiedText = modifiedText.replace(match[0], rendered);
}
}
return modifiedText;
}
async getHighlightList(content, optionsHighlightsList) {
// matches a span containing background-color
const regex1 = /<span[^>]*style\s*=\s*[^>]*background-color:[^>]*?>[\s\S]*?<\/span>/gi;
// matches a span containing color
@@ -152,6 +225,10 @@ export default class HighlightsListWidget extends RightPanelWidget {
const combinedRegex = new RegExp(combinedRegexStr, 'gi');
const $highlightsList = $("<ol>");
let prevEndIndex = -1, hlLiCount = 0;
let prevSubHtml = null;
// Used to determine if a string is only a formula
const onlyMathRegex = /^<span class="math-tex">\\\([^\)]*?\)<\/span>(?:<span class="math-tex">\\\([^\)]*?\)<\/span>)*$/;
for (let match = null, hltIndex = 0; ((match = combinedRegex.exec(content)) !== null); hltIndex++) {
const subHtml = match[0];
const startIndex = match.index;
@@ -166,11 +243,19 @@ export default class HighlightsListWidget extends RightPanelWidget {
const hasText = $(subHtml).text().trim();
if (hasText) {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
const substring = content.substring(prevEndIndex, startIndex);
//If the two elements have the same style and there are only formulas in between, append the formulas and the current element to the end of the previous element.
if (this.areOuterTagsConsistent(prevSubHtml, subHtml) && onlyMathRegex.test(substring)) {
const $lastLi = $highlightsList.children('li').last();
$lastLi.append(await this.replaceMathTextWithKatax(substring));
$lastLi.append(subHtml);
} else {
$highlightsList.append(
$('<li>')
.html(subHtml)
.on("click", () => this.jumpToHighlightsList(findSubStr, hltIndex))
);
}
hlLiCount++;
} else {
@@ -179,6 +264,7 @@ export default class HighlightsListWidget extends RightPanelWidget {
}
}
prevEndIndex = endIndex;
prevSubHtml = subHtml;
}
return {
$highlightsList,
@@ -232,9 +318,17 @@ export default class HighlightsListWidget extends RightPanelWidget {
this.noteContext.viewScope.highlightsListTemporarilyHidden = true;
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
appContext.triggerEvent("reEvaluateHighlightsListWidgetVisibility", { noteId: this.noteId });
}
async entitiesReloadedEvent({loadResults}) {
async showHighlightsListWidgetEvent({ noteId }) {
if (this.noteId === noteId) {
await this.refresh();
this.triggerCommand('reEvaluateRightPaneVisibility');
}
}
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteContentReloaded(this.noteId)) {
await this.refresh();
} else if (loadResults.getAttributeRows().find(attr => attr.type === 'label'

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js";
@@ -27,7 +28,7 @@ const TPL = `<div class="mermaid-widget">
</style>
<div class="mermaid-error alert alert-warning">
<p><strong>The diagram could not be displayed. See <a href="https://mermaid-js.github.io/mermaid/#/flowchart?id=graph">help and examples</a>.</strong></p>
<p><strong>${t('mermaid.diagram_error')}</strong></p>
<p class="error-content"></p>
</div>

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import SpacedUpdate from "../services/spaced_update.js";
@@ -155,7 +156,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
getTypeWidget() {
if (!this.typeWidgets[this.type]) {
throw new Error(`Could not find typeWidget for type '${this.type}'`);
throw new Error(t(`note_detail.could_not_find_typewidget`, { type: this.type }));
}
return this.typeWidgets[this.type];

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import attributeService from "../services/attributes.js";
import server from "../services/server.js";
@@ -66,12 +67,12 @@ const TPL = `
}
</style>
<button class="btn dropdown-toggle note-icon" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Change note icon"></button>
<button class="btn dropdown-toggle note-icon" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="${t("note_icon.change_note_icon")}"></button>
<div class="dropdown-menu" aria-labelledby="note-path-list-button" style="width: 610px;">
<div class="filter-row">
<span>Category:</span> <select name="icon-category" class="form-control"></select>
<span>${t("note_icon.category")}</span> <select name="icon-category" class="form-control"></select>
<span>Search:</span> <input type="text" name="icon-search" class="form-control" />
<span>${t("note_icon.search")}</span> <input type="text" name="icon-search" class="form-control" />
</div>
<div class="icon-list"></div>
@@ -150,7 +151,7 @@ export default class NoteIconWidget extends NoteContextAwareWidget {
this.$iconList.append(
$(`<div style="text-align: center">`)
.append(
$('<button class="btn btn-sm">Reset to default icon</button>')
$(`<button class="btn btn-sm">${t("note_icon.reset-default")}</button>`)
.on('click', () => this.getIconLabels()
.forEach(label => attributeService.removeAttributeById(this.noteId, label.attributeId))
)

View File

@@ -6,6 +6,7 @@ import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import linkContextMenuService from "../menus/link_context_menu.js";
import utils from "../services/utils.js";
import { t } from "../services/i18n.js";
const esc = utils.escapeHtml;
@@ -30,8 +31,8 @@ const TPL = `<div class="note-map-widget" style="position: relative;">
</style>
<div class="btn-group btn-group-sm map-type-switcher" role="group">
<button type="button" class="btn bx bx-network-chart" title="Link Map" data-type="link"></button>
<button type="button" class="btn bx bx-sitemap" title="Tree map" data-type="tree"></button>
<button type="button" class="btn bx bx-network-chart" title="${t("note-map.button-link-map")}" data-type="link"></button>
<button type="button" class="btn bx bx-sitemap" title="${t("note-map.button-tree-map")}" data-type="tree"></button>
</div>
<div class="style-resolver"></div>

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
@@ -27,7 +28,7 @@ const TPL = `
}
</style>
<input autocomplete="off" value="" placeholder="type note's title here..." class="note-title" tabindex="100">
<input autocomplete="off" value="" placeholder="${t('note_title.placeholder')}" class="note-title" tabindex="100">
</div>`;
export default class NoteTitleWidget extends NoteContextAwareWidget {

View File

@@ -18,6 +18,7 @@ import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import dialogService from "../services/dialog.js";
import shortcutService from "../services/shortcuts.js";
import { t } from "../services/i18n.js";
const TPL = `
<div class="tree-wrapper">
@@ -99,15 +100,15 @@ const TPL = `
<div class="tree-actions">
<button class="tree-floating-button bx bx-layer-minus collapse-tree-button"
title="Collapse note tree"
title="${t("note_tree.collapse-title")}"
data-trigger-command="collapseTree"></button>
<button class="tree-floating-button bx bx-crosshair scroll-to-active-note-button"
title="Scroll to active note"
title="${t("note_tree.scroll-active-title")}"
data-trigger-command="scrollToActiveNote"></button>
<button class="tree-floating-button bx bx-cog tree-settings-button"
title="Tree settings"></button>
<button class="tree-floating-button bx bxs-tree tree-settings-button"
title="${t("note_tree.tree-settings-title")}"></button>
</div>
@@ -116,22 +117,22 @@ const TPL = `
<label class="form-check-label">
<input class="form-check-input hide-archived-notes" type="checkbox" value="">
Hide archived notes
${t("note_tree.hide-archived-notes")}
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input auto-collapse-note-tree" type="checkbox" value="">
Automatically collapse notes
${t("note_tree.automatically-collapse-notes")}
<span class="bx bx-info-circle"
title="Notes will be collapsed after period of inactivity to declutter the tree."></span>
title="${t("note_tree.automatically-collapse-notes-title")}"></span>
</label>
</div>
<br/>
<button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">Save & apply changes</button>
<button class="btn btn-sm btn-primary save-tree-settings-button" type="submit">${t("note_tree.save-changes")}</button>
</div>
</div>
`;

View File

@@ -2,25 +2,26 @@ import server from '../services/server.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';
const NOTE_TYPES = [
{ type: "file", title: "File", selectable: false },
{ type: "image", title: "Image", selectable: false },
{ type: "search", title: "Saved Search", selectable: false },
{ type: "noteMap", mime: '', title: "Note Map", selectable: false },
{ type: "launcher", mime: '', title: "Launcher", selectable: false },
{ type: "doc", mime: '', title: "Doc", selectable: false },
{ type: "contentWidget", mime: '', title: "Widget", selectable: false },
{ type: "file", title: t("note_types.file"), selectable: false },
{ type: "image", title: t("note_types.image"), selectable: false },
{ type: "search", title: t("note_types.saved-search"), selectable: false },
{ type: "noteMap", mime: '', title: t("note_types.note-map"), selectable: false },
{ type: "launcher", mime: '', title: t("note_types.launcher"), selectable: false },
{ type: "doc", mime: '', title: t("note_types.doc"), selectable: false },
{ type: "contentWidget", mime: '', title: t("note_types.widget"), selectable: false },
{ type: "text", mime: "text/html", title: "Text", selectable: true },
{ type: "relationMap", mime: "application/json", title: "Relation Map", selectable: true },
{ type: "mindMap", mime: "application/json", "title": "Mind Map", selectable: true },
{ type: "render", mime: '', title: "Render Note", selectable: true },
{ type: "canvas", mime: 'application/json', title: "Canvas", selectable: true },
{ type: "mermaid", mime: 'text/mermaid', title: "Mermaid Diagram", selectable: true },
{ type: "book", mime: '', title: "Book", selectable: true },
{ type: "webView", mime: '', title: "Web View", selectable: true },
{ type: "code", mime: 'text/plain', title: "Code", selectable: true }
{ type: "text", mime: "text/html", title: t("note_types.text"), selectable: true },
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), selectable: true },
{ type: "mindMap", mime: "application/json", "title": t("note_types.mind-map"), selectable: true },
{ type: "render", mime: '', title: t("note_types.render-note"), selectable: true },
{ 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 },
{ type: "book", mime: '', title: t("note_types.book"), selectable: true },
{ type: "webView", mime: '', title: t("note_types.web-view"), selectable: true },
{ type: "code", mime: 'text/plain', title: t("note_types.code"), selectable: true }
];
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter(nt => !nt.selectable).map(nt => nt.type);
@@ -34,7 +35,7 @@ const TPL = `
overflow-x: hidden;
}
</style>
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle note-type-button">
<span class="note-type-desc"></span>
<span class="caret"></span>
</button>
@@ -46,14 +47,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$widget.on('show.bs.dropdown', () => this.renderDropdown());
this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown");
this.$noteTypeButton = this.$widget.find(".note-type-button");
this.$noteTypeDesc = this.$widget.find(".note-type-desc");
this.$widget.on('click', '.dropdown-item',
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
this.$widget.on('click', '.dropdown-item', () => this.dropdown.toggle());
}
async refreshWithNote(note) {
@@ -62,7 +64,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
this.$noteTypeDesc.text(await this.findTypeTitle(note.type, note.mime));
this.$noteTypeButton.dropdown('hide');
this.dropdown.hide();
}
/** the actual body is rendered lazily on note-type button click */
@@ -91,7 +93,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
.append('<span class="check">&check;</span> ')
.append($('<strong>').text(noteType.title));
}
if (this.note.type === noteType.type) {
$typeLink.addClass("selected");
}
@@ -160,7 +162,7 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
return await dialogService.confirm("It is not recommended to change note type when note content is not empty. Do you want to continue anyway?");
}
async entitiesReloadedEvent({loadResults}) {
async entitiesReloadedEvent({ loadResults }) {
if (loadResults.isNoteReloaded(this.noteId)) {
this.refresh();
}

View File

@@ -1,3 +1,4 @@
import { t } from "../services/i18n.js";
import protectedSessionService from "../services/protected_session.js";
import SwitchWidget from "./switch.js";
@@ -5,11 +6,11 @@ export default class ProtectedNoteSwitchWidget extends SwitchWidget {
doRender() {
super.doRender();
this.$switchOnName.text("Protect the note");
this.$switchOnButton.attr("title", "Note is not protected, click to make it protected");
this.$switchOnName.text(t("protect_note.toggle-on"));
this.$switchOnButton.attr("title", t("protect_note.toggle-on-hint"));
this.$switchOffName.text("Unprotect the note");
this.$switchOffButton.attr("title", "Note is protected, click to make it unprotected");
this.$switchOffName.text(t("protect_note.toggle-off"));
this.$switchOffButton.attr("title", t("protect_note.toggle-off-hint"));
}
switchOn() {

View File

@@ -5,6 +5,7 @@ import froca from "../services/froca.js";
import utils from "../services/utils.js";
import appContext from "../components/app_context.js";
import shortcutService from "../services/shortcuts.js";
import { t } from "../services/i18n.js";
const TPL = `
<div class="quick-search input-group input-group-sm">
@@ -30,13 +31,12 @@ const TPL = `
</style>
<div class="input-group-prepend">
<button class="btn btn-outline-secondary search-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-outline-secondary search-button" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bx-search"></span>
</button>
<div class="dropdown-menu dropdown-menu-left"></div>
</div>
<input type="text" class="form-control form-control-sm search-string" placeholder="Quick search">
</div>
<input type="text" class="form-control form-control-sm search-string" placeholder="${t("quick-search.placeholder")}">
</div>`;
const MAX_DISPLAYED_NOTES = 15;
@@ -44,21 +44,20 @@ const MAX_DISPLAYED_NOTES = 15;
export default class QuickSearchWidget extends BasicWidget {
doRender() {
this.$widget = $(TPL);
this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"));
this.$searchString = this.$widget.find('.search-string');
this.$dropdownMenu = this.$widget.find('.dropdown-menu');
this.$dropdownToggle = this.$widget.find('.search-button');
this.$dropdownToggle.dropdown();
this.$widget.find('.input-group-prepend').on('shown.bs.dropdown', () => this.search());
if(utils.isMobile()) {
this.$searchString.keydown(e =>{
if(e.which === 13) {
if (utils.isMobile()) {
this.$searchString.keydown(e => {
if (e.which === 13) {
if (this.$dropdownMenu.is(":visible")) {
this.search(); // just update already visible dropdown
} else {
this.$dropdownToggle.dropdown('show');
this.dropdown.show();
}
e.preventDefault();
e.stopPropagation();
@@ -70,7 +69,7 @@ export default class QuickSearchWidget extends BasicWidget {
if (this.$dropdownMenu.is(":visible")) {
this.search(); // just update already visible dropdown
} else {
this.$dropdownToggle.dropdown('show');
this.dropdown.show();
}
this.$searchString.focus();
@@ -81,7 +80,7 @@ export default class QuickSearchWidget extends BasicWidget {
});
shortcutService.bindElShortcut(this.$searchString, 'esc', () => {
this.$dropdownToggle.dropdown('hide');
this.dropdown.hide();
});
return this.$widget;
@@ -91,25 +90,25 @@ export default class QuickSearchWidget extends BasicWidget {
const searchString = this.$searchString.val().trim();
if (!searchString) {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
return;
}
this.$dropdownMenu.empty();
this.$dropdownMenu.append('<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span> Searching ...</span>');
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
const {searchResultNoteIds, error} = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
const { searchResultNoteIds, error } = await server.get(`quick-search/${encodeURIComponent(searchString)}`);
if (error) {
this.$searchString.tooltip({
let tooltip = new bootstrap.Tooltip(this.$searchString, {
trigger: 'manual',
title: `Search error: ${error}`,
placement: 'right'
});
this.$searchString.tooltip("show");
tooltip.show();
setTimeout(() => this.$searchString.tooltip("dispose"), 4000);
setTimeout(() => tooltip.dispose(), 4000);
}
const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
@@ -117,15 +116,15 @@ export default class QuickSearchWidget extends BasicWidget {
this.$dropdownMenu.empty();
if (displayedNoteIds.length === 0) {
this.$dropdownMenu.append('<span class="dropdown-item disabled">No results found</span>');
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.no-results")}</span>`);
}
for (const note of await froca.getNotes(displayedNoteIds)) {
const $link = await linkService.createLink(note.noteId, {showNotePath: true});
const $link = await linkService.createLink(note.noteId, { showNotePath: true });
$link.addClass('dropdown-item');
$link.attr("tabIndex", "0");
$link.on('click', e => {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
if (!e.target || e.target.nodeName !== 'A') {
// click on the link is handled by link handling, but we want the whole item clickable
@@ -133,7 +132,7 @@ export default class QuickSearchWidget extends BasicWidget {
}
});
shortcutService.bindElShortcut($link, 'return', () => {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
appContext.tabManager.getActiveContext().setNote(note.noteId);
});
@@ -142,11 +141,12 @@ export default class QuickSearchWidget extends BasicWidget {
}
if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) {
this.$dropdownMenu.append(`<span class="dropdown-item disabled">... and ${searchResultNoteIds.length - MAX_DISPLAYED_NOTES} more results.</span>`);
const numRemainingResults = (searchResultNoteIds.length - MAX_DISPLAYED_NOTES);
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.more-results", { number: numRemainingResults })}</span>`);
}
const $showInFullButton = $('<a class="dropdown-item" tabindex="0">')
.append($('<button class="btn btn-sm">Show in full search</button>'));
.append($(`<button class="btn btn-sm">${t("quick-search.show-in-full-search")}</button>`));
this.$dropdownMenu.append($showInFullButton);
@@ -156,11 +156,11 @@ export default class QuickSearchWidget extends BasicWidget {
shortcutService.bindElShortcut(this.$dropdownMenu.find('.dropdown-item:first'), 'up', () => this.$searchString.focus());
this.$dropdownToggle.dropdown('update');
this.dropdown.update();
}
async showInFullSearch() {
this.$dropdownToggle.dropdown("hide");
this.dropdown.hide();
await appContext.triggerCommand('searchNotes', {
searchString: this.$searchString.val()

View File

@@ -17,12 +17,15 @@ const TPL = `
flex-wrap: wrap;
}
.basic-properties-widget > * {
margin-right: 30px;
.basic-properties-widget > * {
margin-top: 9px;
margin-bottom: 2px;
}
.basic-properties-widget > * > :last-child {
margin-right: 30px;
}
.note-type-container, .editability-select-container {
display: flex;
align-items: center;

View File

@@ -18,7 +18,7 @@ const TPL = `
<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-control form-control-sm">
<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>
</select>

View File

@@ -52,7 +52,7 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
title: t("note_map.title"),
icon: 'bx bx-map-alt'
icon: 'bx bxs-network-chart'
};
}

View File

@@ -71,7 +71,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
if (promotedDefAttrs.length === 0) {
return {show: false};
return { show: false };
}
return {
@@ -167,7 +167,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return;
}
attributeValues = attributeValues.map(attribute => ({value: attribute}));
attributeValues = attributeValues.map(attribute => ({ value: attribute }));
$input.autocomplete({
appendTo: document.querySelector('body'),
@@ -229,9 +229,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
.prop("title", t("promoted_attributes.open_external_link"))
.on('click', () => window.open($input.val(), '_blank'));
$input.after($("<div>")
.addClass("input-group-append")
.append($openButton));
$input.after($openButton);
}
else {
ws.logError(t("promoted_attributes.unknown_label_type", { type: definitionAttr.labelType }));
@@ -244,7 +242,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
if (utils.isDesktop()) {
// no need to wait for this
noteAutocompleteService.initNoteAutocomplete($input, {allowCreatingNotes: true});
noteAutocompleteService.initNoteAutocomplete($input, { allowCreatingNotes: true });
$input.on('autocomplete:noteselected', (event, suggestion, dataset) => {
this.promotedAttributeChanged(event);
@@ -257,7 +255,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
}
}
else {
ws.logError(t(`promoted_attributes.unknown_attribute_type`, {type: valueAttr.type}));
ws.logError(t(`promoted_attributes.unknown_attribute_type`, { type: valueAttr.type }));
return;
}
@@ -346,7 +344,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
this.$widget.find(".promoted-attribute-input:first").focus();
}
entitiesReloadedEvent({loadResults}) {
entitiesReloadedEvent({ loadResults }) {
if (loadResults.getAttributeRows(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) {
this.refresh();
}

View File

@@ -119,7 +119,7 @@ const TPL = `
</button>
<div class="dropdown" style="display: inline-block;">
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<button class="btn btn-sm dropdown-toggle action-add-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bxs-zap"></span>
${t('search_definition.action')}
</button>
@@ -219,7 +219,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
});
this.$widget.on('click', '[data-action-add]', async event => {
this.$widget.find('.action-add-toggle').dropdown('toggle');
bootstrap.Dropdown.getOrCreateInstance(this.$widget.find('.action-add-toggle'));
const actionName = $(event.target).attr('data-action-add');

View File

@@ -13,7 +13,7 @@ const TPL = `
<div style="margin-left: 10px; margin-right: 10px">${t('ancestor.depth_label')}:</div>
<select name="depth" class="form-control d-inline ancestor-depth" style="flex-shrink: 3">
<select name="depth" class="form-select d-inline ancestor-depth" style="flex-shrink: 3">
<option value="">${t('ancestor.depth_doesnt_matter')}</option>
<option value="eq1">${t('ancestor.depth_eq', {count: 1})} (${t('ancestor.direct_children')})</option>
<option value="eq2">${t('ancestor.depth_eq', {count: 2})}</option>

View File

@@ -9,10 +9,10 @@ const TPL = `
</td>
<td class="button-column">
<div class="dropdown help-dropdown">
<span class="bx bx-help-circle icon-action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<span class="bx bx-help-circle icon-action" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
<div class="dropdown-menu dropdown-menu-right p-4">
<p>${t("debug.debug_info")}</p>
<p>${t("debug.access_info")}</p>
${t("debug.access_info")}
</div>
</div>
<span class="bx bx-x icon-action search-option-del"></span>

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