Compare commits

...

221 Commits

Author SHA1 Message Date
zadam 4913a5a5fa release 0.60.0-beta
Publish Docker image / Push Docker image to multiple registries (push) Failing after 37s
2023-05-18 23:31:57 +02:00
zadam 14dd2b8827 cleanup 2023-05-18 23:05:28 +02:00
zadam 4e9e7eba48 Merge remote-tracking branch 'origin/master' 2023-05-18 13:46:53 +02:00
zadam f3bc604516 Merge branch 'stable'
# Conflicts:
#	src/public/app/widgets/toc.js
2023-05-18 13:46:41 +02:00
zadam bd278e291a Merge pull request #3948 from soulsands/tooltip
Normalize tooltip style
2023-05-18 13:42:18 +02:00
zadam 074820dae5 cleanup of find in text implementation 2023-05-18 13:41:32 +02:00
zadam 1089e84f56 Merge pull request #3947 from SiriusXT/master
FindInText: Automatically search the selected text
2023-05-18 13:27:37 +02:00
baiyongjie ed3d81ce62 normalize tooltip style 2023-05-18 16:41:07 +08:00
SnnBcd xt 57aa0a0fe1 findInText: Automatically search the selected text 2023-05-18 07:29:21 +00:00
zadam 88a961180f cleanup note open custom 2023-05-17 23:57:32 +02:00
zadam bd8429a0ba Merge pull request #3944 from SiriusXT/toc
toc scrolls smoothly
2023-05-17 23:13:41 +02:00
zadam da348075e8 Merge pull request #3923 from soulsands/fix-include
fix included note display
2023-05-17 23:06:50 +02:00
SnnBcd xt d24d3af87d Other elements in the scroll-container (including text search) scroll smoothly 2023-05-17 12:03:38 +00:00
SnnBcd xt 085c0e76ca toc scrolls smoothly 2023-05-17 10:18:58 +00:00
zadam d7d98b29a7 Merge pull request #3940 from SiriusXT/open-note-custom
Open note custom
2023-05-16 23:55:17 +02:00
zadam cb3b03537c Merge pull request #3938 from meichthys/keyboardshortcut
Change KeybaordLauncher to KeyboardShortcut
2023-05-16 22:37:00 +02:00
SnnBcd xt 7c7bea5e3a Open notes with custom software. 2023-05-16 14:01:55 +00:00
SnnBcd xt 9fc474504b Open notes with custom software 2023-05-16 11:57:28 +00:00
SnnBcd xt 412fd105c9 open-note-custom-test 2023-05-16 06:22:15 +00:00
MeIchthys 470a0c7b21 Update launchbar_command_launcher.html to refer to shortcuts 2023-05-15 10:21:40 -04:00
MeIchthys 9968cba348 singluarify 'mirrors' 2023-05-15 10:15:14 -04:00
MeIchthys aee520a209 Change keyboardLauncher to keyboardShortcut 2023-05-15 10:12:43 -04:00
MeIchthys cf4201b084 update keyboardlauncher to keyboardShortcut 2023-05-15 10:11:07 -04:00
zadam 6089a369e3 added missing font files #3933 2023-05-13 16:29:18 +02:00
zadam 092db03729 update montserrat for better cyrillic support, fixes #3933 2023-05-13 16:28:32 +02:00
zadam 239c14a5df fix search in "view source", closes #3929 2023-05-13 16:00:41 +02:00
zadam 0518cdbe8e Merge pull request #3928 from meichthys/auto-flex-note-title
Auto flex note title in tree
2023-05-13 15:42:12 +02:00
MeIchthys 4849dacd52 auto flex note title in tree 2023-05-12 10:14:34 -04:00
baiyongjie 7cf47520d4 fix included note display 2023-05-11 10:54:20 +08:00
baiyongjie 8eb0a4e1cb fix cursor position when Jumping from note to included note
(cherry picked from commit 6a9aa5eeda)
2023-05-11 00:22:06 +02:00
zadam ad2a7a2da3 Merge pull request #3920 from soulsands/fix-include
fix cursor position when Jumping from note to included note
2023-05-11 00:21:21 +02:00
baiyongjie 6a9aa5eeda fix cursor position when Jumping from note to included note 2023-05-10 18:08:25 +08:00
zadam 04524ae8e4 Merge pull request #3918 from meichthys/ribbon_more_obvious
Make active ribbon item more obvious
2023-05-09 23:46:45 +02:00
zadam 04caba9f5b fix parsing the authentication header with password containing a colon, closes #3916 2023-05-09 23:44:43 +02:00
zadam cc1f831a6a don't allow setting image quality to empty value, #3894 2023-05-09 23:32:06 +02:00
zadam 331d280075 TOC scrolls smoothly (on readonly), #3911 2023-05-09 23:14:56 +02:00
MeIchthys 46ed61d38a Make active ribbon item more obvious 2023-05-09 16:29:04 -04:00
zadam c4a2ff5fa1 fix hamburger icon in canvas, #3780 2023-05-07 12:09:38 +02:00
zadam 5676ff9b2e Merge pull request #3909 from mm21/etapi-generated-client
Enable generated etapi client
2023-05-07 10:50:05 +02:00
zadam aa8c0fdad8 Merge pull request #3908 from mm21/etapi-attribute-position
Allow etapi to update attribute fields isInheritable, position
2023-05-07 10:48:25 +02:00
mm21 d09132dd60 Revert path param changes for POST 2023-05-07 00:32:42 +00:00
mm21 ec662d01d0 Revert "Update etapi test"
This reverts commit 4aeecfdfa9.
2023-05-07 00:18:39 +00:00
mm21 ad0b98e2a1 Omit attributeId/branchId path params from postAttribute/postBranch in etapi.openapi.yaml 2023-05-07 00:18:28 +00:00
mm21 3cafad3bf4 Remove isInheritable from PATCH 2023-05-06 23:42:48 +00:00
zadam fbea3ed94f Merge pull request #3901 from Nriver/master
customize search engine
2023-05-06 23:02:12 +02:00
zadam 0ab18e3069 Merge pull request #3897 from contributor/feature/ZipExportDocumentTitle
zip export/import improvements
2023-05-06 22:52:05 +02:00
mm21 4aeecfdfa9 Update etapi test 2023-05-05 18:41:29 +00:00
mm21 79d4dcf5f1 Enable generated etapi client 2023-05-05 18:33:26 +00:00
mm21 26a6ef8c03 Allow etapi to update attribute fields isInheritable, position 2023-05-05 18:27:26 +00:00
contributor bb9631476e bump metaFile format version 2023-05-04 19:56:11 +03:00
contributor 386e8dd32e zip import - remove html tags added by Trilium 2023-05-04 19:56:02 +03:00
Nriver 61616f708f add default options for search engine options 2023-05-04 18:01:51 +08:00
Nriver 8ce499e958 add runtime check for search engine options 2023-05-04 16:20:09 +08:00
Nriver 22587ee6b5 customize search engine 2023-05-04 14:57:33 +08:00
zadam 5ea8226996 Merge pull request #3777 from PhilippOesch/#3689
change downdown border to light border
2023-05-03 23:25:35 +02:00
contributor bb8fd2b054 zip export - set note title as document.title 2023-05-03 16:19:28 +03:00
zadam c9aab9b479 Merge pull request #3893 from mirrorcult/2023-05-01/ensure-note-return-branch
Return created/existing branch in `EnsureNoteIsPresentInParent`
2023-05-02 21:12:43 +02:00
Kara 53c87f3d25 Return created/existing branch in EnsureNoteIsPresentInParent 2023-05-01 12:41:22 -05:00
zadam 4f1fafdf75 Merge pull request #3870 from soulsands/perf-count
perf: improve descendant count performance
2023-04-28 21:53:41 +02:00
zadam 0896000fcd Merge branch 'stable' 2023-04-24 20:21:22 +02:00
Castor e007aba067 Update note_map.js 2023-04-24 13:43:19 +08:00
zadam a6eda2479f Merge pull request #3871 from soulsands/config-debug
chore: debugger config for vscode developers
2023-04-23 22:32:29 +02:00
zadam eff567ee48 wrong password login screen should return 401 so that it counts to the rate limiter, fixes #3867 2023-04-23 22:22:05 +02:00
zadam d3c2c314ac Merge pull request #3866 from soulsands/custom-match
fix:  match custom path strictly
2023-04-23 22:12:55 +02:00
baiyongjie 89681977b5 perf: improve descendant count performance 2023-04-23 15:36:34 +08:00
baiyongjie e10a7ea9a6 chore: debugger config for vscode developers 2023-04-23 15:26:46 +08:00
soulsands c3c6d73bb2 fix: match custom path strictly 2023-04-22 19:37:49 +08:00
zadam e24d5a6f83 collapse command will collapse the whole tree, including the current active note path, fixes #3664 2023-04-19 21:24:51 +02:00
zadam 8725f7cfb2 Merge branch 'stable'
# Conflicts:
#	src/services/build.js
2023-04-17 22:26:01 +02:00
zadam 83d8908ee8 release 0.59.4
Publish Docker image / Push Docker image to multiple registries (push) Failing after 34s
2023-04-17 21:40:35 +02:00
zadam af9ef83742 Merge pull request #3827 from soulsands/eslint-format
Eslint  and formating enhancement
2023-04-17 20:44:47 +02:00
zadam 3eafdadd1f Merge branch 'master' into eslint-format 2023-04-17 20:44:34 +02:00
zadam 1d3272e9f8 fix displaying error message in mermaid, closes #3841 2023-04-16 23:41:40 +02:00
zadam 447b8f0f0c Merge pull request #3848 from soulsands/auto-reload
chore: dev auto reload
2023-04-16 23:30:23 +02:00
zadam aaf6e3bace Merge pull request #3847 from soulsands/perf-build
Perf: building improvement
2023-04-16 23:27:55 +02:00
zadam 44c61b8e7d Merge pull request #3846 from soulsands/highlighter
change match-hightlighter config
2023-04-16 23:27:24 +02:00
zadam 8226f62ded download offline images from libreoffice, fixes #3842 2023-04-16 23:11:18 +02:00
zadam d3b17f586d Merge pull request #3839 from soulsands/activate
Activating after removal of subContexts conforms to mainContexts
2023-04-16 22:11:32 +02:00
zadam a556ba0959 Merge pull request #3838 from soulsands/patch-1
chore: remove install in script switch-electron
2023-04-16 22:09:52 +02:00
zadam ad40cd72ff Merge pull request #3833 from soulsands/es-version
fix: upgrade version to support top level await
2023-04-16 20:51:17 +02:00
zadam 9a22c3fce7 Merge pull request #3837 from soulsands/fix-tree
fix: correct moving active node
2023-04-16 20:50:11 +02:00
zadam 63e044ffdf refactoring 2023-04-16 11:28:24 +02:00
zadam 2e794ee38f fixes 2023-04-16 09:26:52 +02:00
zadam a1d4e062ed refactoring of "some path" WIP 2023-04-16 09:22:24 +02:00
Castor d2d2a28885 Update ws.js 2023-04-15 19:42:09 +08:00
baiyongjie ead4242735 chore: dev auto reload 2023-04-15 17:58:40 +08:00
baiyongjie 30a8c6b579 perf: building improvement 2023-04-15 17:45:10 +08:00
baiyongjie 25df8f9c52 change match-hightlighter config 2023-04-15 11:01:24 +08:00
baiyongjie 65d2389b2e update eslint config & fix lint 2023-04-15 09:57:50 +08:00
zadam 7aa26580ba refactoring of "some path" WIP 2023-04-15 00:06:13 +02:00
zadam e4376bb9f2 isRoot check 2023-04-14 20:24:28 +02:00
soulsands 0c08126752 activating after removal of subContexts conforms to mainContexts 2023-04-13 21:16:10 +08:00
Castor ea64adc2f9 chore: remove install in script switch-electron
hi.
No need to install since it's already in position.
2023-04-13 17:56:28 +08:00
baiyongjie ad8ec68443 fix: correct active tree node after moving an clone node 2023-04-13 11:05:53 +08:00
baiyongjie 5e5fe2ccf3 fix: upgrade version to support top level await 2023-04-12 10:05:26 +08:00
soulsands 2a34c8fa66 Merge remote-tracking branch 'up/master' into eslint-format 2023-04-11 01:02:07 +08:00
zadam 5e88e24693 package upgrades 2023-04-10 18:50:55 +02:00
soulsands 988c5c6a25 chore: config 2023-04-11 00:37:38 +08:00
soulsands d98c46a275 chore: share editor config 2023-04-11 00:21:24 +08:00
soulsands 185b206627 chore: husky 2023-04-11 00:21:24 +08:00
soulsands cfb04f7d8a chore: add eslint config 2023-04-11 00:21:24 +08:00
zadam dba0c9f373 Merge branch 'stable'
# Conflicts:
#	src/services/build.js
2023-04-09 23:30:41 +02:00
zadam ee60652746 eslint config 2023-04-09 23:08:24 +02:00
zadam fbc79eae62 Merge pull request #3818 from soulsands/fix-lint
Fix lint
2023-04-09 22:54:02 +02:00
zadam b356a634d1 Merge pull request #3817 from soulsands/add-font
feat: add Microsoft YaHei font (for Chinese)
2023-04-09 22:47:46 +02:00
zadam b2ed5a01b3 Merge pull request #3816 from soulsands/fix-attr-ui
fix:don't break words for CJK text
2023-04-09 22:47:02 +02:00
zadam 839b172b92 fix duplicating subtree with internal links, closes #3813 2023-04-09 22:45:31 +02:00
soulsands 745e120a0b fix: remove unused variable 2023-04-08 21:13:37 +08:00
soulsands a0ac603260 fix:Remove duplicates in character class 2023-04-08 21:07:48 +08:00
soulsands 181ddce887 fix: remove misleading await 2023-04-08 20:49:25 +08:00
soulsands 50043f889b fix: remove duplication 2023-04-08 20:00:42 +08:00
soulsands 49d1c5140e fix: equal 2023-04-08 19:54:59 +08:00
soulsands ab7a07a318 fix: remove extra params 2023-04-08 19:51:39 +08:00
soulsands 306e7abb47 feat: add Microsoft YaHei font (for Chinese) 2023-04-08 18:41:18 +08:00
soulsands cec4dfa6c3 fix:don't break words for CJK text 2023-04-08 14:56:37 +08:00
zadam 21813a7b83 Merge pull request #3814 from soulsands/persistent-cache
Perf: add persistent http cache in prod
2023-04-07 21:48:00 +02:00
zadam 293573a0cd don't update attribute detail while composing CJK characters, fixes #3812 2023-04-07 21:45:27 +02:00
soulsands 8695228537 perf: add persistent http cache in prod 2023-04-07 19:55:18 +08:00
zadam 24866a3e25 fix click events propagating from context menu being closed, fixes #3805
(cherry picked from commit e871edc8f3)
2023-04-06 23:16:32 +02:00
zadam e871edc8f3 fix click events propagating from context menu being closed, fixes #3805 2023-04-06 23:05:03 +02:00
zadam 517f1992a1 remove fallback resize window handlers, such old browsers are not supported 2023-04-06 21:30:37 +02:00
zadam e6adf3a522 Merge pull request #3808 from soulsands/fix-resize
Fix: correct note-map resizing
2023-04-06 21:28:01 +02:00
zadam 32e9fd291d promotes attributes tab should be always visible when available, fixes #3806 2023-04-06 21:25:13 +02:00
zadam 8229a97ffb capitalizing ribbon widget names #3806 2023-04-06 21:20:19 +02:00
soulsands 6612a3a550 fix: correct note-map resizing 2023-04-06 21:06:08 +08:00
zadam c44be53673 awaiting on triggered events/commands in the frontend API, fixes #3799 2023-04-05 22:07:08 +02:00
zadam d100b0dc07 allow #newNotesOnTop=false to negate previous setting, closes #3796 2023-04-03 21:08:32 +02:00
zadam a0e9ac73c9 Merge remote-tracking branch 'origin/stable' into stable
# Conflicts:
#	src/services/build.js
2023-04-03 21:00:02 +02:00
zadam 2e9ce962df Merge pull request #3695 from Nriver/master
add natural sort for notes, introduce new label #sortNatural
2023-03-30 23:49:48 +02:00
Nriver 93ad83d2ae add #sortLocale label for natural sort. rephrase feature description. 2023-03-30 11:06:10 +08:00
zadam 72b1cc4d89 fixed loading of parent to froca when e.g. sharing (cloning into not yet loaded _share parent) 2023-03-29 23:16:45 +02:00
zadam e70cca4736 fix trailing slash in shared note in IE, closes #3782 2023-03-29 23:07:47 +02:00
zadam d354c91d7c sort icons by how much they are used, #3784 2023-03-29 22:40:50 +02:00
zadam 2d7b5e4aa2 Merge branch 'stable'
# Conflicts:
#	src/services/build.js
2023-03-29 22:37:44 +02:00
zadam b542a28673 Merge branch 'stable' 2023-03-27 23:35:40 +02:00
zadam ed52919f9e release 0.59.3
Publish Docker image / Push Docker image to multiple registries (push) Failing after 42s
2023-03-27 23:06:14 +02:00
zadam 45a74a046c release 0.59.3 2023-03-27 23:03:27 +02:00
zadam 3192531d4c don't trigger menu items on right click, #3764 2023-03-27 22:49:41 +02:00
Philipp Oeschger 351bb760ae change downdown border to light border 2023-03-27 01:42:50 +02:00
zadam 79a6baca6f adjust logo sizing/positioning 2023-03-26 23:23:13 +02:00
zadam f9645e8971 Merge branch 'stable' 2023-03-26 23:11:47 +02:00
zadam 7611c83148 Merge pull request #3773 from mm21/master
Add svg icons
2023-03-26 23:11:04 +02:00
zadam 9881e6de3e fix hangup on highlighting empty tokens, closes #3772 2023-03-26 23:09:15 +02:00
Matt 53b4f0e0d5 Add svg icons 2023-03-26 19:29:52 +00:00
zadam 9a645c24ca Merge pull request #3771 from eltociear/add-readme_ja
Add Japanese README
2023-03-26 21:10:21 +02:00
zadam 682d821b67 Merge pull request #3766 from meichthys/patch-7
Move .tree-actions left to accomodate wider windows scrollbar and adjust boarder
2023-03-26 20:58:59 +02:00
zadam 71afffaac1 Merge pull request #3765 from meichthys/patch-5
Add bottom border to note-book-header
2023-03-26 20:58:12 +02:00
zadam a099876088 fix escaping in sql console results 2023-03-26 20:50:37 +02:00
Ikko Eltociear Ashimine cf904a874e Add links 2023-03-26 20:34:46 +09:00
Ikko Eltociear Ashimine 5a32a7ce18 minor fix 2023-03-26 20:33:59 +09:00
Ikko Eltociear Ashimine 6d6221636c Add Japanese README 2023-03-26 20:25:17 +09:00
MeIchthys 8a4caaf6fb Add border to tree-actions section and adjust padding 2023-03-24 15:17:17 -04:00
MeIchthys fa6d1879dd Move .tree-actions left to accomodate wider windows scrollbar 2023-03-24 14:46:09 -04:00
MeIchthys 874b6c71a3 Add bottom border to note-book-header 2023-03-24 11:29:16 -04:00
zadam cd72ea524e promoted and inherited attributes should be shown grouped based on the owning note, #3761 2023-03-23 23:47:28 +01:00
zadam 56e8506cb9 Merge pull request #3756 from meichthys/note-book-card-margin
Use same margin all around note-book-card
2023-03-23 23:20:50 +01:00
MeIchthys c86be990d6 Use same margin all around note-book-card 2023-03-21 16:19:09 -04:00
zadam 6b1f9d3243 add #newNotesOnTop, closes #3734
(cherry picked from commit d31b5ac99f)
2023-03-19 22:27:12 +01:00
dymani 117f1101e4 Fix typo in backend API createOrUpdateLauncher()
(cherry picked from commit b3f47bb2b6)
2023-03-19 22:27:12 +01:00
zadam 4284116280 Merge pull request #3736 from dymani/master
Fix typo in backend API createOrUpdateLauncher()
2023-03-19 22:26:16 +01:00
zadam d31b5ac99f add #newNotesOnTop, closes #3734 2023-03-19 22:23:58 +01:00
dymani b3f47bb2b6 Fix typo in backend API createOrUpdateLauncher() 2023-03-18 10:18:34 +08:00
zadam 64d8c7a657 add a check for the hidden note existence, #3728 2023-03-17 22:14:50 +01:00
zadam 4c3fcc3ea6 fix sanitization of autocomplete against XSS 2023-03-17 22:04:49 +01:00
zadam 5ad47d3866 fix cache invalidation upon note title change 2023-03-17 20:44:26 +01:00
zadam 147160ed45 fix dead references in consistency_checks.js 2023-03-16 13:30:33 +01:00
zadam bfbb531863 Merge pull request #3719 from meichthys/patch-sqlconsole
Fixes #3718
2023-03-15 16:41:07 +01:00
MeIchthys 48ba73e06d Fixes #3718 2023-03-15 09:56:26 -04:00
zadam c922322ed3 Merge pull request #3152 from spasche/scrollbar-color
use same variable as Firefox for scrollbar thumb
2023-03-14 22:16:39 +01:00
zadam 53aaffd0f5 Merge pull request #3652 from eliandoran/fix/infinite_loop_autocompletion
Infinite loop when searching for '' in Jump to note
2023-03-14 22:13:40 +01:00
zadam 63a70f2ffa release 0.59.2
Publish Docker image / Push Docker image to multiple registries (push) Failing after 43s
2023-03-14 21:15:08 +01:00
zadam d8e9086bde Merge pull request #3710 from meichthys/patch-3
Capitalize Ribbon Widget titles for consistency
2023-03-14 21:12:38 +01:00
zadam af4470ead1 Merge pull request #3709 from PJB3005/23-03-14-mermaid-code-mode
Avoid EditableCode inheriting mode from previous notes.
2023-03-14 21:12:01 +01:00
zadam 7cda81ec68 fix default keyboard shortcuts for mac forward, back, closes #3708 2023-03-14 21:07:14 +01:00
MeIchthys b1153f8d01 Capitalize Inherited Attributes for consistency 2023-03-14 16:01:08 -04:00
MeIchthys 54e21bf249 Capitalize Owned Attributes title 2023-03-14 15:58:40 -04:00
Pieter-Jan Briers eebf329983 Avoid EditableCode inheriting mode from previous notes.
Previously, if CodeMirror doesn't support a mode explicitly the mode wouldn't be updated on refresh. This means editors like Mermaid would get their mode "inherited" from the previous mode you had open.

Now the mode is set to text/plain if CodeMirror doesn't support the note's MIME type.

P.S. some of the mermaid diagrams in the demo data have their MIME type incorrectly set to text/plain rather than text/mermaid, so it doesn't reproduce on all diagrams in the demo data.
2023-03-14 20:56:12 +01:00
zadam 7b1d6c3937 Merge pull request #3685 from holoword/holoword/issue3677
“Docker non-root healthcheck”
2023-03-14 20:44:35 +01:00
holo e6803e9a9b “Docker non-root healthcheck” 2023-03-15 00:01:25 +00:00
zadam 0b24011468 docker_healthcheck.js should not create log files, #3677 2023-03-14 20:32:35 +01:00
zadam 2699c995ee Merge pull request #3701 from meichthys/patch-2
Add `onenote` Scheme to html sanitizer
2023-03-14 20:23:21 +01:00
MeIchthys 697f6ee2a9 Add onenote Scheme to html sanitizer 2023-03-13 10:36:25 -04:00
zadam 35429f6a93 Merge pull request #3693 from meichthys/patch-1
📝Emojify Readme
2023-03-12 23:10:43 +01:00
zadam 29eae4eeb6 fixed jsdoc 2023-03-12 23:05:54 +01:00
zadam 8de67b6945 small fixes / refactoring 2023-03-12 21:37:13 +01:00
zadam 4f63284d41 Merge pull request #3699 from new-sashok724/unix-sockets
Support listening on the unix socket
2023-03-12 21:31:37 +01:00
zadam 0c806024fb added some extra allowed URI schemes, fixes #3692 2023-03-12 21:20:34 +01:00
Сашка724ая 9a08aa2ab5 Fix healthcheck and some messages 2023-03-11 20:57:16 +11:00
Сашка724ая 02f218389b support listening on unix sockets 2023-03-11 20:21:32 +11:00
Nriver d3ec9f022c add natural sort for notes, introduce new label #sortNatural 2023-03-10 14:35:36 +08:00
MeIchthys 4cd54e5a9a Emojify Readme 2023-03-09 11:51:13 -05:00
zadam 6b4800d2d6 port error clarifications 2023-03-08 07:31:10 +01:00
zadam f60e4a1355 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	package-lock.json
2023-03-07 23:07:06 +01:00
zadam 03897c5c9d use more precise detection of a mobile browser, fixes #3680 2023-03-07 23:06:55 +01:00
zadam ef90f1ff3f Merge pull request #3676 from jrao1/allow-labels-when-adding-notes-via-clipper
Allow arbitrary labels to be added via web clipper
2023-03-07 22:53:11 +01:00
zadam 177a67f59b content null check in full text search, #3672 2023-03-06 23:23:56 +01:00
zadam 1e551581f8 content null check in full text search, #3672 2023-03-06 23:23:32 +01:00
zadam 62c2547557 #run should respect inheritance, fixes #3669 2023-03-06 23:00:29 +01:00
J Rao 2389ab30f8 sanitize label value 2023-03-06 21:28:09 +08:00
J Rao fdffc27bb6 Allow arbitrary labels to be added via web clipper 2023-03-06 21:21:09 +08:00
zadam 540d19c67c fix import of ZIP with images with no meta file, closes #3649 2023-03-02 08:44:55 +01:00
zadam 46eebdc7aa Update README.md 2023-03-01 10:48:34 +01:00
zadam 700c6ffc75 Update README.md 2023-03-01 10:48:11 +01:00
zadam 6b013c05cc added awesome-trilium to README 2023-03-01 10:47:27 +01:00
zadam f541bf186c release 0.59.1
Publish Docker image / Push Docker image to multiple registries (push) Failing after 34s
2023-02-28 23:39:34 +01:00
zadam 9eb3075f65 Merge remote-tracking branch 'origin/master' into next59
# Conflicts:
#	bin/tpl/anonymize-database.sql
2023-02-28 23:25:51 +01:00
zadam e2b8dfe96a fix autocomplete showing hidden paths, closes #3654 2023-02-28 23:23:48 +01:00
zadam f883fde74a small search refactorings 2023-02-28 23:23:17 +01:00
zadam ae6f2624a0 Merge pull request #3650 from eliandoran/fix/vertical_align_table_cells
Centered vertical align does not seem to work on merged table cells
2023-02-28 00:02:29 +01:00
zadam ee2953a5e1 fix display of note revision content, #3637 2023-02-27 22:58:02 +01:00
zadam ec8ed65feb submenu choice of template should override child: settings, fixes #3628 2023-02-27 21:07:32 +01:00
zadam b70699ce1c make the active fancytree selector more specific than the normal color setting 2023-02-27 21:01:25 +01:00
Elian Doran 5820f874fc Fix infinite loop in autocompletion
In some cases, the token used for highlighting autocompletion gets to be
empty, resulting in an infinte loop. One such case is searching for `''`
(without the backticks).
2023-02-27 01:00:30 +02:00
Elian Doran 44c0ecdbfa Fix middle vertical align on table cells
For merged cells, middle vertical alignment would not work properly (it
was either aligned to the top or bottom).
2023-02-26 22:45:58 +02:00
zadam 7be9db8b84 fix ctrl+click opening note twice, closes #3630 2023-02-22 22:59:31 +01:00
zadam 69d7684b0d fix refreshing option cache on sync, closes #3623 2023-02-22 22:10:41 +01:00
zadam a88582c610 Update README-ZH_CN.md 2023-02-21 10:38:37 +01:00
zadam 3fd0c01c05 Update README.ru.md 2023-02-21 10:38:12 +01:00
zadam c16537aecf Update README.md 2023-02-21 10:37:53 +01:00
zadam 79f1eb6e5e improvements in the anonymization script 2023-02-20 21:21:32 +01:00
Sylvain Pasche a904e82d72 use same variable as Firefox for scrollbar thumb
Use --active-item-background-color variable for scrollbar thumb
background color on WebKit browsers (which is already used for Firefox).
This should improve visibility of the scrollbar.
2022-09-21 23:29:17 +02:00
154 changed files with 4047 additions and 1409 deletions
+7
View File
@@ -0,0 +1,7 @@
node_modules
dist
bin
docs
libraries
coverage
play
+213
View File
@@ -0,0 +1,213 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
},
// plugins: ['prettier'], // to be activated
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc', 'prettier'],
overrides: [
{
files: ['*.json', '*.json5', '*.jsonc'],
parser: 'jsonc-eslint-parser',
},
{
files: ['package.json'],
parser: 'jsonc-eslint-parser',
rules: {
'jsonc/sort-keys': [
'off',
{
pathPattern: '^$',
order: [
'name',
'version',
'private',
'packageManager',
'description',
'type',
'keywords',
'homepage',
'bugs',
'license',
'author',
'contributors',
'funding',
'files',
'main',
'module',
'exports',
'unpkg',
'jsdelivr',
'browser',
'bin',
'man',
'directories',
'repository',
'publishConfig',
'scripts',
'peerDependencies',
'peerDependenciesMeta',
'optionalDependencies',
'dependencies',
'devDependencies',
'engines',
'config',
'overrides',
'pnpm',
'husky',
'lint-staged',
'eslintConfig',
],
},
{
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
order: { type: 'asc' },
},
],
},
},
],
globals: {
$: true,
jQuery: true,
glob: true,
log: true,
EditorWatchdog: true,
baseApiUrl: true,
// \src\share\canvas_share.js
React: true,
appState: true,
ExcalidrawLib: true,
elements: true,
files: true,
ReactDOM: true,
// src\public\app\widgets\type_widgets\relation_map.js
jsPlumb: true,
panzoom: true,
logError: true,
// src\public\app\widgets\type_widgets\image.js
WZoom: true,
// \src\public\app\widgets\type_widgets\read_only_text.js
renderMathInElement: true,
// \src\public\app\widgets\type_widgets\editable_text.js
BalloonEditor: true,
CKEditorInspector: true,
// \src\public\app\widgets\type_widgets\editable_code.js
CodeMirror: true,
// \src\public\app\services\resizer.js
Split: true,
// \src\public\app\services\note_content_renderer.js
mermaid: true,
// src\public\app\services\frontend_script_api.js
dayjs: true,
// \src\public\app\widgets\dialogs\markdown_import.js
commonmark: true,
// \src\public\app\widgets\note_map.js
ForceGraph: true,
// \src\public\app\setup.js
ko: true,
syncInProgress: true,
// src\public\app\services\utils.js
logInfo: true,
__non_webpack_require__: true,
//
},
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
rules: {
// eslint:recommended
'no-unused-vars': 'off',
'linebreak-style': 'off',
'no-useless-escape': 'off',
'no-empty': 'off',
'no-constant-condition': 'off',
'getter-return': 'off',
'no-cond-assign': 'off',
'no-async-promise-executor': 'off',
'no-extra-semi': 'off',
'no-inner-declarations': 'off',
// prettier
'prettier/prettier': ['off', { endOfLine: 'auto' }],
// airbnb-base
'no-console': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off',
'global-require': 'off',
'no-use-before-define': 'off',
'no-await-in-loop': 'off',
radix: 'off',
'import/order': 'off',
'import/no-extraneous-dependencies': 'off',
'prefer-destructuring': 'off',
'no-shadow': 'off',
'no-new': 'off',
'no-restricted-syntax': 'off',
strict: 'off',
'class-methods-use-this': 'off',
'no-else-return': 'off',
'import/no-dynamic-require': 'off',
'no-underscore-dangle': 'off',
'prefer-template': 'off',
'consistent-return': 'off',
'no-continue': 'off',
'object-shorthand': 'off',
'one-var': 'off',
'prefer-const': 'off',
'spaced-comment': 'off',
'no-loop-func': 'off',
'arrow-body-style': 'off',
'guard-for-in': 'off',
'no-return-assign': 'off',
'dot-notation': 'off',
'func-names': 'off',
'import/no-useless-path-segments': 'off',
'default-param-last': 'off',
'prefer-arrow-callback': 'off',
'no-unneeded-ternary': 'off',
'no-return-await': 'off',
'import/extensions': 'off',
'no-var': 'off',
'import/newline-after-import': 'off',
'no-restricted-globals': 'off',
'operator-assignment': 'off',
'no-eval': 'off',
'max-classes-per-file': 'off',
'vars-on-top': 'off',
'no-bitwise': 'off',
'no-lonely-if': 'off',
'no-multi-assign': 'off',
'no-promise-executor-return': 'off',
'no-empty-function': 'off',
'import/no-unresolved': 'off',
camelcase: 'off',
eqeqeq: 'off',
'lines-between-class-members': 'off',
'import/no-cycle': 'off',
'new-cap': 'off',
'prefer-object-spread': 'off',
'no-new-func': 'off',
'no-unused-expressions': 'off',
'lines-around-directive': 'off',
'prefer-exponentiation-operator': 'off',
'no-restricted-properties': 'off',
'prefer-rest-params': 'off',
'no-unreachable-loop': 'off',
'no-alert': 'off',
'no-useless-return': 'off',
'no-nested-ternary': 'off',
'prefer-regex-literals': 'off',
'import/no-named-as-default-member': 'off',
yoda: 'off',
'no-script-url': 'off',
'no-prototype-builtins':'off'
},
};
+1
View File
@@ -12,3 +12,4 @@ server-package.json
.idea/httpRequests/
data/
tmp/
.eslintcache
+1
View File
@@ -0,0 +1 @@
_
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
#npx lint-staged
+1 -1
View File
@@ -1,7 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JSUnfilteredForInLoop" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>
+11
View File
@@ -0,0 +1,11 @@
//https://prettier.io/docs/en/options.html
module.exports = {
semi: true,
trailingComma: 'es5',
singleQuote: true,
printWidth: 120,
tabWidth: 4,
// useTabs: false,
// bracketSpacing: true,
// htmlWhitespaceSensitivity: 'ignore',
};
+6
View File
@@ -0,0 +1,6 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
]
}
+12 -7
View File
@@ -1,19 +1,24 @@
{
"version": "0.2.0",
"configurations": [
// nodemon should be installed globally, use npm i -g nodemon
{
"type": "node",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "nodemon start-server",
"program": "${workspaceFolder}/src/www",
"request": "launch",
"name": "start-server",
"skipFiles": [
"<node_internals>/**"
],
"restart": true,
"runtimeExecutable": "nodemon",
"env": {
"TRILIUM_ENV": "dev",
"TRILIUM_DATA_DIR": "./data"
},
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"outputCapture": "std",
"program": "${workspaceFolder}/src/www"
}
},
]
}
+33
View File
@@ -0,0 +1,33 @@
{
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[json]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"editor.formatOnSave": true,
"eslint.format.enable": true,
"eslint.probe": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"html",
"vue",
"markdown",
"json",
"jsonc"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"html",
"vue",
"markdown",
"json",
"jsonc"
],
"files.eol": "\n",
}
+1 -1
View File
@@ -39,4 +39,4 @@ RUN adduser -s /bin/false node; exit 0
EXPOSE 8080
CMD [ "./start-docker.sh" ]
HEALTHCHECK --start-period=10s CMD node docker_healthcheck.js
HEALTHCHECK --start-period=10s CMD exec su-exec node node docker_healthcheck.js
+2 -1
View File
@@ -1,6 +1,6 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md)
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:
@@ -10,6 +10,7 @@ Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
## 特性
+83
View File
@@ -0,0 +1,83 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://github.com/zadam/trilium/wiki/Screenshot-tour)をご覧ください:
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
ウクライナは現在、ロシアの侵略から自国を守っています。[ウクライナ軍や人道的な慈善団体への寄付](https://standforukraine.com/)をご検討ください。
<p float="left">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
</p>
## 🎁 特徴
* ノートは、任意の深さのツリーに配置できます。単一のノートをツリー内の複数の場所に配置できます ([cloning](https://github.com/zadam/trilium/wiki/Cloning-notes) を参照)
* マークダウン[オートフォーマット](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)による、表、画像、[数学](https://github.com/zadam/trilium/wiki/Text-notes#math-support)などの豊富な WYSIWYG ノート編集機能
* シンタックスハイライトを含む[ソースコード付きノート](https://github.com/zadam/trilium/wiki/Code-notes)の編集をサポート
* [ノート間のナビゲーション](https://github.com/zadam/trilium/wiki/Note-navigation)、全文検索、[ノートホイスト](https://github.com/zadam/trilium/wiki/Note-hoisting)が高速かつ簡単に行えます
* シームレスな[ノートのバージョン管理](https://github.com/zadam/trilium/wiki/Note-revisions)
* ノート[属性](https://github.com/zadam/trilium/wiki/Attributes)は、ノート整理、クエリ、高度な[スクリプト](https://github.com/zadam/trilium/wiki/Scripts)に使用できます
* 自己ホスト型同期サーバーとの[同期](https://github.com/zadam/trilium/wiki/Synchronization)
* [同期サーバーをホストするサードパーティ・サービス](https://trilium.cc/paid-hosting)があります
* 公開インターネットへのノートの[共有](https://github.com/zadam/trilium/wiki/Sharing)(公開)
* ノートごとの粒度を持つ強力な[ノート暗号化](https://github.com/zadam/trilium/wiki/Protected-notes)
* 組み込みの Excalidraw を使用した図のスケッチ (ノート タイプ"キャンバス")
* ノートとその関係を可視化するための[関係図](https://github.com/zadam/trilium/wiki/Relation-map)と[リンクマップ](https://github.com/zadam/trilium/wiki/Link-map)
* [スクリプティング](https://github.com/zadam/trilium/wiki/Scripts) - [高度なショーケース](https://github.com/zadam/trilium/wiki/Advanced-showcases)を参照
* 自動化のための [REST API](https://github.com/zadam/trilium/wiki/ETAPI)
* ユーザビリティとパフォーマンスの両方で 100 000 ノート以上に拡張可能
* スマートフォンとタブレット向けのタッチ最適化[モバイルフロントエンド](https://github.com/zadam/trilium/wiki/Mobile-frontend)
* [ナイトテーマ](https://github.com/zadam/trilium/wiki/Themes)
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) と [Markdown のインポートとエクスポート](https://github.com/zadam/trilium/wiki/Markdown)
* Web コンテンツを簡単に保存するための [Web クリッパー](https://github.com/zadam/trilium/wiki/Web-clipper)
サードパーティのテーマ、スクリプト、プラグインなどは、 [awesome-trilium](https://github.com/Nriver/awesome-trilium) をチェックしてください。
## 🏗 ビルド
Trilium は、デスクトップアプリケーション(Linux、Windows)またはサーバー上でホストされるウェブアプリケーション(Linux)として提供されます。 Mac OS のデスクトップビルドも利用可能ですが、 [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support) となっています。
* デスクトップで Trilium を使用したい場合は、 [latest release](https://github.com/zadam/trilium/releases/latest) からお使いのプラットフォームのバイナリリリースをダウンロードし、パッケージを解凍して ``trilium`` の実行ファイルを実行してください。
* サーバーに Trilium をインストールする場合は、[このページ](https://github.com/zadam/trilium/wiki/Server-installation)に従ってください。
* 現在、対応(動作確認)しているブラウザは、最近の Chrome と Firefox のみです。
Trilium は Flatpak としても提供されます:
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
## 📝 ドキュメント
[ドキュメントページの全リストはwikiをご覧ください。](https://github.com/zadam/trilium/wiki/)
また、[個人的な知識基盤のパターン](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base)を読むと、 Trilium の使い方のヒントを得ることができます。
## 💻 コントリビュート
ブラウザベースの開発環境を使用
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/zadam/trilium)
または、ローカルにクローンして実行
```
npm install
npm run start-server
```
## 📢 シャウトアウト
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 市場で最高の WYSIWYG エディター、非常にインタラクティブで聞き上手なチーム
* [FancyTree](https://github.com/mar10/fancytree) - 真の競争相手がいない、非常に機能豊富なツリーライブラリです。 Trilium Notes は、これなしでは成り立たないでしょう。
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 膨大な数の言語をサポートするコードエディタ
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 競合のないビジュアルコネクティビティライブラリです。[関係図](https://github.com/zadam/trilium/wiki/Relation-map)、[リンク図](https://github.com/zadam/trilium/wiki/Link-map)で使用。
## 🤝 サポート
GitHub スポンサー、[PayPal](https://paypal.me/za4am)もしくは Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2) にて Trilium をサポートすることができます。
## 🔑 ライセンス
このプログラムはフリーソフトウェアです:フリーソフトウェア財団が発行した GNU Affero General Public License のバージョン3、またはそれ以降のバージョンのいずれかに従って、再配布および/または改変することができます。
+16 -12
View File
@@ -1,17 +1,19 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases. See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
![](https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png)
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
Ukraine is currently defending itself from Russian aggression, please consider [donating to Ukrainian Army or humanitarian charities](https://standforukraine.com/).
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
<p float="left">
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
</p>
## Features
## 🎁 Features
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://github.com/zadam/trilium/wiki/Text-notes#math-support) with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
@@ -33,7 +35,9 @@ Ukraine is currently defending itself from Russian aggression, please consider [
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) for easy saving of web content
## Builds
Check out [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
## 🏗 Builds
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
@@ -45,13 +49,13 @@ Trilium is also provided as a Flatpak:
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
## Documentation
## 📝 Documentation
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
## Contribute
## 💻 Contribute
Use a browser based dev environment
@@ -63,17 +67,17 @@ npm install
npm run start-server
```
## Shoutouts
## 📢 Shoutouts
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
* [FancyTree](https://github.com/mar10/fancytree) - very feature rich tree library without real competition. Trilium Notes would not be the same without it.
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map)
## Donating
## 🤝 Support
You can donate using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
You can support Trilium using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
## License
## 🔑 License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+2 -1
View File
@@ -1,6 +1,6 @@
# Trilium Notes
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md)
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes – это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://github.com/zadam/trilium/wiki/Screenshot-tour):
@@ -10,6 +10,7 @@ Trilium Notes – это приложение для заметок с иера
Ukraine is currently suffering from Russian aggression, please consider donating to [one of these charities](https://old.reddit.com/r/ukraine/comments/s6g5un/want_to_support_ukraine_heres_a_list_of_charities/).
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="600"/>
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="600"/>
## Возможности
+152 -3
View File
@@ -1,12 +1,161 @@
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE note_revisions SET title = 'title';
UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL;
UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss', 'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass', 'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider', 'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'noteRevisionsWidgetDisabled', 'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass', 'workspaceTabBackgroundColor', 'searchHome', 'workspaceInbox', 'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId', 'bookmarkFolder', 'sorted', 'top', 'fullContentWidth', 'shareHiddenFromTree', 'shareAlias', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation', 'runOnNoteTitleChange', 'runOnNoteContentChange', 'runOnNoteChange', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template', 'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
UPDATE attributes SET name = 'name', value = 'value'
WHERE type = 'label'
AND name NOT IN ('inbox',
'disableVersioning',
'calendarRoot',
'archived',
'excludeFromExport',
'disableInclusion',
'appCss',
'appTheme',
'hidePromotedAttributes',
'readOnly',
'autoReadOnlyDisabled',
'cssClass',
'iconClass',
'keyboardShortcut',
'run',
'runOnInstance',
'runAtHour',
'customRequestHandler',
'customResourceProvider',
'widget',
'noteInfoWidgetDisabled',
'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled',
'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled',
'workspace',
'workspaceIconClass',
'workspaceTabBackgroundColor',
'searchHome',
'workspaceInbox',
'workspaceSearchHome',
'sqlConsoleHome',
'datePattern',
'pageSize',
'viewType',
'mapRootNoteId',
'bookmarkFolder',
'sorted',
'top',
'fullContentWidth',
'shareHiddenFromTree',
'shareAlias',
'shareOmitDefaultCss',
'shareRoot',
'internalLink',
'imageLink',
'relationMapLink',
'includeMapLink',
'runOnNoteCreation',
'runOnNoteTitleChange',
'runOnNoteContentChange',
'runOnNoteChange',
'runOnChildNoteCreation',
'runOnAttributeCreation',
'runOnAttributeChange',
'template',
'inherit',
'widget',
'renderNote',
'shareCss',
'shareJs',
'shareFavicon',
'executeButton',
'keepCurrentHoisting',
'color',
'toc',
'excludeFromNoteMap',
'docName',
'launcherType',
'builtinWidget',
'baseSize',
'growthFactor'
);
UPDATE attributes SET name = 'name'
AND name NOT IN ('inbox',
'disableVersioning',
'calendarRoot',
'archived',
'excludeFromExport',
'disableInclusion',
'appCss',
'appTheme',
'hidePromotedAttributes',
'readOnly',
'autoReadOnlyDisabled',
'cssClass',
'iconClass',
'keyboardShortcut',
'run',
'runOnInstance',
'runAtHour',
'customRequestHandler',
'customResourceProvider',
'widget',
'noteInfoWidgetDisabled',
'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled',
'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled',
'workspace',
'workspaceIconClass',
'workspaceTabBackgroundColor',
'searchHome',
'workspaceInbox',
'workspaceSearchHome',
'sqlConsoleHome',
'datePattern',
'pageSize',
'viewType',
'mapRootNoteId',
'bookmarkFolder',
'sorted',
'top',
'fullContentWidth',
'shareHiddenFromTree',
'shareAlias',
'shareOmitDefaultCss',
'shareRoot',
'internalLink',
'imageLink',
'relationMapLink',
'includeMapLink',
'runOnNoteCreation',
'runOnNoteTitleChange',
'runOnNoteContentChange',
'runOnNoteChange',
'runOnChildNoteCreation',
'runOnAttributeCreation',
'runOnAttributeChange',
'template',
'inherit',
'widget',
'renderNote',
'shareCss',
'shareJs',
'shareFavicon',
'executeButton',
'keepCurrentHoisting',
'color',
'toc',
'excludeFromNoteMap',
'docName',
'launcherType',
'builtinWidget',
'baseSize',
'growthFactor'
);
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
@@ -2,12 +2,18 @@ module.exports = () => {
const cls = require("../../src/services/cls");
const beccaLoader = require("../../src/becca/becca_loader");
const becca = require("../../src/becca/becca");
const log = require("../../src/services/log");
cls.init(() => {
beccaLoader.load();
const hidden = becca.getNote("_hidden");
if (!hidden) {
log.info("MIGRATION 212: no _hidden note, skipping.");
return;
}
for (const noteId of hidden.getSubtreeNoteIds({includeHidden: true})) {
if (noteId.startsWith("_")) { // is "named" note
const note = becca.getNote(noteId);
+21 -6
View File
@@ -1,27 +1,42 @@
const http = require("http");
const config = require("./src/services/config");
const ini = require("ini");
const fs = require("fs");
const dataDir = require("./src/services/data_dir");
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8'));
if (config.https) {
// built-in TLS (terminated by trilium) is not supported yet, PRs are welcome
// for reverse proxy terminated TLS this will works since config.https will be false
process.exit(0);
return;
}
const port = require('./src/services/port');
const host = require('./src/services/host');
const url = `http://${host}:${port}/api/health-check`;
const options = { timeout: 2000 };
const request = http.request(url, options, res => {
const callback = res => {
console.log(`STATUS: ${res.statusCode}`);
if (res.statusCode === 200) {
process.exit(0);
} else {
process.exit(1);
}
});
};
let request;
if (port !== 0) { // TCP socket.
const url = `http://${host}:${port}/api/health-check`;
request = http.request(url, options, callback);
} else { // Unix socket.
options.socketPath = host;
options.path = '/api/health-check';
request = http.request(options, callback);
}
request.on("error", err => {
console.log("ERROR");
process.exit(1);
});
request.end();
request.end();
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="53" height="40">
<g transform="matrix(0.51887405 0 0 0.51887405 -30.622847 -23.349333)">
<g>
<path d="M139.19968 46.483948C 133.79512 46.316727 130.99477 48.84615 130.3185 49.08075C 130.93373 47.91552 131.34544 47.18139 132.91882 45.54309C 127.567184 45.48699 122.60501 46.645172 118.819 50.18706C 113.74112 54.93721 114.13588 58.872803 114.12565 58.8534C 114.12665 58.8554 113.324486 56.28694 113.083885 53.53986C 107.557976 61.18914 105.376045 71.32445 109.565636 78.88518C 112.16631 72.611374 116.29593 67.204346 121.617935 62.940678C 121.79327 58.466747 123.59313 54.686737 126.437935 51.89591C 123.87077 54.96861 122.73801 58.46922 122.8523 61.99735C 129.0425 57.37914 136.62968 54.301147 144.96371 53.29115C 138.92981 54.39993 133.4628 56.804817 128.68442 60.08495C 131.67526 61.4509 135.09087 61.66504 138.68073 60.41479C 135.23903 62.02769 131.41844 62.291573 127.55341 60.88681C 124.123 63.39894 121.072174 66.36721 118.44998 69.62935C 123.10523 71.15123 128.19724 70.83655 133.26453 68.25387C 128.6128 71.25848 123.14792 72.38525 117.30839 71.10572C 114.98041 74.22992 113.02919 77.58555 111.50413 81.04383C 113.382675 82.42883 121.53502 86.77435 135.51878 76.4623C 133.54428 75.878815 131.94408 75.053665 131.94337 75.05225C 131.98787 75.07205 133.38448 75.200424 137.46893 72.753555C 141.07997 70.64182 144.18617 66.81666 146.47923 63.839214C 144.52872 63.120605 143.00966 62.366364 143.00896 62.364605C 143.00795 62.363605 145.52956 62.534286 149.44116 59.539906C 152.93013 56.869026 156.82515 52.997295 156.83716 53.106655C 156.86186 53.129955 148.2449 46.763355 139.19968 46.483955" stroke="none" fill="#000000" fill-rule="nonzero" />
<path d="M76.90892 60.39415C 81.01041 59.20187 83.62202 60.57837 84.18329 60.62363C 83.49581 59.85657 83.04422 59.37815 81.5362 58.43957C 85.61753 57.342182 89.62709 57.24689 93.18141 59.20068C 97.94861 61.82108 98.37924 64.89886 98.38368 64.88219C 98.38287 64.884186 98.51835 62.767998 98.19094 60.626408C 103.84037 65.368546 107.39591 72.664375 105.59999 79.25351C 102.44364 74.98344 98.27924 71.67578 93.41609 69.47424C 92.44918 66.098694 90.369 63.571594 87.674324 62.0049C 90.20933 63.84117 91.72702 66.28648 92.29676 68.9982C 86.70379 66.69781 80.32978 65.84657 73.76949 66.719246C 78.589516 66.37542 88.79929 67.0738 97.083725 73.94838C 93.808014 76.02605 89.85612 76.78993 85.50101 75.81959C 89.617004 77.19292 94.00519 76.97535 98.23167 74.84885C 100.59329 76.77167 102.71017 78.94501 104.520195 81.28075C 103.3416 82.70688 97.917564 87.62569 85.30545 82.520676C 86.70662 81.686905 87.77634 80.74236 87.77674 80.74117C 87.746544 80.76498 86.70259 81.13821 83.12366 80.07813C 79.96972 79.18005 76.88272 76.87608 74.574715 75.058075C 75.932335 74.12624 76.953674 73.25198 76.953674 73.2504C 76.95406 73.24959 75.05817 73.87652 71.50989 72.36501C 68.344666 71.016304 64.64558 68.832634 64.65687 68.918396C 64.642365 68.940994 70.04542 62.389626 76.90893 60.394146" stroke="none" fill="#000000" fill-rule="nonzero" />
<path d="M106.35028 116.78676C 103.35939 114.0036 103.23137 111.1697 102.99563 110.68548C 102.692345 111.63276 102.514435 112.24205 102.46853 113.950066C 99.56593 111.13644 97.53256 107.88377 97.42573 103.98627C 97.2827 98.76002 99.63347 96.87469 99.61758 96.879555C 99.619576 96.879555 97.79303 97.82024 96.170654 99.15473C 97.362595 92.146484 101.699936 85.59194 108.057846 83.79309C 106.043465 88.51463 105.32079 93.58698 105.859375 98.68461C 103.522285 101.15936 102.43365 104.12773 102.44335 107.12493C 102.7356 104.12552 104.0304 101.66008 106.00902 99.8426C 106.82087 105.59001 109.21976 111.25976 113.143005 116.22528C 110.507935 112.4288 106.112755 103.67846 107.79472 93.44062C 111.1198 95.10421 113.68158 97.97678 114.99713 102.04415C 114.133644 97.97323 111.813774 94.46955 107.98454 92.04802C 108.433075 89.14839 109.210045 86.32549 110.27087 83.67378C 112.03185 83.93501 118.768524 85.95295 120.66769 98.87222C 119.291214 98.13378 117.984055 97.72311 117.98273 97.72354C 118.017624 97.73644 118.836945 98.41008 119.69913 101.88272C 120.4889 104.925644 120.07702 108.61209 119.68893 111.41583C 118.25153 110.761665 117.02649 110.35595 117.025604 110.356834C 117.024605 110.356834 118.470055 111.60574 118.94197 115.277824C 119.36268 118.55367 119.348564 122.68398 119.41433 122.632C 119.44033 122.63284 111.35593 121.44354 106.35022 116.78675" stroke="none" fill="#000000" fill-rule="nonzero" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="53" height="40">
<g transform="matrix(0.51887405 0 0 0.51887405 -30.622847 -23.349333)">
<g>
<path d="M139.19968 46.483948C 133.79512 46.316727 130.99477 48.84615 130.3185 49.08075C 130.93373 47.91552 131.34544 47.18139 132.91882 45.54309C 127.567184 45.48699 122.60501 46.645172 118.819 50.18706C 113.74112 54.93721 114.13588 58.872803 114.12565 58.8534C 114.12665 58.8554 113.324486 56.28694 113.083885 53.53986C 107.557976 61.18914 105.376045 71.32445 109.565636 78.88518C 112.16631 72.611374 116.29593 67.204346 121.617935 62.940678C 121.79327 58.466747 123.59313 54.686737 126.437935 51.89591C 123.87077 54.96861 122.73801 58.46922 122.8523 61.99735C 129.0425 57.37914 136.62968 54.301147 144.96371 53.29115C 138.92981 54.39993 133.4628 56.804817 128.68442 60.08495C 131.67526 61.4509 135.09087 61.66504 138.68073 60.41479C 135.23903 62.02769 131.41844 62.291573 127.55341 60.88681C 124.123 63.39894 121.072174 66.36721 118.44998 69.62935C 123.10523 71.15123 128.19724 70.83655 133.26453 68.25387C 128.6128 71.25848 123.14792 72.38525 117.30839 71.10572C 114.98041 74.22992 113.02919 77.58555 111.50413 81.04383C 113.382675 82.42883 121.53502 86.77435 135.51878 76.4623C 133.54428 75.878815 131.94408 75.053665 131.94337 75.05225C 131.98787 75.07205 133.38448 75.200424 137.46893 72.753555C 141.07997 70.64182 144.18617 66.81666 146.47923 63.839214C 144.52872 63.120605 143.00966 62.366364 143.00896 62.364605C 143.00795 62.363605 145.52956 62.534286 149.44116 59.539906C 152.93013 56.869026 156.82515 52.997295 156.83716 53.106655C 156.86186 53.129955 148.2449 46.763355 139.19968 46.483955" stroke="none" fill="#4FA52B" fill-rule="nonzero" />
<path d="M76.90892 60.39415C 81.01041 59.20187 83.62202 60.57837 84.18329 60.62363C 83.49581 59.85657 83.04422 59.37815 81.5362 58.43957C 85.61753 57.342182 89.62709 57.24689 93.18141 59.20068C 97.94861 61.82108 98.37924 64.89886 98.38368 64.88219C 98.38287 64.884186 98.51835 62.767998 98.19094 60.626408C 103.84037 65.368546 107.39591 72.664375 105.59999 79.25351C 102.44364 74.98344 98.27924 71.67578 93.41609 69.47424C 92.44918 66.098694 90.369 63.571594 87.674324 62.0049C 90.20933 63.84117 91.72702 66.28648 92.29676 68.9982C 86.70379 66.69781 80.32978 65.84657 73.76949 66.719246C 78.589516 66.37542 88.79929 67.0738 97.083725 73.94838C 93.808014 76.02605 89.85612 76.78993 85.50101 75.81959C 89.617004 77.19292 94.00519 76.97535 98.23167 74.84885C 100.59329 76.77167 102.71017 78.94501 104.520195 81.28075C 103.3416 82.70688 97.917564 87.62569 85.30545 82.520676C 86.70662 81.686905 87.77634 80.74236 87.77674 80.74117C 87.746544 80.76498 86.70259 81.13821 83.12366 80.07813C 79.96972 79.18005 76.88272 76.87608 74.574715 75.058075C 75.932335 74.12624 76.953674 73.25198 76.953674 73.2504C 76.95406 73.24959 75.05817 73.87652 71.50989 72.36501C 68.344666 71.016304 64.64558 68.832634 64.65687 68.918396C 64.642365 68.940994 70.04542 62.389626 76.90893 60.394146" stroke="none" fill="#E47B19" fill-rule="nonzero" />
<path d="M106.35028 116.78676C 103.35939 114.0036 103.23137 111.1697 102.99563 110.68548C 102.692345 111.63276 102.514435 112.24205 102.46853 113.950066C 99.56593 111.13644 97.53256 107.88377 97.42573 103.98627C 97.2827 98.76002 99.63347 96.87469 99.61758 96.879555C 99.619576 96.879555 97.79303 97.82024 96.170654 99.15473C 97.362595 92.146484 101.699936 85.59194 108.057846 83.79309C 106.043465 88.51463 105.32079 93.58698 105.859375 98.68461C 103.522285 101.15936 102.43365 104.12773 102.44335 107.12493C 102.7356 104.12552 104.0304 101.66008 106.00902 99.8426C 106.82087 105.59001 109.21976 111.25976 113.143005 116.22528C 110.507935 112.4288 106.112755 103.67846 107.79472 93.44062C 111.1198 95.10421 113.68158 97.97678 114.99713 102.04415C 114.133644 97.97323 111.813774 94.46955 107.98454 92.04802C 108.433075 89.14839 109.210045 86.32549 110.27087 83.67378C 112.03185 83.93501 118.768524 85.95295 120.66769 98.87222C 119.291214 98.13378 117.984055 97.72311 117.98273 97.72354C 118.017624 97.73644 118.836945 98.41008 119.69913 101.88272C 120.4889 104.925644 120.07702 108.61209 119.68893 111.41583C 118.25153 110.761665 117.02649 110.35595 117.025604 110.356834C 117.024605 110.356834 118.470055 111.60574 118.94197 115.277824C 119.36268 118.55367 119.348564 122.68398 119.41433 122.632C 119.44033 122.63284 111.35593 121.44354 106.35022 116.78675" stroke="none" fill="#E33F3B" fill-rule="nonzero" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="body_1" width="53" height="40">
<g transform="matrix(0.51887405 0 0 0.51887405 -30.622847 -23.349333)">
<g>
<path d="M139.19968 46.483948C 133.79512 46.316727 130.99477 48.84615 130.3185 49.08075C 130.93373 47.91552 131.34544 47.18139 132.91882 45.54309C 127.567184 45.48699 122.60501 46.645172 118.819 50.18706C 113.74112 54.93721 114.13588 58.872803 114.12565 58.8534C 114.12665 58.8554 113.324486 56.28694 113.083885 53.53986C 107.557976 61.18914 105.376045 71.32445 109.565636 78.88518C 112.16631 72.611374 116.29593 67.204346 121.617935 62.940678C 121.79327 58.466747 123.59313 54.686737 126.437935 51.89591C 123.87077 54.96861 122.73801 58.46922 122.8523 61.99735C 129.0425 57.37914 136.62968 54.301147 144.96371 53.29115C 138.92981 54.39993 133.4628 56.804817 128.68442 60.08495C 131.67526 61.4509 135.09087 61.66504 138.68073 60.41479C 135.23903 62.02769 131.41844 62.291573 127.55341 60.88681C 124.123 63.39894 121.072174 66.36721 118.44998 69.62935C 123.10523 71.15123 128.19724 70.83655 133.26453 68.25387C 128.6128 71.25848 123.14792 72.38525 117.30839 71.10572C 114.98041 74.22992 113.02919 77.58555 111.50413 81.04383C 113.382675 82.42883 121.53502 86.77435 135.51878 76.4623C 133.54428 75.878815 131.94408 75.053665 131.94337 75.05225C 131.98787 75.07205 133.38448 75.200424 137.46893 72.753555C 141.07997 70.64182 144.18617 66.81666 146.47923 63.839214C 144.52872 63.120605 143.00966 62.366364 143.00896 62.364605C 143.00795 62.363605 145.52956 62.534286 149.44116 59.539906C 152.93013 56.869026 156.82515 52.997295 156.83716 53.106655C 156.86186 53.129955 148.2449 46.763355 139.19968 46.483955" stroke="none" fill="#ABABAB" fill-rule="nonzero" />
<path d="M76.90892 60.39415C 81.01041 59.20187 83.62202 60.57837 84.18329 60.62363C 83.49581 59.85657 83.04422 59.37815 81.5362 58.43957C 85.61753 57.342182 89.62709 57.24689 93.18141 59.20068C 97.94861 61.82108 98.37924 64.89886 98.38368 64.88219C 98.38287 64.884186 98.51835 62.767998 98.19094 60.626408C 103.84037 65.368546 107.39591 72.664375 105.59999 79.25351C 102.44364 74.98344 98.27924 71.67578 93.41609 69.47424C 92.44918 66.098694 90.369 63.571594 87.674324 62.0049C 90.20933 63.84117 91.72702 66.28648 92.29676 68.9982C 86.70379 66.69781 80.32978 65.84657 73.76949 66.719246C 78.589516 66.37542 88.79929 67.0738 97.083725 73.94838C 93.808014 76.02605 89.85612 76.78993 85.50101 75.81959C 89.617004 77.19292 94.00519 76.97535 98.23167 74.84885C 100.59329 76.77167 102.71017 78.94501 104.520195 81.28075C 103.3416 82.70688 97.917564 87.62569 85.30545 82.520676C 86.70662 81.686905 87.77634 80.74236 87.77674 80.74117C 87.746544 80.76498 86.70259 81.13821 83.12366 80.07813C 79.96972 79.18005 76.88272 76.87608 74.574715 75.058075C 75.932335 74.12624 76.953674 73.25198 76.953674 73.2504C 76.95406 73.24959 75.05817 73.87652 71.50989 72.36501C 68.344666 71.016304 64.64558 68.832634 64.65687 68.918396C 64.642365 68.940994 70.04542 62.389626 76.90893 60.394146" stroke="none" fill="#ABABAB" fill-rule="nonzero" />
<path d="M106.35028 116.78676C 103.35939 114.0036 103.23137 111.1697 102.99563 110.68548C 102.692345 111.63276 102.514435 112.24205 102.46853 113.950066C 99.56593 111.13644 97.53256 107.88377 97.42573 103.98627C 97.2827 98.76002 99.63347 96.87469 99.61758 96.879555C 99.619576 96.879555 97.79303 97.82024 96.170654 99.15473C 97.362595 92.146484 101.699936 85.59194 108.057846 83.79309C 106.043465 88.51463 105.32079 93.58698 105.859375 98.68461C 103.522285 101.15936 102.43365 104.12773 102.44335 107.12493C 102.7356 104.12552 104.0304 101.66008 106.00902 99.8426C 106.82087 105.59001 109.21976 111.25976 113.143005 116.22528C 110.507935 112.4288 106.112755 103.67846 107.79472 93.44062C 111.1198 95.10421 113.68158 97.97678 114.99713 102.04415C 114.133644 97.97323 111.813774 94.46955 107.98454 92.04802C 108.433075 89.14839 109.210045 86.32549 110.27087 83.67378C 112.03185 83.93501 118.768524 85.95295 120.66769 98.87222C 119.291214 98.13378 117.984055 97.72311 117.98273 97.72354C 118.017624 97.73644 118.836945 98.41008 119.69913 101.88272C 120.4889 104.925644 120.07702 108.61209 119.68893 111.41583C 118.25153 110.761665 117.02649 110.35595 117.025604 110.356834C 117.024605 110.356834 118.470055 111.60574 118.94197 115.277824C 119.36268 118.55367 119.348564 122.68398 119.41433 122.632C 119.44033 122.63284 111.35593 121.44354 106.35022 116.78675" stroke="none" fill="#ABABAB" fill-rule="nonzero" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -46,7 +46,7 @@
const errors = new eslint().verify(text, {
root: true,
parserOptions: {
ecmaVersion: 2019
ecmaVersion: 2022
},
extends: ['eslint:recommended', 'airbnb-base'],
env: {
+13
View File
@@ -0,0 +1,13 @@
{
"restartable": "rs",
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
"verbose": false,
"execMap": {
"js": "node --harmony"
},
"watch": ["src/"],
"env": {
"NODE_ENV": "development"
},
"ext": "js,json"
}
+2096 -536
View File
File diff suppressed because it is too large Load Diff
+37 -21
View File
@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.59.0-beta",
"version": "0.60.0-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -13,20 +13,22 @@
"url": "https://github.com/zadam/trilium.git"
},
"scripts": {
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www",
"start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www",
"start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
"start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www",
"start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
"start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
"switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
"switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild",
"switch-electron": "./node_modules/.bin/electron-rebuild",
"build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
"build-frontend-docs": "rm -rf ./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/right_panel_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js",
"webpack": "webpack -c webpack.config.js",
"test-jasmine": "jasmine",
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
"test": "npm run test-jasmine && npm run test-es6",
"postinstall": "rimraf ./node_modules/canvas"
"postinstall": "rimraf ./node_modules/canvas",
"lint": "eslint . --cache",
"prepare": "husky install"
},
"dependencies": {
"@braintree/sanitize-url": "6.0.2",
@@ -34,7 +36,7 @@
"@excalidraw/excalidraw": "0.14.2",
"archiver": "5.3.1",
"async-mutex": "0.4.0",
"axios": "1.3.3",
"axios": "1.3.5",
"better-sqlite3": "7.4.5",
"chokidar": "3.5.3",
"cls-hooked": "4.2.2",
@@ -45,7 +47,7 @@
"dayjs": "1.11.7",
"dayjs-plugin-utc": "^0.1.2",
"debounce": "^1.2.1",
"ejs": "3.1.8",
"ejs": "3.1.9",
"electron-debug": "3.2.0",
"electron-dl": "3.5.0",
"electron-window-state": "5.0.3",
@@ -54,8 +56,8 @@
"express-partial-content": "1.0.2",
"express-rate-limit": "6.7.0",
"express-session": "1.17.3",
"fs-extra": "11.1.0",
"helmet": "6.0.1",
"fs-extra": "11.1.1",
"helmet": "6.1.2",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "5.0.0",
@@ -64,22 +66,22 @@
"ini": "3.0.1",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.4",
"jimp": "0.22.7",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "21.1.0",
"jsdom": "21.1.1",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.33.0",
"node-abi": "3.35.0",
"normalize-strings": "1.1.1",
"open": "8.4.1",
"rand-token": "1.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"request": "2.88.2",
"rimraf": "3.0.2",
"rimraf": "5.0.0",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.9.0",
"sanitize-html": "2.10.0",
"sax": "1.2.4",
"semver": "7.3.8",
"serve-favicon": "2.5.0",
@@ -87,10 +89,10 @@
"stream-throttle": "0.1.3",
"striptags": "3.2.0",
"tmp": "0.2.1",
"turndown": "7.1.1",
"turndown": "7.1.2",
"unescape": "1.0.1",
"ws": "8.12.1",
"xml2js": "0.4.23",
"ws": "8.13.0",
"xml2js": "0.5.0",
"yauzl": "2.10.0"
},
"devDependencies": {
@@ -99,15 +101,29 @@
"electron-builder": "23.6.0",
"electron-packager": "17.1.1",
"electron-rebuild": "3.2.9",
"eslint": "^8.38.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsonc": "^2.7.0",
"eslint-plugin-prettier": "^4.2.1",
"esm": "3.2.25",
"jasmine": "4.5.0",
"jsdoc": "4.0.1",
"husky": "^8.0.3",
"jsonc-eslint-parser": "^2.2.0",
"lint-staged": "^13.2.1",
"jasmine": "4.6.0",
"jsdoc": "4.0.2",
"lorem-ipsum": "2.0.8",
"prettier": "2.8.7",
"nodemon": "^2.0.22",
"rcedit": "3.0.1",
"webpack": "5.75.0",
"webpack": "5.78.0",
"webpack-cli": "5.0.1"
},
"optionalDependencies": {
"electron-installer-debian": "3.1.0"
},
"lint-staged": {
"*.js": "eslint --cache --fix"
}
}
+22 -11
View File
@@ -5,12 +5,13 @@ const favicon = require('serve-favicon');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const session = require('express-session');
const compression = require('compression')
const compression = require('compression');
const FileStore = require('session-file-store')(session);
const sessionSecret = require('./services/session_secret');
const dataDir = require('./services/data_dir');
const utils = require('./services/utils');
const assetPath = require('./services/asset_path');
const env = require('./services/env');
require('./services/handlers');
require('./becca/becca_loader');
@@ -30,27 +31,37 @@ app.use(helmet({
crossOriginEmbedderPolicy: false
}));
const persistentCacheStatic = (root, options) => {
if (!env.isDev()) {
options = {
maxAge: '1y',
...options
};
}
return express.static(root, options);
};
app.use(express.text({limit: '500mb'}));
app.use(express.json({limit: '500mb'}));
app.use(express.raw({limit: '500mb'}));
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public/root')));
app.use(`/${assetPath}/app`, express.static(path.join(__dirname, 'public/app')));
app.use(`/${assetPath}/app-dist`, express.static(path.join(__dirname, 'public/app-dist')));
app.use(`/${assetPath}/fonts`, express.static(path.join(__dirname, 'public/fonts')));
app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(__dirname, 'public/app')));
app.use(`/${assetPath}/app-dist`, persistentCacheStatic(path.join(__dirname, 'public/app-dist')));
app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(__dirname, 'public/fonts')));
app.use(`/assets/vX/fonts`, express.static(path.join(__dirname, 'public/fonts')));
app.use(`/${assetPath}/stylesheets`, express.static(path.join(__dirname, 'public/stylesheets')));
app.use(`/${assetPath}/stylesheets`, persistentCacheStatic(path.join(__dirname, 'public/stylesheets')));
app.use(`/assets/vX/stylesheets`, express.static(path.join(__dirname, 'public/stylesheets')));
app.use(`/${assetPath}/libraries`, express.static(path.join(__dirname, '..', 'libraries')));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(__dirname, '..', 'libraries')));
app.use(`/assets/vX/libraries`, express.static(path.join(__dirname, '..', 'libraries')));
// excalidraw-view mode in shared notes
app.use(`/${assetPath}/node_modules/react/umd/react.production.min.js`, express.static(path.join(__dirname, '..', 'node_modules/react/umd/react.production.min.js')));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js`, express.static(path.join(__dirname, '..', 'node_modules/react-dom/umd/react-dom.production.min.js')));
app.use(`/${assetPath}/node_modules/react/umd/react.production.min.js`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/react/umd/react.production.min.js')));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/react-dom/umd/react-dom.production.min.js')));
// expose whole dist folder since complete assets are needed in edit and share
app.use(`/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(__dirname, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(__dirname, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/images`, express.static(path.join(__dirname, '..', 'images')));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/images`, persistentCacheStatic(path.join(__dirname, '..', 'images')));
app.use(`/assets/vX/images`, express.static(path.join(__dirname, '..', 'images')));
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
@@ -61,7 +72,7 @@ const sessionParser = session({
cookie: {
// path: "/",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
},
name: 'trilium.sid',
store: new FileStore({
+13 -2
View File
@@ -70,7 +70,9 @@ function reload() {
}
function postProcessEntityUpdate(entityName, entity) {
if (entityName === 'branches') {
if (entityName === 'notes') {
noteUpdated(entity);
} else if (entityName === 'branches') {
branchUpdated(entity);
} else if (entityName === 'attributes') {
attributeUpdated(entity);
@@ -84,7 +86,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
return;
}
if (["notes", "branches", "attributes", "etapi_tokens"].includes(entityName)) {
if (["notes", "branches", "attributes", "etapi_tokens", "options"].includes(entityName)) {
const EntityClass = entityConstructor.getEntityFromEntityName(entityName);
const primaryKeyName = EntityClass.primaryKeyName;
@@ -161,6 +163,15 @@ function branchDeleted(branchId) {
delete becca.branches[branch.branchId];
}
function noteUpdated(entity) {
const note = becca.notes[entity.noteId];
if (note) {
// type / mime could have been changed, and they are present in flatTextCache
note.flatTextCache = null;
}
}
function branchUpdated(branch) {
const childNote = becca.notes[branch.noteId];
+1 -129
View File
@@ -24,49 +24,12 @@ function isNotePathArchived(notePath) {
return false;
}
/**
* This assumes that note is available. "archived" note means that there isn't a single non-archived note-path
* leading to this note.
*
* @param noteId
*/
function isArchived(noteId) {
const notePath = getSomePath(noteId);
return isNotePathArchived(notePath);
}
/**
* @param {string} noteId
* @param {string} ancestorNoteId
* @returns {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived)
*/
function isInAncestor(noteId, ancestorNoteId) {
if (ancestorNoteId === 'root' || ancestorNoteId === noteId) {
return true;
}
const note = becca.notes[noteId];
if (!note) {
return false;
}
for (const parentNote of note.parents) {
if (isInAncestor(parentNote.noteId, ancestorNoteId)) {
return true;
}
}
return false;
}
function getNoteTitle(childNoteId, parentNoteId) {
const childNote = becca.notes[childNoteId];
const parentNote = becca.notes[parentNoteId];
if (!childNote) {
log.info(`Cannot find note in cache for noteId '${childNoteId}'`);
log.info(`Cannot find note '${childNoteId}'`);
return "[error fetching title]";
}
@@ -119,99 +82,8 @@ function getNoteTitleForPath(notePathArray) {
return titles.join(' / ');
}
/**
* Returns notePath for noteId from cache. Note hoisting is respected.
* Archived (and hidden) notes are also returned, but non-archived paths are preferred if available
* - this means that archived paths is returned only if there's no non-archived path
* - you can check whether returned path is archived using isArchived
*/
function getSomePath(note, path = []) {
// first try to find note within hoisted note, otherwise take any existing note path
// each branch needs a separate copy since it's mutable
return getSomePathInner(note, [...path], true)
|| getSomePathInner(note, [...path], false);
}
function getSomePathInner(note, path, respectHoisting) {
if (note.isRoot()) {
path.push(note.noteId);
path.reverse();
if (respectHoisting && !path.includes(cls.getHoistedNoteId())) {
return false;
}
return path;
}
const parents = note.parents;
if (parents.length === 0) {
console.log(`Note ${note.noteId} - "${note.title}" has no parents.`);
return false;
}
for (const parentNote of parents) {
const retPath = getSomePathInner(parentNote, path.concat([note.noteId]), respectHoisting);
if (retPath) {
return retPath;
}
}
return false;
}
function getNotePath(noteId) {
const note = becca.notes[noteId];
if (!note) {
console.trace(`Cannot find note '${noteId}' in cache.`);
return;
}
const retPath = getSomePath(note);
if (retPath) {
const noteTitle = getNoteTitleForPath(retPath);
let branchId;
if (note.isRoot()) {
branchId = 'none_root';
}
else {
const parentNote = note.parents[0];
branchId = becca.getBranchFromChildAndParent(noteId, parentNote.noteId).branchId;
}
return {
noteId: noteId,
branchId: branchId,
title: noteTitle,
notePath: retPath,
path: retPath.join('/')
};
}
}
/**
* @param noteId
* @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting
*/
function isAvailable(noteId) {
const notePath = getNotePath(noteId);
return !!notePath;
}
module.exports = {
getSomePath,
getNotePath,
getNoteTitle,
getNoteTitleForPath,
isAvailable,
isArchived,
isInAncestor,
isNotePathArchived
};
+1 -1
View File
@@ -96,7 +96,7 @@ class BAttribute extends AbstractBeccaEntity {
}
if (this.type === 'relation' && !(this.value in this.becca.notes)) {
throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it target not existing note '${this.value}'.`);
throw new Error(`Cannot save relation '${this.name}' of note '${this.noteId}' since it targets not existing note '${this.value}'.`);
}
}
+97 -13
View File
@@ -97,7 +97,7 @@ class BNote extends AbstractBeccaEntity {
* @private */
this.parents = [];
/** @type {BNote[]}
* @private*/
* @private */
this.children = [];
/** @type {BAttribute[]}
* @private */
@@ -107,11 +107,11 @@ class BNote extends AbstractBeccaEntity {
* @private */
this.__attributeCache = null;
/** @type {BAttribute[]|null}
* @private*/
* @private */
this.inheritableAttributeCache = null;
/** @type {BAttribute[]}
* @private*/
* @private */
this.targetRelations = [];
this.becca.addNote(this.noteId, this);
@@ -532,6 +532,20 @@ class BNote extends AbstractBeccaEntity {
*/
hasLabel(name, value) { return this.hasAttribute(LABEL, name, value); }
/**
* @param {string} name - label name
* @returns {boolean} true if label exists (including inherited) and does not have "false" value.
*/
isLabelTruthy(name) {
const label = this.getLabel(name);
if (!label) {
return false;
}
return label && label.value !== 'false';
}
/**
* @param {string} name - label name
* @param {string} [value] - label value
@@ -733,6 +747,21 @@ class BNote extends AbstractBeccaEntity {
return this.hasAttribute('label', 'archived');
}
areAllNotePathsArchived() {
// there's a slight difference between note being itself archived and all its note paths being archived
// - note is archived when it itself has an archived label or inherits it
// - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable)
// archived label
const bestNotePathRecord = this.getSortedNotePathRecords()[0];
if (!bestNotePathRecord) {
throw new Error(`No note path available for note '${this.noteId}'`);
}
return bestNotePathRecord.isArchived;
}
hasInheritableArchivedLabel() {
for (const attr of this.getAttributes()) {
if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) {
@@ -1136,6 +1165,8 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
*
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
getAllNotePaths() {
@@ -1143,18 +1174,73 @@ class BNote extends AbstractBeccaEntity {
return [['root']];
}
const notePaths = [];
const parentNotes = this.getParentNotes();
let notePaths = [];
for (const parentNote of this.getParentNotes()) {
for (const parentPath of parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
if (parentNotes.length === 1) { // optimization for most common case
notePaths = parentNotes[0].getAllNotePaths();
} else {
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
}
for (const notePath of notePaths) {
notePath.push(this.noteId);
}
return notePaths;
}
/**
* @param {string} [hoistedNoteId='root']
* @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]}
*/
getSortedNotePathRecords(hoistedNoteId = 'root') {
const isHoistedRoot = hoistedNoteId === 'root';
const notePaths = this.getAllNotePaths().map(path => ({
notePath: path,
isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
isArchived: path.some(noteId => this.becca.notes[noteId].isArchived),
isHidden: path.includes('_hidden')
}));
notePaths.sort((a, b) => {
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
return a.isInHoistedSubTree ? -1 : 1;
} else if (a.isArchived !== b.isArchived) {
return a.isArchived ? 1 : -1;
} else if (a.isHidden !== b.isHidden) {
return a.isHidden ? 1 : -1;
} else {
return a.notePath.length - b.notePath.length;
}
});
return notePaths;
}
/**
* Returns note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string[]} array of noteIds constituting the particular note path
*/
getBestNotePath(hoistedNoteId = 'root') {
return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
}
/**
* Returns note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
*/
getBestNotePathString(hoistedNoteId = 'root') {
const notePath = this.getBestNotePath(hoistedNoteId);
return notePath?.join("/");
}
/**
* @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
*/
@@ -1168,9 +1254,7 @@ class BNote extends AbstractBeccaEntity {
return false;
} else if (parentNote.noteId === '_hidden') {
continue;
}
if (!parentNote.isHiddenCompletely()) {
} else if (!parentNote.isHiddenCompletely()) {
return false;
}
}
@@ -1364,7 +1448,7 @@ class BNote extends AbstractBeccaEntity {
/**
* @param parentNoteId
* @returns {{success: boolean, message: string}}
* @returns {{success: boolean, message: string, branchId: string, notePath: string}}
*/
cloneTo(parentNoteId) {
const cloningService = require("../../services/cloning");
+2
View File
@@ -166,12 +166,14 @@ class BNoteRevision extends AbstractBeccaEntity {
utcDateLastEdited: this.utcDateLastEdited,
utcDateCreated: this.utcDateCreated,
utcDateModified: this.utcDateModified,
content: this.content, // used when retrieving full note revision to frontend
contentLength: this.contentLength
};
}
getPojoToSave() {
const pojo = this.getPojo();
delete pojo.content; // not getting persisted
delete pojo.contentLength; // not getting persisted
if (pojo.isProtected) {
+5 -2
View File
@@ -16,6 +16,11 @@ class BOption extends AbstractBeccaEntity {
constructor(row) {
super();
this.updateFromRow(row);
this.becca.options[this.name] = this;
}
updateFromRow(row) {
/** @type {string} */
this.name = row.name;
/** @type {string} */
@@ -24,8 +29,6 @@ class BOption extends AbstractBeccaEntity {
this.isSynced = !!row.isSynced;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
this.becca.options[this.name] = this;
}
beforeSaving() {
+7 -8
View File
@@ -2,7 +2,7 @@ const becca = require('./becca');
const log = require('../services/log');
const beccaService = require('./becca_service');
const dateUtils = require('../services/date_utils');
const { JSDOM } = require("jsdom");
const {JSDOM} = require("jsdom");
const DEBUG = false;
@@ -168,7 +168,6 @@ function trimMime(mime) {
}
mimeCache[mime] = str;
mimeCache[mime] = str;
}
return mimeCache[mime];
@@ -224,8 +223,8 @@ function splitToWords(text) {
*/
function hasConnectingRelation(sourceNote, targetNote) {
return sourceNote.getAttributes().find(attr => attr.type === 'relation'
&& ['includenotelink', 'imagelink'].includes(attr.name)
&& attr.value === targetNote.noteId);
&& ['includenotelink', 'imagelink'].includes(attr.name)
&& attr.value === targetNote.noteId);
}
async function findSimilarNotes(noteId) {
@@ -301,7 +300,7 @@ async function findSimilarNotes(noteId) {
for (const branch of parentNote.getParentBranches()) {
score += gatherRewards(branch.prefix, 0.3)
+ gatherAncestorRewards(branch.parentNote);
+ gatherAncestorRewards(branch.parentNote);
}
}
}
@@ -314,7 +313,7 @@ async function findSimilarNotes(noteId) {
function computeScore(candidateNote) {
let score = gatherRewards(trimMime(candidateNote.mime))
+ gatherAncestorRewards(candidateNote);
+ gatherAncestorRewards(candidateNote);
if (candidateNote.isDecrypted) {
score += gatherRewards(candidateNote.title);
@@ -382,7 +381,7 @@ async function findSimilarNotes(noteId) {
score += 1;
}
else if (utcDateCreated.substr(0, 10) === dateLimits.minDate.substr(0, 10)
|| utcDateCreated.substr(0, 10) === dateLimits.maxDate.substr(0, 10)) {
|| utcDateCreated.substr(0, 10) === dateLimits.maxDate.substr(0, 10)) {
if (displayRewards) {
console.log("Adding reward for same day of creation");
}
@@ -405,7 +404,7 @@ async function findSimilarNotes(noteId) {
let score = computeScore(candidateNote);
if (score >= 1.5) {
const notePath = beccaService.getSomePath(candidateNote);
const notePath = candidateNote.getBestNotePath();
// this takes care of note hoisting
if (!notePath) {
+4 -2
View File
@@ -17,7 +17,8 @@ function register(router) {
'type': [v.mandatory, v.notNull, v.isAttributeType],
'name': [v.mandatory, v.notNull, v.isString],
'value': [v.notNull, v.isString],
'isInheritable': [v.notNull, v.isBoolean]
'isInheritable': [v.notNull, v.isBoolean],
'position': [v.notNull, v.isInteger]
};
eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => {
@@ -40,7 +41,8 @@ function register(router) {
});
const ALLOWED_PROPERTIES_FOR_PATCH = {
'value': [v.notNull, v.isString]
'value': [v.notNull, v.isString],
'position': [v.notNull, v.isInteger]
};
eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
+89 -96
View File
@@ -31,7 +31,7 @@ paths:
'201':
description: note created
content:
application/json:
application/json; charset=utf-8:
schema:
properties:
note:
@@ -43,7 +43,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes:
@@ -163,13 +163,13 @@ paths:
'200':
description: search response
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/SearchResponse'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}:
@@ -186,13 +186,13 @@ paths:
'200':
description: note response
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
patch:
@@ -208,13 +208,13 @@ paths:
'200':
description: note updated
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
delete:
@@ -226,7 +226,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/content:
@@ -288,7 +288,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/note-revision:
@@ -315,32 +315,10 @@ paths:
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/branches/{branchId}:
parameters:
- name: branchId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns a branch identified by its ID
operationId: getBranchById
responses:
'200':
description: branch response
content:
application/json:
schema:
$ref: '#/components/schemas/Branch'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/branches:
post:
description: >
Create a branch (clone a note to a different location in the tree).
@@ -357,19 +335,42 @@ paths:
'200':
description: branch updated (branch between parent note and child note already existed)
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Branch'
'201':
description: branch created
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Branch'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/branches/{branchId}:
parameters:
- name: branchId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns a branch identified by its ID
operationId: getBranchById
responses:
'200':
description: branch response
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Branch'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
patch:
@@ -385,13 +386,13 @@ paths:
'200':
description: branch updated
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Branch'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
delete:
@@ -405,7 +406,30 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/attributes:
post:
description: create an attribute for a given note
operationId: postAttribute
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Attribute'
responses:
'201':
description: attribute created
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attribute'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/attributes/{attributeId}:
@@ -422,35 +446,13 @@ paths:
'200':
description: attribute response
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attribute'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
post:
description: create an attribute for a given note
operationId: postAttribute
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Attribute'
responses:
'201':
description: attribute created
content:
application/json:
schema:
$ref: '#/components/schemas/Attribute'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
patch:
@@ -466,13 +468,13 @@ paths:
'200':
description: attribute updated
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attribute'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
delete:
@@ -484,7 +486,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/refresh-note-ordering/{parentNoteId}:
@@ -506,7 +508,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/inbox/{date}:
@@ -527,13 +529,13 @@ paths:
'200':
description: inbox note
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/calendar/days/{date}:
@@ -552,13 +554,13 @@ paths:
'200':
description: day note
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/calendar/weeks/{date}:
@@ -577,13 +579,13 @@ paths:
'200':
description: week note
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/calendar/months/{month}:
@@ -602,13 +604,13 @@ paths:
'200':
description: month note
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/calendar/years/{year}:
@@ -627,13 +629,13 @@ paths:
'200':
description: year note
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Note'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/auth/login:
@@ -654,7 +656,7 @@ paths:
'201':
description: auth token
content:
application/json:
application/json; charset=utf-8:
schema:
properties:
authToken:
@@ -665,7 +667,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/auth/logout:
@@ -678,7 +680,7 @@ paths:
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/app-info:
@@ -689,13 +691,13 @@ paths:
'200':
description: app info
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/AppInfo'
default:
description: unexpected error
content:
application/json:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
@@ -774,7 +776,7 @@ components:
type: string
type:
type: string
enum: [text, code, render, file, image, search, relationMap, book, noteMap, mermaid, webView, shortcut]
enum: [text, code, render, file, image, search, relationMap, book, noteMap, mermaid, webView, shortcut, doc, contentWidget, launcher]
mime:
type: string
isProtected:
@@ -810,9 +812,6 @@ components:
Branch:
type: object
description: Branch places the note into the tree, it represents the relationship between a parent note and child note
required:
- noteId
- parentNoteId
properties:
branchId:
$ref: '#/components/schemas/EntityId'
@@ -837,8 +836,6 @@ components:
Attribute:
type: object
description: Attribute (Label, Relation) is a key-value record attached to a note.
required:
- noteId
properties:
attributeId:
$ref: '#/components/schemas/EntityId'
@@ -851,7 +848,7 @@ components:
enum: [label, relation]
name:
type: string
pattern: '^[\p{L}\p{N}_:]+'
pattern: '^[^\s]+'
example: shareCss
value:
type: string
@@ -881,7 +878,7 @@ components:
description: debugging info on parsing the search query enabled with &debug=true parameter
EntityId:
type: string
pattern: '[a-zA-Z0-9]{4,32}'
pattern: '[a-zA-Z0-9_]{4,32}'
example: evnnmvHTCgIn
EntityIdList:
type: array
@@ -889,7 +886,7 @@ components:
$ref: '#/components/schemas/EntityId'
LocalDateTime:
type: string
pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}\+[0-9]{4}'
pattern: '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}[\+\-][0-9]{4}'
example: 2021-12-31 20:18:11.939+0100
UtcDateTime:
type: string
@@ -897,10 +894,6 @@ components:
example: 2021-12-31 19:18:11.939Z
AppInfo:
type: object
required:
- statu
- code
- message
properties:
appVersion:
type: string
+5 -5
View File
@@ -20,7 +20,7 @@ function register(router) {
const {date} = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(res, date);
throw getDateInvalidError(date);
}
const note = specialNotesService.getInboxNote(date);
@@ -31,7 +31,7 @@ function register(router) {
const {date} = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(res, date);
throw getDateInvalidError(date);
}
const note = dateNotesService.getDayNote(date);
@@ -42,7 +42,7 @@ function register(router) {
const {date} = req.params;
if (!isValidDate(date)) {
throw getDateInvalidError(res, date);
throw getDateInvalidError(date);
}
const note = dateNotesService.getWeekNote(date);
@@ -53,7 +53,7 @@ function register(router) {
const {month} = req.params;
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
throw getMonthInvalidError(res, month);
throw getMonthInvalidError(month);
}
const note = dateNotesService.getMonthNote(month);
@@ -64,7 +64,7 @@ function register(router) {
const {year} = req.params;
if (!/[0-9]{4}/.test(year)) {
throw getYearInvalidError(res, year);
throw getYearInvalidError(year);
}
const note = dateNotesService.getYearNote(year);
@@ -226,6 +226,10 @@ class NoteContext extends Component {
return true;
}
if (this.viewScope.viewMode === 'source') {
return true;
}
const noteComplement = await this.getNoteComplement();
const sizeLimit = this.note.type === 'text'
@@ -55,6 +55,13 @@ export default class RootCommandExecutor extends Component {
}
}
openNoteCustomCommand() {
const noteId = appContext.tabManager.getActiveContextNoteId();
if (noteId) {
openService.openNoteCustom(noteId);
}
}
enterProtectedSessionCommand() {
protectedSessionService.enterProtectedSession();
}
+6 -1
View File
@@ -388,7 +388,12 @@ export default class TabManager extends Component {
await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove });
if (!noteContextToRemove.isMainContext()) {
await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId);
const siblings = noteContextToRemove.getMainContext().getSubContexts();
const idx = siblings.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId);
const contextToActivateIdx = idx === siblings.length - 1 ? idx - 1 : idx + 1;
const contextToActivate = siblings[contextToActivateIdx];
await this.activateNoteContext(contextToActivate.ntxId);
}
else if (this.mainNoteContexts.length <= 1) {
await this.openAndActivateEmptyTab();
+19 -2
View File
@@ -8,6 +8,7 @@ import contextMenu from "./menus/context_menu.js";
import DesktopLayout from "./layouts/desktop_layout.js";
import glob from "./services/glob.js";
import zoomService from './components/zoom.js';
import options from "./services/options.js";
bundleService.getWidgetBundlesByParent().then(widgetBundles => {
appContext.setLayout(new DesktopLayout(widgetBundles));
@@ -115,11 +116,27 @@ if (utils.isElectron()) {
? (`${params.selectionText.substr(0, 13)}`)
: params.selectionText;
// Read the search engine from the options and fallback to DuckDuckGo if the option is not set.
const customSearchEngineName = options.get("customSearchEngineName");
const customSearchEngineUrl = options.get("customSearchEngineUrl");
let searchEngineName;
let searchEngineUrl;
if (customSearchEngineName && customSearchEngineUrl) {
searchEngineName = customSearchEngineName;
searchEngineUrl = customSearchEngineUrl;
} else {
searchEngineName = "Duckduckgo";
searchEngineUrl = "https://duckduckgo.com/?q={keyword}";
}
// Replace the placeholder with the real search keyword.
let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText));
items.push({
enabled: editFlags.canPaste,
title: `Search for "${shortenedSelection}" with DuckDuckGo`,
title: `Search for "${shortenedSelection}" with ${searchEngineName}`,
uiIcon: "bx bx-search-alt",
handler: () => electron.shell.openExternal(`https://duckduckgo.com/?q=${encodeURIComponent(params.selectionText)}`)
handler: () => electron.shell.openExternal(searchUrl)
});
}
@@ -1 +1 @@
<p>Keyboard launcher for this launcher action can be configured in Options -> Launchers.</p>
<p>Keyboard shortcut for this launcher action can be configured in Options -> Shortcuts.</p>
@@ -3,7 +3,7 @@
<ol>
<li><code>target</code> - note which should be opened upon activating the launcher</li>
<li><code>hoistedNote</code> - optional, will change the hoisted note before opening the target note</li>
<li><code>keyboardLauncher</code> - optional, pressing the keyboard launcher will open the note</li>
<li><code>keyboardShortcut</code> - optional, pressing the keyboard shortcut will open the note</li>
</ol>
<p>Launchbar displays the title / icon from the launcher which does not necessarily mirrors those of the target note.</p>
<p>Launchbar displays the title / icon from the launcher which does not necessarily mirror those of the target note.</p>
@@ -2,7 +2,7 @@
<ol>
<li><code>script</code> - relation to the script note which should be executed upon launcher activation</li>
<li><code>keyboardLauncher</code> - optional, pressing the keyboard launcher will activate the launcher</li>
<li><code>keyboardShortcut</code> - optional, pressing the keyboard shortcut will activate the launcher</li>
</ol>
<h4>Example script</h4>
+89 -42
View File
@@ -73,7 +73,7 @@ class FNote {
this.mime = row.mime;
}
addParent(parentNoteId, branchId) {
addParent(parentNoteId, branchId, sort = true) {
if (parentNoteId === 'none') {
return;
}
@@ -83,6 +83,10 @@ class FNote {
}
this.parentToBranch[parentNoteId] = branchId;
if (sort) {
this.sortParents();
}
}
addChild(childNoteId, branchId, sort = true) {
@@ -189,7 +193,7 @@ class FNote {
// will sort the parents so that non-search & non-archived are first and archived at the end
// this is done so that non-search & non-archived paths are always explored as first when looking for note path
resortParents() {
sortParents() {
this.parents.sort((aNoteId, bNoteId) => {
const aBranchId = this.parentToBranch[aNoteId];
@@ -197,7 +201,7 @@ class FNote {
return 1;
}
const aNote = this.froca.getNoteFromCache([aNoteId]);
const aNote = this.froca.getNoteFromCache(aNoteId);
if (aNote.isArchived || aNote.isHiddenCompletely()) {
return 1;
@@ -243,6 +247,11 @@ class FNote {
return this.__filterAttrs(this.__getCachedAttributes([]), type, name);
}
/**
* @param {string[]} path
* @return {FAttribute[]}
* @private
*/
__getCachedAttributes(path) {
// notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates
// when template instance is a parent of template itself
@@ -295,63 +304,49 @@ class FNote {
return this.noteId === 'root';
}
getAllNotePaths(encounteredNoteIds = null) {
/**
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
*
* @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
getAllNotePaths() {
if (this.noteId === 'root') {
return [['root']];
}
if (!encounteredNoteIds) {
encounteredNoteIds = new Set();
}
encounteredNoteIds.add(this.noteId);
const parentNotes = this.getParentNotes();
let paths;
let notePaths = [];
if (parentNotes.length === 1) { // optimization for the most common case
if (encounteredNoteIds.has(parentNotes[0].noteId)) {
return [];
}
else {
paths = parentNotes[0].getAllNotePaths(encounteredNoteIds);
}
}
else {
paths = [];
for (const parentNote of parentNotes) {
if (encounteredNoteIds.has(parentNote.noteId)) {
continue;
}
const newSet = new Set(encounteredNoteIds);
paths.push(...parentNote.getAllNotePaths(newSet));
}
if (parentNotes.length === 1) { // optimization for most common case
notePaths = parentNotes[0].getAllNotePaths();
} else {
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
}
for (const path of paths) {
path.push(this.noteId);
for (const notePath of notePaths) {
notePath.push(this.noteId);
}
return paths;
return notePaths;
}
getSortedNotePaths(hoistedNotePath = 'root') {
/**
* @param {string} [hoistedNoteId='root']
* @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]}
*/
getSortedNotePathRecords(hoistedNoteId = 'root') {
const isHoistedRoot = hoistedNoteId === 'root';
const notePaths = this.getAllNotePaths().map(path => ({
notePath: path,
isInHoistedSubTree: path.includes(hoistedNotePath),
isArchived: path.find(noteId => froca.notes[noteId].isArchived),
isSearch: path.find(noteId => froca.notes[noteId].type === 'search'),
isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
isArchived: path.some(noteId => froca.notes[noteId].isArchived),
isHidden: path.includes('_hidden')
}));
notePaths.sort((a, b) => {
if (a.isInHoistedSubTree !== b.isInHoistedSubTree) {
return a.isInHoistedSubTree ? -1 : 1;
} else if (a.isSearch !== b.isSearch) {
return a.isSearch ? 1 : -1;
} else if (a.isArchived !== b.isArchived) {
return a.isArchived ? 1 : -1;
} else if (a.isHidden !== b.isHidden) {
@@ -364,6 +359,28 @@ class FNote {
return notePaths;
}
/**
* Returns note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string[]} array of noteIds constituting the particular note path
*/
getBestNotePath(hoistedNoteId = 'root') {
return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath;
}
/**
* Returns note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
*/
getBestNotePathString(hoistedNoteId = 'root') {
const notePath = this.getBestNotePath(hoistedNoteId);
return notePath?.join("/");
}
/**
* @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree
*/
@@ -387,6 +404,13 @@ class FNote {
return true;
}
/**
* @param {FAttribute[]} attributes
* @param {string} type
* @param {string} name
* @return {FAttribute[]}
* @private
*/
__filterAttrs(attributes, type, name) {
this.__validateTypeName(type, name);
@@ -523,7 +547,9 @@ class FNote {
* @returns {boolean} true if note has an attribute with given type and name (including inherited)
*/
hasAttribute(type, name) {
return !!this.getAttribute(type, name);
const attributes = this.getAttributes();
return attributes.some(attr => attr.name === name && attr.type === type);
}
/**
@@ -591,6 +617,20 @@ class FNote {
*/
hasLabel(name) { return this.hasAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {boolean} true if label exists (including inherited) and does not have "false" value.
*/
isLabelTruthy(name) {
const label = this.getLabel(name);
if (!label) {
return false;
}
return label && label.value !== 'false';
}
/**
* @param {string} name - relation name
* @returns {boolean} true if relation exists (excluding inherited)
@@ -702,7 +742,14 @@ class FNote {
});
// attrs are not resorted if position changes after initial load
promotedAttrs.sort((a, b) => a.position < b.position ? -1 : 1);
promotedAttrs.sort((a, b) => {
if (a.noteId === b.noteId) {
return a.position < b.position ? -1 : 1;
} else {
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
return a.noteId < b.noteId ? -1 : 1;
}
});
return promotedAttrs;
}
+4 -1
View File
@@ -128,6 +128,10 @@ export default class DesktopLayout {
)
.child(
new RibbonContainer()
// order of the widgets matter. Some of these want to "activate" themselves
// when visible, when this happens to multiple of them, the first one "wins".
// promoted attributes should always win.
.ribbon(new PromotedAttributesWidget())
.ribbon(new ScriptExecutorWidget())
.ribbon(new SearchDefinitionWidget())
.ribbon(new EditedNotesWidget())
@@ -135,7 +139,6 @@ export default class DesktopLayout {
.ribbon(new NotePropertiesWidget())
.ribbon(new FilePropertiesWidget())
.ribbon(new ImagePropertiesWidget())
.ribbon(new PromotedAttributesWidget())
.ribbon(new BasicPropertiesWidget())
.ribbon(new OwnedAttributeListWidget())
.ribbon(new InheritedAttributesWidget())
+8 -1
View File
@@ -99,11 +99,16 @@ class ContextMenu {
const $item = $("<li>")
.addClass("dropdown-item")
.append($link)
.on('contextmenu', e => false)
// important to use mousedown instead of click since the former does not change focus
// (especially important for focused text for spell check)
.on('mousedown', e => {
e.stopPropagation();
if (e.which !== 1) { // only left click triggers menu items
return false;
}
this.hide();
if (item.handler) {
@@ -142,7 +147,9 @@ class ContextMenu {
// "contextmenu" event also triggers "click" event which depending on the timing can close just opened context menu
// we might filter out right clicks, but then it's better if even right clicks close the context menu
if (Date.now() - this.dateContextMenuOpenedMs > 300) {
this.$widget.hide();
// seems like if we hide the menu immediately, some clicks can get propagated to the underlying component
// see https://github.com/zadam/trilium/pull/3805 for details
setTimeout(() => this.$widget.hide(), 100);
}
}
}
+6 -6
View File
@@ -10,7 +10,7 @@ async function moveBeforeBranch(branchIdsToMove, beforeBranchId) {
branchIdsToMove = filterRootNote(branchIdsToMove);
branchIdsToMove = filterSearchBranches(branchIdsToMove);
const beforeBranch = await froca.getBranch(beforeBranchId);
const beforeBranch = froca.getBranch(beforeBranchId);
if (['root', '_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(beforeBranch.noteId)) {
toastService.showError('Cannot move notes here.');
@@ -31,7 +31,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
branchIdsToMove = filterRootNote(branchIdsToMove);
branchIdsToMove = filterSearchBranches(branchIdsToMove);
const afterNote = await froca.getBranch(afterBranchId).getNote();
const afterNote = froca.getBranch(afterBranchId).getNote();
const forbiddenNoteIds = [
'root',
@@ -59,7 +59,7 @@ async function moveAfterBranch(branchIdsToMove, afterBranchId) {
}
async function moveToParentNote(branchIdsToMove, newParentBranchId) {
const newParentBranch = await froca.getBranch(newParentBranchId);
const newParentBranch = froca.getBranch(newParentBranchId);
if (newParentBranch.noteId === '_lbRoot') {
toastService.showError('Cannot move notes here.');
@@ -165,7 +165,7 @@ function filterRootNote(branchIds) {
const hoistedNoteId = hoistedNoteService.getHoistedNoteId();
return branchIds.filter(branchId => {
const branch = froca.getBranch(branchId);
const branch = froca.getBranch(branchId);
return branch.noteId !== 'root'
&& branch.noteId !== hoistedNoteId;
@@ -227,7 +227,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) {
}
}
async function cloneNoteToNote(childNoteId, parentNoteId, prefix) {
async function cloneNoteToParentNote(childNoteId, parentNoteId, prefix) {
const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
prefix: prefix
});
@@ -254,5 +254,5 @@ export default {
moveNodeUpInHierarchy,
cloneNoteAfter,
cloneNoteToBranch,
cloneNoteToNote,
cloneNoteToParentNote,
};
+2 -1
View File
@@ -14,7 +14,8 @@ function createClassForColor(color) {
const className = `color-${normalizedColorName}`;
if (!registeredClasses.has(className)) {
$("head").append(`<style>.${className} { color: ${color} !important; }</style>`);
// make the active fancytree selector more specific than the normal color setting
$("head").append(`<style>.${className}, span.fancytree-active.${className} { color: ${color} !important; }</style>`);
registeredClasses.add(className);
}
+2 -1
View File
@@ -115,7 +115,7 @@ class Froca {
const childNote = this.notes[branch.noteId];
if (childNote) {
childNote.addParent(branch.parentNoteId, branch.branchId);
childNote.addParent(branch.parentNoteId, branch.branchId, false);
}
const parentNote = this.notes[branch.parentNoteId];
@@ -152,6 +152,7 @@ class Froca {
// sort all of them at once, this avoids repeated sorts (#1480)
for (const noteId of noteIdsToSort) {
this.notes[noteId].sortChildren();
this.notes[noteId].sortParents();
}
}
+11 -3
View File
@@ -14,7 +14,7 @@ async function processEntityChanges(entityChanges) {
if (ec.entityName === 'notes') {
processNoteChange(loadResults, ec);
} else if (ec.entityName === 'branches') {
processBranchChange(loadResults, ec);
await processBranchChange(loadResults, ec);
} else if (ec.entityName === 'attributes') {
processAttributeChange(loadResults, ec);
} else if (ec.entityName === 'note_reordering') {
@@ -105,7 +105,7 @@ function processNoteChange(loadResults, ec) {
}
}
function processBranchChange(loadResults, ec) {
async function processBranchChange(loadResults, ec) {
if (ec.isErased && ec.entityId in froca.branches) {
utils.reloadFrontendApp(`${ec.entityName} ${ec.entityId} is erased, need to do complete reload.`);
return;
@@ -139,7 +139,15 @@ function processBranchChange(loadResults, ec) {
loadResults.addBranch(ec.entityId, ec.componentId);
const childNote = froca.notes[ec.entity.noteId];
const parentNote = froca.notes[ec.entity.parentNoteId];
let parentNote = froca.notes[ec.entity.parentNoteId];
if (childNote && !childNote.isRoot() && !parentNote) {
// a branch cannot exist without the parent
// a note loaded into froca has to also contain all its ancestors
// this problem happened e.g. in sharing where _share was hidden and thus not loaded
// sharing meant cloning into _share, which crashed because _share was not loaded
parentNote = await froca.getNote(ec.entity.parentNoteId);
}
if (branch) {
branch.update(ec.entity);
@@ -65,7 +65,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(notePath);
appContext.triggerEvent('focusAndSelectTitle');
await appContext.triggerEvent('focusAndSelectTitle');
};
/**
@@ -82,7 +82,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
await appContext.tabManager.openContextWithNote(notePath, { activate });
if (activate) {
appContext.triggerEvent('focusAndSelectTitle');
await appContext.triggerEvent('focusAndSelectTitle');
}
};
@@ -100,10 +100,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
const {ntxId} = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath});
await appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath});
if (activate) {
appContext.triggerEvent('focusAndSelectTitle');
await appContext.triggerEvent('focusAndSelectTitle');
}
};
+4 -5
View File
@@ -10,7 +10,6 @@ export async function uploadFiles(parentNoteId, files, options) {
}
const taskId = utils.randomString(10);
let noteId;
let counter = 0;
for (const file of files) {
@@ -25,19 +24,19 @@ export async function uploadFiles(parentNoteId, files, options) {
formData.append(key, options[key]);
}
({noteId} = await $.ajax({
await $.ajax({
url: `${baseApiUrl}notes/${parentNoteId}/import`,
headers: await server.getHeaders(),
data: formData,
dataType: 'json',
type: 'POST',
timeout: 60 * 60 * 1000,
error: function(xhr) {
error: function (xhr) {
toastService.showError(`Import failed: ${xhr.responseText}`);
},
contentType: false, // NEEDED, DON'T REMOVE THIS
processData: false, // NEEDED, DON'T REMOVE THIS
}));
});
}
}
@@ -74,4 +73,4 @@ ws.subscribeToMessages(async message => {
export default {
uploadFiles
}
};
+2 -2
View File
@@ -2,7 +2,6 @@ import server from "./server.js";
import appContext from "../components/app_context.js";
import utils from './utils.js';
import noteCreateService from './note_create.js';
import treeService from './tree.js';
import froca from "./froca.js";
// this key needs to have this value, so it's hit by the tooltip
@@ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) {
templateNoteId: templateNoteId
});
suggestion.notePath = treeService.getSomeNotePath(note);
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
suggestion.notePath = note.getBestNotePathString(hoistedNoteId);
}
$el.setSelectedNotePath(suggestion.notePath);
+27 -25
View File
@@ -12,86 +12,88 @@ const TPL = `
position: relative;
height: 100%;
}
.note-list.grid-view .note-list-container {
display: flex;
flex-wrap: wrap;
}
.note-list.grid-view .note-book-card {
flex-basis: 300px;
border: 1px solid transparent;
}
.note-list.grid-view .note-expander {
display: none;
}
.note-list.grid-view .note-book-card {
max-height: 300px;
}
.note-list.grid-view .note-book-card:hover {
cursor: pointer;
border: 1px solid var(--main-border-color);
background: var(--more-accented-background-color);
}
.note-book-card {
border-radius: 10px;
background-color: var(--accented-background-color);
padding: 10px 15px 15px 8px;
margin: 5px 5px 5px 0;
margin: 5px 5px 5px 5px;
overflow: hidden;
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 1;
}
.note-book-card:not(.expanded) .note-book-content {
display: none !important;
padding: 10px
}
.note-book-card.expanded .note-book-content {
display: block;
min-height: 0;
height: 100%;
padding-top: 10px;
}
.note-book-header {
border-bottom: 1px solid var(--main-border-color);
margin-bottom: 0;
padding-bottom: .5rem;
word-break: break-all;
}
/* not-expanded title is limited to one line only */
.note-book-card:not(.expanded) .note-book-header {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.note-book-header .rendered-note-attributes {
font-size: medium;
}
.note-book-header .rendered-note-attributes:before {
content: "\\00a0\\00a0";
}
.note-book-header .note-icon {
font-size: 100%;
display: inline-block;
padding-right: 7px;
position: relative;
}
.note-book-card .note-book-card {
border: 1px solid var(--main-border-color);
}
.note-book-content.type-image, .note-book-content.type-file, .note-book-content.type-protectedSession {
display: flex;
align-items: center;
@@ -99,46 +101,46 @@ const TPL = `
text-align: center;
padding: 10px;
}
.note-book-content.type-image img, .note-book-content.type-canvas svg {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.note-book-card.type-image .note-book-content img,
.note-book-card.type-text .note-book-content img,
.note-book-card.type-canvas .note-book-content img {
max-width: 100%;
max-height: 100%;
}
.note-book-header {
flex-grow: 0;
}
.note-list-wrapper {
height: 100%;
overflow: auto;
}
.note-expander {
font-size: x-large;
position: relative;
top: 3px;
cursor: pointer;
}
.note-list-pager {
text-align: center;
}
</style>
<div class="note-list-wrapper">
<div class="note-list-pager"></div>
<div class="note-list-container"></div>
<div class="note-list-pager"></div>
</div>
</div>`;
+5 -3
View File
@@ -4,6 +4,7 @@ import froca from "./froca.js";
import utils from "./utils.js";
import attributeRenderer from "./attribute_renderer.js";
import noteContentRenderer from "./note_content_renderer.js";
import appContext from "../components/app_context.js";
function setupGlobalTooltip() {
$(document).on("mouseenter", "a", mouseEnterHandler);
@@ -83,13 +84,14 @@ async function renderTooltip(note) {
return '<div>Note has been deleted.</div>';
}
const someNotePath = treeService.getSomeNotePath(note);
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
const bestNotePath = note.getBestNotePathString(hoistedNoteId);
if (!someNotePath) {
if (!bestNotePath) {
return;
}
let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}</h5>`;
let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}</h5>`;
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
+62
View File
@@ -47,6 +47,67 @@ async function openNoteExternally(noteId, mime) {
}
}
async function openNoteCustom(noteId) {
if (!utils.isElectron() || utils.isMac()) {
return;
}
const resp = await server.post(`notes/${noteId}/save-to-tmp-dir`);
let filePath = resp.tmpFilePath;
const {exec} = utils.dynamicRequire('child_process');
const platform = process.platform;
if (platform === 'linux') {
// we don't know which terminal is available, try in succession
const terminals = ['x-terminal-emulator', 'gnome-terminal', 'konsole', 'xterm', 'xfce4-terminal', 'mate-terminal', 'rxvt', 'terminator', 'terminology'];
const openFileWithTerminal = (terminal) => {
const command = `${terminal} -e 'mimeopen -d "${filePath}"'`;
console.log(`Open Note custom: ${command} `);
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Open Note custom: Failed to open file with ${terminal}: ${error}`);
searchTerminal(terminals.indexOf(terminal) + 1);
} else {
console.log(`Open Note custom: File opened with ${terminal}: ${stdout}`);
}
});
};
const searchTerminal = (index) => {
const terminal = terminals[index];
if (!terminal) {
console.error('Open Note custom: No terminal found!');
open(getFileUrl(noteId), {url: true});
return;
}
exec(`which ${terminal}`, (error, stdout, stderr) => {
if (stdout.trim()) {
openFileWithTerminal(terminal);
} else {
searchTerminal(index + 1);
}
});
};
searchTerminal(0);
} else if (platform === 'win32') {
if (filePath.indexOf("/") !== -1) {
// Note that the path separator must be \ instead of /
filePath = filePath.replace(/\//g, "\\");
}
const command = `rundll32.exe shell32.dll,OpenAs_RunDLL ` + filePath;
exec(command, (err, stdout, stderr) => {
if (err) {
console.error("Open Note custom: ", err);
open(getFileUrl(noteId), {url: true});
return;
}
});
} else {
console.log('Currently "Open Note custom" only supports linux and windows systems');
open(getFileUrl(noteId), {url: true});
}
}
function downloadNoteRevision(noteId, noteRevisionId) {
const url = getUrlForDownload(`api/notes/${noteId}/revisions/${noteRevisionId}/download`);
@@ -76,6 +137,7 @@ export default {
download,
downloadFileNote,
openNoteExternally,
openNoteCustom,
downloadNoteRevision,
getUrlForDownload
}
+8 -39
View File
@@ -58,7 +58,7 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
return;
}
child.resortParents();
child.sortParents();
const parents = child.getParentNotes();
@@ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
You can ignore this message as it is mostly harmless.`);
}
const someNotePath = getSomeNotePath(child, hoistedNoteId);
const bestNotePath = child.getBestNotePath(hoistedNoteId);
if (someNotePath) { // in case it's root the path may be empty
const pathToRoot = someNotePath.split("/").reverse().slice(1);
if (!pathToRoot.includes("root")) {
pathToRoot.push('root');
}
if (bestNotePath) {
const pathToRoot = bestNotePath.reverse().slice(1);
for (const noteId of pathToRoot) {
effectivePathSegments.push(noteId);
@@ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
else {
const note = await froca.getNote(getNoteIdFromNotePath(notePath));
const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId);
const bestNotePath = note.getBestNotePath(hoistedNoteId);
if (!someNotePathSegments) {
throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`);
if (!bestNotePath) {
throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`);
}
// if there isn't actually any note path with hoisted note then return the original resolved note path
return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments;
return bestNotePath.includes(hoistedNoteId) ? bestNotePath : effectivePathSegments;
}
}
function getSomeNotePathSegments(note, hoistedNotePath = 'root') {
utils.assertArguments(note);
const notePaths = note.getSortedNotePaths(hoistedNotePath);
return notePaths.length > 0 ? notePaths[0].notePath : null;
}
function getSomeNotePath(note, hoistedNotePath = 'root') {
const notePath = getSomeNotePathSegments(note, hoistedNotePath);
return notePath === null ? null : notePath.join('/');
}
ws.subscribeToMessages(message => {
if (message.type === 'openNote') {
appContext.tabManager.activateOrOpenNote(message.noteId);
@@ -311,16 +293,6 @@ function isNotePathInAddress() {
|| (notePath === '' && !!ntxId);
}
function parseNotePath(notePath) {
let noteIds = notePath.split('/');
if (noteIds[0] !== 'root') {
noteIds = ['root'].concat(noteIds);
}
return noteIds;
}
function isNotePathInHiddenSubtree(notePath) {
return notePath?.includes("root/_hidden");
}
@@ -328,8 +300,6 @@ function isNotePathInHiddenSubtree(notePath) {
export default {
resolveNotePath,
resolveNotePathToSegments,
getSomeNotePath,
getSomeNotePathSegments,
getParentProtectedStatus,
getNotePath,
getNoteIdFromNotePath,
@@ -340,6 +310,5 @@ export default {
getNoteTitleWithPathAsSuffix,
getHashValueFromAddress,
isNotePathInAddress,
parseNotePath,
isNotePathInHiddenSubtree
};
+5 -5
View File
@@ -29,7 +29,7 @@ function formatTimeWithSeconds(date) {
// this is producing local time!
function formatDate(date) {
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
// return padNum(date.getDate()) + ". " + padNum(date.getMonth() + 1) + ". " + date.getFullYear();
// instead of european format we'll just use ISO as that's pretty unambiguous
return formatDateISO(date);
@@ -45,7 +45,7 @@ function formatDateTime(date) {
}
function localNowDateTime() {
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ')
return dayjs().format('YYYY-MM-DD HH:mm:ss.SSSZZ');
}
function now() {
@@ -101,7 +101,7 @@ async function stopWatch(what, func) {
}
function formatValueWithWhitespace(val) {
return /[^\w_-]/.test(val) ? `"${val}"` : val;
return /[^\w-]/.test(val) ? `"${val}"` : val;
}
function formatLabel(label) {
@@ -318,7 +318,7 @@ function initHelpDropdown($el) {
initHelpButtons($dropdownMenu);
}
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/"
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
function openHelp(e) {
window.open(wikiBaseUrl + $(e.target).attr("data-help-page"), '_blank');
@@ -329,7 +329,7 @@ function initHelpButtons($el) {
// so we do it manually
$el.on("click", e => {
if ($(e.target).attr("data-help-page")) {
openHelp(e)
openHelp(e);
}
});
}
@@ -1,6 +1,5 @@
import server from "../../services/server.js";
import froca from "../../services/froca.js";
import treeService from "../../services/tree.js";
import linkService from "../../services/link.js";
import attributeAutocompleteService from "../../services/attribute_autocomplete.js";
import noteAutocompleteService from "../../services/note_autocomplete.js";
@@ -9,6 +8,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
import SpacedUpdate from "../../services/spaced_update.js";
import utils from "../../services/utils.js";
import shortcutService from "../../services/shortcuts.js";
import appContext from "../../components/app_context.js";
const TPL = `
<div class="attr-detail">
@@ -242,7 +242,8 @@ const ATTR_HELP = {
"keepCurrentHoisting": "Opening this link won't change hoisting even if the note is not displayable in the current hoisted subtree.",
"executeButton": "Title of the button which will execute the current code note",
"executeDescription": "Longer description of the current code note displayed together with the execute button",
"excludeFromNoteMap": "Notes with this label will be hidden from the Note Map"
"excludeFromNoteMap": "Notes with this label will be hidden from the Note Map",
"newNotesOnTop": "New notes will be created at the top of the parent note, not on the bottom."
},
"relation": {
"runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.",
@@ -284,7 +285,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$title = this.$widget.find('.attr-detail-title');
this.$inputName = this.$widget.find('.attr-input-name');
this.$inputName.on('keyup', () => this.userEditedAttribute());
this.$inputName.on('input', ev => {
if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
this.userEditedAttribute();
}
});
this.$inputName.on('change', () => this.userEditedAttribute());
this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute());
@@ -298,7 +303,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowValue = this.$widget.find('.attr-row-value');
this.$inputValue = this.$widget.find('.attr-input-value');
this.$inputValue.on('keyup', () => this.userEditedAttribute());
this.$inputValue.on('input', ev => {
if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
this.userEditedAttribute();
}
});
this.$inputValue.on('change', () => this.userEditedAttribute());
this.$inputValue.on('autocomplete:closed', () => this.userEditedAttribute());
this.$inputValue.on('focus', () => {
@@ -327,7 +336,11 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
this.$rowInverseRelation = this.$widget.find('.attr-row-inverse-relation');
this.$inputInverseRelation = this.$widget.find('.attr-input-inverse-relation');
this.$inputInverseRelation.on('keyup', () => this.userEditedAttribute());
this.$inputInverseRelation.on('input', ev => {
if (!ev.originalEvent?.isComposing) { // https://github.com/zadam/trilium/pull/3812
this.userEditedAttribute();
}
});
this.$rowTargetNote = this.$widget.find('.attr-row-target-note');
this.$inputTargetNote = this.$widget.find('.attr-input-target-note');
@@ -585,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES);
const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId));
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
for (const note of displayedNotes) {
const notePath = treeService.getSomeNotePath(note);
const notePath = note.getBestNotePathString(hoistedNoteId);
const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true});
this.$relatedNotesList.append(
@@ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js";
import froca from "../../services/froca.js";
import attributeRenderer from "../../services/attribute_renderer.js";
import noteCreateService from "../../services/note_create.js";
import treeService from "../../services/tree.js";
import attributeService from "../../services/attributes.js";
const HELP_TEXT = `
@@ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
title: title
});
return treeService.getSomeNotePath(note);
return note.getBestNotePathString();
}
async updateAttributeList(attributes) {
@@ -5,7 +5,9 @@ import protectedSessionHolder from "../../services/protected_session_holder.js";
export default class EditButton extends OnClickButtonWidget {
isEnabled() {
return super.isEnabled() && this.note;
return super.isEnabled()
&& this.note
&& this.noteContext.viewScope.viewMode === 'default';
}
constructor() {
@@ -16,16 +16,17 @@ const TPL = `
}
.global-menu-button {
background-image: url("${window.glob.assetPath}/images/icon-black.png");
background-image: url("${window.glob.assetPath}/images/icon-black.svg");
background-repeat: no-repeat;
background-position: 50% 45%;
background-position: 50% 80%;
background-size: 45px;
width: 100%;
height: 100%;
position: relative;
}
.global-menu-button:hover {
background-image: url("${window.glob.assetPath}/images/icon-color.png");
background-image: url("${window.glob.assetPath}/images/icon-color.svg");
border: 0;
}
@@ -29,6 +29,7 @@ const TPL = `
<a data-trigger-command="findInText" class="dropdown-item find-in-text-button">Search in note <kbd data-command="findInText"></a>
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a>
<a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button"><kbd data-command="openNoteExternally"></kbd> Open note externally</a>
<a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> Open note custom</a>
<a class="dropdown-item import-files-button">Import files</a>
<a class="dropdown-item export-note-button">Export note</a>
<a class="dropdown-item delete-note-button">Delete note</a>
@@ -67,6 +68,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
this.$openNoteCustomButton = this.$widget.find(".open-note-custom-button");
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
this.$deleteNoteButton.on("click", () => {
@@ -88,6 +90,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.$renderNoteButton.toggle(note.type === 'render');
this.$openNoteExternallyButton.toggle(utils.isElectron());
this.$openNoteCustomButton.toggle(utils.isElectron() && !utils.isMac()); // no implementation for Mac yet
}
toggleDisabled($el, enable) {
@@ -39,7 +39,7 @@ const TPL = `
.ribbon-tab-title.active {
color: var(--main-text-color);
border-bottom: 1px solid var(--main-text-color);
border-bottom: 3px solid var(--main-text-color);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -6,6 +6,7 @@ export default class ScrollingContainer extends Container {
this.class("scrolling-container");
this.css('overflow', 'auto');
this.css('scroll-behavior', 'smooth');
this.css('position', 'relative');
}
@@ -172,11 +172,16 @@ export default class NoteRevisionsDialog extends BasicWidget {
const revisionItem = this.revisionItems.find(r => r.noteRevisionId === noteRevisionId);
this.$titleButtons.empty();
this.$content.empty();
this.$title.html(revisionItem.title);
this.renderContentButtons(revisionItem);
await this.renderContent(revisionItem);
}
renderContentButtons(revisionItem) {
this.$titleButtons.empty();
const $restoreRevisionButton = $('<button class="btn btn-sm" type="button">Restore this revision</button>');
$restoreRevisionButton.on('click', async () => {
@@ -222,30 +227,33 @@ export default class NoteRevisionsDialog extends BasicWidget {
if (!revisionItem.isProtected || protectedSessionHolder.isProtectedSessionAvailable()) {
this.$titleButtons.append($downloadButton);
}
}
async renderContent(revisionItem) {
this.$content.empty();
const fullNoteRevision = await server.get(`notes/${revisionItem.noteId}/revisions/${revisionItem.noteRevisionId}`);
console.log(fullNoteRevision);
if (revisionItem.type === 'text') {
this.$content.html(fullNoteRevision.content);
if (this.$content.find('span.math-tex').length > 0) {
await libraryLoader.requireLibrary(libraryLoader.KATEX);
renderMathInElement($content[0], {trust: true});
renderMathInElement(this.$content[0], {trust: true});
}
}
else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
} else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') {
this.$content.html($("<pre>").text(fullNoteRevision.content));
}
else if (revisionItem.type === 'image') {
} else if (revisionItem.type === 'image') {
this.$content.html($("<img>")
// 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 an uploaded as a new note
.attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`)
.css("max-width", "100%")
.css("max-height", "100%"));
}
else if (revisionItem.type === 'file') {
} else if (revisionItem.type === 'file') {
const $table = $("<table cellpadding='10'>")
.append($("<tr>").append(
$("<th>").text("MIME: "),
@@ -267,8 +275,7 @@ export default class NoteRevisionsDialog extends BasicWidget {
}
this.$content.html($table);
}
else if (revisionItem.type === 'canvas') {
} else if (revisionItem.type === 'canvas') {
/**
* FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
* REMOVE external dependency!!!! This is defined in the svg in defs.style
@@ -285,12 +292,11 @@ export default class NoteRevisionsDialog extends BasicWidget {
*/
const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
this.$content.html($('<div>').append($svgHtml));
} catch(err) {
} catch (err) {
console.error("error parsing fullNoteRevision.content as JSON", fullNoteRevision.content, err);
this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
}
}
else {
} else {
this.$content.text("Preview isn't available for this note type.");
}
}
@@ -1,7 +1,6 @@
import linkService from '../../services/link.js';
import utils from '../../services/utils.js';
import server from '../../services/server.js';
import treeService from "../../services/tree.js";
import froca from "../../services/froca.js";
import appContext from "../../components/app_context.js";
import hoistedNoteService from "../../services/hoisted_note.js";
@@ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget {
}
} else {
const note = await froca.getNote(change.noteId);
const notePath = treeService.getSomeNotePath(note);
const notePath = note.getBestNotePathString();
if (notePath) {
$noteLink = await linkService.createNoteLink(notePath, {
@@ -65,6 +65,26 @@ const TPL = `<div class="sort-child-notes-dialog modal mx-auto" tabindex="-1" ro
sort folders at the top
</label>
</div>
<br />
<h5>Natural Sort</h5>
<div class="form-check">
<label class="form-check-label">
<input class="form-check-input" type="checkbox" name="sort-natural" value="1">
sort with respect to different character sorting and collation rules in different languages or regions.
</label>
</div>
<br />
<div class="form-check">
<label>
Natural sort language
<input class="form-control" name="sort-locale">
The language code for natural sort, e.g. "zh-CN" for Chinese.
</label>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Sort <kbd>enter</kbd></button>
@@ -83,8 +103,10 @@ export default class SortChildNotesDialog extends BasicWidget {
const sortBy = this.$form.find("input[name='sort-by']:checked").val();
const sortDirection = this.$form.find("input[name='sort-direction']:checked").val();
const foldersFirst = this.$form.find("input[name='sort-folders-first']").is(":checked");
const sortNatural = this.$form.find("input[name='sort-natural']").is(":checked");
const sortLocale = this.$form.find("input[name='sort-locale']").val();
await server.put(`notes/${this.parentNoteId}/sort-children`, {sortBy, sortDirection, foldersFirst});
await server.put(`notes/${this.parentNoteId}/sort-children`, {sortBy, sortDirection, foldersFirst, sortNatural, sortLocale});
utils.closeActiveDialog();
});
+15 -4
View File
@@ -146,20 +146,31 @@ export default class FindWidget extends NoteContextAwareWidget {
return;
}
this.handler = await this.getHandler();
const selectedText = window.getSelection().toString() || "";
this.$findBox.show();
this.$input.focus();
this.handler = await this.getHandler();
const isAlreadyVisible = this.$findBox.is(":visible");
if (isAlreadyVisible) {
if (selectedText) {
this.$input.val(selectedText);
}
if (this.$input.val()) {
await this.performFind();
}
this.$input.select();
} else {
this.$totalFound.text(0);
this.$currentFound.text(0);
const searchTerm = await this.handler.getInitialSearchTerm();
this.$input.val(searchTerm || "");
if (searchTerm !== "") {
this.$input.val(selectedText);
if (selectedText) {
this.$input.select();
await this.performFind();
}
-17
View File
@@ -16,23 +16,6 @@ export default class FindInCode {
return this.parent.noteContext.getCodeEditor();
}
async getInitialSearchTerm() {
const codeEditor = await this.getCodeEditor();
// highlightSelectionMatches is the overlay that highlights
// the words under the cursor. This occludes the search
// markers style, save it, disable it. Will be restored when
// the focus is back into the note
this.oldHighlightSelectionMatches = codeEditor.getOption("highlightSelectionMatches");
codeEditor.setOption("highlightSelectionMatches", false);
// Fill in the findbox with the current selection if any
const selectedText = codeEditor.getSelection()
if (selectedText !== "") {
return selectedText;
}
}
async performFind(searchTerm, matchCase, wholeWord) {
let findResult = null;
let totalFound = 0;
-4
View File
@@ -16,10 +16,6 @@ export default class FindInHtml {
this.$results = null;
}
async getInitialSearchTerm() {
return ""; // FIXME
}
async performFind(searchTerm, matchCase, wholeWord) {
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
-13
View File
@@ -8,19 +8,6 @@ export default class FindInText {
return this.parent.noteContext.getTextEditor();
}
async getInitialSearchTerm() {
const textEditor = await this.getTextEditor();
const selection = textEditor.model.document.selection;
const range = selection.getFirstRange();
// FIXME
for (const item of range.getItems()) {
// Fill in the findbox with the current selection if any
return item.data;
}
}
async performFind(searchTerm, matchCase, wholeWord) {
// Do this even if the searchTerm is empty so the markers are cleared and
// the counters updated
+16
View File
@@ -11175,6 +11175,22 @@ const icons = [
}
];
function getIconClass(icon) {
if (icon.type_of_icon === 'LOGO') {
return `bxl-${icon.name}`;
}
else if (icon.type_of_icon === 'SOLID') {
return `bxs-${icon.name}`;
}
else {
return `bx-${icon.name}`;
}
}
for (const icon of icons) {
icon.className = getIconClass(icon);
}
export default {
categories,
icons
+2 -3
View File
@@ -1,7 +1,6 @@
import libraryLoader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import froca from "../services/froca.js";
import server from "../services/server.js";
const TPL = `<div class="mermaid-widget">
<style>
@@ -74,6 +73,8 @@ export default class MermaidWidget extends NoteContextAwareWidget {
const wheelZoomLoaded = libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM);
this.$errorContainer.hide();
try {
await this.renderSvg(async renderedSvg => {
this.$display.html(renderedSvg);
@@ -88,8 +89,6 @@ export default class MermaidWidget extends NoteContextAwareWidget {
speed: 20,
zoomOnClick: false
});
this.$errorContainer.hide();
});
} catch (e) {
this.$errorMessage.text(e.message);
+32 -20
View File
@@ -1,5 +1,6 @@
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import attributeService from "../services/attributes.js";
import server from "../services/server.js";
const TPL = `
<div class="note-icon-widget dropdown">
@@ -147,6 +148,8 @@ export default class NoteIconWidget extends NoteContextAwareWidget {
}
async renderDropdown(categoryId, search) {
const iconToCountPromise = this.getIconToCountMap();
this.$iconList.empty();
if (this.getIconLabels().length > 0) {
@@ -165,41 +168,50 @@ export default class NoteIconWidget extends NoteContextAwareWidget {
search = search?.trim()?.toLowerCase();
for (const icon of icons) {
const filteredIcons = icons.filter(icon => {
if (categoryId && icon.category_id !== categoryId) {
continue;
return false;
}
if (search) {
if (!icon.name.includes(search) && !icon.term?.find(t => t.includes(search))) {
continue;
return false;
}
}
this.$iconList.append(
$('<span>')
.addClass(this.getIconClass(icon))
.attr("title", icon.name)
);
return true;
});
const iconToCount = await iconToCountPromise;
filteredIcons.sort((a, b) => {
const countA = iconToCount[a.className] || 0;
const countB = iconToCount[b.className] || 0;
return countB - countA;
});
for (const icon of filteredIcons) {
this.$iconList.append(this.renderIcon(icon));
}
this.$iconSearch.focus();
}
async getIconToCountMap() {
const {iconClassToCountMap} = await server.get('other/icon-usage');
return iconClassToCountMap;
}
renderIcon(icon) {
return $('<span>')
.addClass("bx " + icon.className)
.attr("title", icon.name);
}
getIconLabels() {
return this.note.getOwnedLabels()
.filter(label => ['workspaceIconClass', 'iconClass'].includes(label.name));
}
getIconClass(icon) {
if (icon.type_of_icon === 'LOGO') {
return `bx bxl-${icon.name}`;
}
else if (icon.type_of_icon === 'SOLID') {
return `bx bxs-${icon.name}`;
}
else {
return `bx bx-${icon.name}`;
}
}
}
+1 -1
View File
@@ -55,7 +55,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
this.$container = this.$widget.find(".note-map-container");
this.$styleResolver = this.$widget.find('.style-resolver');
window.addEventListener('resize', () => this.setDimensions(), false);
new ResizeObserver(() => this.setDimensions()).observe(this.$container[0])
this.$widget.find(".map-type-switcher button").on("click", async e => {
const type = $(e.target).closest("button").attr("data-type");
+50 -28
View File
@@ -41,7 +41,6 @@ const TPL = `
}
.tree-actions {
padding: 4px 0;
background-color: var(--launcher-pane-background-color);
z-index: 100;
position: absolute;
@@ -49,13 +48,15 @@ const TPL = `
display: flex;
align-items: flex-end;
justify-content: flex-end;
right: 11.77px;
right: 17px;
border-radius: 7px;
border: 1px solid var(--main-border-color);
}
button.tree-floating-button {
margin: 1px;
font-size: 1.5em;
padding: 5px;
margin-right: 5px;
max-height: 34px;
color: var(--launcher-pane-text-color);
background-color: var(--button-background-color);
@@ -222,7 +223,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return false;
});
this.$treeSettingsPopup.on("click", e => { e.stopPropagation(); });
this.$treeSettingsPopup.on("click", e => {e.stopPropagation();});
$(document).on('click', () => this.$treeSettingsPopup.hide());
@@ -250,12 +251,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
// code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d
const isEnclosing = ($container, $sub) => {
const conOffset = $container.offset();
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
const conOffset = $container.offset();
const conDistanceFromTop = conOffset.top + $container.outerHeight(true);
const conDistanceFromLeft = conOffset.left + $container.outerWidth(true);
const subOffset = $sub.offset();
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
const subOffset = $sub.offset();
const subDistanceFromTop = subOffset.top + $sub.outerHeight(true);
const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true);
return conDistanceFromTop > subDistanceFromTop
@@ -672,7 +673,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return noteList;
}
updateNode(node) {
async updateNode(node) {
const note = froca.getNoteFromCache(node.data.noteId);
const branch = froca.getBranch(node.data.branchId);
@@ -696,7 +697,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
node.title = utils.escapeHtml(title);
if (node.isExpanded() !== branch.isExpanded) {
node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true});
await node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true});
}
node.renderTitle();
@@ -833,11 +834,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
await this.filterHoistedBranch();
const activeNode = await this.getNodeFromPath(appContext.tabManager.getActiveContextNotePath());
if (activeNode) {
activeNode.setActive({noEvents: true, noFocus: false});
}
// don't activate the active note, see discussion in https://github.com/zadam/trilium/issues/3664
}
async expandTree(node = null) {
@@ -848,7 +845,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
await this.setExpandedStatusForSubtree(node, false);
}
collapseTreeEvent() { this.collapseTree(); }
collapseTreeEvent() {this.collapseTree();}
/**
* @returns {FancytreeNode|null}
@@ -919,7 +916,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
if (expand) {
await parentNode.setExpanded(true, {noAnimation: true});
if (!parentNode.isExpanded()) {
await parentNode.setExpanded(true, {noAnimation: true});
}
// although previous line should set the expanded status, it seems to happen asynchronously,
// so we need to make sure it is set properly before calling updateNode which uses this flag
@@ -927,7 +926,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
branch.isExpanded = true;
}
this.updateNode(parentNode);
await this.updateNode(parentNode);
let foundChildNode = this.findChildNode(parentNode, childNoteId);
@@ -1095,10 +1094,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const activeNode = this.getActiveNode();
const activeNodeFocused = activeNode && activeNode.hasFocus();
const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null;
const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
let activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null;
const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null;
const activeNoteId = activeNode ? activeNode.data.noteId : null;
let activeNoteId = activeNode ? activeNode.data.noteId : null;
const noteIdsToUpdate = new Set();
const noteIdsToReload = new Set();
@@ -1141,7 +1140,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
}
for (const ecBranch of loadResults.getBranches()) {
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
let movedActiveNode = null;
let parentsOfAddedNodes = [];
const allBranches = loadResults.getBranches();
const allBranchesDeleted = allBranches.every(branch => !!branch.isDeleted);
for (const ecBranch of allBranches) {
if (ecBranch.parentNoteId === '_share') {
// all shared notes have a sign in the tree, even the descendants of shared notes
noteIdsToReload.add(ecBranch.noteId);
@@ -1154,12 +1160,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
for (const node of this.getNodesByBranch(ecBranch)) {
if (ecBranch.isDeleted) {
if (node.isActive()) {
const newActiveNode = node.getNextSibling()
|| node.getPrevSibling()
|| node.getParent();
if (allBranchesDeleted) {
const newActiveNode = node.getNextSibling()
|| node.getPrevSibling()
|| node.getParent();
if (newActiveNode) {
newActiveNode.setActive(true, {noEvents: true, noFocus: true});
if (newActiveNode) {
newActiveNode.setActive(true, {noEvents: true, noFocus: true});
}
} else {
movedActiveNode = node;
}
}
@@ -1173,12 +1183,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (!ecBranch.isDeleted) {
for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) {
parentsOfAddedNodes.push(parentNode)
if (parentNode.isFolder() && !parentNode.isLoaded()) {
continue;
}
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId);
if (!found) {
// make sure it's loaded
await froca.getNote(ecBranch.noteId);
@@ -1221,7 +1232,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
// for some reason node update cannot be in the batchUpdate() block (node is not re-rendered)
for (const noteId of noteIdsToUpdate) {
for (const node of this.getNodesByNoteId(noteId)) {
this.updateNode(node);
await this.updateNode(node);
}
}
if (movedActiveNode) {
for (const parentNode of parentsOfAddedNodes) {
const found = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId);
if (found) {
activeNotePath = treeService.getNotePath(found);
activeNoteId = found.data.noteId;
break
}
}
}
@@ -44,7 +44,7 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: !this.note.isLaunchBarConfig(),
title: "Inherited attributes",
title: "Inherited Attributes",
icon: "bx bx-list-plus"
};
}
@@ -92,7 +92,18 @@ export default class InheritedAttributesWidget extends NoteContextAwareWidget {
}
getInheritedAttributes(note) {
return note.getAttributes().filter(attr => attr.noteId !== this.noteId);
const attrs = note.getAttributes().filter(attr => attr.noteId !== this.noteId);
attrs.sort((a, b) => {
if (a.noteId === b.noteId) {
return a.position < b.position ? -1 : 1;
} else {
// inherited attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
return a.noteId < b.noteId ? -1 : 1;
}
});
return attrs;
}
entitiesReloadedEvent({loadResults}) {
@@ -87,8 +87,8 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
this.noteMapWidget.setDimensions();
});
window.addEventListener('resize', () => {
if (!this.graph) { // no graph has been even rendered
const handleResize = () => {
if (!this.noteMapWidget.graph) { // no graph has been even rendered
return;
}
@@ -98,7 +98,9 @@ export default class NoteMapRibbonWidget extends NoteContextAwareWidget {
else if (this.openState === 'small') {
this.setSmallSize();
}
}, false);
}
new ResizeObserver(handleResize).observe(this.$widget[0])
}
setSmallSize() {
@@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
return;
}
const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId)
const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId)
.filter(notePath => !notePath.isHidden);
if (sortedNotePaths.length > 0) {
@@ -48,7 +48,7 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
getTitle() {
return {
show: !this.note.isLaunchBarConfig(),
title: "Owned attributes",
title: "Owned Attributes",
icon: "bx bx-list-check"
};
}
@@ -29,6 +29,9 @@ const TPL = `
.promoted-attribute-cell div.input-group {
margin-left: 10px;
}
.promoted-attribute-cell strong {
word-break:keep-all;
}
</style>
<div class="promoted-attributes-container"></div>
@@ -54,13 +57,13 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
const promotedDefAttrs = note.getPromotedDefinitionAttributes();
if (promotedDefAttrs.length === 0) {
return { show: false };
return {show: false};
}
return {
show: true,
activate: true,
title: "Promoted attributes",
title: "Promoted Attributes",
icon: "bx bx-table"
};
}
@@ -144,7 +147,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
return;
}
attributeValues = attributeValues.map(attribute => ({ value: attribute }));
attributeValues = attributeValues.map(attribute => ({value: attribute}));
$input.autocomplete({
appendTo: document.querySelector('body'),
@@ -164,7 +167,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
}
}]);
$input.on('autocomplete:selected', e => this.promotedAttributeChanged(e))
$input.on('autocomplete:selected', e => this.promotedAttributeChanged(e));
});
}
else if (definition.labelType === 'number') {
@@ -180,7 +180,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget {
return {
show: this.isEnabled(),
activate: true,
title: 'Search parameters',
title: 'Search Parameters',
icon: 'bx bx-search'
};
}
+8 -1
View File
@@ -39,7 +39,14 @@ export default class SharedInfoWidget extends NoteContextAwareWidget {
this.$sharedText.text("This note is shared publicly on");
}
else {
link = `${location.protocol}//${location.host}${location.pathname}share/${shareId}`;
let host = location.host;
if (host.endsWith('/')) {
// seems like IE has trailing slash
// https://github.com/zadam/trilium/issues/3782
host = host.substr(0, host.length - 1);
}
link = `${location.protocol}//${host}${location.pathname}share/${shareId}`;
this.$sharedText.text("This note is shared locally on");
}
+1 -1
View File
@@ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget {
}
async switchOn() {
await branchService.cloneNoteToNote(this.noteId, '_share');
await branchService.cloneNoteToParentNote(this.noteId, '_share');
syncService.syncNow(true);
}
+2 -2
View File
@@ -60,7 +60,7 @@ export default class SqlResultWidget extends NoteContextAwareWidget {
const $row = $("<tr>");
for (const key in result) {
$row.append($("<th>").html(key));
$row.append($("<th>").text(key));
}
$table.append($row);
@@ -69,7 +69,7 @@ export default class SqlResultWidget extends NoteContextAwareWidget {
const $row = $("<tr>");
for (const key in result) {
$row.append($("<td>").html(result[key]));
$row.append($("<td>").text(result[key]));
}
$table.append($row);
+7 -6
View File
@@ -6,8 +6,9 @@ const TPL = `
<style>
.sql-table-schemas-widget {
padding: 12px;
padding-right: 10%;
}
.sql-table-schemas button {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
@@ -17,21 +18,21 @@ const TPL = `
background: var(--button-background-color);
color: var(--button-text-color);
}
.sql-console-result-container {
width: 100%;
font-size: smaller;
width: 100%;
font-size: smaller;
margin-top: 10px;
flex-grow: 1;
overflow: auto;
min-height: 0;
}
.table-schema td {
padding: 5px;
}
</style>
Tables:
<span class="sql-table-schemas"></span>
</div>`;
+1 -8
View File
@@ -280,14 +280,7 @@ export default class TabRowWidget extends BasicWidget {
this.layoutTabs();
};
// ResizeObserver exists only in FF69
if (typeof ResizeObserver !== "undefined") {
new ResizeObserver(resizeListener).observe(this.$widget[0]);
}
else {
// for older firefox
window.addEventListener('resize', resizeListener);
}
new ResizeObserver(resizeListener).observe(this.$widget[0]);
this.tabEls.forEach((tabEl) => this.setTabCloseEvent(tabEl));
}
+6 -46
View File
@@ -69,7 +69,8 @@ export default class TocWidget extends RightPanelWidget {
isEnabled() {
return super.isEnabled()
&& this.note.type === 'text'
&& !this.noteContext.viewScope.tocTemporarilyHidden;
&& !this.noteContext.viewScope.tocTemporarilyHidden
&& this.noteContext.viewScope.viewMode === 'default';
}
async doRenderBody() {
@@ -176,7 +177,7 @@ export default class TocWidget extends RightPanelWidget {
const headingElement = $container.find(":header")[headingIndex];
if (headingElement != null) {
headingElement.scrollIntoView();
headingElement.scrollIntoView({ behavior: "smooth" });
}
} else {
const textEditor = await this.noteContext.getTextEditor();
@@ -192,50 +193,9 @@ export default class TocWidget extends RightPanelWidget {
// navigate (note that the TOC rendering and other TOC
// entries' navigation could be wrong too)
if (headingNode != null) {
// Setting the selection alone doesn't scroll to the
// caret, needs to be done explicitly and outside of
// the writer change callback so the scroll is
// guaranteed to happen after the selection is
// updated.
// In addition, scrolling to a caret later in the
// document (ie "forward scrolls"), only scrolls
// barely enough to place the caret at the bottom of
// the screen, which is a usability issue, you would
// like the caret to be placed at the top or center
// of the screen.
// To work around that issue, first scroll to the
// end of the document, then scroll to the desired
// point. This causes all the scrolls to be
// "backward scrolls" no matter the current caret
// position, which places the caret at the top of
// the screen.
// XXX This could be fixed in another way by using
// the underlying CKEditor5
// scrollViewportToShowTarget, which allows to
// provide a larger "viewportOffset", but that
// has coding complications (requires calling an
// internal CKEditor utils funcion and passing
// an HTML element, not a CKEditor node, and
// CKEditor5 doesn't seem to have a
// straightforward way to convert a node to an
// HTML element? (in CKEditor4 this was done
// with $(node.$) )
// Scroll to the end of the note to guarantee the
// next scroll is a backwards scroll that places the
// caret at the top of the screen
model.change(writer => {
writer.setSelection(root.getChild(root.childCount - 1), 0);
$(textEditor.editing.view.domRoots.values().next().value).find(':header')[headingIndex].scrollIntoView({
behavior: 'smooth'
});
textEditor.editing.view.scrollToTheSelection();
// Backwards scroll to the heading
model.change(writer => {
writer.setSelection(headingNode, 0);
});
textEditor.editing.view.scrollToTheSelection();
}
}
}
@@ -302,4 +262,4 @@ class CloseTocButton extends OnClickButtonWidget {
})
.class("icon-action close-toc");
}
}
}
@@ -12,6 +12,14 @@ const TPL = `
.excalidraw .App-menu_top .buttonList {
display: flex;
}
/* Conflict between excalidraw and bootstrap classes keeps the menu hidden */
/* https://github.com/zadam/trilium/issues/3780 */
/* https://github.com/excalidraw/excalidraw/issues/6567 */
.excalidraw .dropdown-menu {
display: block;
}
.excalidraw-wrapper {
height: 100%;
@@ -18,6 +18,7 @@ import PasswordOptions from "./options/password.js";
import EtapiOptions from "./options/etapi.js";
import BackupOptions from "./options/backup.js";
import SyncOptions from "./options/sync.js";
import SearchEngineOptions from "./options/other/search_engine.js";
import TrayOptions from "./options/other/tray.js";
import NoteErasureTimeoutOptions from "./options/other/note_erasure_timeout.js";
import NoteRevisionsSnapshotIntervalOptions from "./options/other/note_revisions_snapshot_interval.js";
@@ -75,6 +76,7 @@ const CONTENT_WIDGETS = {
_optionsBackup: [ BackupOptions ],
_optionsSync: [ SyncOptions ],
_optionsOther: [
SearchEngineOptions,
TrayOptions,
NoteErasureTimeoutOptions,
NoteRevisionsSnapshotIntervalOptions,
@@ -53,7 +53,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
matchBrackets: true,
keyMap: options.is('vimKeymapEnabled') ? "vim": "default",
matchTags: {bothTags: true},
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false},
highlightSelectionMatches: {showToken: false, annotateScrollbar: false},
lint: true,
gutters: ["CodeMirror-lint-markers"],
lineNumbers: true,
@@ -62,7 +62,7 @@ export default class EditableCodeTypeWidget extends TypeWidget {
// all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem
lineWrapping: options.is('codeLineWrapEnabled'),
dragDrop: false, // with true the editor inlines dropped files which is not what we expect
placeholder: "Type the content of your code note here..."
placeholder: "Type the content of your code note here...",
});
this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate());
@@ -77,12 +77,15 @@ export default class EditableCodeTypeWidget extends TypeWidget {
this.codeEditor.setValue(noteComplement.content || "");
this.codeEditor.clearHistory();
const info = CodeMirror.findModeByMIME(note.mime);
if (info) {
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
let info = CodeMirror.findModeByMIME(note.mime);
if (!info) {
// Switch back to plain text if CodeMirror does not have a mode for whatever MIME type we're editing.
// To avoid inheriting a mode from a previously open code note.
info = CodeMirror.findModeByMIME("text/plain");
}
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
});
this.show();
@@ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js';
import utils from "../../services/utils.js";
import keyboardActionService from "../../services/keyboard_actions.js";
import froca from "../../services/froca.js";
import treeService from "../../services/tree.js";
import noteCreateService from "../../services/note_create.js";
import AbstractTextTypeWidget from "./abstract_text_type_widget.js";
import link from "../../services/link.js";
@@ -52,7 +51,7 @@ const TPL = `
cursor: text !important;
}
.note-detail-editable-text *:not(figure):first-child {
.note-detail-editable-text *:not(figure,.include-note):first-child {
margin-top: 0 !important;
}
@@ -187,6 +186,11 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
const noteComplement = await froca.getNoteComplement(note.noteId);
await this.spacedUpdate.allowUpdateWithoutChange(() => {
// https://github.com/zadam/trilium/issues/3914
// todo: quite hacky, but it works. remove it if ckeditor has fixed it.
this.$editor.trigger('focus');
this.$editor.trigger('blur')
this.watchdog.editor.setData(noteComplement.content || "");
});
}
@@ -378,7 +382,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
return;
}
return treeService.getSomeNotePath(resp.note);
return resp.note.getBestNotePathString();
}
async refreshIncludedNoteEvent({noteId}) {
@@ -24,6 +24,7 @@ const FONT_FAMILIES = [
{ value: "Bradley Hand", label: "Bradley Hand" },
{ value: "Luminari", label: "Luminari" },
{ value: "Comic Sans MS", label: "Comic Sans MS" },
{ value: "Microsoft YaHei", label: "Microsoft YaHei" },
];
const TPL = `
@@ -48,7 +48,7 @@ export default class ImageOptions extends OptionsWidget {
this.updateOption('imageMaxWidthHeight', this.$imageMaxWidthHeight.val()));
this.$imageJpegQuality.on('change', () =>
this.updateOption('imageJpegQuality', this.$imageJpegQuality.val()));
this.updateOption('imageJpegQuality', this.$imageJpegQuality.val().trim() || "75"));
this.$downloadImagesAutomatically = this.$widget.find(".download-images-automatically");
@@ -0,0 +1,78 @@
import OptionsWidget from "../options_widget.js";
import utils from "../../../../services/utils.js";
const TPL = `
<div class="options-section">
<h4>Search Engine</h4>
<p>Custom search engine requires both a name and a URL to be set. If either of these is not set, DuckDuckGo will be used as the default search engine.</p>
<form class="sync-setup-form">
<div class="form-group">
<label>Predefined search engine templates</label>
<select class="predefined-search-engine-select form-control">
<option value="Bing">Bing</option>
<option value="Baidu">Baidu</option>
<option value="Duckduckgo">Duckduckgo</option>
<option value="Google">Google</option>
</select>
</div>
<div class="form-group">
<label>Custom search engine name</label>
<input type="text" class="custom-search-engine-name form-control" placeholder="Customize search engine name">
</div>
<div class="form-group">
<label>Custom search engine URL should include <code>{keyword}</code> as a placeholder for the search term.</label>
<input type="text" class="custom-search-engine-url form-control" placeholder="Customize search engine url">
</div>
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-primary">Save</button>
</div>
</form>
</div>`;
const SEARCH_ENGINES = {
"Bing": "https://www.bing.com/search?q={keyword}",
"Baidu": "https://www.baidu.com/s?wd={keyword}",
"Duckduckgo": "https://duckduckgo.com/?q={keyword}",
"Google": "https://www.google.com/search?q={keyword}",
}
export default class SearchEngineOptions extends OptionsWidget {
isEnabled() {
return super.isEnabled() && utils.isElectron();
}
doRender() {
this.$widget = $(TPL);
this.$form = this.$widget.find(".sync-setup-form");
this.$predefinedSearchEngineSelect = this.$widget.find(".predefined-search-engine-select");
this.$customSearchEngineName = this.$widget.find(".custom-search-engine-name");
this.$customSearchEngineUrl = this.$widget.find(".custom-search-engine-url");
this.$predefinedSearchEngineSelect.on('change', () => {
const predefinedSearchEngine = this.$predefinedSearchEngineSelect.val();
this.$customSearchEngineName[0].value = predefinedSearchEngine;
this.$customSearchEngineUrl[0].value = SEARCH_ENGINES[predefinedSearchEngine];
});
this.$form.on('submit', () => {
this.updateMultipleOptions({
'customSearchEngineName': this.$customSearchEngineName.val(),
'customSearchEngineUrl': this.$customSearchEngineUrl.val()
});
});
}
async optionsLoaded(options) {
this.$predefinedSearchEngineSelect.val("");
this.$customSearchEngineName[0].value = options.customSearchEngineName;
this.$customSearchEngineUrl[0].value = options.customSearchEngineUrl;
}
}
@@ -470,11 +470,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
async createNoteBox(noteId, title, x, y) {
const $link = await linkService.createNoteLink(noteId, {title});
$link.mousedown(e => {
console.log(e);
linkService.goToLink(e);
});
$link.mousedown(e => linkService.goToLink(e));
const note = await froca.getNote(noteId);

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