Compare commits

..

208 Commits

Author SHA1 Message Date
Elian Doran 4faf27364f Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop
/ Check Docker build (Dockerfile) (push) Failing after 29s
/ Check Docker build (Dockerfile.alpine) (push) Failing after 41s
/ Build Docker images (Dockerfile, linux/arm/v7) (push) Has been skipped
/ Build Docker images (Dockerfile, linux/arm64) (push) Has been skipped
/ Build Docker images (Dockerfile.alpine, linux/amd64) (push) Has been skipped
/ Merge manifest lists (push) Has been skipped
Release / Make Electron (arm64, map[extension:deb image:ubuntu-latest name:linux]) (push) Failing after 42s
Release / Make Electron (x64, map[extension:deb image:ubuntu-latest name:linux]) (push) Failing after 32s
Release / Build Linux Server x86_64 (push) Failing after 35s
Release / Make Electron (arm64, map[extension:dmg image:macos-latest name:macos]) (push) Has been cancelled
Release / Make Electron (arm64, map[extension:exe image:windows-latest name:windows]) (push) Has been cancelled
Release / Make Electron (x64, map[extension:dmg image:macos-latest name:macos]) (push) Has been cancelled
Release / Make Electron (x64, map[extension:exe image:windows-latest name:windows]) (push) Has been cancelled
2024-10-13 15:45:48 +03:00
Elian Doran 52a6d0b48a Bump version to 0.90.7-beta 2024-10-13 15:45:40 +03:00
Elian Doran cddc9a7b6a i18n: Mark mind map as beta 2024-10-13 14:43:50 +03:00
Elian Doran 75d019863f server: Disable debug mode for i18n 2024-10-13 14:40:07 +03:00
Elian Doran 849a6a3aef electron: Solve server translation loading on macOS 2024-10-13 14:24:46 +03:00
Elian Doran e7378306a2 Merge branch 'develop' of ssh://github.com/TriliumNext/Notes into develop 2024-10-13 13:52:39 +03:00
Elian Doran 1277dfc5d5 electron: Add back server translations 2024-10-13 12:31:37 +03:00
Elian Doran ae680847dc vscode: Pin nightly GitHub Action 2024-10-13 12:28:05 +03:00
Elian Doran a5fd57308a server: Fix translations in standalone build 2024-10-13 12:09:36 +03:00
Elian Doran fa769df7b0 client: Fix table background on dark mode 2024-10-13 11:20:26 +03:00
Elian Doran 8136a2972e Merge pull request #465 from quantum5/pwa-icon-fix
server: fix blurry PWA icon
2024-10-13 10:51:00 +03:00
Elian Doran 8c8c3974f3 Merge pull request #480 from hasecilu/i18n/Spanish_server
i18n: Add more Spanish translations
2024-10-13 10:07:39 +03:00
hasecilu e81bfa3693 i18n: Add more Spanish translations 2024-10-12 17:30:59 -06:00
Elian Doran a857f4816f codemirror: 5.65.15 -> 5.65.18 2024-10-12 20:48:39 +03:00
Elian Doran ade34f9745 client: Use CodeMirror from package.json 2024-10-12 20:41:50 +03:00
Elian Doran fd66cb930d electron: Update electron-forge to latest 2024-10-12 20:22:18 +03:00
Elian Doran 182d9afac1 tools: Update development tools 2024-10-12 20:18:44 +03:00
Elian Doran 4e6ef0be95 i18next: 23.15.1 -> 23.15.2 + http-backend 2024-10-12 20:16:23 +03:00
Elian Doran 88961ea93f mermaid: 11.2.0 -> 11.3.0 2024-10-12 20:14:46 +03:00
Elian Doran a3f2946a17 mind-elixir: 4.1.1 -> 4.1.5 2024-10-12 20:12:45 +03:00
Elian Doran 8ae5f9ea9b electron: Fix translations in packaged builds 2024-10-12 19:40:54 +03:00
Elian Doran 9ec2508f09 i18n: Add more Romanian translations 2024-10-12 18:53:24 +03:00
Elian Doran bae63b08a2 server, electron: Fix crash if locale is undefined 2024-10-12 18:24:10 +03:00
Elian Doran 729a188528 docker: Fix server translation initialization (closes #439) 2024-10-12 10:53:30 +03:00
Elian Doran 2c5a5acffa server: Improve type for keyboard shortcut scope 2024-10-12 10:31:12 +03:00
Elian Doran e45c5f429d server: Fix crash when starting server in Docker due to translations 2024-10-12 10:29:50 +03:00
Elian Doran 5c44ac5ad8 ci: Fix permissions for nightly 2024-10-11 22:15:50 +03:00
Elian Doran b44c2f5ebf ci: Configure nightly deployment 2024-10-11 22:07:22 +03:00
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 0a69189b9b i18n: Reach 100% for Romanian 2024-10-09 22:04:00 +03: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
Quantum d46963e496 server: fix blurry PWA icon 2024-10-08 20:32:17 -04: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
/ Check Docker build (push) Failing after 35s
/ Build Docker images (push) Has been skipped
Release / Make Electron (arm64, map[extension:deb image:ubuntu-latest name:linux]) (push) Failing after 33s
Release / Make Electron (x64, map[extension:deb image:ubuntu-latest name:linux]) (push) Failing after 35s
Release / Build Linux Server x86_64 (push) Failing after 30s
Release / Make Electron (arm64, map[extension:dmg image:macos-latest name:macos]) (push) Has been cancelled
Release / Make Electron (arm64, map[extension:exe image:windows-latest name:windows]) (push) Has been cancelled
Release / Make Electron (x64, map[extension:dmg image:macos-latest name:macos]) (push) Has been cancelled
Release / Make Electron (x64, map[extension:exe image:windows-latest name:windows]) (push) Has been cancelled
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
171 changed files with 4930 additions and 12844 deletions
-153
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
+153 -56
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 }}
+126
View File
@@ -0,0 +1,126 @@
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:
env:
GITHUB_UPLOAD_URL: https://uploads.github.com/repos/TriliumNext/Notes/releases/179589950/assets{?name,label}
GITHUB_RELEASE_ID: 179589950
permissions:
contents: write
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: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
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: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
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: ${{ env.GITHUB_UPLOAD_URL }}
release_id: ${{ env.GITHUB_RELEASE_ID }}
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
View File
@@ -0,0 +1 @@
save-prefix = ''
+2
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)
+5 -1
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"
@@ -17,4 +18,7 @@
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"github-actions.workflows.pinned.workflows": [
".github/workflows/nightly.yml"
],
}
+1 -1
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)以快速了解:
+1 -1
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.
+1 -1
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.
+1 -1
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)をご覧ください:
+8 -2
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
+1 -1
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):
+3 -2
View File
@@ -22,13 +22,14 @@ 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
printf "#!/bin/sh\n./node/bin/node src/main" > $PKG_DIR/trilium.sh
chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r translations $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules
+1 -1
View File
@@ -35,7 +35,7 @@ const copy = async () => {
await fs.copy(file, path.join(DEST_DIR, file));
}
const dirsToCopy = ["images", "libraries", "db"];
const dirsToCopy = ["images", "libraries", "translations", "db"];
for (const dir of dirsToCopy) {
log(`Copying ${dir}`);
await fs.copy(dir, path.join(DEST_DIR, dir));
+2 -2
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)
+3 -3
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
View File
@@ -0,0 +1,4 @@
import { initializeTranslations } from "./src/services/i18n.js";
await initializeTranslations();
await import("./electron.js")
+1 -1
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');
+11 -2
View File
@@ -10,7 +10,13 @@ module.exports = {
overwrite: true,
asar: true,
icon: "./images/app-icons/icon",
extraResource: getExtraResourcesForPlatform(),
extraResource: [
// Moved to root
...getExtraResourcesForPlatform(),
// Moved to resources (TriliumNext Notes.app/Contents/Resources on macOS)
"translations/"
],
afterComplete: [(buildPath, _electronVersion, platform, _arch, callback) => {
const extraResources = getExtraResourcesForPlatform();
for (const resource of extraResources) {
@@ -75,7 +81,10 @@ module.exports = {
function getExtraResourcesForPlatform() {
let resources = ['dump-db/', './bin/tpl/anonymize-database.sql']
let resources = [
'dump-db/',
'./bin/tpl/anonymize-database.sql'
];
const scripts = ['trilium-portable', 'trilium-safe-mode', 'trilium-no-cert-check']
switch (process.platform) {
case 'win32':
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+4 -1
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"
+1197 -1162
View File
File diff suppressed because it is too large Load Diff
+105 -104
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.7-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.18",
"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.2",
"i18next-fs-backend": "2.3.2",
"i18next-http-backend": "2.6.2",
"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.3.0",
"mime-types": "2.1.35",
"mind-elixir": "^4.0.5",
"mind-elixir": "4.1.5",
"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.5.0",
"@electron-forge/maker-deb": "7.5.0",
"@electron-forge/maker-dmg": "7.5.0",
"@electron-forge/maker-squirrel": "7.5.0",
"@electron-forge/maker-zip": "7.5.0",
"@electron-forge/plugin-auto-unpack-natives": "7.5.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.1",
"jsdoc": "4.0.3",
"lorem-ipsum": "2.0.8",
"nodemon": "^3.1.4",
"nodemon": "3.1.7",
"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.3",
"webpack": "5.95.0",
"webpack-cli": "5.1.4"
}
}
+7
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
}
@@ -47,6 +53,7 @@ app.use(cookieParser());
app.use(express.static(path.join(scriptDir, 'public/root')));
app.use(`/manifest.webmanifest`, express.static(path.join(scriptDir, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(scriptDir, 'public/robots.txt')));
app.use(`/icon.png`, express.static(path.join(scriptDir, 'public/icon.png')));
app.use(sessionParser);
app.use(favicon(`${scriptDir}/../images/app-icons/icon.ico`));
+3 -1
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();
+27 -4
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
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();
+3 -3
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";
+4
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())
+22 -6
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
};
};
+27 -26
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);
}
+3 -2
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
});
}
+1 -2
View File
@@ -2,8 +2,7 @@ const CKEDITOR = {"js": ["libraries/ckeditor/ckeditor.js"]};
const CODE_MIRROR = {
js: [
"libraries/codemirror/codemirror.js",
// "node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/lib/codemirror.js",
"node_modules/codemirror/addon/display/placeholder.js",
"node_modules/codemirror/addon/edit/matchbrackets.js",
"node_modules/codemirror/addon/edit/matchtags.js",
+11 -16
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,
+12 -11
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");
+18
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
}
+12 -9
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);
+2 -3
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");
@@ -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();
}
@@ -175,7 +175,7 @@ const editorConfig = {
toolbar: {
items: []
},
placeholder: "Type the labels and relations here",
placeholder: t("attribute_editor.placeholder"),
mention: mentionSetup
};
@@ -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')}
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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();
}
@@ -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 });
}
}
+14 -12
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();
}
@@ -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);
});
+31 -22
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();
}
}
@@ -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");
@@ -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() { }
}
@@ -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();
}
}
}
@@ -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();
}
}
}
+16 -6
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() {
+2 -6
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">
@@ -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'));
}
+4 -10
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());
+6 -10
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 }));
}
}
}
+9 -11
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();
}
}
+8 -17
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">
+5 -6
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));
+3 -6
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>
+11 -9
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
+10 -17
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);
+5 -7
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);
@@ -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();
}
}
@@ -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);
+3 -6
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">
@@ -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();
}
}
@@ -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' });
});
}
+5 -7
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();
});
}
@@ -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();
}
}
@@ -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();
+63 -31
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>")
@@ -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">
@@ -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);
}
+5 -3
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();
}
+9 -14
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("?")
}
}
}
@@ -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() {
@@ -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>
`;
+108 -14
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'
+2 -1
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>
+2 -1
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];
+5 -4
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))
)
+3 -2
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>
+2 -1
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 {
+9 -8
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>
`;
+24 -22
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();
}
@@ -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() {
+25 -25
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()
@@ -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;
@@ -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>
@@ -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'
};
}

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