Compare commits

..

319 Commits

Author SHA1 Message Date
zadam bd22863bb7 release 0.61.2-beta
Publish Docker image / Push Docker image to multiple registries (push) Failing after 34s
2023-07-28 00:06:23 +02:00
zadam ce3834eb9e blob erasure is not synced, need to clean them up before each content hash check 2023-07-27 23:57:12 +02:00
zadam 8edb5428e5 small sync fixes and refactorings 2023-07-27 23:22:08 +02:00
zadam 527718eff7 add indices on blobId 2023-07-27 21:40:47 +02:00
zadam 5b0f487f3f handle ERR_* errors gracefully 2023-07-27 14:50:18 +02:00
zadam a3fa8341ba handle ERR_CONNECTION_RESET gracefully 2023-07-27 10:55:34 +02:00
zadam 04813743e2 fix zooming speed, closes #4121 2023-07-25 22:33:35 +02:00
zadam ddf75cd5e5 note tooltip displays the whole note with scrollbar, other behavior changes. closes #4120 2023-07-25 22:27:15 +02:00
zadam d0e5ad5b7e release 0.61.1-beta
Publish Docker image / Push Docker image to multiple registries (push) Failing after 36s
2023-07-24 23:47:57 +02:00
zadam b4631e927c Merge remote-tracking branch 'origin/master' 2023-07-24 23:47:27 +02:00
zadam b7bc843a37 fix demo document statistics, closes #4117 2023-07-24 23:46:02 +02:00
zadam 98d65f8767 Merge pull request #4114 from SiriusXT/master
Fix bugs in highlights,v0.61.0-beta
2023-07-24 22:58:20 +02:00
zadam 0599891ec0 Merge pull request #4001 from dymani/reopen-in-place
Reopen recently closed tab/split in place
2023-07-24 22:52:14 +02:00
zadam cc06701565 swiftshader deletion is no longer needed 2023-07-24 22:33:36 +02:00
SnnBcd xt 40683985cd Fix bugs in highlights 2023-07-22 19:13:03 +00:00
zadam 074e13a889 release 0.61.0-beta
Publish Docker image / Push Docker image to multiple registries (push) Failing after 39s
2023-07-22 17:51:08 +02:00
zadam 98b0baefe2 Merge remote-tracking branch 'origin/master' into next61
# Conflicts:
#	src/public/app/widgets/buttons/note_actions.js
2023-07-22 17:49:29 +02:00
zadam 3aad0ebc95 Merge remote-tracking branch 'origin/next61' into next61 2023-07-22 17:48:17 +02:00
zadam 479d720a6e upgrades 2023-07-22 17:48:09 +02:00
zadam e4892c9888 fixed order of multi-valued promoted attributes, closes #4105 2023-07-20 23:22:31 +02:00
zadam f8c9178c5d fix migration 2023-07-18 23:23:52 +02:00
zadam 83049c95c3 defensive copies of owned attrs 2023-07-17 22:56:36 +02:00
zadam bc8f531b33 fix a bug which caused immedate deletion of created attributes 2023-07-17 22:53:54 +02:00
zadam 23278f54cb force refresh before activating the attribute detail dialog, fixes #4077 2023-07-17 22:41:37 +02:00
zadam 18801fb3e1 more filtering of note actions based on note type 2023-07-17 22:32:07 +02:00
zadam dc0a0dcf09 Merge pull request #4098 from AndrewPluzhnikov/fix-search-in-text
Disable Search in note button in global search context.
2023-07-17 22:30:49 +02:00
zadam 04c306b360 support combination of shared and cloned in the note tree, fixes #4097 2023-07-17 22:26:41 +02:00
zadam d6a4f1db13 share tree should include branch prefixes, fixes #4096 2023-07-17 22:19:03 +02:00
zadam 84c4b368c8 updated better-sqlite binaries for node 18/electron 25 2023-07-17 21:39:25 +02:00
Andrew Pluzhnikov 4307f25205 Disable Search in note button in global search context.
This button doesn't actually do anything in the global search window, so
it should be greyed out/disabled.
2023-07-16 10:40:34 -07:00
zadam 48f03f7a1b API docs 2023-07-15 12:07:45 +02:00
zadam 9d24499fef upgrade excalidraw to 0.15.2 2023-07-15 12:01:09 +02:00
zadam df97b269a7 autocomplete upgrade to 0.38.1 2023-07-15 11:45:18 +02:00
zadam ee6fb98a0d jquery 3.7.0 2023-07-15 11:41:36 +02:00
zadam 13e30e5855 dayjs frontend update 2023-07-15 11:38:01 +02:00
zadam 5ab2ec0f13 little fixes for the datetime promoted attr 2023-07-15 11:29:29 +02:00
zadam 8465d7fa68 Merge remote-tracking branch 'origin/master' into next61 2023-07-15 11:27:42 +02:00
zadam d153fc7ba5 Merge pull request #4090 from harishkumarc/datetime-attribute
added date and time attribute capability
2023-07-15 11:26:53 +02:00
zadam 1a7c2d4c32 fix hiding toc with multiple splits, closes #4093 2023-07-15 11:19:05 +02:00
zadam b3381a3b53 reimplemented triggering search from URL, fixes #385 2023-07-15 10:59:30 +02:00
zadam ac0d61e9b4 add mindmap mermaid example to demo document 2023-07-15 10:44:38 +02:00
zadam b13a6ced19 cleanup 2023-07-15 10:33:29 +02:00
zadam b61aab06b5 nodejs upgrade 2023-07-15 10:32:26 +02:00
zadam 64b86b2666 use marked instead of commonmark to convert MD to HTML, which supports e.g. tables, closes #2026 2023-07-15 10:31:50 +02:00
zadam 27e6fa9526 Merge branch 'stable' into next61 2023-07-15 10:10:29 +02:00
zadam 3d31742285 fix FNote.isHiddenCompletely() which returned false for _hidden 2023-07-15 09:50:07 +02:00
zadam 2b2f4fbc07 comment 2023-07-15 09:48:30 +02:00
zadam 513bf252cd don't display export for backend log note, fixes #4076 2023-07-15 09:35:03 +02:00
zadam f591e954f3 capitalization of options titles 2023-07-15 09:26:18 +02:00
zadam 59de1925a9 decreased padding 2023-07-14 22:14:07 +02:00
zadam 6c1011341c add help button to attachment detail as well 2023-07-14 22:02:30 +02:00
zadam 906082a6b2 add help button to attachment list 2023-07-14 21:59:43 +02:00
zadam 30bcd1764a check the attachment after upload 2023-07-14 21:28:32 +02:00
zadam cea54aecad check the attachment after conversion 2023-07-14 21:18:56 +02:00
zadam ff6f2a4d44 zoom for the attachment images 2023-07-14 21:08:00 +02:00
zadam 5f6fded833 zoom for the attachment images 2023-07-14 21:06:15 +02:00
zadam fed5f1a7d2 fix content renderer for mermaid 2023-07-14 20:03:28 +02:00
zadam a06ee1ced1 added new mermaid diagram examples to the demo document 2023-07-14 20:00:53 +02:00
zadam 332b92791e upgrade mermaid to 10.2.4 2023-07-14 19:56:37 +02:00
zadam d4a9461460 force graph upgrade 2023-07-14 19:45:10 +02:00
zadam 2dac6ab281 DB migration should convert only "perfect candidates" to attachments 2023-07-14 18:24:15 +02:00
zadam 4d00404f55 more actions triggering new tab should respect current hoisting 2023-07-14 18:15:31 +02:00
zadam 3adab07ddb common style for options number inputs 2023-07-14 18:05:19 +02:00
zadam 3d0c3652ce file note should reload after uploading new revision 2023-07-14 17:55:28 +02:00
zadam c3f5bb5518 image note should reload after uploading new revision 2023-07-14 17:52:35 +02:00
zadam b2d460d5c1 update demo document with images converted to attachments 2023-07-14 17:06:38 +02:00
zadam d475346a09 renamed attachment's parentId to more fitting ownerId 2023-07-14 17:01:56 +02:00
zadam ca41806bc2 import and export fixes 2023-07-13 23:54:47 +02:00
zadam a748710b01 make file attachments accessible in revisions dialog 2023-07-10 20:30:04 +02:00
zadam ac9a4b3afc fixes in etapi tests 2023-07-10 19:10:07 +02:00
zadam b7f0fd2db3 Merge branch 'master' into next61
# Conflicts:
#	package-lock.json
#	src/public/app/services/note_content_renderer.js
#	src/public/app/widgets/note_tree.js
#	src/routes/routes.js
#	src/services/consistency_checks.js
#	src/services/notes.js
#	src/services/task_context.js
2023-07-10 18:20:36 +02:00
harishkumarchellappa 7d6c847d47 added date and time attribute capability 2023-07-10 17:24:34 +05:30
zadam 6ec6d66f17 Merge pull request #4084 from AndrewPluzhnikov/fix-868
Implement saving of global search string.
2023-07-09 23:52:36 +02:00
zadam 977089cc28 Merge pull request #4083 from viperdc/patch-1
Update docker_healthcheck.js
2023-07-09 23:47:55 +02:00
zadam c3f8975166 small refactorings of clipper code 2023-07-09 22:58:34 +02:00
zadam 639f80b603 Merge pull request #4075 from manto89/feature/add-clippings-existing
Feature/add clippings existing url
2023-07-09 22:43:34 +02:00
Andrew Pluzhnikov 927da84d3b Implement saving of global search string.
This implements point 1 of issue 868.

After performing a search, when returning to the global search tab the
previous search term is now retained and preselected, making it easier
to repeat the same search or modify it.
2023-07-06 20:26:55 -07:00
viperdc b8c9943deb Update docker_healthcheck.js
Correcting ini location for HTTPS health-check bypass.
2023-07-06 19:02:51 -04:00
manto89 bb6ab0fe45 Fix clipping html with parser when using web-clipper 2023-07-02 12:52:16 +02:00
manto89 0c86dece5f Fix clipType searching for web-clipper 2023-07-02 12:51:23 +02:00
zadam b7b0bd80ed padding of right pane 2023-06-30 23:01:57 +02:00
zadam f06207207b fix styling for videos 2023-06-30 16:36:03 +02:00
zadam 068684101f remove the option to show CKEditor banner 2023-06-30 15:58:42 +02:00
zadam f78d96a3d4 refactoring of server and suppressing error during conversion of attachment into note 2023-06-30 15:44:30 +02:00
zadam a0d958bf12 refactor uploading files 2023-06-30 15:25:45 +02:00
zadam 0802b81807 improved error logging in frontend 2023-06-30 12:14:58 +02:00
zadam 192e399cb5 typos 2023-06-30 11:18:34 +02:00
zadam fc564f6aed fix eslint validation 2023-06-30 00:02:25 +02:00
zadam eb34f9c64f small refactorings and fixes 2023-06-29 23:32:19 +02:00
zadam 48029cea7c small refactorings 2023-06-29 22:10:13 +02:00
zadam f4ec4e58c7 fixed image styles 2023-06-29 21:30:26 +02:00
zadam faa402fcda API docs 2023-06-29 20:54:58 +02:00
zadam 788841d256 fix mentions 2023-06-29 12:19:01 +02:00
zadam b7c37fdf58 cleanup 2023-06-29 11:44:28 +02:00
zadam a46c5a2243 use isLabelTruthy() for most binary labels 2023-06-29 00:14:12 +02:00
zadam 430f2975f8 make "excludeFromNoteMap" overridable with note.isLabelTruthy(), #4058 2023-06-29 00:04:30 +02:00
zadam dd8226ff4c comment 2023-06-28 21:33:09 +02:00
zadam 807b442c7a fix froca update of erased blob 2023-06-28 21:28:41 +02:00
zadam a452e4add5 sync fixes 2023-06-28 21:05:31 +02:00
zadam 48b30b985e attachment upload 2023-06-28 20:53:55 +02:00
zadam 05d2f4fe96 Merge branch 'stable' 2023-06-27 23:15:20 +02:00
zadam 85a75fc186 getOwnedAttributes() should not make a copy, consistent with similar methods 2023-06-27 23:03:17 +02:00
zadam ddda4d9867 fix constructing result note path in the jump to note dialog, closes #4054 2023-06-27 22:57:13 +02:00
zadam 42c8059ae1 Merge pull request #4057 from dymani/master
Fix closing parenthesis
2023-06-27 21:38:36 +02:00
dymani 0f4eb8ff4c fix closing parenthesis 2023-06-28 02:31:17 +08:00
zadam 8ee537e2eb API comments 2023-06-27 00:14:13 +02:00
zadam 8eb091445b Merge pull request #4032 from mechanarchy/patch-1
Ignore SQL comments in query console
2023-06-26 23:05:42 +02:00
mechanarchy 1fc23c948e Fix SQL comment handling 2023-06-23 09:06:14 +10:00
zadam cb2b97635a centering icons in note book vertically 2023-06-22 23:34:05 +02:00
zadam 8fb42e4517 Merge pull request #4049 from SiriusXT/master
Fix bugs in toc and improve related codes
2023-06-22 22:34:47 +02:00
zadam edae1a5d1c Merge pull request #4050 from kianmeng/fix-typos
Fix typos
2023-06-22 22:33:09 +02:00
zadam 08398a1417 fix search scripts, closes #4048 2023-06-22 22:30:26 +02:00
Kian-Meng Ang 712304bbc6 Fix typos
Found via `codespell -S ./libraries,./package-lock.json -L tabel,tabels,uptodate,isnt,edn,falsy`
2023-06-23 00:27:04 +08:00
SiriusXT afb893c157 Fix bugs in toc and improve related codes 2023-06-22 15:38:36 +08:00
manto89 ebccd48013 Don't search note by url if url begins with 'about:' 2023-06-21 18:06:28 +02:00
manto89 54065672aa Normalize behaviour with the other create method for web-clipper 2023-06-21 16:10:06 +02:00
manto89 8394ce8002 Change method when adding a new note.
Add reference to new backend method
2023-06-21 13:09:49 +02:00
zadam 176784834f simple formatter for source view of text HTML notes 2023-06-20 23:59:36 +02:00
zadam a47004bb4e shift migrations after upmerge 2023-06-20 23:32:31 +02:00
zadam 995f1c9fc4 Merge branch 'stable' into next61
# Conflicts:
#	src/etapi/etapi.openapi.yaml
#	src/etapi/notes.js
#	src/public/app/widgets/type_widgets/editable_text.js
#	src/services/app_info.js
2023-06-20 23:30:45 +02:00
zadam 1f3b6feaff Merge branch 'master' into next61
# Conflicts:
#	src/public/app/widgets/note_tree.js
2023-06-20 23:28:41 +02:00
zadam 8095c77b91 cache icon count to make filtering more responsive on slower connections, #4044 2023-06-20 21:31:25 +02:00
zadam 6cfd18b29b read filter values in icon selector only after async events, #4044 2023-06-20 21:19:56 +02:00
zadam defd997424 release 0.60.4
Publish Docker image / Push Docker image to multiple registries (push) Failing after 46s
2023-06-19 23:26:50 +02:00
zadam 5905950c17 fix notePosition assignment for new children of root 2023-06-19 00:29:36 +02:00
zadam 691fccb769 fix keyboard navigation in the note tree, fixes #4036 2023-06-18 23:45:48 +02:00
zadam bbe3f436d3 avoid scrolling to active node without a reason, fixes #4031 2023-06-18 21:50:22 +02:00
mechanarchy af053b61fc Ignore SQL comments in query console 2023-06-17 17:06:17 +10:00
zadam e22f77eae7 release 0.60.3
Publish Docker image / Push Docker image to multiple registries (push) Failing after 39s
2023-06-15 23:23:37 +02:00
zadam 3223e76787 etapi ZIP import 2023-06-15 23:21:40 +02:00
zadam 74400dad97 fix race condition between script execution and saving, closes #4028 2023-06-15 21:51:41 +02:00
zadam 1b68adf3e4 compatibility with online excalidraw tool 2023-06-15 21:34:42 +02:00
zadam a2cd6f92d7 compatibility with online excalidraw tool 2023-06-15 21:34:02 +02:00
zadam 3ff5fe61b2 unified file and image upload WIP 2023-06-15 01:26:38 +02:00
zadam 75c6afd1c3 fix loading of string content saved as binary 2023-06-14 22:21:22 +02:00
zadam 8d2958738f add option to hide the "Powered by CKEditor" banner 2023-06-14 00:51:05 +02:00
zadam 0496412f4a cleanup of unused options 2023-06-14 00:38:48 +02:00
zadam f80a20d18c don't use non-existing btn-xs 2023-06-14 00:31:15 +02:00
zadam 34c642a49a rename attachment 2023-06-14 00:28:59 +02:00
zadam 2ebbc33081 fix revision rename DB migration 2023-06-13 23:39:42 +02:00
zadam ac56056f65 Merge branch 'master' into next61
# Conflicts:
#	package-lock.json
#	src/routes/api/recent_changes.js
2023-06-13 23:25:35 +02:00
zadam 6f16b4caec Merge branch 'beta' 2023-06-13 23:24:09 +02:00
zadam bea39f37ee stop click propagation in tree item actions 2023-06-13 00:12:55 +02:00
mechanarchy 6548149107 Update etapi.js
(cherry picked from commit b97ebe9f03)
2023-06-12 23:21:38 +02:00
zadam fbc805a221 Merge pull request #4019 from mechanarchy/patch-2
Fix attempted ETAPI token rename after cancel
2023-06-12 23:21:13 +02:00
zadam 6015a067ec sql console outputs results of CTEs, fixes #2800 2023-06-12 23:18:57 +02:00
mechanarchy 4a1ecd906b Update entrypoints.js
(cherry picked from commit 08ec866dd2)
2023-06-12 23:13:40 +02:00
zadam 41844cf817 Merge pull request #4017 from mechanarchy/patch-1
Fix SQL Console error messages
2023-06-12 23:12:35 +02:00
zadam 004cfe1965 allow creating backups via ETAPI, #4014 2023-06-12 23:09:29 +02:00
mechanarchy b97ebe9f03 Update etapi.js 2023-06-10 17:36:23 +10:00
mechanarchy 08ec866dd2 Update entrypoints.js 2023-06-10 07:44:23 +10:00
zadam 2bdd538d7d release 0.60.2-beta
Publish Docker image / Push Docker image to multiple registries (push) Failing after 40s
2023-06-08 22:46:52 +02:00
zadam 6e69cafe54 fix showing deleted notes in the recent changes dialog, closes #4013 2023-06-08 22:45:44 +02:00
zadam 38839532d5 file upload WIP 2023-06-06 12:31:38 +02:00
zadam 0234ff5fca share support for attachment file download 2023-06-06 00:16:32 +02:00
zadam 44bcfd47c0 share support for attachment images 2023-06-05 23:05:05 +02:00
zadam 69c7eb14aa fixes 2023-06-05 21:14:33 +02:00
zadam 00453fc151 attachment ETAPI tests WIP 2023-06-05 17:00:58 +02:00
zadam 5c393f959d got rid of .isDeleted on froca entities (the property is not available there) 2023-06-05 16:26:05 +02:00
zadam e6bf6424e8 rename loadresults methods/properties for clarity that they don't contain entities, only rows 2023-06-05 16:12:02 +02:00
zadam 57702a07a2 attachment ETAPI support WIP 2023-06-05 09:44:36 +02:00
zadam 3b3f6082a7 attachment ETAPI support WIP 2023-06-05 09:23:42 +02:00
zadam 49241ab318 merge 2023-06-05 00:12:08 +02:00
zadam 93addac8bb Merge branch 'master' into next61
# Conflicts:
#	src/becca/entities/bnote.js
#	src/public/app/components/note_context.js
#	src/public/app/layouts/desktop_layout.js
#	src/public/app/services/note_content_renderer.js
#	src/public/app/services/utils.js
#	src/public/app/widgets/ribbon_widgets/file_properties.js
#	src/public/app/widgets/ribbon_widgets/note_info_widget.js
#	src/services/notes.js
2023-06-05 00:09:55 +02:00
zadam fe908759d1 rename "note revision" to just "revision" 2023-06-04 23:02:49 +02:00
zadam 779751a234 rename "note revision" to just "revision" 2023-06-04 23:01:40 +02:00
zadam cb9feab7b2 got rid of the hot/cold blob, all blobs are "cold" now 2023-06-04 22:50:07 +02:00
zadam b79631a35d setContent refactoring 2023-06-04 20:51:08 +02:00
zadam c177aaa901 refactoring of highlight list 2023-06-04 17:46:37 +02:00
zadam eff3e1df85 Merge pull request #3988 from SiriusXT/Highlighted-Text
Show highlighted text in the right pane
2023-06-04 12:37:19 +02:00
zadam b46d5f4a71 add blobId to the ETAPI #4005 2023-06-04 12:00:04 +02:00
SiriusXT 5b421d51b5 Merge branch 'Highlighted-Text' of https://github.com/SiriusXT/trilium into Highlighted-Text 2023-06-04 16:02:32 +08:00
SiriusXT 8852e8e531 Show highlighted text in right panel 2023-06-04 16:02:30 +08:00
zadam 8bc84cfaf6 Merge pull request #4004 from dymani/default-shortcuts
Fix shortcuts not resetting to default
2023-06-03 23:09:31 +02:00
SnnBcd cde13dc580 Merge branch 'zadam:master' into Highlighted-Text 2023-06-04 00:24:08 +08:00
dymani 282d135f0f Fix shortcuts not resetting to default 2023-06-03 22:03:54 +08:00
SiriusXT 3e3d7aa4d7 Show highlighted text in right panel 2023-06-03 17:12:06 +08:00
SiriusXT 446c41d020 Show highlighted text in right panel 2023-06-03 17:00:15 +08:00
SiriusXT 33fdad5159 Show highlighted text in right panel 2023-06-03 14:43:20 +08:00
SiriusXT 463d145cb7 Merge branch 'Highlighted-Text' of https://github.com/SiriusXT/trilium into Highlighted-Text 2023-06-03 11:55:59 +08:00
SiriusXT 1413756d00 Show highlighted text in right panel 2023-06-03 11:55:50 +08:00
zadam d3bf325f19 added some comments to openapi spec 2023-06-03 00:32:16 +02:00
zadam c4f69fd9cb don't allow patching relation's value in ETAPI #3998 2023-06-03 00:21:46 +02:00
dymani 50ec17433d Fix move pane button after reopen split 2023-06-03 05:59:53 +08:00
dymani 2c8eb82d42 Reopen recently closed tab/split in place 2023-06-03 05:54:33 +08:00
zadam af67cf64b1 Merge pull request #3984 from dymani/move-pane
Add buttons to reorder split panes
2023-06-02 16:07:15 +02:00
zadam 095da691a4 Merge remote-tracking branch 'origin/master' 2023-06-02 16:05:10 +02:00
zadam 7e71029d1c use note size format also in file properties 2023-06-02 16:05:02 +02:00
SnnBcd 045ee18237 Merge branch 'zadam:master' into Highlighted-Text 2023-06-02 21:02:31 +08:00
zadam e47c3a27b0 remove the workaround test 2023-06-01 23:18:50 +02:00
zadam ced99f8ac8 Merge pull request #3995 from mm21/master
Add ETAPI test to repro inherited attribute issue for cloned note #3994
2023-06-01 23:15:10 +02:00
zadam 0c4492bcd0 invalidate attribute cache on branch create/update, fixes #3994 2023-06-01 23:14:17 +02:00
zadam d9359c7c55 downgrade excalidraw to 0.14.2, #3979 2023-06-01 22:51:34 +02:00
mm21 86a080e7ec Add ETAPI test to repro inherited attribute issue for cloned note #3994 2023-06-01 17:20:11 +00:00
SiriusXT 0a66809bf0 Not show when nothing is checked in the options 2023-06-01 20:38:55 +08:00
SiriusXT ecfdde2f2a Show highlighted text in the right pane 2023-06-01 20:17:00 +08:00
zadam 758dba9ba5 fix event 2023-06-01 00:13:08 +02:00
zadam ae42e0efc7 refactoring 2023-06-01 00:07:57 +02:00
zadam b5cfc28912 rename inheritableAttributeCache to __inheritableAttributeCache for consistency 2023-05-31 23:21:06 +02:00
zadam c802bf7d8b Merge pull request #3990 from mm21/master
Add ETAPI tests to repro inherited attribute issue #3989 and workaround
2023-05-31 22:55:57 +02:00
mm21 9e5a02094c Add ETAPI tests to repro inherited attribute issue and workaround 2023-05-31 17:11:42 +00:00
dymani 58253567cd Simplify noteContextReorder event call 2023-06-01 00:48:37 +08:00
SiriusXT 92d5aeae41 Show highlighted text in the right panel 2023-05-31 22:34:24 +08:00
SiriusXT d2d286a4ff Show highlighted text in the left pane 2023-05-31 21:47:43 +08:00
zadam a6ade790c6 script api docs 2023-05-31 14:35:08 +02:00
zadam a9dae7823f script api docs 2023-05-31 14:00:37 +02:00
SiriusXT 86861f6ec3 Show highlighted text in the left pane 2023-05-31 18:32:33 +08:00
zadam d26a0fae17 if a note context has sub contexts, then it has to be saved even if empty, fixes #3985 2023-05-31 00:03:20 +02:00
zadam 375dba3264 fixed infinite recursion with search notes 2023-05-30 23:54:11 +02:00
zadam 74044754e2 Merge remote-tracking branch 'origin/master' 2023-05-30 22:59:23 +02:00
zadam 64aed9b462 display icon for non-supported note types in content renderer 2023-05-30 22:52:48 +02:00
zadam 5428cafa50 Merge pull request #3963 from SiriusXT/master
keep this window on top
2023-05-30 22:37:10 +02:00
dymani 735852b3c1 Use noteContextReorder event instead 2023-05-31 01:53:55 +08:00
zadam f4b5d43899 inline file attachments when exporting single HTML file 2023-05-29 23:42:08 +02:00
zadam 235b779dec detect existing attachment in target note 2023-05-29 22:37:19 +02:00
dymani 2a39906993 Cleanup 2023-05-30 04:22:51 +08:00
dymani e1b67e20ec Add buttons to move split panes 2023-05-30 03:44:05 +08:00
zadam df17840dbc fixes attachments 2023-05-29 13:02:25 +02:00
zadam 9d52f80c2f linking attachments finished 2023-05-29 10:21:34 +02:00
zadam 0aa119af2c attachments WIP 2023-05-29 00:19:54 +02:00
zadam c2f70031d0 Merge branch 'master' into next61
# Conflicts:
#	package-lock.json
#	package.json
#	src/public/app/entities/fnote.js
#	src/public/app/services/link.js
2023-05-28 16:57:48 +02:00
SiriusXT 1a2beb941e keep this window on top 2023-05-27 14:23:50 +08:00
SiriusXT 01b2887b36 Keep this window on top. 2023-05-27 14:18:26 +08:00
SiriusXT 5286d4a97c Merge branch 'master' of https://github.com/SiriusXT/trilium 2023-05-27 14:14:31 +08:00
zadam 8e913cb453 wip 2023-05-26 10:36:05 +02:00
SiriusXT 79dca12274 top 2023-05-23 20:50:03 +08:00
SiriusXT 66c25e7f29 Bring this window to the top 2023-05-23 15:55:35 +08:00
SiriusXT dc4983a013 Bring this window to the top 2023-05-23 15:49:03 +08:00
SiriusXT 039959a48b Put this window top. 2023-05-23 15:44:26 +08:00
SiriusXT 63be25f5bb put window on top 2023-05-23 11:14:23 +08:00
SiriusXT ce7489049f put window on top 2023-05-23 10:32:24 +08:00
zadam 579ed7e194 attachment content rendering 2023-05-21 18:14:17 +02:00
zadam e20fac19ba attachment WIP 2023-05-20 23:46:45 +02:00
zadam 9e9fb2979f Merge branch 'master' into next61
# Conflicts:
#	package-lock.json
#	package.json
#	src/public/app/components/note_context.js
#	src/public/app/services/open.js
#	src/public/app/widgets/buttons/note_actions.js
#	src/public/app/widgets/find_in_code.js
#	src/public/app/widgets/type_widgets/canvas.js
#	src/services/options.js
2023-05-20 20:55:57 +02:00
zadam 1f8798d7ac upgrades 2023-05-17 23:47:30 +02:00
zadam af95d387b9 Merge remote-tracking branch 'origin/master' into next60
# Conflicts:
#	src/public/app/services/open.js
#	src/public/app/widgets/buttons/note_actions.js
2023-05-17 23:10:02 +02:00
zadam 76196ed42c Merge remote-tracking branch 'origin/master' into next60 2023-05-14 17:32:59 +02:00
zadam 8a6002c6be fix search in "view source", closes #3929 2023-05-13 15:58:32 +02:00
zadam 38781d33ac Merge remote-tracking branch 'origin/master' into next60
# Conflicts:
#	src/public/app/widgets/type_widgets/editable_text.js
2023-05-13 15:43:35 +02:00
zadam ff5d3a5f0c import WIP 2023-05-09 00:05:27 +02:00
zadam 626af84f42 refactoring + uploading attachments WIP 2023-05-08 00:02:08 +02:00
zadam b05ce12e7b adding links to owning note and list to attachments 2023-05-07 23:32:30 +02:00
zadam 274505188b fix "go to note" from note autocomplete 2023-05-07 21:29:09 +02:00
zadam f85209a72f getting rid of attributes like data-note-path in favor of the whole nav state in URLs 2023-05-07 21:18:21 +02:00
zadam 291f0e79d9 make a special backup before migrating to 0.60 2023-05-07 15:46:17 +02:00
zadam 64db5e2542 cleanup of app.js, wwww 2023-05-07 15:23:46 +02:00
zadam 9f5f0aeddd fix hamburger icon in canvas 2023-05-07 11:54:04 +02:00
zadam 4d5612e845 fix updating non-text notes 2023-05-07 11:20:51 +02:00
zadam 371f174756 run hiddenSubtreeService.checkHiddenSubtree() also periodically, useful for server deployments, #3907 2023-05-07 10:54:06 +02:00
zadam 8284c673f9 can't allow opening externally on attachment list 2023-05-07 10:43:51 +02:00
zadam cc02546ed3 Merge remote-tracking branch 'origin/master' into next60
# Conflicts:
#	src/routes/api/options.js
#	src/services/import/zip.js
#	src/services/options_init.js
2023-05-07 09:41:33 +02:00
zadam 2aa987c072 fixes for zip export/import in regard to attachments 2023-05-06 22:50:28 +02:00
zadam a06ddc439d improving import WIP 2023-05-06 15:07:38 +02:00
zadam f6944b8219 introducing meta types for import/export, fixes for attachments export 2023-05-06 14:38:45 +02:00
zadam fa3cbb4645 grammar 2023-05-05 23:41:11 +02:00
zadam 6dfc72c065 many small fixes from Intellij analysis 2023-05-05 23:17:23 +02:00
zadam 0af6f91d21 exposing blobs in APIs 2023-05-05 22:21:51 +02:00
zadam 5e1f81e53e getting rid of note complement WIP 2023-05-05 16:37:39 +02:00
zadam 9cdcbb3125 fix runOnNoteContentChange event 2023-05-05 15:42:53 +02:00
zadam 35338e9ec6 fix runOnNoteContentChange event 2023-05-05 15:40:44 +02:00
zadam 78d83b8826 fix note size calculation with blobs 2023-05-05 15:18:55 +02:00
zadam 8edf06d28d small refactorings 2023-05-04 22:16:18 +02:00
zadam ecd2a5cbac Merge remote-tracking branch 'origin/master' into next60 2023-05-04 21:01:53 +02:00
zadam c0cf319521 upgrade electron to 25 and node to 18 2023-05-04 21:01:25 +02:00
zadam 9b32d86f78 take branch prefix into account while sorting, fixes #3896 2023-05-03 23:42:44 +02:00
zadam fbffc6b2b5 inline attachment images into single HTML export 2023-05-03 23:17:07 +02:00
zadam cd01886eb2 attachment revision upload 2023-05-03 22:49:24 +02:00
zadam d8bc9c2982 attachment actions 2023-05-03 10:23:20 +02:00
zadam d232694dec tree context menu to convert notes into attachments 2023-05-02 23:04:41 +02:00
zadam 735ac55bb8 fixes, allowing conversion of note into an attachment 2023-05-02 22:46:39 +02:00
zadam 330e7ac08e copying attachments WIP 2023-04-25 00:01:58 +02:00
zadam 49fb913eab erasing unused attachments 2023-04-24 21:22:34 +02:00
zadam 4b074365e7 Merge branch 'master' into next60 2023-04-24 20:21:45 +02:00
zadam 5e2efca933 scheduled erasure of attachments WIP 2023-04-21 00:19:17 +02:00
zadam e71b0d82a1 scheduled attachment deletion WIP 2023-04-20 00:11:09 +02:00
zadam c6c162cdda restore revision with attachments 2023-04-19 22:47:33 +02:00
zadam 8b46d6c718 Merge branch 'stable' into next60 2023-04-19 22:03:44 +02:00
zadam e28fbf4617 fixes 2023-04-17 23:21:28 +02:00
zadam 0e4a040ed8 fixes of new attachment URLs 2023-04-17 22:40:53 +02:00
zadam b84670d503 Merge branch 'master' into next60 2023-04-17 22:27:21 +02:00
zadam f01657e1dd refactorings 2023-04-14 16:49:06 +02:00
zadam 34ecd77bd4 note revision save also saves attachments 2023-04-11 23:24:39 +02:00
zadam 6cf0fe0b73 attachments have a position 2023-04-11 22:55:50 +02:00
zadam 3f8bf7cacc renamed openTabs to openNoteContexts 2023-04-11 22:00:04 +02:00
zadam 17128c5874 navigation state is more nicely and completely serialized into URL 2023-04-11 21:41:55 +02:00
zadam 9e71c44c76 open attachment by double click 2023-04-11 18:05:57 +02:00
zadam 54c0268593 attachment improvements 2023-04-11 17:45:51 +02:00
zadam a5f0b2a81e Merge remote-tracking branch 'origin/next60' into next60 2023-04-11 17:02:53 +02:00
zadam a154bea016 Merge branch 'master' into next60 2023-04-11 17:02:24 +02:00
zadam 15a11daf38 Merge branch 'master' into next60
# Conflicts:
#	package-lock.json
#	package.json
2023-04-10 18:52:30 +02:00
zadam 5d6d9ab6d6 wip attachment support 2023-04-03 23:47:24 +02:00
zadam 2bc78ccafb wip attachment widget 2023-04-01 23:55:04 +02:00
zadam 53aebf1448 wip attachment widget 2023-04-01 13:58:53 +02:00
zadam fa406d3ded wip attachment widget 2023-03-30 23:48:26 +02:00
zadam 9be524ef89 attachment improvements 2023-03-24 10:57:32 +01:00
zadam fd8a2d4d92 hide global menu after changing note/context 2023-03-24 09:32:06 +01:00
zadam 0b270ee87a remove "hide included images" feature since with attachments it's no longer necessary 2023-03-24 09:17:17 +01:00
zadam 5cc5859211 migrate images to attachments 2023-03-24 09:13:35 +01:00
zadam bd489d5780 node upgrades 2023-03-24 07:45:18 +01:00
zadam a24c56a9d5 fixes 2023-03-20 23:11:32 +01:00
zadam 95e38a037b scrollbar tweak color 2023-03-20 20:50:18 +01:00
zadam 256b800d0e scrollbar tweak color 2023-03-20 20:48:52 +01:00
zadam b3c4c850db Merge branch 'master' into next60 2023-03-20 20:21:30 +01:00
zadam e698aa5822 uploading image to attachment 2023-03-16 20:13:34 +01:00
zadam 1fdb23746a uploading image to attachment 2023-03-16 18:34:39 +01:00
zadam 9cd5bdeb53 fix note revision 2023-03-16 17:49:35 +01:00
zadam e6183b9a1d improved saving 2023-03-16 17:43:37 +01:00
zadam d83005fe4d improved saving attachment 2023-03-16 16:37:31 +01:00
zadam 2b84f1be00 unify .setContent() .getContent() handling across notes, revisions, attachments 2023-03-16 15:19:26 +01:00
zadam bb45c67e60 Merge branch 'stable' into next60 2023-03-16 13:39:28 +01:00
zadam 515fb4e5db blob WIP 2023-03-16 13:29:11 +01:00
zadam b6efc954bd blob WIP 2023-03-16 12:17:55 +01:00
zadam eee05a4d01 blob WIP 2023-03-16 12:11:00 +01:00
zadam dc97400dbf ancillary => attachment 2023-03-16 11:03:28 +01:00
zadam e16bedfab4 WIP blobs 2023-03-16 11:02:07 +01:00
zadam 5a8e216dec WIP blob 2023-03-15 22:44:08 +01:00
zadam 1faf8225c7 Merge branch 'master' into next60 2023-03-15 20:19:47 +01:00
zadam 5225a99995 wip 2023-03-09 09:38:02 +01:00
zadam 929f8ef720 note ancillary/attachment backported from dev branch 2023-03-08 09:01:23 +01:00
433 changed files with 19783 additions and 13707 deletions
+2 -4
View File
@@ -75,7 +75,6 @@ module.exports = {
glob: true,
log: true,
EditorWatchdog: true,
baseApiUrl: true,
// \src\share\canvas_share.js
React: true,
appState: true,
@@ -93,17 +92,16 @@ module.exports = {
renderMathInElement: true,
// \src\public\app\widgets\type_widgets\editable_text.js
BalloonEditor: true,
FancytreeNode: 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
// \src\public\app\services\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
+1 -1
View File
@@ -2,7 +2,7 @@ image:
file: .gitpod.dockerfile
tasks:
- before: nvm install 16.19.1 && nvm use 16.19.1
- before: nvm install 18.16.1 && nvm use 18.16.1
init: npm install
command: npm run start-server
+2 -2
View File
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="SQLite - document.db" uuid="30cef30d-e704-484d-a4ca-5d3bfc2ece63">
<data-source source="LOCAL" name="document.db" uuid="2a4ac1e6-b828-4a2a-8e4a-3f59f10aff26">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/data/document.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
+1 -1
View File
@@ -1,5 +1,5 @@
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
FROM node:16.19.1-alpine
FROM node:18.16.1-alpine
# Create app directory
WORKDIR /usr/src/app
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-3
View File
@@ -24,9 +24,6 @@ mv "./dist/Trilium Notes-linux-x64" $BUILD_DIR
cp images/app-icons/png/128x128.png $BUILD_DIR/icon.png
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db $BUILD_DIR/
+1 -1
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=16.19.1
NODE_VERSION=18.16.1
if [ "$1" != "DONTCOPY" ]
then
-3
View File
@@ -22,9 +22,6 @@ rm -rf $BUILD_DIR
mv "./dist/Trilium Notes-win32-x64" $BUILD_DIR
# removing software WebGL binaries because they are pretty huge and not necessary
rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db $BUILD_DIR/
+2 -2
View File
@@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
exit 1
fi
n exec 16.19.1 npm run webpack
n exec 18.16.1 npm run webpack
DIR=$1
@@ -27,7 +27,7 @@ cp -r electron.js $DIR/
cp webpack-* $DIR/
# run in subshell (so we return to original dir)
(cd $DIR && n exec 16.19.1 npm install --only=prod)
(cd $DIR && n exec 18.16.1 npm install --only=prod)
# cleanup of useless files in dependencies
rm -r $DIR/node_modules/image-q/demo
+4 -5
View File
@@ -1,9 +1,8 @@
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
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 blobs SET content = 'text' WHERE content IS NOT NULL;
UPDATE revisions SET title = 'title';
UPDATE attributes SET name = 'name', value = 'value'
WHERE type = 'label'
@@ -29,7 +28,7 @@ UPDATE attributes SET name = 'name', value = 'value'
'widget',
'noteInfoWidgetDisabled',
'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled',
'revisionsWidgetDisabled',
'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled',
'workspace',
@@ -104,7 +103,7 @@ UPDATE attributes SET name = 'name'
'widget',
'noteInfoWidgetDisabled',
'linkMapWidgetDisabled',
'noteRevisionsWidgetDisabled',
'revisionsWidgetDisabled',
'whatLinksHereWidgetDisabled',
'similarNotesWidgetDisabled',
'workspace',
+1 -1
View File
@@ -16,7 +16,7 @@ noBackup=false
# host=0.0.0.0
# port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable)
port=8080
# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
# true for TLS/SSL/HTTPS (secure), false for HTTP (insecure).
https=false
# path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true
certPath=
-1
View File
@@ -1,5 +1,4 @@
- isDeleted = 0 by default
- rename openTabs to openNoteContexts
- unify readOnly handling to a single attribute:
* readOnly - like now
* readOnly=auto - like without readOnly (used to override inherited readOnly)
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -3,11 +3,11 @@ module.exports = async () => {
const beccaLoader = require("../../src/becca/becca_loader");
const log = require("../../src/services/log");
const consistencyChecks = require("../../src/services/consistency_checks");
const noteService = require("../../src/services/notes");
const eraseService = require("../../src/services/erase");
await cls.init(async () => {
// precaution for the 0211 migration
noteService.eraseDeletedNotesNow();
eraseService.eraseDeletedNotesNow();
beccaLoader.load();
@@ -18,7 +18,7 @@ module.exports = () => {
if (noteId.startsWith("_")) { // is "named" note
const note = becca.getNote(noteId);
for (const attr of note.getOwnedAttributes()) {
for (const attr of note.getOwnedAttributes().slice()) {
attr.markAsDeleted("0212__delete_all_attributes_of_named_notes");
}
}
@@ -0,0 +1 @@
UPDATE branches SET notePosition = notePosition - 999899999 WHERE parentNoteId = 'root' AND notePosition > 999999999;
+10
View File
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS "blobs" (
`blobId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
`dateModified` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`blobId`)
);
ALTER TABLE notes ADD blobId TEXT DEFAULT NULL;
ALTER TABLE note_revisions ADD blobId TEXT DEFAULT NULL;
@@ -0,0 +1,62 @@
module.exports = () => {
const sql = require("../../src/services/sql");
const utils = require("../../src/services/utils");
const existingBlobIds = new Set();
for (const noteId of sql.getColumn(`SELECT noteId FROM note_contents`)) {
const row = sql.getRow(`SELECT noteId, content, dateModified, utcDateModified FROM note_contents WHERE noteId = ?`, [noteId]);
const blobId = utils.hashedBlobId(row.content);
if (!existingBlobIds.has(blobId)) {
existingBlobIds.add(blobId);
sql.insert('blobs', {
blobId,
content: row.content,
dateModified: row.dateModified,
utcDateModified: row.utcDateModified
});
sql.execute("UPDATE entity_changes SET entityName = 'blobs', entityId = ? WHERE entityName = 'note_contents' AND entityId = ?", [blobId, row.noteId]);
} else {
// duplicates
sql.execute("DELETE FROM entity_changes WHERE entityName = 'note_contents' AND entityId = ?", [row.noteId]);
}
sql.execute('UPDATE notes SET blobId = ? WHERE noteId = ?', [blobId, row.noteId]);
}
for (const noteRevisionId of sql.getColumn(`SELECT noteRevisionId FROM note_revision_contents`)) {
const row = sql.getRow(`SELECT noteRevisionId, content, utcDateModified FROM note_revision_contents WHERE noteRevisionId = ?`, [noteRevisionId]);
const blobId = utils.hashedBlobId(row.content);
if (!existingBlobIds.has(blobId)) {
existingBlobIds.add(blobId);
sql.insert('blobs', {
blobId,
content: row.content,
dateModified: row.utcDateModified,
utcDateModified: row.utcDateModified
});
sql.execute("UPDATE entity_changes SET entityName = 'blobs', entityId = ? WHERE entityName = 'note_revision_contents' AND entityId = ?", [blobId, row.noteRevisionId]);
} else {
// duplicates
sql.execute("DELETE FROM entity_changes WHERE entityName = 'note_revision_contents' AND entityId = ?", [row.noteId]);
}
sql.execute('UPDATE note_revisions SET blobId = ? WHERE noteRevisionId = ?', [blobId, row.noteRevisionId]);
}
const notesWithoutBlobIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId IS NULL");
if (notesWithoutBlobIds.length > 0) {
throw new Error("BlobIds were not filled correctly in notes: " + JSON.stringify(notesWithoutBlobIds));
}
const noteRevisionsWithoutBlobIds = sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE blobId IS NULL");
if (noteRevisionsWithoutBlobIds.length > 0) {
throw new Error("BlobIds were not filled correctly in note revisions: " + JSON.stringify(noteRevisionsWithoutBlobIds));
}
};
@@ -0,0 +1,4 @@
DROP TABLE note_contents;
DROP TABLE note_revision_contents;
DELETE FROM entity_changes WHERE entityName IN ('note_contents', 'note_revision_contents');
@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
type TEXT DEFAULT '' NOT NULL,
mime TEXT DEFAULT '' NOT NULL,
`title` TEXT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
`utcDateLastEdited` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
`dateLastEdited` TEXT NOT NULL,
`dateCreated` TEXT NOT NULL);
INSERT INTO revisions (revisionId, noteId, type, mime, title, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, blobId)
SELECT noteRevisionId, noteId, type, mime, title, isProtected, utcDateLastEdited, utcDateCreated, utcDateModified, dateLastEdited, dateCreated, blobId FROM note_revisions;
DROP TABLE note_revisions;
CREATE INDEX `IDX_revisions_noteId` ON `revisions` (`noteId`);
CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
UPDATE entity_changes SET entityName = 'revisions' WHERE entityName = 'note_revisions';
+21
View File
@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS "attachments"
(
attachmentId TEXT not null primary key,
ownerId TEXT not null,
role TEXT not null,
mime TEXT not null,
title TEXT not null,
isProtected INT not null DEFAULT 0,
position INT default 0 not null,
blobId TEXT DEFAULT null,
dateModified TEXT NOT NULL,
utcDateModified TEXT not null,
utcDateScheduledForErasureSince TEXT DEFAULT NULL,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);
CREATE INDEX IDX_attachments_ownerId_role
on attachments (ownerId, role);
CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince
on attachments (utcDateScheduledForErasureSince);
@@ -0,0 +1,23 @@
module.exports = () => {
const beccaLoader = require("../../src/becca/becca_loader");
const becca = require("../../src/becca/becca");
const cls = require("../../src/services/cls");
const log = require("../../src/services/log");
cls.init(() => {
beccaLoader.load();
for (const note of Object.values(becca.notes)) {
try {
const attachment = note.convertToParentAttachment({autoConversion: true});
if (attachment) {
log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`);
}
}
catch (e) {
log.error(`Cannot convert note '${note.noteId}' to attachment: ${e.message} ${e.stack}`);
}
}
});
};
@@ -0,0 +1,2 @@
DELETE FROM options WHERE name = 'hideIncludedImages_main';
DELETE FROM entity_changes WHERE entityName = 'options' AND entityId = 'hideIncludedImages_main';
@@ -0,0 +1,2 @@
UPDATE options SET name = 'openNoteContexts' WHERE name = 'openTabs';
UPDATE entity_changes SET entityId = 'openNoteContexts' WHERE entityName = 'options' AND entityId = 'openTabs';
@@ -0,0 +1,3 @@
CREATE INDEX IDX_notes_blobId on notes (blobId);
CREATE INDEX IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IDX_attachments_blobId on attachments (blobId);
+36 -16
View File
@@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS "notes" (
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
blobId TEXT DEFAULT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`dateCreated` TEXT NOT NULL,
@@ -42,27 +43,18 @@ CREATE TABLE IF NOT EXISTS "notes" (
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
CREATE TABLE IF NOT EXISTS "note_contents" (
`noteId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
`dateModified` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`)
);
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
CREATE TABLE IF NOT EXISTS "revisions" (`revisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
type TEXT DEFAULT '' NOT NULL,
mime TEXT DEFAULT '' NOT NULL,
`title` TEXT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
blobId TEXT DEFAULT NULL,
`utcDateLastEdited` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
`dateLastEdited` TEXT NOT NULL,
`dateCreated` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "note_revision_contents" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT,
`utcDateModified` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "options"
(
name TEXT not null PRIMARY KEY,
@@ -94,11 +86,11 @@ CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (`noteId`);
CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCreated`);
CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`);
CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`);
CREATE INDEX `IDX_revisions_noteId` ON `revisions` (`noteId`);
CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
CREATE INDEX `IDX_entity_changes_changeId` ON `entity_changes` (`changeId`);
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
@@ -112,3 +104,31 @@ CREATE TABLE IF NOT EXISTS "recent_notes"
notePath TEXT not null,
utcDateCreated TEXT not null
);
CREATE TABLE IF NOT EXISTS "blobs" (
`blobId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
`dateModified` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`blobId`)
);
CREATE TABLE IF NOT EXISTS "attachments"
(
attachmentId TEXT not null primary key,
ownerId TEXT not null,
role TEXT not null,
mime TEXT not null,
title TEXT not null,
isProtected INT not null DEFAULT 0,
position INT default 0 not null,
blobId TEXT DEFAULT null,
dateModified TEXT NOT NULL,
utcDateModified TEXT not null,
utcDateScheduledForErasureSince TEXT DEFAULT NULL,
isDeleted INT not null,
deleteId TEXT DEFAULT NULL);
CREATE INDEX IDX_attachments_ownerId_role
on attachments (ownerId, role);
CREATE INDEX IDX_notes_blobId on notes (blobId);
CREATE INDEX IDX_revisions_blobId on revisions (blobId);
CREATE INDEX IDX_attachments_blobId on attachments (blobId);
+1 -1
View File
@@ -4,7 +4,7 @@ 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) {
if (config.Network.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);
+202 -13
View File
@@ -93,7 +93,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line16">line 16</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
</li></ul></dd>
@@ -183,7 +183,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -205,6 +205,195 @@
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -259,7 +448,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -343,7 +532,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -427,7 +616,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -511,7 +700,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -595,7 +784,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -679,7 +868,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -725,7 +914,7 @@
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -833,7 +1022,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -921,7 +1110,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -985,13 +1174,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+211 -12
View File
@@ -268,7 +268,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -766,6 +766,205 @@ and relation (representing named relationship between source and target note)</d
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_getContent">AbstractBeccaEntity#_getContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_setContent">AbstractBeccaEntity#_setContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -825,7 +1024,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -914,7 +1113,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -1003,7 +1202,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -1092,7 +1291,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -1286,7 +1485,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -1480,7 +1679,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -1628,7 +1827,7 @@ and relation (representing named relationship between source and target note)</d
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -1741,7 +1940,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -1834,7 +2033,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -1898,13 +2097,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+316 -15
View File
@@ -203,7 +203,7 @@ Always check noteId instead.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -463,7 +463,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line133">line 133</a>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line134">line 134</a>
</li></ul></dd>
@@ -657,7 +657,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line111">line 111</a>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line112">line 112</a>
</li></ul></dd>
@@ -886,6 +886,205 @@ of deletion should not act as a clone.
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_getContent">AbstractBeccaEntity#_getContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_setContent">AbstractBeccaEntity#_setContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -945,7 +1144,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -1034,7 +1233,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -1216,7 +1415,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line145">line 145</a>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line146">line 146</a>
</li></ul></dd>
@@ -1327,7 +1526,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -1416,7 +1615,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -1446,6 +1645,108 @@ of deletion should not act as a clone.
<h4 class="name" id="getNote"><span class="type-signature"></span>getNote<span class="signature">()</span><span class="type-signature"> &rarr; {<a href="BNote.html">BNote</a>}</span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_bbranch.js.html">becca/entities/bbranch.js</a>, <a href="becca_entities_bbranch.js.html#line107">line 107</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type"><a href="BNote.html">BNote</a></span>
</dd>
</dl>
@@ -1505,7 +1806,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -1594,7 +1895,7 @@ of deletion should not act as a clone.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -1640,7 +1941,7 @@ of deletion should not act as a clone.
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -1753,7 +2054,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -1846,7 +2147,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -1910,13 +2211,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+211 -12
View File
@@ -205,7 +205,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -635,6 +635,205 @@ from tokenHash and token.</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_getContent">AbstractBeccaEntity#_getContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_setContent">AbstractBeccaEntity#_setContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -694,7 +893,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -783,7 +982,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -872,7 +1071,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -961,7 +1160,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -1050,7 +1249,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -1139,7 +1338,7 @@ from tokenHash and token.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -1185,7 +1384,7 @@ from tokenHash and token.</div>
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -1298,7 +1497,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -1391,7 +1590,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -1455,13 +1654,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+2395 -306
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+216 -17
View File
@@ -30,7 +30,7 @@
<h2><span class="attribs"><span class="type-signature"></span></span>BOption<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Option represents name-value pair, either directly configurable by the user or some system property.</div>
<div class="class-description">Option represents a name-value pair, either directly configurable by the user or some system property.</div>
</header>
@@ -199,7 +199,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -267,7 +267,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line24">line 24</a>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line29">line 29</a>
</li></ul></dd>
@@ -335,7 +335,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line20">line 20</a>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line25">line 25</a>
</li></ul></dd>
@@ -403,7 +403,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line26">line 26</a>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line31">line 31</a>
</li></ul></dd>
@@ -471,7 +471,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line22">line 22</a>
<a href="becca_entities_boption.js.html">becca/entities/boption.js</a>, <a href="becca_entities_boption.js.html#line27">line 27</a>
</li></ul></dd>
@@ -493,6 +493,205 @@
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_getContent">AbstractBeccaEntity#_getContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_setContent">AbstractBeccaEntity#_setContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -552,7 +751,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -641,7 +840,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -730,7 +929,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -819,7 +1018,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -908,7 +1107,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -997,7 +1196,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -1043,7 +1242,7 @@
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -1156,7 +1355,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -1249,7 +1448,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -1313,13 +1512,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+211 -12
View File
@@ -199,7 +199,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line53">line 53</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line55">line 55</a>
</li></ul></dd>
@@ -425,6 +425,205 @@
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="_getContent"><span class="type-signature">(protected) </span>_getContent<span class="signature">()</span><span class="type-signature"> &rarr; {string|Buffer}</span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_getContent">AbstractBeccaEntity#_getContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line244">line 244</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">string</span>
|
<span class="param-type">Buffer</span>
</dd>
</dl>
<h4 class="name" id="_setContent"><span class="type-signature">(protected) </span>_setContent<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-overrides">Overrides:</dt>
<dd class="tag-overrides"><ul class="dummy"><li>
<a href="AbstractBeccaEntity.html#_setContent">AbstractBeccaEntity#_setContent</a>
</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line124">line 124</a>
</li></ul></dd>
</dl>
@@ -484,7 +683,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line62">line 62</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line64">line 64</a>
</li></ul></dd>
@@ -573,7 +772,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line18">line 18</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line20">line 20</a>
</li></ul></dd>
@@ -662,7 +861,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line30">line 30</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line32">line 32</a>
</li></ul></dd>
@@ -751,7 +950,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line23">line 23</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line25">line 25</a>
</li></ul></dd>
@@ -840,7 +1039,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line74">line 74</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line76">line 76</a>
</li></ul></dd>
@@ -929,7 +1128,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line45">line 45</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line47">line 47</a>
</li></ul></dd>
@@ -975,7 +1174,7 @@
<div class="description">
Mark the entity as (soft) deleted. It will be completely erased later.
This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
</div>
@@ -1088,7 +1287,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line128">line 128</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line261">line 261</a>
</li></ul></dd>
@@ -1181,7 +1380,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line83">line 83</a>
<a href="becca_entities_abstract_becca_entity.js.html">becca/entities/abstract_becca_entity.js</a>, <a href="becca_entities_abstract_becca_entity.js.html#line85">line 85</a>
</li></ul></dd>
@@ -1245,13 +1444,13 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+11 -11
View File
@@ -423,7 +423,7 @@ available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.
<td class="description last">where script is currently executing. Don't mix this up with concept of active note</td>
<td class="description last">where the script is currently executing. Don't mix this up with the concept of active note</td>
</tr>
@@ -643,7 +643,7 @@ available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.
<td class="description last">whose event triggered this executions</td>
<td class="description last">whose event triggered this execution</td>
</tr>
@@ -863,7 +863,7 @@ available in the JS backend notes. You can use e.g. <code>api.log(api.startNote.
<td class="description last">where script started executing</td>
<td class="description last">where the script started executing</td>
</tr>
@@ -3254,7 +3254,7 @@ JSON MIME type. See also createNewNote() for more options.
<h4 class="name" id="ensureNoteIsPresentInParent"><span class="type-signature"></span>ensureNoteIsPresentInParent<span class="signature">(noteId, parentNoteId, prefix)</span><span class="type-signature"> &rarr; {void}</span></h4>
<h4 class="name" id="ensureNoteIsPresentInParent"><span class="type-signature"></span>ensureNoteIsPresentInParent<span class="signature">(noteId, parentNoteId, prefix)</span><span class="type-signature"> &rarr; {Object}</span></h4>
@@ -3262,7 +3262,7 @@ JSON MIME type. See also createNewNote() for more options.
<div class="description">
If there's no branch between note and parent note, create one. Otherwise, do nothing.
If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch.
</div>
@@ -3360,7 +3360,7 @@ JSON MIME type. See also createNewNote() for more options.
<td class="description last">if branch will be created between note and parent note, set this prefix</td>
<td class="description last">if branch is created between note and parent note, set this prefix</td>
</tr>
@@ -3437,7 +3437,7 @@ JSON MIME type. See also createNewNote() for more options.
</dt>
<dd>
<span class="param-type">void</span>
<span class="param-type">Object</span>
</dd>
@@ -6272,7 +6272,7 @@ if some action needs to happen on only one specific instance.
<td class="type">
<span class="param-type">number</span>
<span class="param-type">int</span>
@@ -7463,7 +7463,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
<td class="description last">if branch will be created between note and parent note, set this prefix</td>
<td class="description last">if branch is created between note and parent note, set this prefix</td>
</tr>
@@ -7883,13 +7883,13 @@ exists, then we'll use that transaction.
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -35,6 +35,8 @@ const eventService = require("../../services/events");
const dateUtils = require("../../services/date_utils");
const cls = require("../../services/cls");
const log = require("../../services/log");
const protectedSessionService = require("../../services/protected_session");
const blobService = require("../../services/blob");
let becca = null;
@@ -146,10 +148,141 @@ class AbstractBeccaEntity {
return this;
}
/** @protected */
_setContent(content, opts = {}) {
// client code asks to save entity even if blobId didn't change (something else was changed)
opts.forceSave = !!opts.forceSave;
opts.forceFrontendReload = !!opts.forceFrontendReload;
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`);
}
if (this.hasStringContent()) {
content = content.toString();
} else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content);
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
} else {
throw new Error(`Cannot update content of blob since protected session is not available.`);
}
}
sql.transactional(() => {
const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts);
const oldBlobId = this.blobId;
if (newBlobId !== oldBlobId || opts.forceSave) {
this.blobId = newBlobId;
this.save();
if (newBlobId !== oldBlobId) {
this.#deleteBlobIfNotUsed(oldBlobId);
}
}
});
}
#deleteBlobIfNotUsed(oldBlobId) {
if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
if (sql.getValue("SELECT 1 FROM attachments WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
if (sql.getValue("SELECT 1 FROM revisions WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
sql.execute("DELETE FROM blobs WHERE blobId = ?", [oldBlobId]);
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
}
#getUnencryptedContentForHashCalculation(unencryptedContent) {
if (this.isProtected) {
// a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content
const encryptedPrefixSuffix = "t$[nvQg7q)&amp;_ENCRYPTED_?M:Bf&amp;j3jr_";
return Buffer.isBuffer(unencryptedContent)
? Buffer.concat([Buffer.from(encryptedPrefixSuffix), unencryptedContent])
: `${encryptedPrefixSuffix}${unencryptedContent}`;
} else {
return unencryptedContent;
}
}
#saveBlob(content, unencryptedContentForHashCalculation, opts = {}) {
/*
* We're using the unencrypted blob for the hash calculation, because otherwise the random IV would
* cause every content blob to be unique which would balloon the database size (esp. with revisioning).
* This has minor security implications (it's easy to infer that given content is shared between different
* notes/attachments), but the trade-off comes out clearly positive.
*/
const newBlobId = utils.hashedBlobId(unencryptedContentForHashCalculation);
const blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]);
if (!blobNeedsInsert) {
return newBlobId;
}
const pojo = {
blobId: newBlobId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
sql.upsert("blobs", "blobId", pojo);
// we can't reuse blobId as an entity_changes hash, because this one has to be calculatable without having
// access to the decrypted content
const hash = blobService.calculateContentHash(pojo);
entityChangesService.addEntityChange({
entityName: 'blobs',
entityId: newBlobId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true,
// overriding componentId will cause frontend to think the change is coming from a different component
// and thus reload
componentId: opts.forceFrontendReload ? utils.randomString(10) : null
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'blobs',
entity: this
});
return newBlobId;
}
/**
* @protected
* @returns {string|Buffer}
*/
_getContent() {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
}
return blobService.processContent(row.content, this.isProtected, this.hasStringContent());
}
/**
* Mark the entity as (soft) deleted. It will be completely erased later.
*
* This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
* This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
*
* @param [deleteId=null]
*/
@@ -206,13 +339,13 @@ module.exports = AbstractBeccaEntity;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -124,7 +124,7 @@ class BAttribute extends AbstractBeccaEntity {
}
if (this.type === 'relation' &amp;&amp; !(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}'.`);
}
}
@@ -169,7 +169,7 @@ class BAttribute extends AbstractBeccaEntity {
*/
getTargetNote() {
if (this.type !== 'relation') {
throw new Error(`Attribute ${this.attributeId} is not relation`);
throw new Error(`Attribute '${this.attributeId}' is not a relation.`);
}
if (!this.value) {
@@ -216,9 +216,11 @@ class BAttribute extends AbstractBeccaEntity {
this.value = "";
}
if (this.position === undefined) {
// TODO: can be calculated from becca
this.position = 1 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
if (this.position === undefined || this.position === null) {
const maxExistingPosition = this.getNote().getAttributes()
.reduce((maxPosition, attr) => Math.max(maxPosition, attr.position || 0), 0);
this.position = maxExistingPosition + 10;
}
if (!this.isInheritable) {
@@ -270,13 +272,13 @@ module.exports = BAttribute;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -131,6 +131,7 @@ class BBranch extends AbstractBeccaEntity {
return this.becca.notes[this.noteId];
}
/** @returns {BNote} */
getNote() {
return this.childNote;
}
@@ -214,11 +215,11 @@ class BBranch extends AbstractBeccaEntity {
// first delete children and then parent - this will show up better in recent changes
log.info(`Deleting note ${note.noteId}`);
log.info(`Deleting note '${note.noteId}'`);
this.becca.notes[note.noteId].isBeingDeleted = true;
for (const attribute of note.getOwnedAttributes()) {
for (const attribute of note.getOwnedAttributes().slice()) {
attribute.markAsDeleted(deleteId);
}
@@ -226,6 +227,10 @@ class BBranch extends AbstractBeccaEntity {
relation.markAsDeleted(deleteId);
}
for (const attachment of note.getAttachments()) {
attachment.markAsDeleted(deleteId);
}
note.markAsDeleted(deleteId);
return true;
@@ -247,7 +252,7 @@ class BBranch extends AbstractBeccaEntity {
for (const childBranch of this.parentNote.getChildBranches()) {
if (maxNotePos &lt; childBranch.notePosition
&amp;&amp; childBranch.noteId !== '_hidden' // hidden has very large notePosition to always stay last
&amp;&amp; childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
) {
maxNotePos = childBranch.notePosition;
}
@@ -313,13 +318,13 @@ module.exports = BBranch;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -114,13 +114,13 @@ module.exports = BEtapiToken;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+394 -210
View File
@@ -33,9 +33,9 @@ const log = require('../../services/log');
const sql = require('../../services/sql');
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const BNoteRevision = require("./bnote_revision");
const BRevision = require("./brevision");
const BAttachment = require("./battachment");
const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc');
@@ -46,14 +46,14 @@ const LABEL = 'label';
const RELATION = 'relation';
/**
* Trilium's main entity which can represent text note, image, code note, file attachment etc.
* Trilium's main entity, which can represent text note, image, code note, file attachment etc.
*
* @extends AbstractBeccaEntity
*/
class BNote extends AbstractBeccaEntity {
static get entityName() { return "notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime"]; }
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime", "blobId"]; }
constructor(row) {
super();
@@ -73,6 +73,7 @@ class BNote extends AbstractBeccaEntity {
row.type,
row.mime,
row.isProtected,
row.blobId,
row.dateCreated,
row.dateModified,
row.utcDateCreated,
@@ -80,19 +81,21 @@ class BNote extends AbstractBeccaEntity {
]);
}
update([noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
update([noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
// ------ Database persisted attributes ------
/** @type {string} */
this.noteId = noteId;
/** @type {string} */
this.title = title;
/** @type {boolean} */
this.isProtected = !!isProtected;
/** @type {string} */
this.type = type;
/** @type {string} */
this.mime = mime;
/** @type {boolean} */
this.isProtected = !!isProtected;
/** @type {string} */
this.blobId = blobId;
/** @type {string} */
this.dateCreated = dateCreated || dateUtils.localNowDateTime();
/** @type {string} */
@@ -112,7 +115,7 @@ class BNote extends AbstractBeccaEntity {
this.decrypt();
/** @type {string|null} */
this.flatTextCache = null;
this.__flatTextCache = null;
return this;
}
@@ -125,7 +128,7 @@ class BNote extends AbstractBeccaEntity {
* @private */
this.parents = [];
/** @type {BNote[]}
* @private*/
* @private */
this.children = [];
/** @type {BAttribute[]}
* @private */
@@ -135,20 +138,20 @@ class BNote extends AbstractBeccaEntity {
* @private */
this.__attributeCache = null;
/** @type {BAttribute[]|null}
* @private*/
this.inheritableAttributeCache = null;
* @private */
this.__inheritableAttributeCache = null;
/** @type {BAttribute[]}
* @private*/
* @private */
this.targetRelations = [];
this.becca.addNote(this.noteId, this);
/** @type {BNote[]|null}
* @private */
this.ancestorCache = null;
this.__ancestorCache = null;
// following attributes are filled during searching from database
// following attributes are filled during searching in the database
/**
* size of the content in bytes
@@ -231,49 +234,41 @@ class BNote extends AbstractBeccaEntity {
* - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
if (!row) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note content for noteId=${this.noteId}`);
}
}
let content = row.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
/** @returns {string|Buffer} */
getContent() {
return this._getContent();
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
/** @returns {{dateModified, utcDateModified}} */
getContentMetadata() {
return sql.getRow(`
SELECT
LENGTH(content) AS contentLength,
dateModified,
utcDateModified
FROM note_contents
WHERE noteId = ?`, [this.noteId]);
return sql.getRow(`SELECT dateModified, utcDateModified FROM blobs WHERE blobId = ?`, [this.blobId]);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BNote entity
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
*/
setContent(content, opts) {
this._setContent(content, opts);
eventService.emit(eventService.NOTE_CONTENT_CHANGE, { entity: this });
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
get dateCreatedObj() {
@@ -292,68 +287,6 @@ class BNote extends AbstractBeccaEntity {
return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
setContent(content, ignoreMissingProtectedSession = false) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note '${this.noteId}'`);
}
if (this.isStringNote()) {
content = content.toString();
}
else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
const pojo = {
noteId: this.noteId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.content = protectedSessionService.encrypt(pojo.content);
}
else if (!ignoreMissingProtectedSession) {
throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`);
}
}
sql.upsert("note_contents", "noteId", pojo);
const hash = utils.hash(`${this.noteId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'note_contents',
entityId: this.noteId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'note_contents',
entity: this
});
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
isRoot() {
return this.noteId === 'root';
@@ -384,8 +317,13 @@ class BNote extends AbstractBeccaEntity {
|| (this.type === 'file' &amp;&amp; this.mime?.startsWith('image/'));
}
/** @returns {boolean} true if the note has string content (not binary) */
/** @deprecated use hasStringContent() instead */
isStringNote() {
return this.hasStringContent();
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
return utils.isStringNote(this.type, this.mime);
}
@@ -407,6 +345,9 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Beware that the method must not create a copy of the array, but actually returns its internal array
* (for performance reasons)
*
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {BAttribute[]} all note's attributes, including inherited ones
@@ -425,7 +366,6 @@ class BNote extends AbstractBeccaEntity {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
// a bit unsafe to return the original array, but defensive copy would be costly
return this.__attributeCache;
}
}
@@ -482,11 +422,11 @@ class BNote extends AbstractBeccaEntity {
}
}
this.inheritableAttributeCache = [];
this.__inheritableAttributeCache = [];
for (const attr of this.__attributeCache) {
if (attr.isInheritable) {
this.inheritableAttributeCache.push(attr);
this.__inheritableAttributeCache.push(attr);
}
}
}
@@ -503,11 +443,11 @@ class BNote extends AbstractBeccaEntity {
return [];
}
if (!this.inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache
if (!this.__inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
}
return this.inheritableAttributeCache;
return this.__inheritableAttributeCache;
}
__validateTypeName(type, name) {
@@ -560,6 +500,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 &amp;&amp; label.value !== 'false';
}
/**
* @param {string} name - label name
* @param {string} [value] - label value
@@ -642,7 +596,8 @@ class BNote extends AbstractBeccaEntity {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {BAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
* @returns {BAttribute} attribute of the given type and name. If there are more such attributes, first is returned.
* Returns null if there's no such attribute belonging to this note.
*/
getAttribute(type, name) {
const attributes = this.getAttributes();
@@ -721,6 +676,9 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Beware that the method must not create a copy of the array, but actually returns its internal array
* (for performance reasons)
*
* @param {string|null} [type] - (optional) attribute type to filter
* @param {string|null} [name] - (optional) attribute name to filter
* @param {string|null} [value] - (optional) attribute value to filter
@@ -742,7 +700,7 @@ class BNote extends AbstractBeccaEntity {
return this.ownedAttributes.filter(attr => attr.name === name);
}
else {
return this.ownedAttributes.slice();
return this.ownedAttributes;
}
}
@@ -761,6 +719,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 path 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' &amp;&amp; attr.type === LABEL &amp;&amp; attr.isInheritable) {
@@ -812,40 +785,40 @@ class BNote extends AbstractBeccaEntity {
* @returns {string} - returns flattened textual representation of note, prefixes and attributes
*/
getFlatText() {
if (!this.flatTextCache) {
this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
if (!this.__flatTextCache) {
this.__flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
for (const branch of this.parentBranches) {
if (branch.prefix) {
this.flatTextCache += `${branch.prefix} `;
this.__flatTextCache += `${branch.prefix} `;
}
}
this.flatTextCache += `${this.title} `;
this.__flatTextCache += `${this.title} `;
for (const attr of this.getAttributes()) {
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
this.flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
this.__flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
if (attr.value) {
this.flatTextCache += `=${attr.value}`;
this.__flatTextCache += `=${attr.value}`;
}
this.flatTextCache += ' ';
this.__flatTextCache += ' ';
}
this.flatTextCache = utils.normalize(this.flatTextCache);
this.__flatTextCache = utils.normalize(this.__flatTextCache);
}
return this.flatTextCache;
return this.__flatTextCache;
}
invalidateThisCache() {
this.flatTextCache = null;
this.__flatTextCache = null;
this.__attributeCache = null;
this.inheritableAttributeCache = null;
this.ancestorCache = null;
this.__inheritableAttributeCache = null;
this.__ancestorCache = null;
}
invalidateSubTree(path = []) {
@@ -874,24 +847,6 @@ class BNote extends AbstractBeccaEntity {
}
}
invalidateSubtreeFlatText() {
this.flatTextCache = null;
for (const childNote of this.children) {
childNote.invalidateSubtreeFlatText();
}
for (const targetRelation of this.targetRelations) {
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
const note = targetRelation.note;
if (note) {
note.invalidateSubtreeFlatText();
}
}
}
}
getRelationDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
@@ -1020,7 +975,7 @@ class BNote extends AbstractBeccaEntity {
};
}
/** @returns {String[]} - includes the subtree node as well */
/** @returns {string[]} - includes the subtree root note as well */
getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
return this.getSubtree({includeArchived, includeHidden, resolveSearch})
.notes
@@ -1082,28 +1037,33 @@ class BNote extends AbstractBeccaEntity {
/** @returns {BNote[]} */
getAncestors() {
if (!this.ancestorCache) {
if (!this.__ancestorCache) {
const noteIds = new Set();
this.ancestorCache = [];
this.__ancestorCache = [];
for (const parent of this.parents) {
if (noteIds.has(parent.noteId)) {
continue;
}
this.ancestorCache.push(parent);
this.__ancestorCache.push(parent);
noteIds.add(parent.noteId);
for (const ancestorNote of parent.getAncestors()) {
if (!noteIds.has(ancestorNote.noteId)) {
this.ancestorCache.push(ancestorNote);
this.__ancestorCache.push(ancestorNote);
noteIds.add(ancestorNote.noteId);
}
}
}
}
return this.ancestorCache;
return this.__ancestorCache;
}
/** @returns {string[]} */
getAncestorNoteIds() {
return this.getAncestors().map(note => note.noteId);
}
/** @returns {boolean} */
@@ -1121,6 +1081,7 @@ class BNote extends AbstractBeccaEntity {
return this.noteId === '_hidden' || this.hasAncestor('_hidden');
}
/** @returns {BAttribute[]} */
getTargetRelations() {
return this.targetRelations;
}
@@ -1157,13 +1118,60 @@ class BNote extends AbstractBeccaEntity {
return minDistance;
}
/** @returns {BNoteRevision[]} */
getNoteRevisions() {
return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId])
.map(row => new BNoteRevision(row));
/** @returns {BRevision[]} */
getRevisions() {
return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
.map(row => new BRevision(row));
}
/** @returns {BAttachment[]} */
getAttachments(opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
// from testing, it looks like calculating length does not make a difference in performance even on large-ish DB
// given that we're always fetching attachments only for a specific note, we might just do it always
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE ownerId = ? AND isDeleted = 0
ORDER BY position`
: `SELECT * FROM attachments WHERE ownerId = ? AND isDeleted = 0 ORDER BY position`;
return sql.getRows(query, [this.noteId])
.map(row => new BAttachment(row));
}
/** @returns {BAttachment|null} */
getAttachmentById(attachmentId, opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
return sql.getRows(query, [this.noteId, attachmentId])
.map(row => new BAttachment(row))[0];
}
/** @returns {BAttachment[]} */
getAttachmentByRole(role) {
return sql.getRows(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
AND role = ?
AND isDeleted = 0
ORDER BY position`, [this.noteId, role])
.map(row => new BAttachment(row));
}
/**
* 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() {
@@ -1171,18 +1179,70 @@ class BNote extends AbstractBeccaEntity {
return [['root']];
}
const notePaths = [];
const parentNotes = this.getParentNotes();
for (const parentNote of this.getParentNotes()) {
for (const parentPath of parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
const notePaths = parentNotes.length === 1
? parentNotes[0].getAllNotePaths() // optimization for the most common case
: parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
for (const notePath of notePaths) {
notePath.push(this.noteId);
}
return notePaths;
}
/**
* @param {string} [hoistedNoteId='root']
* @return {Array&lt;{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array&lt;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 a 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 a 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
*/
@@ -1196,9 +1256,7 @@ class BNote extends AbstractBeccaEntity {
return false;
} else if (parentNote.noteId === '_hidden') {
continue;
}
if (!parentNote.isHiddenCompletely()) {
} else if (!parentNote.isHiddenCompletely()) {
return false;
}
}
@@ -1272,10 +1330,10 @@ class BNote extends AbstractBeccaEntity {
* @param {string} name - name of the attribute, not including the leading ~/#
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
* @param {boolean} [isInheritable=false]
* @param {int} [position]
* @param {int|null} [position]
* @returns {BAttribute}
*/
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
addAttribute(type, name, value = "", isInheritable = false, position = null) {
const BAttribute = require("./battribute");
return new BAttribute({
@@ -1314,7 +1372,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Based on enabled, attribute is either set or removed.
* Based on enabled, the attribute is either set or removed.
*
* @param {string} type - attribute type ('relation', 'label' etc.)
* @param {boolean} enabled - toggle On or Off
@@ -1373,7 +1431,7 @@ class BNote extends AbstractBeccaEntity {
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
* Remove relation name-value pair, if it exists.
* Remove the relation name-value pair, if it exists.
*
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
@@ -1392,7 +1450,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");
@@ -1402,13 +1460,89 @@ class BNote extends AbstractBeccaEntity {
return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
}
isEligibleForConversionToAttachment(opts = {autoConversion: false}) {
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
return false;
}
const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
if (opts.autoConversion &amp;&amp; targetRelations.length === 0) {
return false;
} else if (targetRelations.length > 1) {
return false;
}
const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent
const referencingNote = targetRelations[0]?.getNote();
if (referencingNote &amp;&amp; parentNote !== referencingNote) {
return false;
} else if (parentNote.type !== 'text' || !parentNote.isContentAvailable()) {
return false;
}
return true;
}
/**
* Some notes are eligible for conversion into an attachment of its parent, note must have these properties:
* - it has exactly one target relation
* - it has a relation from its parent note
* - it has no children
* - it has no clones
* - the parent is of type text
* - both notes are either unprotected or user is in protected session
*
* Currently, works only for image notes.
*
* In the future, this functionality might get more generic and some of the requirements relaxed.
*
* @params {Object} [opts]
* @params {bolean} [opts.autoConversion=false} if true, the action is not triggered by user, but e.g. by migration,
* and only perfect candidates will be migrated
*
* @returns {BAttachment|null} - null if note is not eligible for conversion
*/
convertToParentAttachment(opts = {autoConversion: false}) {
if (!this.isEligibleForConversionToAttachment(opts)) {
return null;
}
const content = this.getContent();
const parentNote = this.getParentNotes()[0];
const attachment = parentNote.saveAttachment({
role: 'image',
mime: this.mime,
title: this.title,
content: content
});
let parentContent = parentNote.getContent();
const oldNoteUrl = `api/images/${this.noteId}/`;
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
const fixedContent = utils.replaceAll(parentContent, oldNoteUrl, newAttachmentUrl);
parentNote.setContent(fixedContent);
const noteService = require("../../services/notes");
noteService.asyncPostProcessContent(parentNote, fixedContent); // to mark an unused attachment for deletion
this.deleteNote();
return attachment;
}
/**
* (Soft) delete a note and all its descendants.
*
* @param {string} [deleteId] - optional delete identified
* @param {string} [deleteId=null] - optional delete identified
* @param {TaskContext} [taskContext]
*/
deleteNote(deleteId, taskContext) {
deleteNote(deleteId = null, taskContext = null) {
if (this.isDeleted) {
return;
}
@@ -1435,7 +1569,7 @@ class BNote extends AbstractBeccaEntity {
if (this.isProtected &amp;&amp; !this.isDecrypted &amp;&amp; protectedSessionService.isProtectedSessionAvailable()) {
try {
this.title = protectedSessionService.decryptString(this.title);
this.flatTextCache = null;
this.__flatTextCache = null;
this.isDecrypted = true;
}
@@ -1454,42 +1588,91 @@ class BNote extends AbstractBeccaEntity {
}
get isDeleted() {
// isBeingDeleted is relevant only in the transition period when the deletion process has begun, but not yet
// finished (note is still in becca)
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
}
/**
* @returns {BNoteRevision|null}
* @returns {BRevision|null}
*/
saveNoteRevision() {
const content = this.getContent();
saveRevision() {
return sql.transactional(() => {
let noteContent = this.getContent();
const contentMetadata = this.getContentMetadata();
if (!content || (Buffer.isBuffer(content) &amp;&amp; content.byteLength === 0)) {
return null;
const revision = new BRevision({
noteId: this.noteId,
// title and text should be decrypted now
title: this.title,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
? this.utcDateModified
: contentMetadata.utcDateModified,
utcDateCreated: dateUtils.utcNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
dateLastEdited: this.dateModified > contentMetadata.dateModified
? this.dateModified
: contentMetadata.dateModified,
dateCreated: dateUtils.localNowDateTime()
}, true);
revision.save(); // to generate revisionId, which is then used to save attachments
if (this.type === 'text') {
for (const noteAttachment of this.getAttachments()) {
if (noteAttachment.utcDateScheduledForErasureSince) {
continue;
}
const revisionAttachment = noteAttachment.copy();
revisionAttachment.ownerId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
// content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
`attachments/${revisionAttachment.attachmentId}`);
noteContent = noteContent.replaceAll(new RegExp(`href="[^"]*attachmentId=${noteAttachment.attachmentId}[^"]*"`, 'gi'),
`href="api/attachments/${revisionAttachment.attachmentId}/download"`);
}
revision.setContent(noteContent, {forceSave: true});
}
return revision;
});
}
/**
* @returns {BAttachment}
*/
saveAttachment({attachmentId, role, mime, title, content, position}) {
let attachment;
if (attachmentId) {
attachment = this.becca.getAttachmentOrThrow(attachmentId);
} else {
attachment = new BAttachment({
ownerId: this.noteId,
title,
role,
mime,
isProtected: this.isProtected,
position
});
}
const contentMetadata = this.getContentMetadata();
content = content || "";
attachment.setContent(content, {forceSave: true});
const noteRevision = new BNoteRevision({
noteId: this.noteId,
// title and text should be decrypted now
title: this.title,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
? this.utcDateModified
: contentMetadata.utcDateModified,
utcDateCreated: dateUtils.utcNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
dateLastEdited: this.dateModified > contentMetadata.dateModified
? this.dateModified
: contentMetadata.dateModified,
dateCreated: dateUtils.localNowDateTime()
}, true).save();
return attachment;
}
noteRevision.setContent(content);
return noteRevision;
getFileName() {
return utils.formatDownloadTitle(this.title, this.type, this.mime);
}
beforeSaving() {
@@ -1508,6 +1691,7 @@ class BNote extends AbstractBeccaEntity {
isProtected: this.isProtected,
type: this.type,
mime: this.mime,
blobId: this.blobId,
isDeleted: false,
dateCreated: this.dateCreated,
dateModified: this.dateModified,
@@ -1544,13 +1728,13 @@ module.exports = BNote;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -1,242 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: becca/entities/bnote_revision.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: becca/entities/bnote_revision.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>"use strict";
const protectedSessionService = require('../../services/protected_session');
const utils = require('../../services/utils');
const sql = require('../../services/sql');
const dateUtils = require('../../services/date_utils');
const becca = require('../becca');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
/**
* NoteRevision represents snapshot of note's title and content at some point in the past.
* It's used for seamless note versioning.
*
* @extends AbstractBeccaEntity
*/
class BNoteRevision extends AbstractBeccaEntity {
static get entityName() { return "note_revisions"; }
static get primaryKeyName() { return "noteRevisionId"; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
constructor(row, titleDecrypted = false) {
super();
/** @type {string} */
this.noteRevisionId = row.noteRevisionId;
/** @type {string} */
this.noteId = row.noteId;
/** @type {string} */
this.type = row.type;
/** @type {string} */
this.mime = row.mime;
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.title = row.title;
/** @type {string} */
this.dateLastEdited = row.dateLastEdited;
/** @type {string} */
this.dateCreated = row.dateCreated;
/** @type {string} */
this.utcDateLastEdited = row.utcDateLastEdited;
/** @type {string} */
this.utcDateCreated = row.utcDateCreated;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {number} */
this.contentLength = row.contentLength;
if (this.isProtected &amp;&amp; !titleDecrypted) {
this.title = protectedSessionService.isProtectedSessionAvailable()
? protectedSessionService.decryptString(this.title)
: "[protected]";
}
}
getNote() {
return becca.notes[this.noteId];
}
/** @returns {boolean} true if the note has string content (not binary) */
isStringNote() {
return utils.isStringNote(this.type, this.mime);
}
/*
* Note revision content has quite special handling - it's not a separate entity, but a lazily loaded
* part of NoteRevision entity with its own sync. Reason behind this hybrid design is that
* content can be quite large, and it's not necessary to load it / fill memory for any note access even
* if we don't need a content, especially for bulk operations like search.
*
* This is the same approach as is used for Note's content.
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const res = sql.getRow(`SELECT content FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]);
if (!res) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note revision content for noteRevisionId=${this.noteRevisionId}`);
}
}
let content = res.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
}
setContent(content) {
const pojo = {
noteRevisionId: this.noteRevisionId,
content: content,
utcDateModified: dateUtils.utcNowDateTime()
};
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.content = protectedSessionService.encrypt(pojo.content);
}
else {
throw new Error(`Cannot update content of noteRevisionId=${this.noteRevisionId} since we're out of protected session.`);
}
}
sql.upsert("note_revision_contents", "noteRevisionId", pojo);
const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'note_revision_contents',
entityId: this.noteRevisionId,
hash: hash,
isErased: false,
utcDateChanged: this.getUtcDateChanged(),
isSynced: true
});
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
getContentMetadata() {
return sql.getRow(`
SELECT
LENGTH(content) AS contentLength,
dateModified,
utcDateModified
FROM note_revision_contents
WHERE noteRevisionId = ?`, [this.noteRevisionId]);
}
beforeSaving() {
super.beforeSaving();
this.utcDateModified = dateUtils.utcNowDateTime();
}
getPojo() {
return {
noteRevisionId: this.noteRevisionId,
noteId: this.noteId,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
title: this.title,
dateLastEdited: this.dateLastEdited,
dateCreated: this.dateCreated,
utcDateLastEdited: this.utcDateLastEdited,
utcDateCreated: this.utcDateCreated,
utcDateModified: this.utcDateModified,
contentLength: this.contentLength
};
}
getPojoToSave() {
const pojo = this.getPojo();
delete pojo.contentLength; // not getting persisted
if (pojo.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.title = protectedSessionService.encrypt(this.title);
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
delete pojo.title;
}
}
return pojo;
}
}
module.exports = BNoteRevision;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>
@@ -32,7 +32,7 @@ const dateUtils = require('../../services/date_utils');
const AbstractBeccaEntity = require("./abstract_becca_entity");
/**
* Option represents name-value pair, either directly configurable by the user or some system property.
* Option represents a name-value pair, either directly configurable by the user or some system property.
*
* @extends AbstractBeccaEntity
*/
@@ -44,6 +44,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} */
@@ -52,8 +57,6 @@ class BOption extends AbstractBeccaEntity {
this.isSynced = !!row.isSynced;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
this.becca.options[this.name] = this;
}
beforeSaving() {
@@ -83,13 +86,13 @@ module.exports = BOption;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -71,13 +71,13 @@ module.exports = BRecentNote;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+2 -2
View File
@@ -50,13 +50,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+2 -2
View File
@@ -1294,13 +1294,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -55,11 +55,11 @@ const exportService = require("./export/zip");
* @constructor
*/
function BackendScriptApi(currentNote, apiParams) {
/** @property {BNote} note where script started executing */
/** @property {BNote} note where the script started executing */
this.startNote = apiParams.startNote;
/** @property {BNote} note where script is currently executing. Don't mix this up with concept of active note */
/** @property {BNote} note where the script is currently executing. Don't mix this up with the concept of active note */
this.currentNote = currentNote;
/** @property {AbstractBeccaEntity} entity whose event triggered this executions */
/** @property {AbstractBeccaEntity} entity whose event triggered this execution */
this.originEntity = apiParams.originEntity;
for (const key in apiParams) {
@@ -165,13 +165,13 @@ function BackendScriptApi(currentNote, apiParams) {
this.getNoteWithLabel = attributeService.getNoteWithLabel;
/**
* If there's no branch between note and parent note, create one. Otherwise, do nothing.
* If there's no branch between note and parent note, create one. Otherwise, do nothing. Returns the new or existing branch.
*
* @method
* @param {string} noteId
* @param {string} parentNoteId
* @param {string} prefix - if branch will be created between note and parent note, set this prefix
* @returns {void}
* @param {string} prefix - if branch is created between note and parent note, set this prefix
* @returns {{branch: BBranch|null}}
*/
this.ensureNoteIsPresentInParent = cloningService.ensureNoteIsPresentInParent;
@@ -192,7 +192,7 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {boolean} present - true if we want the branch to exist, false if we want it gone
* @param {string} noteId
* @param {string} parentNoteId
* @param {string} prefix - if branch will be created between note and parent note, set this prefix
* @param {string} prefix - if branch is created between note and parent note, set this prefix
* @returns {void}
*/
this.toggleNoteInParent = cloningService.toggleNoteInParent;
@@ -272,7 +272,7 @@ function BackendScriptApi(currentNote, apiParams) {
const parentNote = becca.getNote(parentNoteId);
// code note type can be inherited, otherwise text is default
// code note type can be inherited, otherwise "text" is the default
extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
@@ -440,7 +440,7 @@ function BackendScriptApi(currentNote, apiParams) {
* Return randomly generated string of given length. This random string generation is NOT cryptographically secure.
*
* @method
* @param {number} length of the string
* @param {int} length of the string
* @returns {string} random string
*/
this.randomString = utils.randomString;
@@ -499,11 +499,11 @@ function BackendScriptApi(currentNote, apiParams) {
if (opts.type === 'script' &amp;&amp; !opts.scriptNoteId) { throw new Error("scriptNoteId is mandatory for launchers of type 'script'"); }
if (opts.type === 'customWidget' &amp;&amp; !opts.widgetNoteId) { throw new Error("widgetNoteId is mandatory for launchers of type 'customWidget'"); }
const parentNoteId = !!opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers';
const parentNoteId = opts.isVisible ? '_lbVisibleLaunchers' : '_lbAvailableLaunchers';
const noteId = 'al_' + opts.id;
const launcherNote =
becca.getNote(opts.id) ||
becca.getNote(noteId) ||
specialNotesService.createLauncher({
noteId: noteId,
parentNoteId: parentNoteId,
@@ -542,7 +542,7 @@ function BackendScriptApi(currentNote, apiParams) {
if (opts.icon) {
launcherNote.setLabel('iconClass', `bx ${opts.icon}`);
} else {
launcherNote.removeLabel('keyboardShortcut');
launcherNote.removeLabel('iconClass');
}
return {note: launcherNote};
@@ -578,13 +578,13 @@ module.exports = BackendScriptApi;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+4 -4
View File
@@ -245,7 +245,7 @@ function wrap(query, func) {
// in these cases error should be simply ignored.
console.log(e.message);
return null
return null;
}
throw e;
@@ -309,7 +309,7 @@ function fillParamList(paramIds, truncate = true) {
}
// doing it manually to avoid this showing up on the sloq query list
const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`, paramIds);
const s = stmt(`INSERT INTO param_list VALUES ${paramIds.map(paramId => `(?)`).join(',')}`);
s.run(paramIds);
}
@@ -407,13 +407,13 @@ module.exports = {
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BNoteRevision.html">BNoteRevision</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+79 -11
View File
@@ -194,7 +194,75 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line16">line 16</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line17">line 17</a>
</li></ul></dd>
</dl>
<h4 class="name" id="froca"><span class="type-signature"></span>froca<span class="type-signature"> :Froca</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">Froca</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line10">line 10</a>
</li></ul></dd>
@@ -262,7 +330,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line28">line 28</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line29">line 29</a>
</li></ul></dd>
@@ -330,7 +398,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line22">line 22</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line23">line 23</a>
</li></ul></dd>
@@ -398,7 +466,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line18">line 18</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line19">line 19</a>
</li></ul></dd>
@@ -466,7 +534,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line26">line 26</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line27">line 27</a>
</li></ul></dd>
@@ -534,7 +602,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line20">line 20</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line21">line 21</a>
</li></ul></dd>
@@ -602,7 +670,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line24">line 24</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line25">line 25</a>
</li></ul></dd>
@@ -678,7 +746,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line32">line 32</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line33">line 33</a>
</li></ul></dd>
@@ -780,7 +848,7 @@ and relation (representing named relationship between source and target note)</d
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line37">line 37</a>
<a href="entities_fattribute.js.html">entities/fattribute.js</a>, <a href="entities_fattribute.js.html#line38">line 38</a>
</li></ul></dd>
@@ -844,13 +912,13 @@ and relation (representing named relationship between source and target note)</d
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+82 -14
View File
@@ -198,7 +198,75 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line17">line 17</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line18">line 18</a>
</li></ul></dd>
</dl>
<h4 class="name" id="froca"><span class="type-signature"></span>froca<span class="type-signature"> :Froca</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">Froca</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line8">line 8</a>
</li></ul></dd>
@@ -266,7 +334,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line29">line 29</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line30">line 30</a>
</li></ul></dd>
@@ -334,7 +402,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line27">line 27</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line28">line 28</a>
</li></ul></dd>
@@ -402,7 +470,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line19">line 19</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line20">line 20</a>
</li></ul></dd>
@@ -470,7 +538,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line23">line 23</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line24">line 24</a>
</li></ul></dd>
@@ -538,7 +606,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line21">line 21</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line22">line 22</a>
</li></ul></dd>
@@ -606,7 +674,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line25">line 25</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line26">line 26</a>
</li></ul></dd>
@@ -682,7 +750,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line33">line 33</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line34">line 34</a>
</li></ul></dd>
@@ -784,7 +852,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line38">line 38</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line39">line 39</a>
</li></ul></dd>
@@ -886,7 +954,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line43">line 43</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line44">line 44</a>
</li></ul></dd>
@@ -988,7 +1056,7 @@ parents.</div>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line48">line 48</a>
<a href="entities_fbranch.js.html">entities/fbranch.js</a>, <a href="entities_fbranch.js.html#line49">line 49</a>
</li></ul></dd>
@@ -1017,7 +1085,7 @@ parents.</div>
<div class="param-desc">
true if it's top level, meaning its parent is root note
true if it's top level, meaning its parent is the root note
</div>
@@ -1056,13 +1124,13 @@ parents.</div>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
File diff suppressed because it is too large Load Diff
-790
View File
@@ -1,790 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: FNoteComplement</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: FNoteComplement</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>FNoteComplement<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">Complements the FNote with the main note content and other extra attributes</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="FNoteComplement"><span class="type-signature"></span>new FNoteComplement<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line4">line 4</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="combinedDateModified"><span class="type-signature"></span>combinedDateModified<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line33">line 33</a>
</li></ul></dd>
</dl>
<h4 class="name" id="combinedUtcDateModified"><span class="type-signature"></span>combinedUtcDateModified<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line36">line 36</a>
</li></ul></dd>
</dl>
<h4 class="name" id="content"><span class="type-signature"></span>content<span class="type-signature"> :string</span></h4>
<div class="description">
can either contain the whole content (in e.g. string notes), only part (large text notes) or nothing at all (binary notes, images)
</div>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line13">line 13</a>
</li></ul></dd>
</dl>
<h4 class="name" id="contentLength"><span class="type-signature"></span>contentLength<span class="type-signature"> :int</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">int</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line16">line 16</a>
</li></ul></dd>
</dl>
<h4 class="name" id="dateCreated"><span class="type-signature"></span>dateCreated<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line19">line 19</a>
</li></ul></dd>
</dl>
<h4 class="name" id="dateModified"><span class="type-signature"></span>dateModified<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line22">line 22</a>
</li></ul></dd>
</dl>
<h4 class="name" id="noteId"><span class="type-signature"></span>noteId<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line7">line 7</a>
</li></ul></dd>
</dl>
<h4 class="name" id="utcDateCreated"><span class="type-signature"></span>utcDateCreated<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line25">line 25</a>
</li></ul></dd>
</dl>
<h4 class="name" id="utcDateModified"><span class="type-signature"></span>utcDateModified<span class="type-signature"> :string</span></h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">string</span>
</li>
</ul>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_fnote_complement.js.html">entities/fnote_complement.js</a>, <a href="entities_fnote_complement.js.html#line28">line 28</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>
File diff suppressed because it is too large Load Diff
@@ -34,6 +34,7 @@
*/
class FAttribute {
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
this.update(row);
@@ -115,13 +116,13 @@ export default FAttribute;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+4 -3
View File
@@ -32,6 +32,7 @@
*/
class FBranch {
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
this.update(row);
@@ -72,7 +73,7 @@ class FBranch {
return this.froca.getNote(this.parentNoteId);
}
/** @returns {boolean} true if it's top level, meaning its parent is root note */
/** @returns {boolean} true if it's top level, meaning its parent is the root note */
isTopLevel() {
return this.parentNoteId === 'root';
}
@@ -99,13 +100,13 @@ export default FBranch;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+158 -70
View File
@@ -29,7 +29,6 @@
<pre class="prettyprint source linenums"><code>import server from '../services/server.js';
import noteAttributeCache from "../services/note_attribute_cache.js";
import ws from "../services/ws.js";
import options from "../services/options.js";
import froca from "../services/froca.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import cssClassManager from "../services/css_class_manager.js";
@@ -60,6 +59,7 @@ class FNote {
* @param {Object.&lt;string, Object>} row
*/
constructor(froca, row) {
/** @type {Froca} */
this.froca = froca;
/** @type {string[]} */
@@ -79,6 +79,9 @@ class FNote {
/** @type {Object.&lt;string, string>} */
this.childToBranch = {};
/** @type {FAttachment[]|null} */
this.attachments = null; // lazy loaded
this.update(row);
}
@@ -101,7 +104,7 @@ class FNote {
this.mime = row.mime;
}
addParent(parentNoteId, branchId) {
addParent(parentNoteId, branchId, sort = true) {
if (parentNoteId === 'none') {
return;
}
@@ -111,6 +114,10 @@ class FNote {
}
this.parentToBranch[parentNoteId] = branchId;
if (sort) {
this.sortParents();
}
}
addChild(childNoteId, branchId, sort = true) {
@@ -216,8 +223,8 @@ class FNote {
}
// will sort the parents so that non-search &amp; non-archived are first and archived at the end
// this is done so that non-search &amp; non-archived paths are always explored as first when looking for note path
resortParents() {
// this is done so that non-search &amp; non-archived paths are always explored as first when looking for a note path
sortParents() {
this.parents.sort((aNoteId, bNoteId) => {
const aBranchId = this.parentToBranch[aNoteId];
@@ -225,7 +232,7 @@ class FNote {
return 1;
}
const aNote = this.froca.getNoteFromCache([aNoteId]);
const aNote = this.froca.getNoteFromCache(aNoteId);
if (aNote.isArchived || aNote.isHiddenCompletely()) {
return 1;
@@ -249,6 +256,45 @@ class FNote {
return await this.froca.getNotes(this.children);
}
/** @returns {Promise&lt;FAttachment[]>} */
async getAttachments() {
if (!this.attachments) {
this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
}
return this.attachments;
}
/** @returns {Promise&lt;FAttachment>} */
async getAttachmentById(attachmentId) {
const attachments = await this.getAttachments();
return attachments.find(att => att.attachmentId === attachmentId);
}
isEligibleForConversionToAttachment() {
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
return false;
}
const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
if (targetRelations.length > 1) {
return false;
}
const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent
const referencingNote = targetRelations[0]?.getNote();
if (referencingNote &amp;&amp; referencingNote !== parentNote) {
return false;
} else if (parentNote.type !== 'text' || !parentNote.isContentAvailable()) {
return false;
}
return true;
}
/**
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
@@ -271,6 +317,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
@@ -323,54 +374,40 @@ 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();
const parentNotes = this.getParentNotes().filter(note => note.type !== 'search');
const notePaths = parentNotes.length === 1
? parentNotes[0].getAllNotePaths() // optimization for the most common case
: parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
for (const notePath of notePaths) {
notePath.push(this.noteId);
}
encounteredNoteIds.add(this.noteId);
const parentNotes = this.getParentNotes();
let paths;
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));
}
}
for (const path of paths) {
path.push(this.noteId);
}
return paths;
return notePaths;
}
getSortedNotePaths(hoistedNotePath = 'root') {
/**
* @param {string} [hoistedNoteId='root']
* @return {Array&lt;{isArchived: boolean, isInHoistedSubTree: boolean, isSearch: boolean, notePath: Array&lt;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),
isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId),
isArchived: path.some(noteId => froca.notes[noteId].isArchived),
isSearch: path.find(noteId => froca.notes[noteId].type === 'search'),
isHidden: path.includes('_hidden')
}));
@@ -378,12 +415,12 @@ class FNote {
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) {
return a.isHidden ? 1 : -1;
} else if (a.isSearch !== b.isSearch) {
return a.isSearch ? 1 : -1;
} else {
return a.notePath.length - b.notePath.length;
}
@@ -392,18 +429,42 @@ class FNote {
return notePaths;
}
/**
* Returns the 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 the 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
*/
isHiddenCompletely() {
if (this.noteId === 'root') {
if (this.noteId === '_hidden') {
return true;
} else if (this.noteId === 'root') {
return false;
}
for (const parentNote of this.getParentNotes()) {
if (parentNote.noteId === 'root') {
return false;
} else if (parentNote.noteId === '_hidden') {
} else if (parentNote.noteId === '_hidden' || parentNote.type === 'search') {
continue;
}
@@ -415,6 +476,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);
@@ -514,17 +582,10 @@ class FNote {
return;
}
if (options.is("hideIncludedImages_main")) {
const imageLinks = this.getRelations('imageLink');
// image is already visible in the parent note so no need to display it separately in the book
childBranches = childBranches.filter(branch => !imageLinks.find(rel => rel.value === branch.noteId));
}
// we're not checking hideArchivedNotes since that would mean we need to lazy load the child notes
// which would seriously slow down everything.
// we check this flag only once user chooses to expand the parent. This has the negative consequence that
// note may appear as folder but not contain any children when all of them are archived
// note may appear as a folder but not contain any children when all of them are archived
return childBranches;
}
@@ -551,7 +612,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 &amp;&amp; attr.type === type);
}
/**
@@ -566,7 +629,7 @@ class FNote {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {FAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
* @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
*/
getOwnedAttribute(type, name) {
const attributes = this.getOwnedAttributes();
@@ -577,7 +640,7 @@ class FNote {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {FAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
* @returns {FAttribute} attribute of the given type and name. If there are more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
*/
getAttribute(type, name) {
const attributes = this.getAttributes();
@@ -588,7 +651,7 @@ class FNote {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {string} attribute value of given type and name or null if no such attribute exists.
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
*/
getOwnedAttributeValue(type, name) {
const attr = this.getOwnedAttribute(type, name);
@@ -599,7 +662,7 @@ class FNote {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {string} attribute value of given type and name or null if no such attribute exists.
* @returns {string} attribute value of the given type and name or null if no such attribute exists.
*/
getAttributeValue(type, name) {
const attr = this.getAttribute(type, name);
@@ -619,6 +682,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 &amp;&amp; label.value !== 'false';
}
/**
* @param {string} name - relation name
* @returns {boolean} true if relation exists (excluding inherited)
@@ -717,7 +794,7 @@ class FNote {
}
getPromotedDefinitionAttributes() {
if (this.hasLabel('hidePromotedAttributes')) {
if (this.isLabelTruthy('hidePromotedAttributes')) {
return [];
}
@@ -729,8 +806,15 @@ class FNote {
return def &amp;&amp; def.isPromoted;
});
// attrs are not resorted if position changes after initial load
promotedAttrs.sort((a, b) => a.position &lt; b.position ? -1 : 1);
// attrs are not resorted if position changes after the initial load
promotedAttrs.sort((a, b) => {
if (a.noteId === b.noteId) {
return a.position &lt; b.position ? -1 : 1;
} else {
// inherited promoted attributes should stay grouped: https://github.com/zadam/trilium/issues/3761
return a.noteId &lt; b.noteId ? -1 : 1;
}
});
return promotedAttrs;
}
@@ -788,7 +872,7 @@ class FNote {
/**
* Get relations which target this note
*
* @returns {FNote[]}
* @returns {Promise&lt;FNote[]>}
*/
async getTargetRelationSourceNotes() {
const targetRelations = this.getTargetRelations();
@@ -797,12 +881,16 @@ class FNote {
}
/**
* Return note complement which is most importantly note's content
*
* @returns {Promise&lt;FNoteComplement>}
* @deprecated use getBlob() instead
* @return {Promise&lt;FBlob>}
*/
async getNoteComplement() {
return await this.froca.getNoteComplement(this.noteId);
return this.getBlob();
}
/** @return {Promise&lt;FBlob>} */
async getBlob() {
return await this.froca.getBlob('notes', this.noteId);
}
toString() {
@@ -924,13 +1012,13 @@ export default FNote;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: entities/fnote_complement.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: entities/fnote_complement.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Complements the FNote with the main note content and other extra attributes
*/
class FNoteComplement {
constructor(row) {
/** @type {string} */
this.noteId = row.noteId;
/**
* can either contain the whole content (in e.g. string notes), only part (large text notes) or nothing at all (binary notes, images)
* @type {string}
*/
this.content = row.content;
/** @type {int} */
this.contentLength = row.contentLength;
/** @type {string} */
this.dateCreated = row.dateCreated;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateCreated = row.utcDateCreated;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
// "combined" date modified give larger out of note's and note_content's dateModified
/** @type {string} */
this.combinedDateModified = row.combinedDateModified;
/** @type {string} */
this.combinedUtcDateModified = row.combinedUtcDateModified;
}
}
export default FNoteComplement;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>
+2 -2
View File
@@ -50,13 +50,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
@@ -53,9 +53,9 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
/** @property {jQuery} container of all the rendered script content */
this.$container = $container;
/** @property {object} note where script started executing */
/** @property {object} note where the script started executing */
this.startNote = startNote;
/** @property {object} note where script is currently executing */
/** @property {object} note where the script is currently executing */
this.currentNote = currentNote;
/** @property {object|null} entity whose event triggered this execution */
this.originEntity = originEntity;
@@ -63,36 +63,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
/** @property {dayjs} day.js library for date manipulation. See {@link https://day.js.org} for documentation */
this.dayjs = dayjs;
/**
* @property {RightPanelWidget}
* @deprecated use api.RightPanelWidget instead
*/
this.CollapsibleWidget = RightPanelWidget;
/** @property {RightPanelWidget} */
this.RightPanelWidget = RightPanelWidget;
/** @property {NoteContextAwareWidget} */
this.NoteContextAwareWidget = NoteContextAwareWidget;
/**
* @property {NoteContextAwareWidget}
* @deprecated use NoteContextAwareWidget instead
*/
this.TabAwareWidget = NoteContextAwareWidget;
/**
* @property {NoteContextAwareWidget}
* @deprecated use NoteContextAwareWidget instead
*/
this.TabCachingWidget = NoteContextAwareWidget;
/**
* @property {NoteContextAwareWidget}
* @deprecated use NoteContextAwareWidget instead
*/
this.NoteContextCachingWidget = NoteContextAwareWidget;
/** @property {BasicWidget} */
this.BasicWidget = BasicWidget;
@@ -117,7 +93,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext().setNote(notePath);
appContext.triggerEvent('focusAndSelectTitle');
await appContext.triggerEvent('focusAndSelectTitle');
};
/**
@@ -131,10 +107,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.openTabWithNote = async (notePath, activate) => {
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.openContextWithNote(notePath, { activate });
await appContext.tabManager.openTabWithNoteWithHoisting(notePath, { activate });
if (activate) {
appContext.triggerEvent('focusAndSelectTitle');
await appContext.triggerEvent('focusAndSelectTitle');
}
};
@@ -152,10 +128,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');
}
};
@@ -216,7 +192,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
params: prepareParams(params),
startNoteId: startNote.noteId,
currentNoteId: currentNote.noteId,
originEntityName: "notes", // currently there's no other entity on frontend which can trigger event
originEntityName: "notes", // currently there's no other entity on the frontend which can trigger event
originEntityId: originEntity ? originEntity.noteId : null
}, "script");
@@ -257,7 +233,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
};
/**
* Returns note by given noteId. If note is missing from cache, it's loaded.
* Returns note by given noteId. If note is missing from the cache, it's loaded.
**
* @method
* @param {string} noteId
@@ -266,10 +242,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.getNote = async noteId => await froca.getNote(noteId);
/**
* Returns list of notes. If note is missing from cache, it's loaded.
* Returns list of notes. If note is missing from the cache, it's loaded.
*
* This is often used to bulk-fill the cache with notes which would have to be picked one by one
* otherwise (by e.g. createNoteLink())
* otherwise (by e.g. createLink())
*
* @method
* @param {string[]} noteIds
@@ -310,7 +286,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.parseDate = utils.parseDate;
/**
* Show info message to the user.
* Show an info message to the user.
*
* @method
* @param {string} message
@@ -318,7 +294,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.showMessage = toastService.showMessage;
/**
* Show error message to the user.
* Show an error message to the user.
*
* @method
* @param {string} message
@@ -326,7 +302,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.showError = toastService.showError;
/**
* Trigger command.
* Trigger command. This is a very low-level API which should be avoided if possible.
*
* @method
* @param {string} name
@@ -335,7 +311,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.triggerCommand = (name, data) => appContext.triggerCommand(name, data);
/**
* Trigger event.
* Trigger event. This is a very low-level API which should be avoided if possible.
*
* @method
* @param {string} name
@@ -344,7 +320,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.triggerEvent = (name, data) => appContext.triggerEvent(name, data);
/**
* Create note link (jQuery object) for given note.
* Create a note link (jQuery object) for given note.
*
* @method
* @param {string} notePath (or noteId)
@@ -354,7 +330,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
* @param {boolean} [params.showNoteIcon=false] - show also note icon before the title
* @param {string} [params.title=] - custom link tile with note's title as default
*/
this.createNoteLink = linkService.createNoteLink;
this.createLink = linkService.createLink;
/** @deprecated - use api.createLink() instead */
this.createNoteLink = linkService.createLink;
/**
* Adds given text to the editor cursor
@@ -371,10 +350,10 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote();
/**
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for documentation on the returned instance.
*
* @method
* @returns {Promise&lt;CKEditor>} instance of CKEditor
* @returns {Promise&lt;BalloonEditor>} instance of CKEditor
*/
this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor();
@@ -397,12 +376,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
/**
* @method
* @returns {Promise&lt;string|null>} returns note path of active note or null if there isn't active note
* @returns {Promise&lt;string|null>} returns a note path of active note or null if there isn't active note
*/
this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath();
/**
* Returns component which owns given DOM element (the nearest parent component in DOM tree)
* Returns component which owns the given DOM element (the nearest parent component in DOM tree)
*
* @method
* @param {Element} el - DOM element
@@ -507,11 +486,11 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
this.bindGlobalShortcut = shortcutService.bindGlobalShortcut;
/**
* Trilium runs in backend and frontend process, when something is changed on the backend from script,
* Trilium runs in a backend and frontend process, when something is changed on the backend from a script,
* frontend will get asynchronously synchronized.
*
* This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
* Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
* Typical use case is when a new note has been created, we should wait until it is synced into frontend and only then activate it.
*
* @method
* @returns {Promise&lt;void>}
@@ -530,11 +509,26 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
* Return randomly generated string of given length. This random string generation is NOT cryptographically secure.
*
* @method
* @param {number} length of the string
* @param {int} length of the string
* @returns {string} random string
*/
this.randomString = utils.randomString;
/**
* @method
* @param {int} size in bytes
* @return {string} formatted string
*/
this.formatSize = utils.formatSize;
/**
* @method
* @param {int} size in bytes
* @return {string} formatted string
* @deprecated - use api.formatSize()
*/
this.formatNoteSize = utils.formatSize;
this.logMessages = {};
this.logSpacedUpdates = {};
@@ -575,13 +569,13 @@ export default FrontendScriptApi;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FNoteComplement.html">FNoteComplement</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FAttribute.html">FAttribute</a></li><li><a href="FBranch.html">FBranch</a></li><li><a href="FNote.html">FNote</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.1</a>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
</footer>
<script> prettyPrint(); </script>
+11 -11
View File
@@ -29,12 +29,12 @@ function dumpDocument(documentPath, targetPath, options) {
function dumpNote(targetPath, noteId) {
console.log(`Reading note '${noteId}'`);
let childTargetPath, note, fileNameWithPath;
let childTargetPath, noteRow, fileNameWithPath;
try {
note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
if (note.isDeleted) {
if (noteRow.isDeleted) {
stats.deleted++;
if (!options.includeDeleted) {
@@ -44,13 +44,13 @@ function dumpDocument(documentPath, targetPath, options) {
}
}
if (note.isProtected) {
if (noteRow.isProtected) {
stats.protected++;
note.title = decryptService.decryptString(dataKey, note.title);
noteRow.title = decryptService.decryptString(dataKey, noteRow.title);
}
let safeTitle = sanitize(note.title);
let safeTitle = sanitize(noteRow.title);
if (safeTitle.length > 20) {
safeTitle = safeTitle.substring(0, 20);
@@ -64,8 +64,8 @@ function dumpDocument(documentPath, targetPath, options) {
existingPaths[childTargetPath] = true;
if (note.noteId in noteIdToPath) {
const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[note.noteId]}`;
if (noteRow.noteId in noteIdToPath) {
const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[noteRow.noteId]}`;
console.log(message);
@@ -74,16 +74,16 @@ function dumpDocument(documentPath, targetPath, options) {
return;
}
let {content} = sql.getRow("SELECT content FROM note_contents WHERE noteId = ?", [noteId]);
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
if (content !== null && note.isProtected && dataKey) {
if (content !== null && noteRow.isProtected && dataKey) {
content = decryptService.decrypt(dataKey, content);
}
if (isContentEmpty(content)) {
console.log(`Note '${noteId}' is empty, skipping.`);
} else {
fileNameWithPath = extensionService.getFileName(note, childTargetPath, safeTitle);
fileNameWithPath = extensionService.getFileName(noteRow, childTargetPath, safeTitle);
fs.writeFileSync(fileNameWithPath, content);
File diff suppressed because one or more lines are too long
+159 -163
View File
@@ -5,8 +5,8 @@
}
/*
* CKEditor 5 (v35.4.0) content styles.
* Generated on Wed, 14 Dec 2022 12:53:04 GMT.
* CKEditor 5 (v38.0.1) content styles.
* Generated on Thu, 25 May 2023 13:25:51 GMT.
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
*/
@@ -28,29 +28,7 @@
--ck-todo-list-checkmark-size: 16px;
}
/* ckeditor5-basic-styles/theme/code.css */
.ck-content code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
}
/* ckeditor5-font/theme/fontsize.css */
.ck-content .text-tiny {
font-size: .7em;
}
/* ckeditor5-font/theme/fontsize.css */
.ck-content .text-small {
font-size: .85em;
}
/* ckeditor5-font/theme/fontsize.css */
.ck-content .text-big {
font-size: 1.4em;
}
/* ckeditor5-font/theme/fontsize.css */
.ck-content .text-huge {
font-size: 1.8em;
}
/* ckeditor5-block-quote/theme/blockquote.css */
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content blockquote {
overflow: hidden;
padding-right: 1.5em;
@@ -60,75 +38,60 @@
font-style: italic;
border-left: solid 5px hsl(0, 0%, 80%);
}
/* ckeditor5-block-quote/theme/blockquote.css */
/* @ckeditor/ckeditor5-block-quote/theme/blockquote.css */
.ck-content[dir="rtl"] blockquote {
border-left: 0;
border-right: solid 5px hsl(0, 0%, 80%);
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-basic-styles/theme/code.css */
.ck-content code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-tiny {
font-size: .7em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-small {
font-size: .85em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-big {
font-size: 1.4em;
}
/* @ckeditor/ckeditor5-font/theme/fontsize.css */
.ck-content .text-huge {
font-size: 1.8em;
}
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* ckeditor5-highlight/theme/highlight.css */
/* @ckeditor/ckeditor5-highlight/theme/highlight.css */
.ck-content .pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* ckeditor5-image/theme/image.css */
.ck-content .image {
display: table;
clear: both;
text-align: center;
margin: 0.9em auto;
min-width: 50px;
}
/* ckeditor5-image/theme/image.css */
.ck-content .image img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 100%;
}
/* ckeditor5-image/theme/image.css */
.ck-content .image-inline {
/*
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
* This strange behavior does not happen with inline-flex.
*/
display: inline-flex;
max-width: 100%;
align-items: flex-start;
}
/* ckeditor5-image/theme/image.css */
.ck-content .image-inline picture {
display: flex;
}
/* ckeditor5-image/theme/image.css */
.ck-content .image-inline picture,
.ck-content .image-inline img {
flex-grow: 1;
flex-shrink: 1;
max-width: 100%;
}
/* ckeditor5-image/theme/imagecaption.css */
/* @ckeditor/ckeditor5-image/theme/imagecaption.css */
.ck-content .image > figcaption {
display: table-caption;
caption-side: bottom;
@@ -139,98 +102,167 @@
font-size: .75em;
outline-offset: -1px;
}
/* ckeditor5-image/theme/imageresize.css */
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image {
display: table;
clear: both;
text-align: center;
margin: 0.9em auto;
min-width: 50px;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline {
/*
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
* This strange behavior does not happen with inline-flex.
*/
display: inline-flex;
max-width: 100%;
align-items: flex-start;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture {
display: flex;
}
/* @ckeditor/ckeditor5-image/theme/image.css */
.ck-content .image-inline picture,
.ck-content .image-inline img {
flex-grow: 1;
flex-shrink: 1;
max-width: 100%;
}
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* ckeditor5-image/theme/imageresize.css */
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized img {
width: 100%;
}
/* ckeditor5-image/theme/imageresize.css */
/* @ckeditor/ckeditor5-image/theme/imageresize.css */
.ck-content .image.image_resized > figcaption {
display: block;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left,
.ck-content .image-style-block-align-right {
max-width: calc(100% - var(--ck-image-style-spacing));
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left,
.ck-content .image-style-align-right {
clear: none;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-side {
float: right;
margin-left: var(--ck-image-style-spacing);
max-width: 50%;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-left {
float: left;
margin-right: var(--ck-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-center {
margin-left: auto;
margin-right: auto;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-align-right {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-right {
margin-right: 0;
margin-left: auto;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-style-block-align-left {
margin-left: 0;
margin-right: auto;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content p + .image-style-align-left,
.ck-content p + .image-style-align-right,
.ck-content p + .image-style-side {
margin-top: 0;
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left,
.ck-content .image-inline.image-style-align-right {
margin-top: var(--ck-inline-image-style-spacing);
margin-bottom: var(--ck-inline-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-left {
margin-right: var(--ck-inline-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
/* @ckeditor/ckeditor5-image/theme/imagestyle.css */
.ck-content .image-inline.image-style-align-right {
margin-left: var(--ck-inline-image-style-spacing);
}
/* ckeditor5-language/theme/language.css */
.ck-content span[lang] {
font-style: italic;
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol {
list-style-type: decimal;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol {
list-style-type: lower-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol {
list-style-type: lower-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol {
list-style-type: upper-latin;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ol ol ol ol ol {
list-style-type: upper-roman;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul {
list-style-type: disc;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul {
list-style-type: circle;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-list/theme/list.css */
.ck-content ul ul ul ul {
list-style-type: square;
}
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list {
list-style: none;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list li {
margin-bottom: 5px;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list li .todo-list {
margin-top: 5px;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input {
-webkit-appearance: none;
display: inline-block;
@@ -244,7 +276,7 @@
right: 0;
margin-left: 0;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::before {
display: block;
position: absolute;
@@ -256,7 +288,7 @@
border-radius: 2px;
transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input::after {
display: block;
position: absolute;
@@ -272,27 +304,27 @@
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
transform: rotate(45deg);
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input[checked]::before {
background: hsl(126, 64%, 41%);
border-color: hsl(126, 64%, 41%);
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label > input[checked]::after {
border-color: hsl(0, 0%, 100%);
}
/* ckeditor5-list/theme/todolist.css */
/* @ckeditor/ckeditor5-list/theme/todolist.css */
.ck-content .todo-list .todo-list__label .todo-list__label__description {
vertical-align: middle;
}
/* ckeditor5-media-embed/theme/mediaembed.css */
/* @ckeditor/ckeditor5-media-embed/theme/mediaembed.css */
.ck-content .media {
clear: both;
margin: 0.9em 0;
display: block;
min-width: 15em;
}
/* ckeditor5-page-break/theme/pagebreak.css */
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
position: relative;
clear: both;
@@ -301,14 +333,14 @@
align-items: center;
justify-content: center;
}
/* ckeditor5-page-break/theme/pagebreak.css */
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
content: '';
position: absolute;
border-bottom: 2px dashed hsl(0, 0%, 77%);
width: 100%;
}
/* ckeditor5-page-break/theme/pagebreak.css */
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break__label {
position: relative;
z-index: 1;
@@ -328,12 +360,25 @@
-ms-user-select: none;
user-select: none;
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table .ck-table-resized {
table-layout: fixed;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table table {
overflow: hidden;
}
/* @ckeditor/ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table td,
.ck-content .table th {
position: relative;
}
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table {
margin: 0.9em auto;
display: table;
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table {
border-collapse: collapse;
border-spacing: 0;
@@ -341,40 +386,27 @@
height: 100%;
border: 1px double hsl(0, 0%, 70%);
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table td,
.ck-content .table table th {
min-width: 2em;
padding: .4em;
border: 1px solid hsl(0, 0%, 75%);
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content .table table th {
font-weight: bold;
background: hsla(0, 0%, 0%, 5%);
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="rtl"] .table th {
text-align: right;
}
/* ckeditor5-table/theme/table.css */
/* @ckeditor/ckeditor5-table/theme/table.css */
.ck-content[dir="ltr"] .table th {
text-align: left;
}
/* ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table .ck-table-resized {
table-layout: fixed;
}
/* ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table table {
overflow: hidden;
}
/* ckeditor5-table/theme/tablecolumnresize.css */
.ck-content .table td,
.ck-content .table th {
position: relative;
}
/* ckeditor5-table/theme/tablecaption.css */
/* @ckeditor/ckeditor5-table/theme/tablecaption.css */
.ck-content .table > figcaption {
display: table-caption;
caption-side: top;
@@ -386,7 +418,7 @@
font-size: .75em;
outline-offset: -1px;
}
/* ckeditor5-code-block/theme/codeblock.css */
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre {
padding: 1em;
color: hsl(0, 0%, 20.8%);
@@ -400,66 +432,30 @@
font-style: normal;
min-width: 200px;
}
/* ckeditor5-code-block/theme/codeblock.css */
/* @ckeditor/ckeditor5-code-block/theme/codeblock.css */
.ck-content pre code {
background: unset;
padding: 0;
border-radius: 0;
}
/* ckeditor5-horizontal-line/theme/horizontalline.css */
/* @ckeditor/ckeditor5-horizontal-line/theme/horizontalline.css */
.ck-content hr {
margin: 15px 0;
height: 4px;
background: hsl(0, 0%, 87%);
border: 0;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ol {
list-style-type: decimal;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ol ol {
list-style-type: lower-latin;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ol ol ol {
list-style-type: lower-roman;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ol ol ol ol {
list-style-type: upper-latin;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ol ol ol ol ol {
list-style-type: upper-roman;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ul {
list-style-type: circle;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ul ul {
list-style-type: disc;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ul ul ul {
list-style-type: square;
}
/* ckeditor5-list/theme/liststyles.css */
.ck-content ul ul ul ul {
list-style-type: square;
}
/* ckeditor5-mention/theme/mention.css */
/* @ckeditor/ckeditor5-mention/theme/mention.css */
.ck-content .mention {
background: var(--ck-color-mention-background);
color: var(--ck-color-mention-text);
}
@media print {
/* ckeditor5-page-break/theme/pagebreak.css */
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break {
padding: 0;
}
/* ckeditor5-page-break/theme/pagebreak.css */
/* @ckeditor/ckeditor5-page-break/theme/pagebreak.css */
.ck-content .page-break::after {
display: none;
}
+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
+2 -2
View File
@@ -29,8 +29,8 @@
async function validatorJavaScript(text, options) {
if (glob.isMobile()
|| glob.getActiveTabNote() == null
|| glob.getActiveTabNote().mime === 'application/json') {
|| glob.getActiveContextNote() == null
|| glob.getActiveContextNote().mime === 'application/json') {
// eslint doesn't seem to validate pure JSON well
return [];
}
-1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+843 -1048
View File
File diff suppressed because it is too large Load Diff
+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
+1561 -1215
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1961 -2282
View File
File diff suppressed because it is too large Load Diff
+30 -30
View File
@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.60.1-beta",
"version": "0.61.2-beta",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -32,19 +32,18 @@
},
"dependencies": {
"@braintree/sanitize-url": "6.0.2",
"@electron/remote": "2.0.9",
"@electron/remote": "2.0.10",
"@excalidraw/excalidraw": "0.15.2",
"archiver": "5.3.1",
"async-mutex": "0.4.0",
"axios": "1.4.0",
"better-sqlite3": "7.4.5",
"better-sqlite3": "8.4.0",
"chokidar": "3.5.3",
"cls-hooked": "4.2.2",
"commonmark": "0.30.0",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"csurf": "1.11.0",
"dayjs": "1.11.7",
"dayjs": "1.11.9",
"dayjs-plugin-utc": "0.1.2",
"debounce": "1.2.1",
"ejs": "3.1.9",
@@ -54,36 +53,37 @@
"escape-html": "1.0.3",
"express": "4.18.2",
"express-partial-content": "1.0.2",
"express-rate-limit": "6.7.0",
"express-rate-limit": "6.8.1",
"express-session": "1.17.3",
"fs-extra": "11.1.1",
"helmet": "6.1.2",
"helmet": "7.0.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "5.0.0",
"https-proxy-agent": "5.0.1",
"http-proxy-agent": "7.0.0",
"https-proxy-agent": "7.0.1",
"image-type": "4.1.0",
"ini": "3.0.1",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.7",
"jimp": "0.22.10",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "21.1.1",
"jsdom": "22.1.0",
"marked": "5.1.2",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.40.0",
"node-abi": "3.45.0",
"normalize-strings": "1.1.1",
"open": "8.4.1",
"rand-token": "1.0.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"request": "2.88.2",
"rimraf": "5.0.1",
"safe-compare": "1.1.4",
"sanitize-filename": "1.6.3",
"sanitize-html": "2.10.0",
"sanitize-html": "2.11.0",
"sax": "1.2.4",
"semver": "7.5.1",
"semver": "7.5.4",
"serve-favicon": "2.5.0",
"session-file-store": "1.5.0",
"stream-throttle": "0.1.3",
@@ -92,33 +92,33 @@
"turndown": "7.1.2",
"unescape": "1.0.1",
"ws": "8.13.0",
"xml2js": "0.6.0",
"xml2js": "0.6.2",
"yauzl": "2.10.0"
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "16.2.8",
"electron-builder": "23.6.0",
"electron": "25.3.2",
"electron-builder": "24.6.3",
"electron-packager": "17.1.1",
"electron-rebuild": "3.2.9",
"eslint": "8.38.0",
"eslint": "8.45.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "8.8.0",
"eslint-config-prettier": "8.9.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsonc": "2.8.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-jsonc": "2.9.0",
"eslint-plugin-prettier": "5.0.0",
"esm": "3.2.25",
"husky": "8.0.3",
"jsonc-eslint-parser": "2.3.0",
"lint-staged": "13.2.2",
"jasmine": "4.6.0",
"jasmine": "5.1.0",
"jsdoc": "4.0.2",
"jsonc-eslint-parser": "2.3.0",
"lint-staged": "13.2.3",
"lorem-ipsum": "2.0.8",
"prettier": "2.8.8",
"nodemon": "2.0.22",
"nodemon": "3.0.1",
"prettier": "3.0.0",
"rcedit": "3.0.1",
"webpack": "5.84.1",
"webpack-cli": "5.1.1"
"webpack": "5.88.2",
"webpack-cli": "5.1.4"
},
"optionalDependencies": {
"electron-installer-debian": "3.1.0"
+2 -2
View File
@@ -31,7 +31,7 @@ describe("Parser", () => {
const rootExp = parse({
fulltextTokens: tokens(["hello", "hi"]),
expressionTokens: [],
searchContext: new SearchContext({includeNoteContent: false, excludeArchived: true})
searchContext: new SearchContext({excludeArchived: true})
});
expect(rootExp.constructor.name).toEqual("AndExp");
@@ -45,7 +45,7 @@ describe("Parser", () => {
const rootExp = parse({
fulltextTokens: tokens(["hello", "hi"]),
expressionTokens: [],
searchContext: new SearchContext({includeNoteContent: true})
searchContext: new SearchContext()
});
expect(rootExp.constructor.name).toEqual("AndExp");
+5 -91
View File
@@ -1,17 +1,12 @@
const log = require('./services/log');
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const session = require('express-session');
const compression = require('compression');
const FileStore = require('session-file-store')(session);
const sessionSecret = require('./services/session_secret');
const dataDir = require('./services/data_dir');
const sessionParser = require('./routes/session_parser');
const utils = require('./services/utils');
const assetPath = require('./services/asset_path');
const env = require('./services/env');
require('./services/handlers');
require('./becca/becca_loader');
@@ -31,99 +26,21 @@ 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`, 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`, persistentCacheStatic(path.join(__dirname, 'public/stylesheets')));
app.use(`/assets/vX/stylesheets`, express.static(path.join(__dirname, 'public/stylesheets')));
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`, 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/`, 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')));
const sessionParser = session({
secret: sessionSecret,
resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request.
saveUninitialized: false, // true forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified.
cookie: {
// path: "/",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
},
name: 'trilium.sid',
store: new FileStore({
ttl: 30 * 24 * 3600,
path: `${dataDir.TRILIUM_DATA_DIR}/sessions`
})
});
app.use(sessionParser);
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
require('./routes/assets').register(app);
require('./routes/routes').register(app);
require('./routes/custom').register(app);
app.use((err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') {
return next(err);
}
log.error(`Invalid CSRF token: ${req.headers['x-csrf-token']}, secret: ${req.cookies['_csrf']}`);
err = new Error('Invalid CSRF token');
err.status = 403;
next(err);
});
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error(`Router not found for request ${req.url}`);
err.status = 404;
next(err);
});
// error handler
app.use((err, req, res, next) => {
if (err && err.message && (
(err.message.includes("Router not found for request") && err.message.includes(".js.map"))
|| (err.message.includes("Router not found for request") && err.message.includes(".css.map"))
)) {
// ignore
}
else {
log.info(err);
}
res.status(err.status || 500);
res.send({
message: err.message
});
});
require('./routes/error_handlers').register(app);
// triggers sync timer
require('./services/sync');
@@ -140,7 +57,4 @@ if (utils.isElectron()) {
require('@electron/remote/main').initialize();
}
module.exports = {
app,
sessionParser
};
module.exports = app;
+86 -12
View File
@@ -2,9 +2,11 @@
const sql = require("../services/sql");
const NoteSet = require("../services/search/note_set");
const NotFoundError = require("../errors/not_found_error");
/**
* Becca is a backend cache of all notes, branches and attributes. There's a similar frontend cache Froca.
* Becca is a backend cache of all notes, branches, and attributes.
* There's a similar frontend cache Froca, and share cache Shaca.
*/
class Becca {
constructor() {
@@ -77,6 +79,16 @@ class Becca {
return this.notes[noteId];
}
/** @returns {BNote|null} */
getNoteOrThrow(noteId) {
const note = this.notes[noteId];
if (!note) {
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
return note;
}
/** @returns {BNote[]} */
getNotes(noteIds, ignoreMissing = false) {
const filteredNotes = [];
@@ -103,22 +115,82 @@ class Becca {
return this.branches[branchId];
}
/** @returns {BBranch|null} */
getBranchOrThrow(branchId) {
const branch = this.getBranch(branchId);
if (!branch) {
throw new NotFoundError(`Branch '${branchId}' was not found in becca.`);
}
return branch;
}
/** @returns {BAttribute|null} */
getAttribute(attributeId) {
return this.attributes[attributeId];
}
/** @returns {BAttribute} */
getAttributeOrThrow(attributeId) {
const attribute = this.getAttribute(attributeId);
if (!attribute) {
throw new NotFoundError(`Attribute '${attributeId}' does not exist.`);
}
return attribute;
}
/** @returns {BBranch|null} */
getBranchFromChildAndParent(childNoteId, parentNoteId) {
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
}
/** @returns {BNoteRevision|null} */
getNoteRevision(noteRevisionId) {
const row = sql.getRow("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [noteRevisionId]);
/** @returns {BRevision|null} */
getRevision(revisionId) {
const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
const BNoteRevision = require("./entities/bnote_revision"); // avoiding circular dependency problems
return row ? new BNoteRevision(row) : null;
const BRevision = require("./entities/brevision"); // avoiding circular dependency problems
return row ? new BRevision(row) : null;
}
/** @returns {BAttachment|null} */
getAttachment(attachmentId, opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0`;
const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
return sql.getRows(query, [attachmentId])
.map(row => new BAttachment(row))[0];
}
/** @returns {BAttachment} */
getAttachmentOrThrow(attachmentId, opts = {}) {
const attachment = this.getAttachment(attachmentId, opts);
if (!attachment) {
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
}
return attachment;
}
/** @returns {BAttachment[]} */
getAttachments(attachmentIds) {
const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
.map(row => new BAttachment(row));
}
/** @returns {BBlob|null} */
getBlob(entity) {
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
const BBlob = require("./entities/bblob"); // avoiding circular dependency problems
return row ? new BBlob(row) : null;
}
/** @returns {BOption|null} */
@@ -141,8 +213,10 @@ class Becca {
return null;
}
if (entityName === 'note_revisions') {
return this.getNoteRevision(entityId);
if (entityName === 'revisions') {
return this.getRevision(entityId);
} else if (entityName === 'attachments') {
return this.getAttachment(entityId);
}
const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g,
@@ -167,12 +241,12 @@ class Becca {
return rows.map(row => new BRecentNote(row));
}
/** @returns {BNoteRevision[]} */
getNoteRevisionsFromQuery(query, params = []) {
/** @returns {BRevision[]} */
getRevisionsFromQuery(query, params = []) {
const rows = sql.getRows(query, params);
const BNoteRevision = require("./entities/bnote_revision"); // avoiding circular dependency problems
return rows.map(row => new BNoteRevision(row));
const BRevision = require("./entities/brevision"); // avoiding circular dependency problems
return rows.map(row => new BRevision(row));
}
/** Should be called when the set of all non-skeleton notes changes (added/removed) */
+36 -22
View File
@@ -27,10 +27,10 @@ function load() {
const start = Date.now();
becca.reset();
// using raw query and passing arrays to avoid allocating new objects
// this is worth it for becca load since it happens every run and blocks the app until finished
// using a raw query and passing arrays to avoid allocating new objects,
// this is worth it for the becca load since it happens every run and blocks the app until finished
for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`)) {
for (const row of sql.getRawRows(`SELECT noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes WHERE isDeleted = 0`)) {
new BNote().update(row).init();
}
@@ -69,18 +69,6 @@ function reload() {
require('../services/ws').reloadFrontend();
}
function postProcessEntityUpdate(entityName, entity) {
if (entityName === 'notes') {
noteUpdated(entity);
} else if (entityName === 'branches') {
branchUpdated(entity);
} else if (entityName === 'attributes') {
attributeUpdated(entity);
} else if (entityName === 'note_reordering') {
noteReorderingUpdated(entity);
}
}
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
if (!becca.loaded) {
return;
@@ -112,6 +100,25 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en
postProcessEntityUpdate(entityName, entity);
});
/**
* This gets run on entity being created or updated.
*
* @param entityName
* @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync).
* It should be therefore treated as a row.
*/
function postProcessEntityUpdate(entityName, entityRow) {
if (entityName === 'notes') {
noteUpdated(entityRow);
} else if (entityName === 'branches') {
branchUpdated(entityRow);
} else if (entityName === 'attributes') {
attributeUpdated(entityRow);
} else if (entityName === 'note_reordering') {
noteReorderingUpdated(entityRow);
}
}
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
if (!becca.loaded) {
return;
@@ -149,6 +156,7 @@ function branchDeleted(branchId) {
.filter(parentBranch => parentBranch.branchId !== branch.branchId);
if (childNote.parents.length > 0) {
// subtree notes might lose some inherited attributes
childNote.invalidateSubTree();
}
}
@@ -163,8 +171,8 @@ function branchDeleted(branchId) {
delete becca.branches[branch.branchId];
}
function noteUpdated(entity) {
const note = becca.notes[entity.noteId];
function noteUpdated(entityRow) {
const note = becca.notes[entityRow.noteId];
if (note) {
// type / mime could have been changed, and they are present in flatTextCache
@@ -172,15 +180,19 @@ function noteUpdated(entity) {
}
}
function branchUpdated(branch) {
const childNote = becca.notes[branch.noteId];
function branchUpdated(branchRow) {
const childNote = becca.notes[branchRow.noteId];
if (childNote) {
childNote.flatTextCache = null;
childNote.sortParents();
// notes in the subtree can get new inherited attributes
// this is in theory needed upon branch creation, but there's no "create" event for sync changes
childNote.invalidateSubTree();
}
const parentNote = becca.notes[branch.parentNoteId];
const parentNote = becca.notes[branchRow.parentNoteId];
if (parentNote) {
parentNote.sortChildren();
@@ -222,8 +234,10 @@ function attributeDeleted(attributeId) {
}
}
function attributeUpdated(attribute) {
const note = becca.notes[attribute.noteId];
/** @param {BAttribute} attributeRow */
function attributeUpdated(attributeRow) {
const attribute = becca.attributes[attributeRow.attributeId];
const note = becca.notes[attributeRow.noteId];
if (note) {
if (attribute.isAffectingSubtree || note.isInherited()) {
+1 -1
View File
@@ -54,7 +54,7 @@ function getNoteTitleArrayForPath(notePathArray) {
let parentNoteId = 'root';
let hoistedNotePassed = false;
// this is a notePath from outside of hoisted subtree so full title path needs to be returned
// this is a notePath from outside of hoisted subtree, so the full title path needs to be returned
const hoistedNoteId = cls.getHoistedNoteId();
const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId);
+136 -1
View File
@@ -7,6 +7,8 @@ const eventService = require("../../services/events");
const dateUtils = require("../../services/date_utils");
const cls = require("../../services/cls");
const log = require("../../services/log");
const protectedSessionService = require("../../services/protected_session");
const blobService = require("../../services/blob");
let becca = null;
@@ -118,10 +120,143 @@ class AbstractBeccaEntity {
return this;
}
/** @protected */
_setContent(content, opts = {}) {
// client code asks to save entity even if blobId didn't change (something else was changed)
opts.forceSave = !!opts.forceSave;
opts.forceFrontendReload = !!opts.forceFrontendReload;
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`);
}
if (this.hasStringContent()) {
content = content.toString();
} else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content);
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.encrypt(content);
} else {
throw new Error(`Cannot update content of blob since protected session is not available.`);
}
}
sql.transactional(() => {
const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts);
const oldBlobId = this.blobId;
if (newBlobId !== oldBlobId || opts.forceSave) {
this.blobId = newBlobId;
this.save();
if (newBlobId !== oldBlobId) {
this.#deleteBlobIfNotUsed(oldBlobId);
}
}
});
}
#deleteBlobIfNotUsed(oldBlobId) {
if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
if (sql.getValue("SELECT 1 FROM attachments WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
if (sql.getValue("SELECT 1 FROM revisions WHERE blobId = ? LIMIT 1", [oldBlobId])) {
return;
}
sql.execute("DELETE FROM blobs WHERE blobId = ?", [oldBlobId]);
// blobs are not marked as erased in entity_changes, they are just purged completely
// this is because technically every keystroke can create a new blob and there would be just too many
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
}
#getUnencryptedContentForHashCalculation(unencryptedContent) {
if (this.isProtected) {
// a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content
const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_";
return Buffer.isBuffer(unencryptedContent)
? Buffer.concat([Buffer.from(encryptedPrefixSuffix), unencryptedContent])
: `${encryptedPrefixSuffix}${unencryptedContent}`;
} else {
return unencryptedContent;
}
}
#saveBlob(content, unencryptedContentForHashCalculation, opts = {}) {
/*
* We're using the unencrypted blob for the hash calculation, because otherwise the random IV would
* cause every content blob to be unique which would balloon the database size (esp. with revisioning).
* This has minor security implications (it's easy to infer that given content is shared between different
* notes/attachments), but the trade-off comes out clearly positive.
*/
const newBlobId = utils.hashedBlobId(unencryptedContentForHashCalculation);
const blobNeedsInsert = !sql.getValue('SELECT 1 FROM blobs WHERE blobId = ?', [newBlobId]);
if (!blobNeedsInsert) {
return newBlobId;
}
const pojo = {
blobId: newBlobId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
sql.upsert("blobs", "blobId", pojo);
// we can't reuse blobId as an entity_changes hash, because this one has to be calculatable without having
// access to the decrypted content
const hash = blobService.calculateContentHash(pojo);
entityChangesService.addEntityChange({
entityName: 'blobs',
entityId: newBlobId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true,
// overriding componentId will cause frontend to think the change is coming from a different component
// and thus reload
componentId: opts.forceFrontendReload ? utils.randomString(10) : null
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'blobs',
entity: this
});
return newBlobId;
}
/**
* @protected
* @returns {string|Buffer}
*/
_getContent() {
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
if (!row) {
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
}
return blobService.processContent(row.content, this.isProtected, this.hasStringContent());
}
/**
* Mark the entity as (soft) deleted. It will be completely erased later.
*
* This is a low level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
* This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
*
* @param [deleteId=null]
*/
+233
View File
@@ -0,0 +1,233 @@
"use strict";
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const sql = require("../../services/sql");
const protectedSessionService = require("../../services/protected_session");
const log = require("../../services/log");
const attachmentRoleToNoteTypeMapping = {
'image': 'image'
};
/**
* Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
* larger amounts of data and generally not accessible to the user.
*
* @extends AbstractBeccaEntity
*/
class BAttachment extends AbstractBeccaEntity {
static get entityName() { return "attachments"; }
static get primaryKeyName() { return "attachmentId"; }
static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId",
"utcDateScheduledForErasureSince", "utcDateModified"]; }
constructor(row) {
super();
if (!row.ownerId?.trim()) {
throw new Error("'ownerId' must be given to initialize a Attachment entity");
} else if (!row.role?.trim()) {
throw new Error("'role' must be given to initialize a Attachment entity");
} else if (!row.mime?.trim()) {
throw new Error("'mime' must be given to initialize a Attachment entity");
} else if (!row.title?.trim()) {
throw new Error("'title' must be given to initialize a Attachment entity");
}
/** @type {string} */
this.attachmentId = row.attachmentId;
/** @type {string} either noteId or revisionId to which this attachment belongs */
this.ownerId = row.ownerId;
/** @type {string} */
this.role = row.role;
/** @type {string} */
this.mime = row.mime;
/** @type {string} */
this.title = row.title;
/** @type {int} */
this.position = row.position;
/** @type {string} */
this.blobId = row.blobId;
/** @type {boolean} */
this.isProtected = !!row.isProtected;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {string} */
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
/** @type {int} optionally added to the entity */
this.contentLength = row.contentLength;
this.decrypt();
}
/** @returns {BAttachment} */
copy() {
return new BAttachment({
ownerId: this.ownerId,
role: this.role,
mime: this.mime,
title: this.title,
blobId: this.blobId,
isProtected: this.isProtected
});
}
/** @returns {BNote} */
getNote() {
return this.becca.notes[this.ownerId];
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
return utils.isStringNote(this.type, this.mime);
}
isContentAvailable() {
return !this.attachmentId // new attachment which was not encrypted yet
|| !this.isProtected
|| protectedSessionService.isProtectedSessionAvailable()
}
getTitleOrProtected() {
return this.isContentAvailable() ? this.title : '[protected]';
}
decrypt() {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
try {
this.title = protectedSessionService.decryptString(this.title);
this.isDecrypted = true;
}
catch (e) {
log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`);
}
}
}
/** @returns {string|Buffer} */
getContent() {
return this._getContent();
}
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BAttachment entity
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
*/
setContent(content, opts) {
this._setContent(content, opts);
}
/** @returns {{note: BNote, branch: BBranch}} */
convertToNote() {
if (this.type === 'search') {
throw new Error(`Note of type search cannot have child notes`);
}
if (!this.getNote()) {
throw new Error("Cannot find note of this attachment. It is possible that this is note revision's attachment. " +
"Converting note revision's attachments to note is not (yet) supported.");
}
if (!(this.role in attachmentRoleToNoteTypeMapping)) {
throw new Error(`Mapping from attachment role '${this.role}' to note's type is not defined`);
}
if (!this.isContentAvailable()) { // isProtected is the same for attachment
throw new Error(`Cannot convert protected attachment outside of protected session`);
}
const noteService = require('../../services/notes');
const { note, branch } = noteService.createNewNote({
parentNoteId: this.ownerId,
title: this.title,
type: attachmentRoleToNoteTypeMapping[this.role],
mime: this.mime,
content: this.getContent(),
isProtected: this.isProtected
});
this.markAsDeleted();
const parentNote = this.getNote();
if (this.role === 'image' && parentNote.type === 'text') {
const origContent = parentNote.getContent();
const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`;
const newNoteUrl = `api/images/${note.noteId}/`;
const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl);
if (fixedContent !== origContent) {
parentNote.setContent(fixedContent);
}
noteService.asyncPostProcessContent(note, fixedContent);
}
return { note, branch };
}
getFileName() {
const type = this.role === 'image' ? 'image' : 'file';
return utils.formatDownloadTitle(this.title, type, this.mime);
}
beforeSaving() {
super.beforeSaving();
if (this.position === undefined || this.position === null) {
this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0)
FROM attachments
WHERE ownerId = ?`, [this.noteId]);
}
this.dateModified = dateUtils.localNowDateTime();
this.utcDateModified = dateUtils.utcNowDateTime();
}
getPojo() {
return {
attachmentId: this.attachmentId,
ownerId: this.ownerId,
role: this.role,
mime: this.mime,
title: this.title,
position: this.position,
blobId: this.blobId,
isProtected: !!this.isProtected,
isDeleted: false,
dateModified: this.dateModified,
utcDateModified: this.utcDateModified,
utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince,
contentLength: this.contentLength
};
}
getPojoToSave() {
const pojo = this.getPojo();
delete pojo.contentLength;
if (pojo.isProtected) {
if (this.isDecrypted) {
pojo.title = protectedSessionService.encrypt(pojo.title);
}
else {
// updating protected note outside of protected session means we will keep original ciphertexts
delete pojo.title;
}
}
return pojo;
}
}
module.exports = BAttachment;
+6 -4
View File
@@ -141,7 +141,7 @@ class BAttribute extends AbstractBeccaEntity {
*/
getTargetNote() {
if (this.type !== 'relation') {
throw new Error(`Attribute ${this.attributeId} is not relation`);
throw new Error(`Attribute '${this.attributeId}' is not a relation.`);
}
if (!this.value) {
@@ -188,9 +188,11 @@ class BAttribute extends AbstractBeccaEntity {
this.value = "";
}
if (this.position === undefined) {
// TODO: can be calculated from becca
this.position = 1 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
if (this.position === undefined || this.position === null) {
const maxExistingPosition = this.getNote().getAttributes()
.reduce((maxPosition, attr) => Math.max(maxPosition, attr.position || 0), 0);
this.position = maxExistingPosition + 10;
}
if (!this.isInheritable) {
+30
View File
@@ -0,0 +1,30 @@
class BBlob {
static get entityName() { return "blobs"; }
static get primaryKeyName() { return "blobId"; }
static get hashedProperties() { return ["blobId", "content"]; }
constructor(row) {
/** @type {string} */
this.blobId = row.blobId;
/** @type {string|Buffer} */
this.content = row.content;
/** @type {int} */
this.contentLength = row.contentLength;
/** @type {string} */
this.dateModified = row.dateModified;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}
getPojo() {
return {
blobId: this.blobId,
content: this.content,
contentLength: this.contentLength,
dateModified: this.dateModified,
utcDateModified: this.utcDateModified
};
}
}
module.exports = BBlob;
+8 -3
View File
@@ -103,6 +103,7 @@ class BBranch extends AbstractBeccaEntity {
return this.becca.notes[this.noteId];
}
/** @returns {BNote} */
getNote() {
return this.childNote;
}
@@ -186,11 +187,11 @@ class BBranch extends AbstractBeccaEntity {
// first delete children and then parent - this will show up better in recent changes
log.info(`Deleting note ${note.noteId}`);
log.info(`Deleting note '${note.noteId}'`);
this.becca.notes[note.noteId].isBeingDeleted = true;
for (const attribute of note.getOwnedAttributes()) {
for (const attribute of note.getOwnedAttributes().slice()) {
attribute.markAsDeleted(deleteId);
}
@@ -198,6 +199,10 @@ class BBranch extends AbstractBeccaEntity {
relation.markAsDeleted(deleteId);
}
for (const attachment of note.getAttachments()) {
attachment.markAsDeleted(deleteId);
}
note.markAsDeleted(deleteId);
return true;
@@ -219,7 +224,7 @@ class BBranch extends AbstractBeccaEntity {
for (const childBranch of this.parentNote.getChildBranches()) {
if (maxNotePos < childBranch.notePosition
&& childBranch.noteId !== '_hidden' // hidden has very large notePosition to always stay last
&& childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
) {
maxNotePos = childBranch.notePosition;
}
+305 -205
View File
@@ -5,9 +5,9 @@ const log = require('../../services/log');
const sql = require('../../services/sql');
const utils = require('../../services/utils');
const dateUtils = require('../../services/date_utils');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const BNoteRevision = require("./bnote_revision");
const BRevision = require("./brevision");
const BAttachment = require("./battachment");
const TaskContext = require("../../services/task_context");
const dayjs = require("dayjs");
const utc = require('dayjs/plugin/utc');
@@ -18,14 +18,14 @@ const LABEL = 'label';
const RELATION = 'relation';
/**
* Trilium's main entity which can represent text note, image, code note, file attachment etc.
* Trilium's main entity, which can represent text note, image, code note, file attachment etc.
*
* @extends AbstractBeccaEntity
*/
class BNote extends AbstractBeccaEntity {
static get entityName() { return "notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime"]; }
static get hashedProperties() { return ["noteId", "title", "isProtected", "type", "mime", "blobId"]; }
constructor(row) {
super();
@@ -45,6 +45,7 @@ class BNote extends AbstractBeccaEntity {
row.type,
row.mime,
row.isProtected,
row.blobId,
row.dateCreated,
row.dateModified,
row.utcDateCreated,
@@ -52,19 +53,21 @@ class BNote extends AbstractBeccaEntity {
]);
}
update([noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
update([noteId, title, type, mime, isProtected, blobId, dateCreated, dateModified, utcDateCreated, utcDateModified]) {
// ------ Database persisted attributes ------
/** @type {string} */
this.noteId = noteId;
/** @type {string} */
this.title = title;
/** @type {boolean} */
this.isProtected = !!isProtected;
/** @type {string} */
this.type = type;
/** @type {string} */
this.mime = mime;
/** @type {boolean} */
this.isProtected = !!isProtected;
/** @type {string} */
this.blobId = blobId;
/** @type {string} */
this.dateCreated = dateCreated || dateUtils.localNowDateTime();
/** @type {string} */
@@ -84,7 +87,7 @@ class BNote extends AbstractBeccaEntity {
this.decrypt();
/** @type {string|null} */
this.flatTextCache = null;
this.__flatTextCache = null;
return this;
}
@@ -108,7 +111,7 @@ class BNote extends AbstractBeccaEntity {
this.__attributeCache = null;
/** @type {BAttribute[]|null}
* @private */
this.inheritableAttributeCache = null;
this.__inheritableAttributeCache = null;
/** @type {BAttribute[]}
* @private */
@@ -118,9 +121,9 @@ class BNote extends AbstractBeccaEntity {
/** @type {BNote[]|null}
* @private */
this.ancestorCache = null;
this.__ancestorCache = null;
// following attributes are filled during searching from database
// following attributes are filled during searching in the database
/**
* size of the content in bytes
@@ -203,49 +206,41 @@ class BNote extends AbstractBeccaEntity {
* - but to the user note content and title changes are one and the same - single dateModified (so all changes must go through Note and content is not a separate entity)
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const row = sql.getRow(`SELECT content FROM note_contents WHERE noteId = ?`, [this.noteId]);
if (!row) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note content for noteId=${this.noteId}`);
}
}
let content = row.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = content === null ? null : protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
/** @returns {string|Buffer} */
getContent() {
return this._getContent();
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
/** @returns {{dateModified, utcDateModified}} */
getContentMetadata() {
return sql.getRow(`
SELECT
LENGTH(content) AS contentLength,
dateModified,
utcDateModified
FROM note_contents
WHERE noteId = ?`, [this.noteId]);
return sql.getRow(`SELECT dateModified, utcDateModified FROM blobs WHERE blobId = ?`, [this.blobId]);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BNote entity
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
*/
setContent(content, opts) {
this._setContent(content, opts);
eventService.emit(eventService.NOTE_CONTENT_CHANGE, { entity: this });
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
get dateCreatedObj() {
@@ -264,68 +259,6 @@ class BNote extends AbstractBeccaEntity {
return this.utcDateModified === null ? null : dayjs.utc(this.utcDateModified);
}
/** @returns {*} */
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
setContent(content, ignoreMissingProtectedSession = false) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note '${this.noteId}'`);
}
if (this.isStringNote()) {
content = content.toString();
}
else {
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
}
const pojo = {
noteId: this.noteId,
content: content,
dateModified: dateUtils.localNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime()
};
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.content = protectedSessionService.encrypt(pojo.content);
}
else if (!ignoreMissingProtectedSession) {
throw new Error(`Cannot update content of noteId '${this.noteId}' since we're out of protected session.`);
}
}
sql.upsert("note_contents", "noteId", pojo);
const hash = utils.hash(`${this.noteId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'note_contents',
entityId: this.noteId,
hash: hash,
isErased: false,
utcDateChanged: pojo.utcDateModified,
isSynced: true
});
eventService.emit(eventService.ENTITY_CHANGED, {
entityName: 'note_contents',
entity: this
});
}
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
isRoot() {
return this.noteId === 'root';
@@ -356,8 +289,13 @@ class BNote extends AbstractBeccaEntity {
|| (this.type === 'file' && this.mime?.startsWith('image/'));
}
/** @returns {boolean} true if the note has string content (not binary) */
/** @deprecated use hasStringContent() instead */
isStringNote() {
return this.hasStringContent();
}
/** @returns {boolean} true if the note has string content (not binary) */
hasStringContent() {
return utils.isStringNote(this.type, this.mime);
}
@@ -379,6 +317,9 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Beware that the method must not create a copy of the array, but actually returns its internal array
* (for performance reasons)
*
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {BAttribute[]} all note's attributes, including inherited ones
@@ -397,7 +338,6 @@ class BNote extends AbstractBeccaEntity {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
// a bit unsafe to return the original array, but defensive copy would be costly
return this.__attributeCache;
}
}
@@ -454,11 +394,11 @@ class BNote extends AbstractBeccaEntity {
}
}
this.inheritableAttributeCache = [];
this.__inheritableAttributeCache = [];
for (const attr of this.__attributeCache) {
if (attr.isInheritable) {
this.inheritableAttributeCache.push(attr);
this.__inheritableAttributeCache.push(attr);
}
}
}
@@ -475,11 +415,11 @@ class BNote extends AbstractBeccaEntity {
return [];
}
if (!this.inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.inheritableAttributeCache
if (!this.__inheritableAttributeCache) {
this.__getAttributes(path); // will refresh also this.__inheritableAttributeCache
}
return this.inheritableAttributeCache;
return this.__inheritableAttributeCache;
}
__validateTypeName(type, name) {
@@ -628,7 +568,8 @@ class BNote extends AbstractBeccaEntity {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {BAttribute} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
* @returns {BAttribute} attribute of the given type and name. If there are more such attributes, first is returned.
* Returns null if there's no such attribute belonging to this note.
*/
getAttribute(type, name) {
const attributes = this.getAttributes();
@@ -707,6 +648,9 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Beware that the method must not create a copy of the array, but actually returns its internal array
* (for performance reasons)
*
* @param {string|null} [type] - (optional) attribute type to filter
* @param {string|null} [name] - (optional) attribute name to filter
* @param {string|null} [value] - (optional) attribute value to filter
@@ -728,7 +672,7 @@ class BNote extends AbstractBeccaEntity {
return this.ownedAttributes.filter(attr => attr.name === name);
}
else {
return this.ownedAttributes.slice();
return this.ownedAttributes;
}
}
@@ -750,7 +694,7 @@ class BNote extends AbstractBeccaEntity {
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)
// - note does not have or inherit archived label, but each note path contains a note with (non-inheritable)
// archived label
const bestNotePathRecord = this.getSortedNotePathRecords()[0];
@@ -813,40 +757,40 @@ class BNote extends AbstractBeccaEntity {
* @returns {string} - returns flattened textual representation of note, prefixes and attributes
*/
getFlatText() {
if (!this.flatTextCache) {
this.flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
if (!this.__flatTextCache) {
this.__flatTextCache = `${this.noteId} ${this.type} ${this.mime} `;
for (const branch of this.parentBranches) {
if (branch.prefix) {
this.flatTextCache += `${branch.prefix} `;
this.__flatTextCache += `${branch.prefix} `;
}
}
this.flatTextCache += `${this.title} `;
this.__flatTextCache += `${this.title} `;
for (const attr of this.getAttributes()) {
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
this.flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
this.__flatTextCache += `${attr.type === 'label' ? '#' : '~'}${attr.name}`;
if (attr.value) {
this.flatTextCache += `=${attr.value}`;
this.__flatTextCache += `=${attr.value}`;
}
this.flatTextCache += ' ';
this.__flatTextCache += ' ';
}
this.flatTextCache = utils.normalize(this.flatTextCache);
this.__flatTextCache = utils.normalize(this.__flatTextCache);
}
return this.flatTextCache;
return this.__flatTextCache;
}
invalidateThisCache() {
this.flatTextCache = null;
this.__flatTextCache = null;
this.__attributeCache = null;
this.inheritableAttributeCache = null;
this.ancestorCache = null;
this.__inheritableAttributeCache = null;
this.__ancestorCache = null;
}
invalidateSubTree(path = []) {
@@ -875,24 +819,6 @@ class BNote extends AbstractBeccaEntity {
}
}
invalidateSubtreeFlatText() {
this.flatTextCache = null;
for (const childNote of this.children) {
childNote.invalidateSubtreeFlatText();
}
for (const targetRelation of this.targetRelations) {
if (targetRelation.name === 'template' || targetRelation.name === 'inherit') {
const note = targetRelation.note;
if (note) {
note.invalidateSubtreeFlatText();
}
}
}
}
getRelationDefinitions() {
return this.getLabels()
.filter(l => l.name.startsWith("relation:"));
@@ -1021,7 +947,7 @@ class BNote extends AbstractBeccaEntity {
};
}
/** @returns {String[]} - includes the subtree node as well */
/** @returns {string[]} - includes the subtree root note as well */
getSubtreeNoteIds({includeArchived = true, includeHidden = false, resolveSearch = false} = {}) {
return this.getSubtree({includeArchived, includeHidden, resolveSearch})
.notes
@@ -1083,28 +1009,33 @@ class BNote extends AbstractBeccaEntity {
/** @returns {BNote[]} */
getAncestors() {
if (!this.ancestorCache) {
if (!this.__ancestorCache) {
const noteIds = new Set();
this.ancestorCache = [];
this.__ancestorCache = [];
for (const parent of this.parents) {
if (noteIds.has(parent.noteId)) {
continue;
}
this.ancestorCache.push(parent);
this.__ancestorCache.push(parent);
noteIds.add(parent.noteId);
for (const ancestorNote of parent.getAncestors()) {
if (!noteIds.has(ancestorNote.noteId)) {
this.ancestorCache.push(ancestorNote);
this.__ancestorCache.push(ancestorNote);
noteIds.add(ancestorNote.noteId);
}
}
}
}
return this.ancestorCache;
return this.__ancestorCache;
}
/** @returns {string[]} */
getAncestorNoteIds() {
return this.getAncestors().map(note => note.noteId);
}
/** @returns {boolean} */
@@ -1122,6 +1053,7 @@ class BNote extends AbstractBeccaEntity {
return this.noteId === '_hidden' || this.hasAncestor('_hidden');
}
/** @returns {BAttribute[]} */
getTargetRelations() {
return this.targetRelations;
}
@@ -1158,10 +1090,55 @@ class BNote extends AbstractBeccaEntity {
return minDistance;
}
/** @returns {BNoteRevision[]} */
getNoteRevisions() {
return sql.getRows("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId])
.map(row => new BNoteRevision(row));
/** @returns {BRevision[]} */
getRevisions() {
return sql.getRows("SELECT * FROM revisions WHERE noteId = ?", [this.noteId])
.map(row => new BRevision(row));
}
/** @returns {BAttachment[]} */
getAttachments(opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
// from testing, it looks like calculating length does not make a difference in performance even on large-ish DB
// given that we're always fetching attachments only for a specific note, we might just do it always
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE ownerId = ? AND isDeleted = 0
ORDER BY position`
: `SELECT * FROM attachments WHERE ownerId = ? AND isDeleted = 0 ORDER BY position`;
return sql.getRows(query, [this.noteId])
.map(row => new BAttachment(row));
}
/** @returns {BAttachment|null} */
getAttachmentById(attachmentId, opts = {}) {
opts.includeContentLength = !!opts.includeContentLength;
const query = opts.includeContentLength
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
FROM attachments
JOIN blobs USING (blobId)
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
return sql.getRows(query, [this.noteId, attachmentId])
.map(row => new BAttachment(row))[0];
}
/** @returns {BAttachment[]} */
getAttachmentByRole(role) {
return sql.getRows(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
AND role = ?
AND isDeleted = 0
ORDER BY position`, [this.noteId, role])
.map(row => new BAttachment(row));
}
/**
@@ -1175,13 +1152,10 @@ class BNote extends AbstractBeccaEntity {
}
const parentNotes = this.getParentNotes();
let notePaths = [];
if (parentNotes.length === 1) { // optimization for most common case
notePaths = parentNotes[0].getAllNotePaths();
} else {
notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
}
const notePaths = parentNotes.length === 1
? parentNotes[0].getAllNotePaths() // optimization for the most common case
: parentNotes.flatMap(parentNote => parentNote.getAllNotePaths());
for (const notePath of notePaths) {
notePath.push(this.noteId);
@@ -1192,7 +1166,7 @@ class BNote extends AbstractBeccaEntity {
/**
* @param {string} [hoistedNoteId='root']
* @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]}
* @return {Array<{isArchived: boolean, isInHoistedSubTree: boolean, notePath: Array<string>, isHidden: boolean}>}
*/
getSortedNotePathRecords(hoistedNoteId = 'root') {
const isHoistedRoot = hoistedNoteId === 'root';
@@ -1220,7 +1194,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Returns note path considered to be the "best"
* Returns a note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string[]} array of noteIds constituting the particular note path
@@ -1230,7 +1204,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Returns note path considered to be the "best"
* Returns a note path considered to be the "best"
*
* @param {string} [hoistedNoteId='root']
* @return {string} serialized note path (e.g. 'root/a1h315/js725h')
@@ -1328,10 +1302,10 @@ class BNote extends AbstractBeccaEntity {
* @param {string} name - name of the attribute, not including the leading ~/#
* @param {string} [value] - value of the attribute - text for labels, target note ID for relations; optional.
* @param {boolean} [isInheritable=false]
* @param {int} [position]
* @param {int|null} [position]
* @returns {BAttribute}
*/
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
addAttribute(type, name, value = "", isInheritable = false, position = null) {
const BAttribute = require("./battribute");
return new BAttribute({
@@ -1370,7 +1344,7 @@ class BNote extends AbstractBeccaEntity {
}
/**
* Based on enabled, attribute is either set or removed.
* Based on enabled, the attribute is either set or removed.
*
* @param {string} type - attribute type ('relation', 'label' etc.)
* @param {boolean} enabled - toggle On or Off
@@ -1429,7 +1403,7 @@ class BNote extends AbstractBeccaEntity {
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
* Remove relation name-value pair, if it exists.
* Remove the relation name-value pair, if it exists.
*
* @param {string} name - relation name
* @param {string} [value] - relation value (noteId)
@@ -1458,13 +1432,89 @@ class BNote extends AbstractBeccaEntity {
return cloningService.cloneNoteToBranch(this.noteId, branch.branchId);
}
isEligibleForConversionToAttachment(opts = {autoConversion: false}) {
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
return false;
}
const targetRelations = this.getTargetRelations().filter(relation => relation.name === 'imageLink');
if (opts.autoConversion && targetRelations.length === 0) {
return false;
} else if (targetRelations.length > 1) {
return false;
}
const parentNote = this.getParentNotes()[0]; // at this point note can have only one parent
const referencingNote = targetRelations[0]?.getNote();
if (referencingNote && parentNote !== referencingNote) {
return false;
} else if (parentNote.type !== 'text' || !parentNote.isContentAvailable()) {
return false;
}
return true;
}
/**
* Some notes are eligible for conversion into an attachment of its parent, note must have these properties:
* - it has exactly one target relation
* - it has a relation from its parent note
* - it has no children
* - it has no clones
* - the parent is of type text
* - both notes are either unprotected or user is in protected session
*
* Currently, works only for image notes.
*
* In the future, this functionality might get more generic and some of the requirements relaxed.
*
* @params {Object} [opts]
* @params {bolean} [opts.autoConversion=false} if true, the action is not triggered by user, but e.g. by migration,
* and only perfect candidates will be migrated
*
* @returns {BAttachment|null} - null if note is not eligible for conversion
*/
convertToParentAttachment(opts = {autoConversion: false}) {
if (!this.isEligibleForConversionToAttachment(opts)) {
return null;
}
const content = this.getContent();
const parentNote = this.getParentNotes()[0];
const attachment = parentNote.saveAttachment({
role: 'image',
mime: this.mime,
title: this.title,
content: content
});
let parentContent = parentNote.getContent();
const oldNoteUrl = `api/images/${this.noteId}/`;
const newAttachmentUrl = `api/attachments/${attachment.attachmentId}/image/`;
const fixedContent = utils.replaceAll(parentContent, oldNoteUrl, newAttachmentUrl);
parentNote.setContent(fixedContent);
const noteService = require("../../services/notes");
noteService.asyncPostProcessContent(parentNote, fixedContent); // to mark an unused attachment for deletion
this.deleteNote();
return attachment;
}
/**
* (Soft) delete a note and all its descendants.
*
* @param {string} [deleteId] - optional delete identified
* @param {string} [deleteId=null] - optional delete identified
* @param {TaskContext} [taskContext]
*/
deleteNote(deleteId, taskContext) {
deleteNote(deleteId = null, taskContext = null) {
if (this.isDeleted) {
return;
}
@@ -1491,7 +1541,7 @@ class BNote extends AbstractBeccaEntity {
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
try {
this.title = protectedSessionService.decryptString(this.title);
this.flatTextCache = null;
this.__flatTextCache = null;
this.isDecrypted = true;
}
@@ -1510,42 +1560,91 @@ class BNote extends AbstractBeccaEntity {
}
get isDeleted() {
// isBeingDeleted is relevant only in the transition period when the deletion process has begun, but not yet
// finished (note is still in becca)
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
}
/**
* @returns {BNoteRevision|null}
* @returns {BRevision|null}
*/
saveNoteRevision() {
const content = this.getContent();
saveRevision() {
return sql.transactional(() => {
let noteContent = this.getContent();
const contentMetadata = this.getContentMetadata();
if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
return null;
const revision = new BRevision({
noteId: this.noteId,
// title and text should be decrypted now
title: this.title,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
? this.utcDateModified
: contentMetadata.utcDateModified,
utcDateCreated: dateUtils.utcNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
dateLastEdited: this.dateModified > contentMetadata.dateModified
? this.dateModified
: contentMetadata.dateModified,
dateCreated: dateUtils.localNowDateTime()
}, true);
revision.save(); // to generate revisionId, which is then used to save attachments
if (this.type === 'text') {
for (const noteAttachment of this.getAttachments()) {
if (noteAttachment.utcDateScheduledForErasureSince) {
continue;
}
const revisionAttachment = noteAttachment.copy();
revisionAttachment.ownerId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
// content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
`attachments/${revisionAttachment.attachmentId}`);
noteContent = noteContent.replaceAll(new RegExp(`href="[^"]*attachmentId=${noteAttachment.attachmentId}[^"]*"`, 'gi'),
`href="api/attachments/${revisionAttachment.attachmentId}/download"`);
}
revision.setContent(noteContent, {forceSave: true});
}
return revision;
});
}
/**
* @returns {BAttachment}
*/
saveAttachment({attachmentId, role, mime, title, content, position}) {
let attachment;
if (attachmentId) {
attachment = this.becca.getAttachmentOrThrow(attachmentId);
} else {
attachment = new BAttachment({
ownerId: this.noteId,
title,
role,
mime,
isProtected: this.isProtected,
position
});
}
const contentMetadata = this.getContentMetadata();
content = content || "";
attachment.setContent(content, {forceSave: true});
const noteRevision = new BNoteRevision({
noteId: this.noteId,
// title and text should be decrypted now
title: this.title,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
? this.utcDateModified
: contentMetadata.utcDateModified,
utcDateCreated: dateUtils.utcNowDateTime(),
utcDateModified: dateUtils.utcNowDateTime(),
dateLastEdited: this.dateModified > contentMetadata.dateModified
? this.dateModified
: contentMetadata.dateModified,
dateCreated: dateUtils.localNowDateTime()
}, true).save();
return attachment;
}
noteRevision.setContent(content);
return noteRevision;
getFileName() {
return utils.formatDownloadTitle(this.title, this.type, this.mime);
}
beforeSaving() {
@@ -1564,6 +1663,7 @@ class BNote extends AbstractBeccaEntity {
isProtected: this.isProtected,
type: this.type,
mime: this.mime,
blobId: this.blobId,
isDeleted: false,
dateCreated: this.dateCreated,
dateModified: this.dateModified,
+1 -1
View File
@@ -4,7 +4,7 @@ const dateUtils = require('../../services/date_utils');
const AbstractBeccaEntity = require("./abstract_becca_entity");
/**
* Option represents name-value pair, either directly configurable by the user or some system property.
* Option represents a name-value pair, either directly configurable by the user or some system property.
*
* @extends AbstractBeccaEntity
*/
@@ -2,28 +2,29 @@
const protectedSessionService = require('../../services/protected_session');
const utils = require('../../services/utils');
const sql = require('../../services/sql');
const dateUtils = require('../../services/date_utils');
const becca = require('../becca');
const entityChangesService = require('../../services/entity_changes');
const AbstractBeccaEntity = require("./abstract_becca_entity");
const sql = require("../../services/sql");
const BAttachment = require("./battachment");
/**
* NoteRevision represents snapshot of note's title and content at some point in the past.
* Revision represents a snapshot of note's title and content at some point in the past.
* It's used for seamless note versioning.
*
* @extends AbstractBeccaEntity
*/
class BNoteRevision extends AbstractBeccaEntity {
static get entityName() { return "note_revisions"; }
static get primaryKeyName() { return "noteRevisionId"; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated", "utcDateLastEdited", "utcDateCreated", "utcDateModified"]; }
class BRevision extends AbstractBeccaEntity {
static get entityName() { return "revisions"; }
static get primaryKeyName() { return "revisionId"; }
static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated",
"utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; }
constructor(row, titleDecrypted = false) {
super();
/** @type {string} */
this.noteRevisionId = row.noteRevisionId;
this.revisionId = row.revisionId;
/** @type {string} */
this.noteId = row.noteId;
/** @type {string} */
@@ -35,6 +36,8 @@ class BNoteRevision extends AbstractBeccaEntity {
/** @type {string} */
this.title = row.title;
/** @type {string} */
this.blobId = row.blobId;
/** @type {string} */
this.dateLastEdited = row.dateLastEdited;
/** @type {string} */
this.dateCreated = row.dateCreated;
@@ -44,7 +47,7 @@ class BNoteRevision extends AbstractBeccaEntity {
this.utcDateCreated = row.utcDateCreated;
/** @type {string} */
this.utcDateModified = row.utcDateModified;
/** @type {number} */
/** @type {int} */
this.contentLength = row.contentLength;
if (this.isProtected && !titleDecrypted) {
@@ -59,92 +62,47 @@ class BNoteRevision extends AbstractBeccaEntity {
}
/** @returns {boolean} true if the note has string content (not binary) */
isStringNote() {
hasStringContent() {
return utils.isStringNote(this.type, this.mime);
}
isContentAvailable() {
return !this.revisionId // new note which was not encrypted yet
|| !this.isProtected
|| protectedSessionService.isProtectedSessionAvailable()
}
/*
* Note revision content has quite special handling - it's not a separate entity, but a lazily loaded
* part of NoteRevision entity with its own sync. Reason behind this hybrid design is that
* part of Revision entity with its own sync. The reason behind this hybrid design is that
* content can be quite large, and it's not necessary to load it / fill memory for any note access even
* if we don't need a content, especially for bulk operations like search.
*
* This is the same approach as is used for Note's content.
*/
/** @returns {*} */
getContent(silentNotFoundError = false) {
const res = sql.getRow(`SELECT content FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]);
if (!res) {
if (silentNotFoundError) {
return undefined;
}
else {
throw new Error(`Cannot find note revision content for noteRevisionId=${this.noteRevisionId}`);
}
}
let content = res.content;
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
content = protectedSessionService.decrypt(content);
}
else {
content = "";
}
}
if (this.isStringNote()) {
return content === null
? ""
: content.toString("UTF-8");
}
else {
return content;
}
/** @returns {string|Buffer} */
getContent() {
return this._getContent();
}
setContent(content) {
const pojo = {
noteRevisionId: this.noteRevisionId,
content: content,
utcDateModified: dateUtils.utcNowDateTime()
};
if (this.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
pojo.content = protectedSessionService.encrypt(pojo.content);
}
else {
throw new Error(`Cannot update content of noteRevisionId=${this.noteRevisionId} since we're out of protected session.`);
}
}
sql.upsert("note_revision_contents", "noteRevisionId", pojo);
const hash = utils.hash(`${this.noteRevisionId}|${pojo.content.toString()}`);
entityChangesService.addEntityChange({
entityName: 'note_revision_contents',
entityId: this.noteRevisionId,
hash: hash,
isErased: false,
utcDateChanged: this.getUtcDateChanged(),
isSynced: true
});
/**
* @param content
* @param {object} [opts]
* @param {object} [opts.forceSave=false] - will also save this BRevision entity
*/
setContent(content, opts) {
this._setContent(content, opts);
}
/** @returns {{contentLength, dateModified, utcDateModified}} */
getContentMetadata() {
return sql.getRow(`
SELECT
LENGTH(content) AS contentLength,
dateModified,
utcDateModified
FROM note_revision_contents
WHERE noteRevisionId = ?`, [this.noteRevisionId]);
/** @returns {BAttachment[]} */
getAttachments() {
return sql.getRows(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
AND isDeleted = 0`, [this.revisionId])
.map(row => new BAttachment(row));
}
beforeSaving() {
@@ -155,12 +113,13 @@ class BNoteRevision extends AbstractBeccaEntity {
getPojo() {
return {
noteRevisionId: this.noteRevisionId,
revisionId: this.revisionId,
noteId: this.noteId,
type: this.type,
mime: this.mime,
isProtected: this.isProtected,
title: this.title,
blobId: this.blobId,
dateLastEdited: this.dateLastEdited,
dateCreated: this.dateCreated,
utcDateLastEdited: this.utcDateLastEdited,
@@ -190,4 +149,4 @@ class BNoteRevision extends AbstractBeccaEntity {
}
}
module.exports = BNoteRevision;
module.exports = BRevision;
+13 -11
View File
@@ -1,26 +1,28 @@
const BNote = require('./entities/bnote');
const BNoteRevision = require('./entities/bnote_revision');
const BBranch = require('./entities/bbranch');
const BAttachment = require("./entities/battachment");
const BAttribute = require('./entities/battribute');
const BRecentNote = require('./entities/brecent_note');
const BBlob = require("./entities/bblob");
const BBranch = require('./entities/bbranch');
const BEtapiToken = require('./entities/betapi_token');
const BNote = require('./entities/bnote');
const BOption = require('./entities/boption');
const BRecentNote = require('./entities/brecent_note');
const BRevision = require('./entities/brevision');
const ENTITY_NAME_TO_ENTITY = {
"attachments": BAttachment,
"attributes": BAttribute,
"blobs": BBlob,
"branches": BBranch,
"notes": BNote,
"note_contents": BNote,
"note_revisions": BNoteRevision,
"note_revision_contents": BNoteRevision,
"recent_notes": BRecentNote,
"etapi_tokens": BEtapiToken,
"options": BOption
"notes": BNote,
"options": BOption,
"recent_notes": BRecentNote,
"revisions": BRevision
};
function getEntityFromEntityName(entityName) {
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
throw new Error(`Entity for table ${entityName} not found!`);
throw new Error(`Entity for table '${entityName}' not found!`);
}
return ENTITY_NAME_TO_ENTITY[entityName];
+8 -8
View File
@@ -24,7 +24,7 @@ const IGNORED_ATTR_NAMES = [
"keyboardshortcut",
"noteinfowidgetdisabled",
"linkmapwidgetdisabled",
"noterevisionswidgetdisabled",
"revisionswidgetdisabled",
"whatlinksherewidgetdisabled",
"similarnoteswidgetdisabled",
"disableinclusion",
@@ -132,9 +132,9 @@ function buildRewardMap(note) {
}
}
// title is the top with weight 1 so smaller headings will have lower weight
// the title is the top with weight 1 so smaller headings will have lower weight
// technically H1 is not supported but for the case it's present let's weigh it just as H2
// technically H1 is not supported, but for the case it's present let's weigh it just as H2
addHeadingsToRewardMap("h1", 0.9);
addHeadingsToRewardMap("h2", 0.9);
addHeadingsToRewardMap("h3", 0.8);
@@ -260,7 +260,7 @@ async function findSimilarNotes(noteId) {
let counter = 0;
// when the title is very long then weight of each individual word should be lowered
// when the title is very long, then weight of each individual word should be lowered,
// also pretty important in e.g. long URLs in label values
const lengthPenalization = 1 / Math.pow(text.length, 0.3);
@@ -364,10 +364,10 @@ async function findSimilarNotes(noteId) {
}
/**
* We want to improve standing of notes which have been created in similar time to each other since
* We want to improve the standing of notes which have been created in similar time to each other since
* there's a good chance they are related.
*
* But there's an exception - if they were created really close to each other (withing few seconds) then
* But there's an exception - if they were created really close to each other (within few seconds) then
* they are probably part of the import and not created by hand - these OTOH should not benefit.
*/
const {utcDateCreated} = candidateNote;
@@ -386,7 +386,7 @@ async function findSimilarNotes(noteId) {
console.log("Adding reward for same day of creation");
}
// smaller bonus when outside of the window but within same date
// smaller bonus when outside of the window but within the same date
score += 0.5;
}
}
@@ -447,7 +447,7 @@ async function findSimilarNotes(noteId) {
}
/**
* Point of this is to break up long-running sync process to avoid blocking
* The point of this is to break up the long-running sync process to avoid blocking
* see https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/
*/
function setImmediatePromise() {
+103
View File
@@ -0,0 +1,103 @@
const becca = require("../becca/becca");
const eu = require("./etapi_utils");
const mappers = require("./mappers");
const v = require("./validators");
const utils = require("../services/utils");
function register(router) {
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = {
'ownerId': [v.notNull, v.isNoteId],
'role': [v.notNull, v.isString],
'mime': [v.notNull, v.isString],
'title': [v.notNull, v.isString],
'position': [v.notNull, v.isInteger],
'content': [v.isString],
};
eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => {
const params = {};
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
try {
const note = becca.getNoteOrThrow(params.ownerId);
const attachment = note.saveAttachment(params);
res.status(201).json(mappers.mapAttachmentToPojo(attachment));
}
catch (e) {
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
}
});
eu.route(router, 'get', '/etapi/attachments/:attachmentId', (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
res.json(mappers.mapAttachmentToPojo(attachment));
});
const ALLOWED_PROPERTIES_FOR_PATCH = {
'role': [v.notNull, v.isString],
'mime': [v.notNull, v.isString],
'title': [v.notNull, v.isString],
'position': [v.notNull, v.isInteger],
};
eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`);
}
eu.validateAndPatch(attachment, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
attachment.save();
res.json(mappers.mapAttachmentToPojo(attachment));
});
eu.route(router, 'get', '/etapi/attachments/:attachmentId/content', (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and content cannot be read through ETAPI.`);
}
const filename = utils.formatDownloadTitle(attachment.title, attachment.role, attachment.mime);
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader('Content-Type', attachment.mime);
res.send(attachment.getContent());
});
eu.route(router, 'put', '/etapi/attachments/:attachmentId/content', (req, res, next) => {
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
if (attachment.isProtected) {
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`);
}
attachment.setContent(req.body);
return res.sendStatus(204);
});
eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
const attachment = becca.getAttachment(req.params.attachmentId);
if (!attachment) {
return res.sendStatus(204);
}
attachment.markAsDeleted();
res.sendStatus(204);
});
}
module.exports = {
register
};
+11 -5
View File
@@ -40,19 +40,25 @@ function register(router) {
}
});
const ALLOWED_PROPERTIES_FOR_PATCH = {
const ALLOWED_PROPERTIES_FOR_PATCH_LABEL = {
'value': [v.notNull, v.isString],
'position': [v.notNull, v.isInteger]
};
const ALLOWED_PROPERTIES_FOR_PATCH_RELATION = {
'position': [v.notNull, v.isInteger]
};
eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
if (attribute.type === 'relation') {
if (attribute.type === 'label') {
eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH_LABEL);
} else if (attribute.type === 'relation') {
eu.getAndCheckNote(req.body.value);
}
eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
eu.validateAndPatch(attribute, req.body, ALLOWED_PROPERTIES_FOR_PATCH_RELATION);
}
attribute.save();
@@ -62,7 +68,7 @@ function register(router) {
eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
const attribute = becca.getAttribute(req.params.attributeId);
if (!attribute || attribute.isDeleted) {
if (!attribute) {
return res.sendStatus(204);
}
+2 -2
View File
@@ -1,6 +1,6 @@
const becca = require("../becca/becca");
const eu = require("./etapi_utils");
const passwordEncryptionService = require("../services/password_encryption");
const passwordEncryptionService = require("../services/encryption/password_encryption");
const etapiTokenService = require("../services/etapi_tokens");
function register(router, loginMiddleware) {
@@ -29,7 +29,7 @@ function register(router, loginMiddleware) {
if (!etapiToken) {
// shouldn't happen since this already passed auth validation
throw new Error(`Cannot find the token ${parsed.etapiTokenId}.`);
throw new Error(`Cannot find the token '${parsed.etapiTokenId}'.`);
}
etapiToken.markAsDeletedSimple();
+14
View File
@@ -0,0 +1,14 @@
const eu = require("./etapi_utils");
const backupService = require("../services/backup");
function register(router) {
eu.route(router, 'put', '/etapi/backup/:backupName', async (req, res, next) => {
await backupService.backupNow(req.params.backupName);
res.sendStatus(204);
});
}
module.exports = {
register
};
+1 -2
View File
@@ -13,7 +13,6 @@ function register(router) {
});
const ALLOWED_PROPERTIES_FOR_CREATE_BRANCH = {
'branchId': [v.mandatory, v.notNull, v.isValidEntityId],
'noteId': [v.mandatory, v.notNull, v.isNoteId],
'parentNoteId': [v.mandatory, v.notNull, v.isNoteId],
'notePosition': [v.notNull, v.isInteger],
@@ -64,7 +63,7 @@ function register(router) {
eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
const branch = becca.getBranch(req.params.branchId);
if (!branch || branch.isDeleted) {
if (!branch) {
return res.sendStatus(204);
}
+64 -14
View File
@@ -33,13 +33,7 @@ paths:
content:
application/json; charset=utf-8:
schema:
properties:
note:
$ref: '#/components/schemas/Note'
description: Created note
branch:
$ref: '#/components/schemas/Branch'
description: Created branch
$ref: '#/components/schemas/NoteWithBranch'
default:
description: unexpected error
content:
@@ -237,7 +231,7 @@ paths:
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns note content idenfied by its ID
description: Returns note content identified by its ID
operationId: getNoteContent
responses:
'200':
@@ -247,7 +241,7 @@ paths:
schema:
type: string
put:
description: Updates note content idenfied by its ID
description: Updates note content identified by its ID
operationId: putNoteContentById
requestBody:
description: html content of note
@@ -291,7 +285,30 @@ paths:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/note-revision:
/notes/{noteId}/import:
parameters:
- name: noteId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
post:
description: Imports ZIP file into a given note.
operationId: importZip
responses:
'201':
description: note created
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/NoteWithBranch'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/revision:
parameters:
- name: noteId
in: path
@@ -308,7 +325,7 @@ paths:
default: html
post:
description: Create a note revision for the given note
operationId: createNoteRevision
operationId: createRevision
responses:
'204':
description: revision has been created
@@ -374,7 +391,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
patch:
description: patch a branch identified by the branchId with changes in the body
description: patch a branch identified by the branchId with changes in the body. Only prefix and notePosition can be updated. If you want to update other properties, you need to delete the old branch and create a new one.
operationId: patchBranchById
requestBody:
required: true
@@ -456,7 +473,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
patch:
description: patch a attribute identified by the attributeId with changes in the body
description: patch a attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one.
operationId: patchAttributeById
requestBody:
required: true
@@ -700,7 +717,26 @@ paths:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/backup/{backupName}:
parameters:
- name: backupName
in: path
required: true
description: If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file
schema:
$ref: '#/components/schemas/StringId'
put:
description: Create a database backup under a given name
operationId: createBackup
responses:
'204':
description: backup has been created
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
components:
securitySchemes:
EtapiTokenAuth:
@@ -782,6 +818,9 @@ components:
isProtected:
type: boolean
readOnly: true
blobId:
type: string
description: ID of the blob object which effectively serves as a content hash
attributes:
$ref: '#/components/schemas/AttributeList'
readOnly: true
@@ -833,6 +872,13 @@ components:
utcDateModified:
$ref: '#/components/schemas/UtcDateTime'
readOnly: true
NoteWithBranch:
type: object
properties:
note:
$ref: '#/components/schemas/Note'
branch:
$ref: '#/components/schemas/Branch'
Attribute:
type: object
description: Attribute (Label, Relation) is a key-value record attached to a note.
@@ -880,6 +926,10 @@ components:
type: string
pattern: '[a-zA-Z0-9_]{4,32}'
example: evnnmvHTCgIn
StringId:
type: string
pattern: '[a-zA-Z0-9_]{1,32}'
example: my_ID
EntityIdList:
type: array
items:

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