Compare commits

...

241 Commits

Author SHA1 Message Date
azivner
3972c27e7a release 0.17.0 2018-07-09 21:22:12 +02:00
azivner
14cffbbe62 docker commands with sudo 2018-07-09 21:21:28 +02:00
azivner
599c3c04af correct sourceMappingURL which gets rid of error message, fixes #114 2018-07-08 23:23:49 +02:00
azivner
f1412b631d hide log polluting error message 2018-07-08 23:13:56 +02:00
azivner
41908050bb ctrl+u now doesn't trigger show source since it's occupied by underline 2018-07-08 21:45:05 +02:00
azivner
f07033423c ckeditor upgraded to 10.1.0 plus new plugins (table, strikethrough, underline) 2018-07-08 21:21:52 +02:00
azivner
daf96fcbf2 using zeit/pkg to package easy to use linux server edition 2018-07-06 00:05:06 +02:00
azivner
2bca94529e minor upgrades 2018-07-05 23:17:53 +02:00
azivner
b2c9a0da21 fix note sources, fixes #123 2018-07-04 20:37:23 +02:00
azivner
7a9542b4fc release 0.16.0 2018-06-16 13:34:39 -04:00
azivner
3a95c9e1bc all dialogs are now non-modal because of high cpu usage 2018-06-16 13:31:56 -04:00
azivner
3d2ef6be01 remove optionId, closes #117 2018-06-13 19:10:28 -04:00
azivner
d67246699a Introduced separate sync version (previously DB version was used to check sync compatibility), closes #120 2018-06-10 15:55:29 -04:00
azivner
14c704d6db db upgrades are now handled transparently in the background without bothering the user, closes #119 2018-06-10 15:49:22 -04:00
azivner
4c8eeb2e6f added docker build, closes #106 2018-06-10 15:06:52 -04:00
azivner
c1b245c8b1 fix unnecessary change events, closes #118 2018-06-10 11:51:13 -04:00
azivner
74202d67bb got rid of "Trilium Notes" branding - not necessary and takes valuable space 2018-06-10 10:57:45 -04:00
azivner
26066f39b1 chaged "focused mode" - now title is displayed as well and together with content takes whole window 2018-06-10 10:53:39 -04:00
azivner
b255cf190c fixes for zoom factor setting 2018-06-09 10:34:51 -04:00
azivner
bc77b143b0 darker outlines so inverted dark themes are more visible 2018-06-09 09:48:18 -04:00
azivner
9f0ff6ae7a note actions dropdown sizing 2018-06-09 09:44:40 -04:00
azivner
736704c7d6 fix show paths 2018-06-09 09:32:13 -04:00
azivner
654c116c58 use backgrounds for icon buttons so that dark and black themes look better 2018-06-09 09:28:50 -04:00
azivner
89a5cab98f added too options new tab appearance with possibility to change theme (white, black, dark) and zoom factor 2018-06-08 23:18:53 -04:00
azivner
c39d0be8cd refactoring of icon button styles 2018-06-08 22:17:00 -04:00
azivner
e75b4cd848 execute on script note is icon, closes #116 2018-06-08 21:59:40 -04:00
azivner
378e8f35e5 release 0.15.0 2018-06-07 23:09:21 -04:00
azivner
bdb5e2f13f no results also for "add link" 2018-06-07 23:08:41 -04:00
azivner
8211bed449 renamed icons according to their size, fixes #113 2018-06-07 23:02:21 -04:00
azivner
b243632483 usability improvements to autocomplete ("no results" etc.), needs refactoring 2018-06-07 20:18:46 -04:00
azivner
e4d2513451 close search button 2018-06-07 19:50:16 -04:00
azivner
385144451b renamed hideInAutocomplete label to archived 2018-06-07 19:26:28 -04:00
azivner
c8c533844e icons for render & toggle edit since text titles caused unwanted horizontal scrolling in smaller window sizes 2018-06-06 23:37:57 -04:00
azivner
0e69f0c079 fix recent notes issues 2018-06-06 22:38:36 -04:00
azivner
aee60c444f added "show results in full text" 2018-06-05 23:28:10 -04:00
azivner
e7a504c66b fixes and optimizations for search 2018-06-05 22:47:47 -04:00
azivner
45d9c7164c search refactorings 2018-06-05 19:12:52 -04:00
azivner
bd913a63a8 search note fixes 2018-06-04 23:21:45 -04:00
azivner
5a1938c078 better sizing of search pane 2018-06-04 20:22:41 -04:00
azivner
015cd68756 renaming/refactoring of search services 2018-06-04 19:48:02 -04:00
azivner
76c0e5b2b8 new UI for search, closes #108 (still needs cleanup) 2018-06-03 20:42:25 -04:00
azivner
0f8f707acd persisting zoom setting in electron, fixes #112 2018-06-02 13:02:20 -04:00
azivner
083cccea28 better protected/unprotected note indicator, fixes #110 2018-06-02 11:47:16 -04:00
azivner
31b76b23ce release 0.14.1 2018-06-02 09:39:37 -04:00
azivner
af529f82e5 fixed false sync error reporting 2018-06-02 09:39:04 -04:00
azivner
fc6669d254 initialization and schema fixes, closes #111 2018-06-01 22:26:37 -04:00
azivner
c07785be67 release 0.14.0 2018-05-31 23:23:44 -04:00
azivner
80d2457b23 moved parent list next to note title 2018-05-31 23:21:47 -04:00
azivner
5dde2752d2 add switch to manually enter/leave protected session, fixes #107 2018-05-31 20:00:39 -04:00
azivner
8bf4633cd0 fixes 2018-05-30 23:18:56 -04:00
azivner
bd66b8a1c8 fix issue with limitation of number of SQLite parameters (999) which caused problems when loading tree which was too expanded 2018-05-30 20:28:10 -04:00
azivner
be51e533fc OPML import support (issue #78) 2018-05-29 20:32:13 -04:00
azivner
f47ae12019 OPML export support (issue #78), import missing for now 2018-05-27 12:26:34 -04:00
azivner
cab54a458f unifying surrogate keys for event log and options, fixes #103 2018-05-26 23:25:09 -04:00
azivner
a30734f1bc Add history backwards/forwards buttons, fixes #94 2018-05-26 22:54:06 -04:00
azivner
7ad9f7b129 fixed layouting issues 2018-05-26 19:58:08 -04:00
azivner
40a32e6826 render notes can be edited and can contain HTML markup 2018-05-26 19:27:47 -04:00
azivner
ab0486aaf1 expose root node, fixes #101 2018-05-26 16:16:34 -04:00
azivner
874593a167 fix code editor growing 2018-05-26 15:28:36 -04:00
azivner
03bf33630e unify audit fields, fixes #102 2018-05-26 12:38:25 -04:00
azivner
933cce1b94 fix hideInAutocomplete bug 2018-05-26 10:50:13 -04:00
azivner
4a6ff573f8 fixed autocomplete issues with capitalization 2018-05-26 10:24:33 -04:00
azivner
1a737f7d19 expose add link on UI, fixes #95 2018-05-26 10:04:40 -04:00
azivner
cb69914f09 release 0.13.0-beta 2018-05-22 23:51:43 -04:00
azivner
a372cbb2df fix #105 2018-05-22 23:51:13 -04:00
azivner
0ce5caefe8 refactoring 2018-05-22 22:22:15 -04:00
azivner
94dabb81f6 fix sync of unsyncable options 2018-05-22 19:29:18 -04:00
azivner
cd45bcfd03 converted option operations to repository 2018-05-22 00:22:43 -04:00
azivner
49a53f7a45 added hash columns for faster sync check calculation 2018-05-22 00:15:54 -04:00
azivner
9fa6c0918c add index for note's type + some fixes 2018-05-21 20:12:46 -04:00
azivner
e8d089e37e ckeditor 10.0.0 2018-05-21 19:35:49 -04:00
azivner
a931ce25fa attempt to fix the hoek security warning with package upgrade 2018-05-21 16:08:34 -04:00
azivner
b507abb4f7 electron upgrade to 2.0.0 2018-05-08 16:39:01 -04:00
azivner
66e7c6de62 fix ordering 2018-04-21 12:23:35 -04:00
azivner
4ce5ea9886 autocomplete supports encrypted notes now as well 2018-04-20 00:12:01 -04:00
azivner
8c54b62f07 fix protect branch 2018-04-19 22:18:19 -04:00
azivner
85eb50ed0f autocomplete with prefixes 2018-04-19 20:59:44 -04:00
azivner
5ffd621e9d autocomplete respects hideInAutocomplete label 2018-04-19 00:13:55 -04:00
azivner
df93cb09da fix hide-toggle 2018-04-18 23:13:37 -04:00
azivner
bbf04209f0 autocomplete cache gets updated with note update 2018-04-18 23:11:30 -04:00
azivner
834bfa39c7 limit number of results to 200, other tweaks 2018-04-18 20:56:23 -04:00
azivner
52b445f70b Merge branch 'stable' 2018-04-18 20:22:16 -04:00
azivner
7b9b4fbb0c backend autocomplete WIP 2018-04-18 00:26:42 -04:00
azivner
5af0ba1fcb layout fixes 2018-04-17 20:04:27 -04:00
azivner
85a9748291 fix for clones & optimizations 2018-04-16 23:34:56 -04:00
azivner
b4005a7ffe optimizations to the lazy loading - expanding tree now takes only one request 2018-04-16 23:13:33 -04:00
azivner
82de1c88d4 basic lazy loading of tree now works, still WIP 2018-04-16 20:40:18 -04:00
azivner
1687ed7e0b load only expanded tree with the rest being lazy loaded, WIP 2018-04-16 16:26:47 -04:00
azivner
c8b9c7d936 release 0.12.0 2018-04-14 08:28:50 -04:00
azivner
d57057ba28 fix note ordering sync 2018-04-14 08:23:06 -04:00
azivner
66cee8daa4 restructuring CSS grid/flex which fixes jumpy scrolling in tree 2018-04-13 19:58:33 -04:00
azivner
afd7df0942 fix collapse tree button 2018-04-13 19:22:12 -04:00
azivner
bd6ae33d32 fancytree upgrade to 2.28.1 2018-04-12 20:42:12 -04:00
azivner
70660a0d68 Merge branch 'stable' 2018-04-12 20:04:01 -04:00
azivner
cdad18551a upgrade CKEditor to 1.0 beta.2, fixes #93 2018-04-12 20:03:23 -04:00
azivner
592c51d1a5 fix note reordering sync again 2018-04-12 18:31:29 -04:00
azivner
6a57b8a7e7 fix ordering sync 2018-04-12 18:13:48 -04:00
azivner
7a94e21c54 tabindex 2 for text and code editor so that tabbing from title leads to editor focus 2018-04-11 22:44:33 -04:00
azivner
5b43f321e2 release 0.11.1 2018-04-11 00:10:33 -04:00
azivner
a4eafb934f non null note title and content in the DB schema, allow saving non-valid JSON notes, children overview style changes 2018-04-11 00:10:11 -04:00
azivner
7b59a665dd hideChildrenOverview label which can disable children overview for specific notes 2018-04-10 23:15:41 -04:00
azivner
3d15450ffc children overview styling 2018-04-10 21:08:00 -04:00
azivner
b0c6d52461 can't rollback transaction multiple times 2018-04-10 20:28:02 -04:00
azivner
2dc16dd29f release 0.11.0-beta 2018-04-09 22:38:37 -04:00
azivner
d8924c536b Merge branch 'master' into stable 2018-04-09 22:30:50 -04:00
azivner
3ebbf2cc46 fix generating build.js 2018-04-09 22:30:11 -04:00
azivner
f4079604c9 basic implementation of children overview, closes #80 2018-04-08 22:38:52 -04:00
azivner
1f96a6beab export & import work correctly with clones 2018-04-08 13:14:30 -04:00
azivner
b277a250e5 protected notes are not in autocomplete when not in protected session, fixes #46 2018-04-08 12:27:10 -04:00
azivner
5b0e1a644d codemirror now doesn't hijack alt-left/right, fixes #86 2018-04-08 12:17:42 -04:00
azivner
6bb3cfa9a3 note revisions for code is now properly formatted, fixes #97 2018-04-08 12:13:52 -04:00
azivner
9720868f5a added type and mime to note revisions 2018-04-08 11:57:14 -04:00
azivner
8d8ee2a87a small sync refactorings 2018-04-08 10:09:33 -04:00
azivner
542e82ee5d upgraded uncompressed jquery 2018-04-08 09:40:28 -04:00
azivner
0104b19502 naming standards 2018-04-08 09:25:35 -04:00
azivner
120888b53e fix JSON saving bug 2018-04-08 08:31:19 -04:00
azivner
d2e2caed62 refactoring of note saving code & API 2018-04-08 08:21:49 -04:00
azivner
63066802a8 fix showMessage, showError
(cherry picked from commit 6128bb4)
2018-04-08 07:49:21 -04:00
azivner
6128bb4ff3 fix showMessage, showError 2018-04-08 07:48:47 -04:00
azivner
982796255d sync content check refactoring 2018-04-07 22:59:47 -04:00
azivner
36b15f474d sync cleanup 2018-04-07 22:32:46 -04:00
azivner
13f71f8967 bulk push sync 2018-04-07 22:25:28 -04:00
azivner
64336ffbee implemented bulk sync pull for increased performance 2018-04-07 21:53:42 -04:00
azivner
b09463d1b2 async logging of info messages 2018-04-07 21:30:01 -04:00
azivner
b5e6f46b9c release 0.10.2-beta 2018-04-07 16:07:25 -04:00
azivner
08af4a0465 fix code mirror loading 2018-04-07 15:56:46 -04:00
azivner
8c5df6321f fix windows sqlite binary for electron 2.0 2018-04-07 13:18:08 -04:00
azivner
d19f044961 fix bug 2018-04-07 13:14:01 -04:00
azivner
e378d9f645 label service refactoring + rename of doInTransaction to transactional 2018-04-07 13:03:16 -04:00
azivner
39dc0f71b4 fix execute note 2018-04-06 19:41:48 -04:00
azivner
0cef5c6b8c added showMessage/showError to script api as they are being used 2018-04-06 19:08:42 -04:00
azivner
9b5a44cef4 fix bugs 2018-04-06 18:49:37 -04:00
azivner
29769ed91d fix force note sync 2018-04-06 18:46:29 -04:00
azivner
867d794e17 release 0.10.1-beta 2018-04-06 00:15:04 -04:00
azivner
fdd8458336 fix sync branch route 2018-04-05 23:45:39 -04:00
azivner
a0bec22e96 fix non-200 logging 2018-04-05 23:35:49 -04:00
azivner
5aeb5cd214 jquery upgrade to 3.3.1 2018-04-05 23:18:15 -04:00
azivner
e827ddffb9 electron fixes 2018-04-05 23:17:19 -04:00
azivner
98f80998b9 fix electron build 2018-04-05 19:29:27 -04:00
azivner
69727d0b12 release 0.10.0-beta 2018-04-04 23:57:46 -04:00
azivner
84faf32b98 updated scripts 2018-04-04 23:55:19 -04:00
azivner
6ed6e27602 startup script running fix 2018-04-04 23:51:47 -04:00
azivner
fb54678fef getNoteWithLabel fix 2018-04-04 23:43:54 -04:00
azivner
2cdcb3af12 camel casing and fixes 2018-04-04 23:04:31 -04:00
azivner
cf7a336ac2 camel case for reddit labels and run values 2018-04-04 22:29:11 -04:00
azivner
abfc64af95 script to generate large documents, closes #55 2018-04-03 22:15:28 -04:00
azivner
42dd8d4754 smaller refactorings 2018-04-02 22:53:01 -04:00
azivner
a4e64350e9 fixed schema, initial setup 2018-04-02 22:33:54 -04:00
azivner
6f567e3e10 camelCase builtin labels 2018-04-02 21:56:55 -04:00
azivner
c6c76ba360 option names now follow camelCase 2018-04-02 21:47:46 -04:00
azivner
429d3f518e moved instanceName to index.ejs 2018-04-02 21:34:28 -04:00
azivner
26e4ad9bf9 separated DB initialization methods into sql_init 2018-04-02 21:25:20 -04:00
azivner
6ab0cea4e3 split out dateUtils on the backend 2018-04-02 20:46:46 -04:00
azivner
277368ab43 simplified new entity ID allocation 2018-04-02 20:30:00 -04:00
azivner
e2921a648d refactored backend to use new naming convention for modules 2018-04-01 21:27:46 -04:00
azivner
c765dbc5cf continuing in API review 2018-04-01 20:50:58 -04:00
azivner
a066c6fe2b changes in API format 2018-04-01 20:33:10 -04:00
azivner
311952d4dd renamed settings to options for consistency 2018-04-01 17:41:28 -04:00
azivner
96dab5d51e smaller refactorings continued 2018-04-01 17:38:24 -04:00
azivner
15d951b04e smaller refactorings continued 2018-04-01 12:45:35 -04:00
azivner
8ba830c04b smaller refactorings continued 2018-04-01 12:03:21 -04:00
azivner
acc82f39c4 smaller refactorings continued 2018-04-01 11:42:12 -04:00
azivner
fad0ec757b smaller refactorings (mostly entitization) 2018-04-01 11:05:09 -04:00
azivner
c9d73c6115 renamed outstanding attribute references to labels 2018-04-01 09:59:44 -04:00
azivner
ab2f28ceef added missing sync check hashes 2018-03-31 23:19:54 -04:00
azivner
87e415992c removed support for old option schema of opt_name and opt_value 2018-03-31 23:11:43 -04:00
azivner
12439d8761 refactoring of note deletion 2018-03-31 23:08:22 -04:00
azivner
4f200c73dc refactoring of note creation 2018-03-31 22:23:40 -04:00
azivner
5f7e74e15c refactoring of note update 2018-03-31 22:15:06 -04:00
azivner
e8a5d0ae16 converted note revision protection to repository/entities 2018-03-31 10:51:37 -04:00
azivner
088fb00ca9 repository is now stateless 2018-03-31 09:07:58 -04:00
azivner
05676f3459 removed dataKey where it's not necessary anymore (use of CLS instead) 2018-03-31 08:53:52 -04:00
azivner
5d203b2278 removed sourceId where it's not necessary (stored in CLS instead) 2018-03-30 19:41:54 -04:00
azivner
795d50f02e converted of web (non-api) routes, basic conversion completed 2018-03-30 19:31:22 -04:00
azivner
cfe0ae1eda converted file, script, search and sender routes 2018-03-30 17:29:13 -04:00
azivner
aa57a64c61 converted image and maintainance routes 2018-03-30 17:07:41 -04:00
azivner
e36a81e189 converted export/import notes 2018-03-30 15:34:07 -04:00
azivner
88c07a9e48 converted sync route 2018-03-30 14:27:41 -04:00
azivner
bfd9f292a6 Merge remote-tracking branch 'origin/stable' 2018-03-30 14:05:11 -04:00
azivner
9edee9340b converted settings, note revisions, password change and recent changes routes 2018-03-30 13:56:46 -04:00
azivner
8550ed72f2 converted cloning and label routes 2018-03-30 13:20:36 -04:00
azivner
efffc29649 initial work on new router model 2018-03-30 12:57:22 -04:00
azivner
0ec909fd7a added basic CLS support with re-entrant transactions 2018-03-28 23:41:22 -04:00
azivner
b10b0048f3 split out library loader 2018-03-27 22:42:46 -04:00
azivner
9bb188b519 fix unnecessary popups about leaving the page 2018-03-27 22:27:46 -04:00
azivner
7464835058 renamed "attachment" to "file" for consistency 2018-03-27 22:11:06 -04:00
azivner
913b6bb6f6 abstracted note detail components 2018-03-27 21:46:38 -04:00
azivner
000cf99546 split out render and search from note detail service 2018-03-27 21:36:01 -04:00
azivner
c918267750 separated attachments out of note detail 2018-03-27 00:27:38 -04:00
azivner
68921ee59b separated text and code handling out of note detail service 2018-03-27 00:22:02 -04:00
azivner
7e856283ee some refactorings of note detail service 2018-03-26 23:48:45 -04:00
azivner
9c1b8da573 split out keybindings out of tree service 2018-03-26 23:25:54 -04:00
azivner
cb39b9cca8 split out tree_builder 2018-03-26 23:18:50 -04:00
azivner
788ac43ad1 further refactorings, got rid of init.js 2018-03-26 22:29:14 -04:00
azivner
57d19f3302 refactored moving note in the tree 2018-03-26 22:11:45 -04:00
azivner
68bba623b6 split up autocomplete related functionality 2018-03-26 21:50:47 -04:00
azivner
35998058ce introduced NoteFull entity, fixes 2018-03-25 23:25:17 -04:00
azivner
cdf94181d2 got rid of instanceName in tree service 2018-03-25 22:56:23 -04:00
azivner
91ee90d827 cleanup in tree service 2018-03-25 22:37:02 -04:00
azivner
d3316cd09c reduced dependencies of utils 2018-03-25 21:29:35 -04:00
azivner
ac1b06967f decoupled protected session holder from presentation stuff and similar things 2018-03-25 21:16:57 -04:00
azivner
47eb1e3e02 refactored all mentions of "history" to "revision" 2018-03-25 20:52:38 -04:00
azivner
a69d8737ce fixed image upload and eslint 2018-03-25 20:18:08 -04:00
azivner
341f47f0f2 moved dialog entrypoints into bootstrap, fixes 2018-03-25 19:49:33 -04:00
azivner
19c605a9a8 labels have now alt-l shortcut to correspond with the renaming 2018-03-25 15:47:31 -04:00
azivner
54e4f54678 all access to notes and branches is now async so we can lazy load it in the future 2018-03-25 14:49:20 -04:00
azivner
297a2cd9da renamed service variables to conform to new naming scheme 2018-03-25 13:41:29 -04:00
azivner
d746d707b5 unblocking infinite cycle 2018-03-25 13:13:26 -04:00
azivner
299252b650 making WS connection asynchronous to not block module registration 2018-03-25 13:08:58 -04:00
azivner
fddd1c278f moved services into the service directory 2018-03-25 13:02:39 -04:00
azivner
f52d7e3c28 split tree_cache and entities from note_tree 2018-03-25 12:29:00 -04:00
azivner
a699210a29 using ES6 modules for whole frontend SPA app 2018-03-25 11:09:17 -04:00
azivner
b3c32a39e9 fix for moving the notes 2018-03-25 10:06:14 -04:00
azivner
df27533b66 fixes after refactorings 2018-03-25 00:20:55 -04:00
azivner
b96a1274c5 all JS functions are now inside old-school JS modules, preparation for ES6 modularization 2018-03-24 23:58:58 -04:00
azivner
001a5107dd ScriptApi separated from ScriptContext, preparation for modularisation 2018-03-24 23:45:36 -04:00
azivner
c8e456cdb1 refactoring utils into module 2018-03-24 23:37:55 -04:00
azivner
0f6b00e1c8 fixes after refactoring, base functionality works again 2018-03-24 23:00:12 -04:00
azivner
5ea060a054 renaming attributes to labels in readme 2018-03-24 22:17:31 -04:00
azivner
95bb2cf0bb renaming attributes to labels, fixes #79 2018-03-24 22:02:26 -04:00
azivner
4c472ce78b renaming note_tree to branch 2018-03-24 21:39:15 -04:00
azivner
511fb89af0 WIP refactoring of data structures in note tree 2018-03-24 20:41:27 -04:00
azivner
7e524c0cd1 removed some warnings in idea 2018-03-24 12:52:58 -04:00
azivner
e3e2dc9fff first POC of ES6 module 2018-03-24 11:18:46 -04:00
azivner
1612e9093d removed all onclick handlers from index template 2018-03-24 00:54:50 -04:00
azivner
f8649feea4 saved search can now be created from the search dialog 2018-03-23 23:08:29 -04:00
azivner
ac978c3fa7 upgrade to electron 2.0.0-beta.5, fixes #65 2018-03-23 21:01:49 -04:00
azivner
efcc804149 redesign search buttons 2018-03-13 20:02:00 -04:00
azivner
db514e8f41 display note type only for non-search notes 2018-03-13 19:47:34 -04:00
azivner
9c32f66329 context menu disables actions not applicable to search note 2018-03-13 19:31:07 -04:00
azivner
0fd5102a26 added consistency check for search note not containing children 2018-03-13 19:18:52 -04:00
azivner
f1b0b3bcdb basic implementation of saved searches finished, closes #83 2018-03-12 23:27:21 -04:00
azivner
e5c0acbb43 note tree refactorings 2018-03-12 23:14:09 -04:00
azivner
834661c461 Merge remote-tracking branch 'origin/master' 2018-03-12 20:00:29 -04:00
azivner
5204ab5a7e refactoring of note tree 2018-03-12 20:00:19 -04:00
azivner
74862536a8 Merge branch 'stable' 2018-03-12 19:46:43 -04:00
zadam
a24f1f5b95 Merge pull request #90 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2018-03-11 18:52:32 -04:00
The Gitter Badger
0be76f746a Add Gitter badge 2018-03-11 22:46:54 +00:00
azivner
fad89ff63f added Theming to list of pages 2018-03-11 13:09:28 -04:00
azivner
b8ae791191 icon for saved seach 2018-03-11 10:53:10 -04:00
azivner
ce754cbd91 saving saved search, #73 2018-03-11 10:49:22 -04:00
255 changed files with 15064 additions and 9747 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
npm-debug.log
dist
.idea

14
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="document.db" uuid="a2c75661-f9e2-478f-a69f-6a9409e69997">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$USER_HOME$/trilium-data/document.db</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
</component>
</project>

View File

@@ -0,0 +1,678 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db">
<database-model serializer="dbm" rdbms="SQLITE" format-version="4.9">
<root id="1">
<ServerVersion>3.16.1</ServerVersion>
</root>
<schema id="2" parent="1" name="main">
<Current>1</Current>
<Visible>1</Visible>
</schema>
<collation id="3" parent="1" name="BINARY"/>
<collation id="4" parent="1" name="NOCASE"/>
<collation id="5" parent="1" name="RTRIM"/>
<table id="6" parent="2" name="api_tokens"/>
<table id="7" parent="2" name="branches"/>
<table id="8" parent="2" name="event_log"/>
<table id="9" parent="2" name="images"/>
<table id="10" parent="2" name="labels"/>
<table id="11" parent="2" name="note_images"/>
<table id="12" parent="2" name="note_revisions"/>
<table id="13" parent="2" name="notes"/>
<table id="14" parent="2" name="options"/>
<table id="15" parent="2" name="recent_notes"/>
<table id="16" parent="2" name="source_ids"/>
<table id="17" parent="2" name="sqlite_master">
<System>1</System>
</table>
<table id="18" parent="2" name="sqlite_sequence">
<System>1</System>
</table>
<table id="19" parent="2" name="sync"/>
<column id="20" parent="6" name="apiTokenId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="21" parent="6" name="token">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="22" parent="6" name="dateCreated">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="23" parent="6" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="24" parent="6" name="hash">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="25" parent="6" name="sqlite_autoindex_api_tokens_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>apiTokenId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="26" parent="6">
<ColNames>apiTokenId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_api_tokens_1</UnderlyingIndexName>
</key>
<column id="27" parent="7" name="branchId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="28" parent="7" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="29" parent="7" name="parentNoteId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="30" parent="7" name="notePosition">
<Position>4</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="31" parent="7" name="prefix">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="32" parent="7" name="isExpanded">
<Position>6</Position>
<DataType>BOOLEAN|0s</DataType>
</column>
<column id="33" parent="7" name="isDeleted">
<Position>7</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="34" parent="7" name="dateModified">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="35" parent="7" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="36" parent="7" name="dateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="37" parent="7" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="38" parent="7" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId
parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="39" parent="7" name="IDX_branches_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="40" parent="7" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="41" parent="7">
<ColNames>branchId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_branches_1</UnderlyingIndexName>
</key>
<column id="42" parent="8" name="eventId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="43" parent="8" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="44" parent="8" name="comment">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="45" parent="8" name="dateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="46" parent="8" name="sqlite_autoindex_event_log_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>eventId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="47" parent="8">
<ColNames>eventId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_event_log_1</UnderlyingIndexName>
</key>
<column id="48" parent="9" name="imageId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="49" parent="9" name="format">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="50" parent="9" name="checksum">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="51" parent="9" name="name">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="52" parent="9" name="data">
<Position>5</Position>
<DataType>BLOB|0s</DataType>
</column>
<column id="53" parent="9" name="isDeleted">
<Position>6</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="54" parent="9" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="55" parent="9" name="dateCreated">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="56" parent="9" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="57" parent="9" name="sqlite_autoindex_images_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="58" parent="9">
<ColNames>imageId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_images_1</UnderlyingIndexName>
</key>
<column id="59" parent="10" name="labelId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="60" parent="10" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="61" parent="10" name="name">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="62" parent="10" name="value">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="63" parent="10" name="position">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="64" parent="10" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="65" parent="10" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="66" parent="10" name="isDeleted">
<Position>8</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="67" parent="10" name="hash">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="68" parent="10" name="sqlite_autoindex_labels_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>labelId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="69" parent="10" name="IDX_labels_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="70" parent="10" name="IDX_labels_name_value">
<ColNames>name
value</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="71" parent="10">
<ColNames>labelId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_labels_1</UnderlyingIndexName>
</key>
<column id="72" parent="11" name="noteImageId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="73" parent="11" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="74" parent="11" name="imageId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="75" parent="11" name="isDeleted">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="76" parent="11" name="dateModified">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="77" parent="11" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="78" parent="11" name="hash">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="79" parent="11" name="sqlite_autoindex_note_images_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteImageId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="80" parent="11" name="IDX_note_images_noteId_imageId">
<ColNames>noteId
imageId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="81" parent="11" name="IDX_note_images_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="82" parent="11" name="IDX_note_images_imageId">
<ColNames>imageId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="83" parent="11">
<ColNames>noteImageId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_images_1</UnderlyingIndexName>
</key>
<column id="84" parent="12" name="noteRevisionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="85" parent="12" name="noteId">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="86" parent="12" name="title">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="87" parent="12" name="content">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="88" parent="12" name="isProtected">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="89" parent="12" name="dateModifiedFrom">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="90" parent="12" name="dateModifiedTo">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="91" parent="12" name="type">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="92" parent="12" name="mime">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;&apos;</DefaultExpression>
</column>
<column id="93" parent="12" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="94" parent="12" name="sqlite_autoindex_note_revisions_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteRevisionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="95" parent="12" name="IDX_note_revisions_noteId">
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="96" parent="12" name="IDX_note_revisions_dateModifiedFrom">
<ColNames>dateModifiedFrom</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<index id="97" parent="12" name="IDX_note_revisions_dateModifiedTo">
<ColNames>dateModifiedTo</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="98" parent="12">
<ColNames>noteRevisionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_note_revisions_1</UnderlyingIndexName>
</key>
<column id="99" parent="13" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="100" parent="13" name="title">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;unnamed&quot;</DefaultExpression>
</column>
<column id="101" parent="13" name="content">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="102" parent="13" name="isProtected">
<Position>4</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="103" parent="13" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="104" parent="13" name="dateCreated">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="105" parent="13" name="dateModified">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="106" parent="13" name="type">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text&apos;</DefaultExpression>
</column>
<column id="107" parent="13" name="mime">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;text/html&apos;</DefaultExpression>
</column>
<column id="108" parent="13" name="hash">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="109" parent="13" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="110" parent="13" name="IDX_notes_type">
<ColNames>type</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="111" parent="13">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
</key>
<column id="112" parent="14" name="optionId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="113" parent="14" name="name">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="114" parent="14" name="value">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="115" parent="14" name="dateModified">
<Position>4</Position>
<DataType>INT|0s</DataType>
</column>
<column id="116" parent="14" name="isSynced">
<Position>5</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="117" parent="14" name="hash">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="118" parent="14" name="dateCreated">
<Position>7</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&apos;1970-01-01T00:00:00.000Z&apos;</DefaultExpression>
</column>
<index id="119" parent="14" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>optionId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="120" parent="14">
<ColNames>optionId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key>
<column id="121" parent="15" name="branchId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="122" parent="15" name="notePath">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="123" parent="15" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="124" parent="15" name="dateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="125" parent="15" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
</column>
<index id="126" parent="15" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="127" parent="15">
<ColNames>branchId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
</key>
<column id="128" parent="16" name="sourceId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="129" parent="16" name="dateCreated">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="130" parent="16" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<key id="131" parent="16">
<ColNames>sourceId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
</key>
<column id="132" parent="17" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="133" parent="17" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="134" parent="17" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="135" parent="17" name="rootpage">
<Position>4</Position>
<DataType>integer|0s</DataType>
</column>
<column id="136" parent="17" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="137" parent="18" name="name">
<Position>1</Position>
</column>
<column id="138" parent="18" name="seq">
<Position>2</Position>
</column>
<column id="139" parent="19" name="id">
<Position>1</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="140" parent="19" name="entityName">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="141" parent="19" name="entityId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="19" name="sourceId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="143" parent="19" name="syncDate">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="144" parent="19" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<ColumnCollations></ColumnCollations>
<Unique>1</Unique>
</index>
<index id="145" parent="19" name="IDX_sync_syncDate">
<ColNames>syncDate</ColNames>
<ColumnCollations></ColumnCollations>
</index>
<key id="146" parent="19">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>
</database-model>
</dataSource>

View File

@@ -0,0 +1,2 @@
#n:main
!<md> [0, 0, null, null, -2147483648, -2147483648]

View File

@@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

9
.idea/jsLinters/jslint.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JSLintConfiguration">
<option devel="true" />
<option es6="true" />
<option maxerr="50" />
<option node="true" />
</component>
</project>

9
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/trilium.iml" filepath="$PROJECT_DIR$/trilium.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

21
Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM node:8.11.2
RUN apt-get update && apt-get install -y nasm
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN npm install --production
# If you are building your code for production
# RUN npm install --only=production
# Bundle app source
COPY . .
EXPOSE 8080
CMD [ "node", "src/www" ]

View File

@@ -1,4 +1,6 @@
# Trilium Notes
[![Join the chat at https://gitter.im/trilium-notes/Lobby](https://badges.gitter.im/trilium-notes/Lobby.svg)](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Trilium Notes is a hierarchical note taking application. Picture tells a thousand words:
![](https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png)
@@ -10,7 +12,7 @@ Trilium Notes is a hierarchical note taking application. Picture tells a thousan
* WYSIWYG (What You See Is What You Get) editing
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation)
* Seamless note versioning
* Note attributes can be used to tag/label notes as an alternative note organization and querying
* Note labels can be used to tag/label notes as an alternative note organization and querying
* Can be deployed as web application and / or desktop application with offline access (electron based)
* [Synchronization with](https://github.com/zadam/trilium/wiki/Synchronization) self-hosted sync server
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes)
@@ -35,11 +37,12 @@ List of documentation pages:
* [Installation as webapp](https://github.com/zadam/trilium/wiki/Installation-as-webapp)
* [Note navigation](https://github.com/zadam/trilium/wiki/Note-navigation)
* [Tree manipulation](https://github.com/zadam/trilium/wiki/Tree-manipulation)
* [Attributes](https://github.com/zadam/trilium/wiki/Attributes)
* [Labels](https://github.com/zadam/trilium/wiki/Labels)
* [Links](https://github.com/zadam/trilium/wiki/Links)
* [Cloning notes](https://github.com/zadam/trilium/wiki/Cloning-notes)
* [Protected notes](https://github.com/zadam/trilium/wiki/Protected-notes)
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization)
* [Document](https://github.com/zadam/trilium/wiki/Document)
* [Theming](https://github.com/zadam/trilium/wiki/Theming)
* [Keyboard shortcuts](https://github.com/zadam/trilium/wiki/Keyboard-shortcuts)
* [Troubleshooting](https://github.com/zadam/trilium/wiki/Troubleshooting)

8
bin/build-docker.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
sudo docker build -t zadam/trilium:latest -t zadam/trilium:$1 .

9
bin/push-docker-image.sh Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
if [[ $# -eq 0 ]] ; then
echo "Missing argument of new version"
exit 1
fi
sudo docker push zadam/trilium:latest
sudo docker push zadam/trilium:$1

View File

@@ -24,9 +24,9 @@ jq '.version = "'$VERSION'"' package.json|sponge package.json
git add package.json
echo 'module.exports = { build_date:"'`date --iso-8601=seconds`'", build_revision: "'`git log -1 --format="%H"`'" };' > services/build.js
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
git add services/build.js
git add src/services/build.js
TAG=v$VERSION
@@ -47,6 +47,7 @@ bin/package.sh
LINUX_X64_BUILD=trilium-linux-x64-$VERSION.7z
LINUX_IA32_BUILD=trilium-linux-ia32-$VERSION.7z
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.7z
SERVER_BUILD=trilium-linux-x64-server.elf
echo "Creating release in GitHub"
@@ -75,4 +76,21 @@ github-release upload \
--name "$WINDOWS_X64_BUILD" \
--file "dist/$WINDOWS_X64_BUILD"
echo "Packaging server version"
npm run build-pkg
github-release upload \
--tag $TAG \
--name "$SERVER_BUILD" \
--file "dist/$SERVER_BUILD"
echo "Building docker image"
bin/build-docker.sh $VERSION
echo "Pushing docker image to dockerhub"
bin/push-docker-image.sh $VERSION
echo "Release finished!"

53
db/main_branches.sql Normal file
View File

@@ -0,0 +1,53 @@
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('root', 'root', 'none', 0, null, 1, 0, '2018-01-01T00:00:00.000Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('dLgtLUFn3GoN', '1Heh2acXfPNt', 'root', 21, null, 1, 0, '2017-12-23T00:46:39.304Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QLfS835GSfIh', '3RkyK9LI18dO', '1Heh2acXfPNt', 1, null, 1, 0, '2017-12-23T01:20:04.181Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QJAcYJ1gGUh9', 'L1Ox40M1aEyy', '3RkyK9LI18dO', 0, null, 0, 0, '2017-12-23T01:20:45.365Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('wLTa2l3lYi83', 'HJusZTbBU494', '3RkyK9LI18dO', 2, null, 1, 0, '2017-12-23T01:20:50.709Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('yMhwsE7uvEij', '3oldoiMUPOlr', 'HJusZTbBU494', 1, null, 1, 0, '2017-12-23T01:20:55.775Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('EjQTcVVHFmmZ', 'MG0wntwILQW6', '3oldoiMUPOlr', 1, null, 1, 0, '2017-12-23T01:21:10.517Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('jvhKcwz4pYTr', 'ZC78NlmdXeC6', 'WdWZFuWNVDZk', 0, null, 1, 0, '2017-12-23T04:06:21.579Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('CarTrwkGVcPz', 'NncfGH8dyNjJ', 'WdWZFuWNVDZk', 1, null, 0, 0, '2017-12-23T04:06:24.012Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('6M7qPlr7at6N', 'eouCLkjbruai', 'NncfGH8dyNjJ', 0, null, 0, 0, '2017-12-23T01:23:28.291Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('tQgognnAH9WI', 'C44aq4mkaX67', 'NncfGH8dyNjJ', 1, null, 0, 0, '2017-12-23T01:23:31.879Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('xyAi7MmgvAgR', 'C44aq4mkaX67', 'ZC78NlmdXeC6', 1, null, 0, 0, '2017-12-23T01:23:47.756Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('xQ3fjRp9yaPq', 'I6Cw88AirBBl', 'C44aq4mkaX67', 0, null, 0, 0, '2017-12-23T01:24:04.681Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('2GOsNT5LsvTP', 'mcEwFMSjhlvL', 'C44aq4mkaX67', 1, null, 0, 0, '2017-12-23T01:29:35.974Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('RxUiraiR655R', 'CF2lUIJAr6Ey', 'NncfGH8dyNjJ', 2, null, 0, 0, '2017-12-23T01:34:37.658Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('mZuSrZ18Zmv0', 'xkXwueRoDNeN', 'MG0wntwILQW6', 0, null, 0, 0, '2017-12-23T01:35:40.306Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('hbcWTnEnXPwF', 'eXHZAKsMYgur', '1Heh2acXfPNt', 3, null, 1, 0, '2017-12-23T03:32:42.868Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('8a3aNxjG0nu7', '2WU27ekfy07E', 'eXHZAKsMYgur', 0, null, 0, 0, '2017-12-23T03:32:49.379Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('4Tu6vaPdCxCM', 'TjWEndYCCg7g', 'eXHZAKsMYgur', 1, null, 0, 0, '2017-12-23T03:33:23.584Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('lBPOmhP12egP', '8nRNDJGyGs2Z', 'TjWEndYCCg7g', 0, null, 0, 0, '2017-12-23T03:33:37.327Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('C5ipVqeDWySp', '9zSwD89vgzNO', '8nRNDJGyGs2Z', 0, null, 0, 0, '2017-12-23T03:37:04.912Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('uSitzbGcSATJ', 'u5t1EvWa3CMO', 'TjWEndYCCg7g', 1, null, 0, 0, '2017-12-23T03:39:21.918Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('GZ6aRI8rdSJt', '8nRNDJGyGs2Z', 'MG0wntwILQW6', 1, '', 0, 0, '2017-12-23T03:42:28.310Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('HsN4600rQoL9', 'Iha4YwchR413', '3oldoiMUPOlr', 0, null, 1, 0, '2017-12-23T03:44:30.945Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('uipfvAfwWRgx', '6ZuXjCSWgjB4', 'HJusZTbBU494', 0, null, 0, 0, '2017-12-23T03:44:54.096Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('nMRpPWWH8WRk', 'GpGnjmcAPeWG', '6ZuXjCSWgjB4', 0, null, 1, 0, '2017-12-23T03:44:57.036Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('c4wt27WNjepw', '21K84UqGhqlt', 'GpGnjmcAPeWG', 0, null, 0, 0, '2017-12-23T03:45:10.933Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('0fpnraUGs9Kl', 'rz5t0r9Qr2WC', 'HJusZTbBU494', 2, null, 1, 0, '2017-12-23T03:45:20.914Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('d8L8zYlLTbym', 'R6pheWjdwmNU', 'rz5t0r9Qr2WC', 0, null, 1, 0, '2017-12-23T03:45:28.002Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('T4USGzfllu5t', '5v5Dx6LMHXIO', 'Iha4YwchR413', 0, null, 0, 0, '2017-12-23T03:45:44.184Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('c4JgFNIobvQW', 'MLQjmREtcnJ3', 'R6pheWjdwmNU', 0, null, 0, 0, '2017-12-23T03:47:48.208Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('nfWjptAU2ZDg', 'pTTjrxgnvURB', 'R6pheWjdwmNU', 1, null, 0, 0, '2017-12-23T03:47:55.932Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('T2ToYBfyPy0g', 'cFK9sGYZaMWs', 'rz5t0r9Qr2WC', 1, null, 0, 0, '2017-12-23T03:49:32.210Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('NG4gbKOnsM3v', '21K84UqGhqlt', 'MLQjmREtcnJ3', 0, '28. 11. 2017', 0, 0, '2017-12-23T03:53:38.110Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('Fstg4tkccO4N', '5v5Dx6LMHXIO', 'MLQjmREtcnJ3', 1, '21. 12. 2017', 0, 0, '2017-12-23T03:53:49.737Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('MN8B7qXDUViO', 'xkXwueRoDNeN', 'MLQjmREtcnJ3', 2, '22. 12. 2017', 0, 0, '2017-12-23T03:53:57.486Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('gSRkHpB7Bu3D', 'pOFVzbXLmzhX', 'R6pheWjdwmNU', 2, null, 0, 0, '2017-12-23T03:54:46.138Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('6brdjeWDOB6w', '0xtvjqrcGiRB', 'ZC78NlmdXeC6', 0, null, 0, 0, '2017-12-23T04:02:06.650Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('AqKUM2zUVFUF', 'Zl69uXBSen0w', 'ZC78NlmdXeC6', 2, null, 1, 0, '2017-12-23T04:02:16.685Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('Ez7NN2WVzRc4', '62BKAQMVP2KW', 'Zl69uXBSen0w', 1, null, 0, 0, '2017-12-23T04:02:39.164Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('t3vVElqMIQVa', 'h4OfLEAYspud', 'WdWZFuWNVDZk', 2, null, 1, 0, '2017-12-23T04:06:25.769Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('O983DHtLpgmr', '1hASbLRDL7oo', 'h4OfLEAYspud', 0, null, 0, 0, '2017-12-23T16:42:26.347Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('RsvL795Mk1bp', '1hASbLRDL7oo', 'GpGnjmcAPeWG', 1, '', 0, 0, '2017-12-23T04:04:56.830Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('79e4hrHLFmx6', 'jyqG9GucsMdn', 'Iha4YwchR413', 1, null, 0, 0, '2017-12-23T04:05:16.439Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('oWO8rctUjf7d', 'WdWZFuWNVDZk', '1Heh2acXfPNt', 5, null, 1, 0, '2017-12-23T04:06:16.179Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('GOxcrZrxalFN', 'yK4SBJfwD3tY', '1Heh2acXfPNt', 8, null, 1, 0, '2017-12-23T04:06:32.833Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bSPmEvjLzQKU', 'r4BnsmSQeVr1', 'yK4SBJfwD3tY', 0, null, 0, 0, '2017-12-23T04:06:37.427Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bMtxCD6cwNR9', 'QbL3pTvhgzM8', 'yK4SBJfwD3tY', 2, null, 0, 0, '2017-12-23T04:06:43.841Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('o4ycR7xIi4oI', 'moMbTKwN15Ps', 'yK4SBJfwD3tY', 3, null, 1, 0, '2017-12-23T04:06:49.331Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('abTEhnOsAsSg', 'PEGQGg0In3Ar', 'GpGnjmcAPeWG', 2, null, 0, 0, '2017-12-23T16:44:35.900Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bryQseMhyzaI', 'IlULcDiOTI4K', '1Heh2acXfPNt', 0, null, 0, 0, '2017-12-23T18:04:26.439Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('ccslPJf3wQV3', 'vBv6ovBupfTj', 'IlULcDiOTI4K', 0, null, 0, 0, '2017-12-23T18:04:50.904Z');
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('5Dt9YCMn59sY', 'mw4f2xB4J5fV', 'IlULcDiOTI4K', 1, null, 0, 0, '2017-12-23T18:05:24.868Z');

View File

@@ -1,52 +0,0 @@
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('dLgtLUFn3GoN', '1Heh2acXfPNt', 'root', 21, null, 1, 0, '2017-12-23T00:46:39.304Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QLfS835GSfIh', '3RkyK9LI18dO', '1Heh2acXfPNt', 1, null, 1, 0, '2017-12-23T01:20:04.181Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('QJAcYJ1gGUh9', 'L1Ox40M1aEyy', '3RkyK9LI18dO', 0, null, 0, 0, '2017-12-23T01:20:45.365Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('wLTa2l3lYi83', 'HJusZTbBU494', '3RkyK9LI18dO', 2, null, 1, 0, '2017-12-23T01:20:50.709Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('yMhwsE7uvEij', '3oldoiMUPOlr', 'HJusZTbBU494', 1, null, 1, 0, '2017-12-23T01:20:55.775Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('EjQTcVVHFmmZ', 'MG0wntwILQW6', '3oldoiMUPOlr', 1, null, 1, 0, '2017-12-23T01:21:10.517Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('jvhKcwz4pYTr', 'ZC78NlmdXeC6', 'WdWZFuWNVDZk', 0, null, 1, 0, '2017-12-23T04:06:21.579Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('CarTrwkGVcPz', 'NncfGH8dyNjJ', 'WdWZFuWNVDZk', 1, null, 0, 0, '2017-12-23T04:06:24.012Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('6M7qPlr7at6N', 'eouCLkjbruai', 'NncfGH8dyNjJ', 0, null, 0, 0, '2017-12-23T01:23:28.291Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('tQgognnAH9WI', 'C44aq4mkaX67', 'NncfGH8dyNjJ', 1, null, 0, 0, '2017-12-23T01:23:31.879Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('xyAi7MmgvAgR', 'C44aq4mkaX67', 'ZC78NlmdXeC6', 1, null, 0, 0, '2017-12-23T01:23:47.756Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('xQ3fjRp9yaPq', 'I6Cw88AirBBl', 'C44aq4mkaX67', 0, null, 0, 0, '2017-12-23T01:24:04.681Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('2GOsNT5LsvTP', 'mcEwFMSjhlvL', 'C44aq4mkaX67', 1, null, 0, 0, '2017-12-23T01:29:35.974Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('RxUiraiR655R', 'CF2lUIJAr6Ey', 'NncfGH8dyNjJ', 2, null, 0, 0, '2017-12-23T01:34:37.658Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('mZuSrZ18Zmv0', 'xkXwueRoDNeN', 'MG0wntwILQW6', 0, null, 0, 0, '2017-12-23T01:35:40.306Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('hbcWTnEnXPwF', 'eXHZAKsMYgur', '1Heh2acXfPNt', 3, null, 1, 0, '2017-12-23T03:32:42.868Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('8a3aNxjG0nu7', '2WU27ekfy07E', 'eXHZAKsMYgur', 0, null, 0, 0, '2017-12-23T03:32:49.379Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('4Tu6vaPdCxCM', 'TjWEndYCCg7g', 'eXHZAKsMYgur', 1, null, 0, 0, '2017-12-23T03:33:23.584Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('lBPOmhP12egP', '8nRNDJGyGs2Z', 'TjWEndYCCg7g', 0, null, 0, 0, '2017-12-23T03:33:37.327Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('C5ipVqeDWySp', '9zSwD89vgzNO', '8nRNDJGyGs2Z', 0, null, 0, 0, '2017-12-23T03:37:04.912Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('uSitzbGcSATJ', 'u5t1EvWa3CMO', 'TjWEndYCCg7g', 1, null, 0, 0, '2017-12-23T03:39:21.918Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('GZ6aRI8rdSJt', '8nRNDJGyGs2Z', 'MG0wntwILQW6', 1, '', 0, 0, '2017-12-23T03:42:28.310Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('HsN4600rQoL9', 'Iha4YwchR413', '3oldoiMUPOlr', 0, null, 1, 0, '2017-12-23T03:44:30.945Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('uipfvAfwWRgx', '6ZuXjCSWgjB4', 'HJusZTbBU494', 0, null, 0, 0, '2017-12-23T03:44:54.096Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('nMRpPWWH8WRk', 'GpGnjmcAPeWG', '6ZuXjCSWgjB4', 0, null, 1, 0, '2017-12-23T03:44:57.036Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('c4wt27WNjepw', '21K84UqGhqlt', 'GpGnjmcAPeWG', 0, null, 0, 0, '2017-12-23T03:45:10.933Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('0fpnraUGs9Kl', 'rz5t0r9Qr2WC', 'HJusZTbBU494', 2, null, 1, 0, '2017-12-23T03:45:20.914Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('d8L8zYlLTbym', 'R6pheWjdwmNU', 'rz5t0r9Qr2WC', 0, null, 1, 0, '2017-12-23T03:45:28.002Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('T4USGzfllu5t', '5v5Dx6LMHXIO', 'Iha4YwchR413', 0, null, 0, 0, '2017-12-23T03:45:44.184Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('c4JgFNIobvQW', 'MLQjmREtcnJ3', 'R6pheWjdwmNU', 0, null, 0, 0, '2017-12-23T03:47:48.208Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('nfWjptAU2ZDg', 'pTTjrxgnvURB', 'R6pheWjdwmNU', 1, null, 0, 0, '2017-12-23T03:47:55.932Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('T2ToYBfyPy0g', 'cFK9sGYZaMWs', 'rz5t0r9Qr2WC', 1, null, 0, 0, '2017-12-23T03:49:32.210Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('NG4gbKOnsM3v', '21K84UqGhqlt', 'MLQjmREtcnJ3', 0, '28. 11. 2017', 0, 0, '2017-12-23T03:53:38.110Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('Fstg4tkccO4N', '5v5Dx6LMHXIO', 'MLQjmREtcnJ3', 1, '21. 12. 2017', 0, 0, '2017-12-23T03:53:49.737Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('MN8B7qXDUViO', 'xkXwueRoDNeN', 'MLQjmREtcnJ3', 2, '22. 12. 2017', 0, 0, '2017-12-23T03:53:57.486Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('gSRkHpB7Bu3D', 'pOFVzbXLmzhX', 'R6pheWjdwmNU', 2, null, 0, 0, '2017-12-23T03:54:46.138Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('6brdjeWDOB6w', '0xtvjqrcGiRB', 'ZC78NlmdXeC6', 0, null, 0, 0, '2017-12-23T04:02:06.650Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('AqKUM2zUVFUF', 'Zl69uXBSen0w', 'ZC78NlmdXeC6', 2, null, 1, 0, '2017-12-23T04:02:16.685Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('Ez7NN2WVzRc4', '62BKAQMVP2KW', 'Zl69uXBSen0w', 1, null, 0, 0, '2017-12-23T04:02:39.164Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('t3vVElqMIQVa', 'h4OfLEAYspud', 'WdWZFuWNVDZk', 2, null, 1, 0, '2017-12-23T04:06:25.769Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('O983DHtLpgmr', '1hASbLRDL7oo', 'h4OfLEAYspud', 0, null, 0, 0, '2017-12-23T16:42:26.347Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('RsvL795Mk1bp', '1hASbLRDL7oo', 'GpGnjmcAPeWG', 1, '', 0, 0, '2017-12-23T04:04:56.830Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('79e4hrHLFmx6', 'jyqG9GucsMdn', 'Iha4YwchR413', 1, null, 0, 0, '2017-12-23T04:05:16.439Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('oWO8rctUjf7d', 'WdWZFuWNVDZk', '1Heh2acXfPNt', 5, null, 1, 0, '2017-12-23T04:06:16.179Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('GOxcrZrxalFN', 'yK4SBJfwD3tY', '1Heh2acXfPNt', 8, null, 1, 0, '2017-12-23T04:06:32.833Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bSPmEvjLzQKU', 'r4BnsmSQeVr1', 'yK4SBJfwD3tY', 0, null, 0, 0, '2017-12-23T04:06:37.427Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bMtxCD6cwNR9', 'QbL3pTvhgzM8', 'yK4SBJfwD3tY', 2, null, 0, 0, '2017-12-23T04:06:43.841Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('o4ycR7xIi4oI', 'moMbTKwN15Ps', 'yK4SBJfwD3tY', 3, null, 1, 0, '2017-12-23T04:06:49.331Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('abTEhnOsAsSg', 'PEGQGg0In3Ar', 'GpGnjmcAPeWG', 2, null, 0, 0, '2017-12-23T16:44:35.900Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('bryQseMhyzaI', 'IlULcDiOTI4K', '1Heh2acXfPNt', 0, null, 0, 0, '2017-12-23T18:04:26.439Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('ccslPJf3wQV3', 'vBv6ovBupfTj', 'IlULcDiOTI4K', 0, null, 0, 0, '2017-12-23T18:04:50.904Z');
INSERT INTO note_tree (noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified) VALUES ('5Dt9YCMn59sY', 'mw4f2xB4J5fV', 'IlULcDiOTI4K', 1, null, 0, 0, '2017-12-23T18:05:24.868Z');

View File

@@ -0,0 +1,38 @@
CREATE TABLE "branches" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` BOOLEAN,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`dateModified` TEXT NOT NULL,
PRIMARY KEY(`branchId`)
);
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified)
SELECT noteTreeId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, dateModified FROM note_tree;
DROP TABLE note_tree;
CREATE INDEX `IDX_branches_noteId` ON `branches` (
`noteId`
);
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (
`noteId`,
`parentNoteId`
);
CREATE TABLE `recent_notes_mig` (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
`dateAccessed` TEXT NOT NULL,
isDeleted INT
);
INSERT INTO recent_notes_mig (branchId, notePath, dateAccessed, isDeleted)
SELECT noteTreeId, notePath, dateAccessed, isDeleted FROM recent_notes;
DROP TABLE recent_notes;
ALTER TABLE recent_notes_mig RENAME TO recent_notes;

View File

@@ -0,0 +1,22 @@
create table labels
(
labelId TEXT not null primary key,
noteId TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
dateCreated TEXT not null,
dateModified TEXT not null,
isDeleted INT not null
);
create index IDX_labels_name_value
on labels (name, value);
create index IDX_labels_noteId
on labels (noteId);
INSERT INTO labels (labelId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted)
SELECT attributeId, noteId, name, "value", "position", dateCreated, dateModified, isDeleted FROM attributes;
DROP TABLE attributes;

View File

@@ -0,0 +1 @@
UPDATE options SET name = 'note_revision_snapshot_time_interval' WHERE name = 'history_snapshot_time_interval';

View File

@@ -0,0 +1,14 @@
UPDATE "options" SET "name" = 'passwordVerificationHash' WHERE "name" = 'password_verification_hash';
UPDATE "options" SET "name" = 'dbVersion' WHERE "name" = 'db_version';
UPDATE "options" SET "name" = 'passwordDerivedKeySalt' WHERE "name" = 'password_derived_key_salt';
UPDATE "options" SET "name" = 'documentId' WHERE "name" = 'document_id';
UPDATE "options" SET "name" = 'lastSyncedPull' WHERE "name" = 'last_synced_pull';
UPDATE "options" SET "name" = 'startNotePath' WHERE "name" = 'start_note_path';
UPDATE "options" SET "name" = 'lastSyncedPush' WHERE "name" = 'last_synced_push';
UPDATE "options" SET "name" = 'documentSecret' WHERE "name" = 'document_secret';
UPDATE "options" SET "name" = 'lastBackupDate' WHERE "name" = 'last_backup_date';
UPDATE "options" SET "name" = 'noteRevisionSnapshotTimeInterval' WHERE "name" = 'note_revision_snapshot_time_interval';
UPDATE "options" SET "name" = 'protectedSessionTimeout' WHERE "name" = 'protected_session_timeout';
UPDATE "options" SET "name" = 'encryptedDataKey' WHERE "name" = 'encrypted_data_key';
UPDATE "options" SET "name" = 'encryptedDataKeyIv' WHERE "name" = 'encrypted_data_key_iv';
UPDATE "options" SET "name" = 'passwordVerificationSalt' WHERE "name" = 'password_verification_salt';

View File

@@ -0,0 +1,7 @@
UPDATE labels SET name = 'disableVersioning' WHERE name = 'disable_versioning';
UPDATE labels SET name = 'calendarRoot' WHERE name = 'calendar_root';
UPDATE labels SET name = 'hideInAutocomplete' WHERE name = 'hide_in_autocomplete';
UPDATE labels SET name = 'excludeFromExport' WHERE name = 'exclude_from_export';
UPDATE labels SET name = 'manualTransactionHandling' WHERE name = 'manual_transaction_handling';
UPDATE labels SET name = 'disableInclusion' WHERE name = 'disable_inclusion';
UPDATE labels SET name = 'appCss' WHERE name = 'app_css';

View File

@@ -0,0 +1,4 @@
UPDATE labels SET name = 'redditId' WHERE name = 'reddit_id';
UPDATE labels SET name = 'redditKind' WHERE name = 'reddit_kind';
UPDATE labels SET name = 'redditCreatedUtc' WHERE name = 'reddit_created_utc';
UPDATE labels SET name = 'redditDateNote' WHERE name = 'reddit_date_note';

View File

@@ -0,0 +1,2 @@
UPDATE labels SET value = 'frontendStartup' WHERE value = 'frontend_startup';
UPDATE labels SET value = 'backendStartup' WHERE value = 'backend_startup';

View File

@@ -0,0 +1,7 @@
UPDATE labels SET name = 'dateData' WHERE name = 'date_data';
UPDATE labels SET name = 'dateNote' WHERE name = 'date_note';
UPDATE labels SET name = 'fileSize' WHERE name = 'file_size';
UPDATE labels SET name = 'hideInAutocomplete' WHERE name = 'hide_in_autocomplete';
UPDATE labels SET name = 'monthNote' WHERE name = 'month_note';
UPDATE labels SET name = 'originalFileName' WHERE name = 'original_file_name';
UPDATE labels SET name = 'yearNote' WHERE name = 'year_note';

View File

@@ -0,0 +1,5 @@
ALTER TABLE note_revisions ADD type TEXT DEFAULT '' NOT NULL;
ALTER TABLE note_revisions ADD mime TEXT DEFAULT '' NOT NULL;
UPDATE note_revisions SET type = (SELECT type FROM notes WHERE notes.noteId = note_revisions.noteId);
UPDATE note_revisions SET mime = (SELECT mime FROM notes WHERE notes.noteId = note_revisions.noteId);

View File

@@ -0,0 +1,34 @@
CREATE TABLE event_logc027
(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
noteId TEXT,
comment TEXT,
dateAdded TEXT NOT NULL
);
INSERT INTO event_logc027(id, noteId, comment, dateAdded) SELECT id, noteId, comment, dateAdded FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_logc027 RENAME TO event_log;
CREATE TABLE IF NOT EXISTS "notes_mig" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "unnamed",
`content` TEXT NOT NULL DEFAULT "",
`isProtected` INT NOT NULL DEFAULT 0,
`isDeleted` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text',
mime TEXT NOT NULL DEFAULT 'text/html',
PRIMARY KEY(`noteId`)
);
INSERT INTO notes_mig (noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime)
SELECT noteId, title, content, isProtected, isDeleted, dateCreated, dateModified, type, mime FROM notes;
DROP TABLE notes;
ALTER TABLE notes_mig RENAME TO notes;
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
`isDeleted`
);

View File

@@ -0,0 +1,5 @@
INSERT INTO branches (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, dateModified)
VALUES ('root', 'root', 'none', 0, null, 1, '2018-01-01T00:00:00.000Z');
INSERT INTO sync (entityName, entityId, sourceId, syncDate)
VALUES ('branches' ,'root', 'SYNC_FILL', '2018-01-01T00:00:00.000Z');

View File

@@ -0,0 +1 @@
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);

View File

@@ -0,0 +1,2 @@
-- index confuses planner and is mostly useless anyway since we're mostly used in non-deleted notes (which are presumably majority)
DROP INDEX IDX_notes_isDeleted;

View File

@@ -0,0 +1,2 @@
create index IDX_notes_type
on notes (type);

View File

@@ -0,0 +1,9 @@
ALTER TABLE notes ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE branches ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE note_revisions ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE recent_notes ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE options ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE note_images ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE images ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE labels ADD hash TEXT DEFAULT "" NOT NULL;
ALTER TABLE api_tokens ADD hash TEXT DEFAULT "" NOT NULL;

View File

@@ -0,0 +1,30 @@
ALTER TABLE branches ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
CREATE TABLE `event_log_mig` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`noteId` TEXT,
`comment` TEXT,
`dateCreated` TEXT NOT NULL
);
INSERT INTO event_log_mig (id, noteId, comment, dateCreated)
SELECT id, noteId, comment, dateAdded FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_log_mig RENAME TO event_log;
ALTER TABLE options ADD dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
CREATE TABLE `recent_notes_mig` (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
`dateCreated` TEXT NOT NULL,
isDeleted INT
);
INSERT INTO recent_notes_mig (branchId, notePath, hash, dateCreated, isDeleted)
SELECT branchId, notePath, hash, dateAccessed, isDeleted FROM recent_notes;
DROP TABLE recent_notes;
ALTER TABLE recent_notes_mig RENAME TO recent_notes;

View File

@@ -0,0 +1 @@
UPDATE notes SET mime = 'text/html' WHERE type = 'render';

View File

@@ -0,0 +1,29 @@
CREATE TABLE `event_log_mig` (
`eventId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT,
`comment` TEXT,
`dateCreated` TEXT NOT NULL
);
INSERT INTO event_log_mig (eventId, noteId, comment, dateCreated)
SELECT id, noteId, comment, dateCreated FROM event_log;
DROP TABLE event_log;
ALTER TABLE event_log_mig RENAME TO event_log;
create table options_mig
(
optionId TEXT NOT NULL PRIMARY KEY,
name TEXT not null,
value TEXT,
dateModified INT,
isSynced INTEGER default 0 not null,
hash TEXT default "" not null,
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
);
INSERT INTO options_mig (optionId, name, value, dateModified, isSynced, hash, dateCreated)
SELECT name || "_key", name, value, dateModified, isSynced, hash, dateCreated FROM options;
DROP TABLE options;
ALTER TABLE options_mig RENAME TO options;

View File

@@ -0,0 +1,2 @@
INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced)
VALUES ('zoomFactor_key', 'zoomFactor', '1.0', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);

View File

@@ -0,0 +1 @@
UPDATE labels SET name = 'archived' WHERE name = 'hideInAutocomplete'

View File

@@ -0,0 +1,2 @@
INSERT INTO options (optionId, name, value, dateCreated, dateModified, isSynced)
VALUES ('theme_key', 'theme', 'white', '2018-06-01T03:35:55.041Z', '2018-06-01T03:35:55.041Z', 0);

View File

@@ -0,0 +1,15 @@
create table options_mig
(
name TEXT not null PRIMARY KEY,
value TEXT,
dateModified INT,
isSynced INTEGER default 0 not null,
hash TEXT default "" not null,
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
);
INSERT INTO options_mig (name, value, dateModified, isSynced, hash, dateCreated)
SELECT name, value, dateModified, isSynced, hash, dateCreated FROM options;
DROP TABLE options;
ALTER TABLE options_mig RENAME TO options;

View File

@@ -1,49 +1,21 @@
CREATE TABLE IF NOT EXISTS "options" (
`name` TEXT NOT NULL PRIMARY KEY,
`value` TEXT,
`dateModified` INT,
isSynced INTEGER NOT NULL DEFAULT 0);
CREATE TABLE IF NOT EXISTS "sync" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`entityName` TEXT NOT NULL,
`entityId` TEXT NOT NULL,
`sourceId` TEXT NOT NULL,
`syncDate` TEXT NOT NULL);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
);
CREATE INDEX `IDX_sync_syncDate` ON `sync` (
`syncDate`
);
CREATE TABLE IF NOT EXISTS "source_ids" (
`sourceId` TEXT NOT NULL,
`dateCreated` TEXT NOT NULL,
PRIMARY KEY(`sourceId`)
);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT,
`content` TEXT,
`isProtected` INT NOT NULL DEFAULT 0,
`isDeleted` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text',
mime TEXT NOT NULL DEFAULT 'text/html',
PRIMARY KEY(`noteId`)
);
CREATE TABLE IF NOT EXISTS "event_log" (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`noteId` TEXT,
`comment` TEXT,
`dateAdded` TEXT NOT NULL,
FOREIGN KEY(noteId) REFERENCES notes(noteId)
);
CREATE TABLE IF NOT EXISTS "note_tree" (
`noteTreeId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` BOOLEAN,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`dateModified` TEXT NOT NULL,
PRIMARY KEY(`noteTreeId`)
);
CREATE TABLE IF NOT EXISTS "note_revisions" (
`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
@@ -52,12 +24,15 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (
`isProtected` INT NOT NULL DEFAULT 0,
`dateModifiedFrom` TEXT NOT NULL,
`dateModifiedTo` TEXT NOT NULL
, type TEXT DEFAULT '' NOT NULL, mime TEXT DEFAULT '' NOT NULL, hash TEXT DEFAULT "" NOT NULL);
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
`noteId`
);
CREATE TABLE IF NOT EXISTS "recent_notes" (
`noteTreeId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
`dateAccessed` TEXT NOT NULL,
isDeleted INT
CREATE INDEX `IDX_note_revisions_dateModifiedFrom` ON `note_revisions` (
`dateModifiedFrom`
);
CREATE INDEX `IDX_note_revisions_dateModifiedTo` ON `note_revisions` (
`dateModifiedTo`
);
CREATE TABLE IF NOT EXISTS "images"
(
@@ -69,7 +44,7 @@ CREATE TABLE IF NOT EXISTS "images"
isDeleted INT NOT NULL DEFAULT 0,
dateModified TEXT NOT NULL,
dateCreated TEXT NOT NULL
);
, hash TEXT DEFAULT "" NOT NULL);
CREATE TABLE note_images
(
noteImageId TEXT PRIMARY KEY NOT NULL,
@@ -78,54 +53,84 @@ CREATE TABLE note_images
isDeleted INT NOT NULL DEFAULT 0,
dateModified TEXT NOT NULL,
dateCreated TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "attributes"
(
attributeId TEXT PRIMARY KEY NOT NULL,
noteId TEXT NOT NULL,
name TEXT NOT NULL,
value TEXT,
position INT NOT NULL DEFAULT 0,
dateCreated TEXT NOT NULL,
dateModified TEXT NOT NULL,
isDeleted INT NOT NULL
);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
);
CREATE INDEX `IDX_sync_syncDate` ON `sync` (
`syncDate`
);
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (
`isDeleted`
);
CREATE INDEX `IDX_note_tree_noteId` ON `note_tree` (
`noteId`
);
CREATE INDEX `IDX_note_tree_noteId_parentNoteId` ON `note_tree` (
`noteId`,
`parentNoteId`
);
CREATE INDEX `IDX_note_revisions_noteId` ON `note_revisions` (
`noteId`
);
CREATE INDEX `IDX_note_revisions_dateModifiedFrom` ON `note_revisions` (
`dateModifiedFrom`
);
CREATE INDEX `IDX_note_revisions_dateModifiedTo` ON `note_revisions` (
`dateModifiedTo`
);
, hash TEXT DEFAULT "" NOT NULL);
CREATE INDEX IDX_note_images_noteId ON note_images (noteId);
CREATE INDEX IDX_note_images_imageId ON note_images (imageId);
CREATE INDEX IDX_note_images_noteId_imageId ON note_images (noteId, imageId);
CREATE INDEX IDX_attributes_noteId ON attributes (noteId);
CREATE INDEX IDX_attributes_name_value ON attributes (name, value);
CREATE TABLE IF NOT EXISTS "api_tokens"
(
apiTokenId TEXT PRIMARY KEY NOT NULL,
token TEXT NOT NULL,
dateCreated TEXT NOT NULL,
isDeleted INT NOT NULL DEFAULT 0
);
, hash TEXT DEFAULT "" NOT NULL);
CREATE TABLE IF NOT EXISTS "branches" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` BOOLEAN,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`dateModified` TEXT NOT NULL, hash TEXT DEFAULT "" NOT NULL, dateCreated TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z',
PRIMARY KEY(`branchId`)
);
CREATE INDEX `IDX_branches_noteId` ON `branches` (
`noteId`
);
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (
`noteId`,
`parentNoteId`
);
CREATE TABLE labels
(
labelId TEXT not null primary key,
noteId TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
dateCreated TEXT not null,
dateModified TEXT not null,
isDeleted INT not null
, hash TEXT DEFAULT "" NOT NULL);
CREATE INDEX IDX_labels_name_value
on labels (name, value);
CREATE INDEX IDX_labels_noteId
on labels (noteId);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "unnamed",
`content` TEXT NOT NULL DEFAULT "",
`isProtected` INT NOT NULL DEFAULT 0,
`isDeleted` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text',
mime TEXT NOT NULL DEFAULT 'text/html', hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`noteId`)
);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE INDEX IDX_notes_type
on notes (type);
CREATE TABLE IF NOT EXISTS "recent_notes" (
`branchId` TEXT NOT NULL PRIMARY KEY,
`notePath` TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
`dateCreated` TEXT NOT NULL,
isDeleted INT
);
CREATE TABLE IF NOT EXISTS "event_log" (
`eventId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT,
`comment` TEXT,
`dateCreated` TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "options"
(
name TEXT not null PRIMARY KEY,
value TEXT,
dateModified INT,
isSynced INTEGER default 0 not null,
hash TEXT default "" not null,
dateCreated TEXT default '1970-01-01T00:00:00.000Z' not null
);

View File

@@ -73,15 +73,15 @@ app.on('ready', () => {
mainWindow = createMainWindow();
const result = globalShortcut.register('CommandOrControl+Alt+P', async () => {
const date_notes = require('./src/services/date_notes');
const utils = require('./src/services/utils');
const dateNoteService = require('./src/services/date_notes');
const dateUtils = require('./src/services/date_utils');
const parentNoteId = await date_notes.getDateNoteId(utils.nowDate());
const parentNote = await dateNoteService.getDateNote(dateUtils.nowDate());
// window may be hidden / not in focus
mainWindow.focus();
mainWindow.webContents.send('create-day-sub-note', parentNoteId);
mainWindow.webContents.send('create-day-sub-note', parentNote.noteId);
});
if (!result) {

6446
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,81 @@
{
"name": "trilium",
"description": "Trilium Notes",
"version": "0.9.2",
"version": "0.17.0",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
"trilium": "./src/www"
},
"repository": {
"type": "git",
"url": "https://github.com/zadam/trilium.git"
},
"scripts": {
"start": "node ./bin/www",
"start": "node ./src/www",
"test-electron": "xo",
"rebuild-electron": "electron-rebuild",
"start-electron": "electron .",
"build-electron": "electron-packager . --out=dist --asar --overwrite --all",
"start-electron": "electron . --disable-gpu",
"build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version=",
"start-forge": "electron-forge start",
"package-forge": "electron-forge package",
"make-forge": "electron-forge make",
"publish-forge": "electron-forge publish"
"publish-forge": "electron-forge publish",
"build-pkg": "pkg . --targets node8-linux-x64 --output dist/trilium-linux-x64-server.elf"
},
"dependencies": {
"async-mutex": "^0.1.3",
"axios": "^0.17.1",
"body-parser": "~1.18.2",
"axios": "^0.18",
"body-parser": "^1.18.3",
"cls-hooked": "^4.2.2",
"cookie-parser": "~1.4.3",
"debug": "~3.1.0",
"devtron": "^1.4.0",
"ejs": "~2.5.7",
"electron": "^1.8.2",
"electron-debug": "^1.5.0",
"electron-dl": "^1.11.0",
"electron-in-page-search": "^1.2.4",
"express": "~4.16.2",
"express-promise-wrap": "^0.2.2",
"ejs": "~2.6.1",
"electron-debug": "^2.0.0",
"electron-dl": "^1.12.0",
"electron-in-page-search": "^1.3.2",
"express": "~4.16.3",
"express-session": "^1.15.6",
"fs-extra": "^4.0.2",
"helmet": "^3.9.0",
"fs-extra": "^6.0.1",
"helmet": "^3.12.1",
"html": "^1.0.0",
"image-type": "^3.0.0",
"imagemin": "^5.3.1",
"imagemin-giflossy": "^5.1.10",
"imagemin-mozjpeg": "^7.0.0",
"imagemin-pngquant": "^5.0.1",
"ini": "^1.3.4",
"imagemin-pngquant": "^5.1.0",
"ini": "^1.3.5",
"jimp": "^0.2.28",
"moment": "^2.20.1",
"multer": "^1.3.0",
"moment": "^2.22.2",
"multer": "^1.3.1",
"open": "0.0.5",
"rand-token": "^0.4.0",
"request": "^2.83.0",
"rcedit": "^1.1.0",
"request": "^2.87.0",
"request-promise": "^4.2.2",
"rimraf": "^2.6.2",
"sanitize-filename": "^1.6.1",
"scrypt": "^6.0.3",
"serve-favicon": "~2.4.5",
"session-file-store": "^1.1.2",
"simple-node-logger": "^0.93.30",
"sqlite": "^2.9.0",
"tar-stream": "^1.5.5",
"serve-favicon": "~2.5.0",
"session-file-store": "^1.2.0",
"simple-node-logger": "^0.93.37",
"sqlite": "^2.9.2",
"tar-stream": "^1.6.1",
"unescape": "^1.0.1",
"ws": "^3.3.2"
"ws": "^5.2.1",
"xml2js": "^0.4.19"
},
"devDependencies": {
"electron-compile": "^6.4.2",
"electron-packager": "^11.0.1",
"electron-prebuilt-compile": "1.8.2",
"electron-rebuild": "^1.7.3",
"tape": "^4.8.0",
"xo": "^0.18.0"
"electron": "^2.0.4",
"electron-compile": "^6.4.3",
"electron-packager": "^12.1.0",
"electron-prebuilt-compile": "2.0.4",
"electron-rebuild": "^1.8.1",
"lorem-ipsum": "^1.0.5",
"tape": "^4.9.1",
"xo": "^0.21.1",
"pkg": "^4.3.3"
},
"config": {
"forge": {
@@ -106,5 +114,12 @@
"node",
"browser"
]
},
"pkg": {
"assets": [
"./db/**/*",
"./src/public/**/*",
"./src/views/**/*"
]
}
}

View File

@@ -9,6 +9,8 @@ const session = require('express-session');
const FileStore = require('session-file-store')(session);
const os = require('os');
const sessionSecret = require('./services/session_secret');
const cls = require('./services/cls');
require('./entities/entity_constructor');
const app = express();
@@ -23,6 +25,17 @@ app.use((req, res, next) => {
next();
});
app.use((req, res, next) => {
cls.namespace.bindEmitter(req);
cls.namespace.bindEmitter(res);
cls.init(() => {
cls.namespace.set("Hi");
next();
});
});
app.use(bodyParser.json({limit: '50mb'}));
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());

24
src/entities/api_token.js Normal file
View File

@@ -0,0 +1,24 @@
"use strict";
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
class ApiToken extends Entity {
static get tableName() { return "api_tokens"; }
static get primaryKeyName() { return "apiTokenId"; }
static get hashedProperties() { return ["apiTokenId", "token", "dateCreated", "isDeleted"]; }
beforeSaving() {
super.beforeSaving();
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
}
}
module.exports = ApiToken;

View File

@@ -1,14 +0,0 @@
"use strict";
const Entity = require('./entity');
class Attribute extends Entity {
static get tableName() { return "attributes"; }
static get primaryKeyName() { return "attributeId"; }
async getNote() {
return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
}
}
module.exports = Attribute;

38
src/entities/branch.js Normal file
View File

@@ -0,0 +1,38 @@
"use strict";
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
const repository = require('../services/repository');
const sql = require('../services/sql');
class Branch extends Entity {
static get tableName() { return "branches"; }
static get primaryKeyName() { return "branchId"; }
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "dateModified", "isDeleted", "prefix"]; }
async getNote() {
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
}
async beforeSaving() {
super.beforeSaving();
if (this.notePosition === undefined) {
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
this.notePosition = maxNotePos === null ? 0 : maxNotePos + 1;
}
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}
module.exports = Branch;

View File

@@ -1,17 +1,34 @@
"use strict";
const utils = require('../services/utils');
const repository = require('../services/repository');
class Entity {
constructor(repository, row) {
utils.assertArguments(repository, row);
this.repository = repository;
constructor(row = {}) {
for (const key in row) {
this[key] = row[key];
}
}
beforeSaving() {
if (!this[this.constructor.primaryKeyName]) {
this[this.constructor.primaryKeyName] = utils.newEntityId();
}
let contentToHash = "";
for (const propertyName of this.constructor.hashedProperties) {
contentToHash += "|" + this[propertyName];
}
this["hash"] = utils.hash(contentToHash).substr(0, 10);
}
async save() {
await repository.updateEntity(this);
return this;
}
}
module.exports = Entity;

View File

@@ -0,0 +1,53 @@
const Note = require('../entities/note');
const NoteRevision = require('../entities/note_revision');
const Image = require('../entities/image');
const NoteImage = require('../entities/note_image');
const Branch = require('../entities/branch');
const Label = require('../entities/label');
const RecentNote = require('../entities/recent_note');
const ApiToken = require('../entities/api_token');
const Option = require('../entities/option');
const repository = require('../services/repository');
function createEntityFromRow(row) {
let entity;
if (row.labelId) {
entity = new Label(row);
}
else if (row.noteRevisionId) {
entity = new NoteRevision(row);
}
else if (row.noteImageId) {
entity = new NoteImage(row);
}
else if (row.imageId) {
entity = new Image(row);
}
else if (row.branchId && row.notePath) {
entity = new RecentNote(row);
}
else if (row.apiTokenId) {
entity = new ApiToken(row);
}
else if (row.branchId) {
entity = new Branch(row);
}
else if (row.noteId) {
entity = new Note(row);
}
else if (row.name) {
entity = new Option(row);
}
else {
throw new Error('Unknown entity type for row: ' + JSON.stringify(row));
}
return entity;
}
repository.setEntityConstructor(createEntityFromRow);
module.exports = {
createEntityFromRow
};

26
src/entities/image.js Normal file
View File

@@ -0,0 +1,26 @@
"use strict";
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
class Image extends Entity {
static get tableName() { return "images"; }
static get primaryKeyName() { return "imageId"; }
static get hashedProperties() { return ["imageId", "format", "checksum", "name", "isDeleted", "dateModified", "dateCreated"]; }
beforeSaving() {
super.beforeSaving();
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}
module.exports = Image;

41
src/entities/label.js Normal file
View File

@@ -0,0 +1,41 @@
"use strict";
const Entity = require('./entity');
const repository = require('../services/repository');
const dateUtils = require('../services/date_utils');
const sql = require('../services/sql');
class Label extends Entity {
static get tableName() { return "labels"; }
static get primaryKeyName() { return "labelId"; }
static get hashedProperties() { return ["labelId", "noteId", "name", "value", "dateModified", "dateCreated"]; }
async getNote() {
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
}
async beforeSaving() {
super.beforeSaving();
if (!this.value) {
// null value isn't allowed
this.value = "";
}
if (this.position === undefined) {
this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM labels WHERE noteId = ?`, [this.noteId]);
}
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}
module.exports = Label;

View File

@@ -1,26 +1,37 @@
"use strict";
const Entity = require('./entity');
const protected_session = require('../services/protected_session');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
const dateUtils = require('../services/date_utils');
class Note extends Entity {
static get tableName() { return "notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "title", "content", "type", "dateModified", "isProtected", "isDeleted"]; }
constructor(repository, row) {
super(repository, row);
constructor(row) {
super(row);
if (this.isProtected) {
protected_session.decryptNote(this.dataKey, this);
// check if there's noteId, otherwise this is a new entity which wasn't encrypted yet
if (this.isProtected && this.noteId) {
protectedSessionService.decryptNote(this);
}
if (this.isJson()) {
this.setContent(this.content);
}
setContent(content) {
this.content = content;
try {
this.jsonContent = JSON.parse(this.content);
}
catch(e) {}
}
isJson() {
return this.type === "code" && this.mime === "application/json";
return this.mime === "application/json";
}
isJavaScript() {
@@ -29,7 +40,7 @@ class Note extends Entity {
}
isHtml() {
return (this.type === "code" || this.type === "file") && this.mime === "text/html";
return (this.type === "code" || this.type === "file" || this.type === "render") && this.mime === "text/html";
}
getScriptEnv() {
@@ -48,89 +59,106 @@ class Note extends Entity {
return null;
}
async getAttributes() {
return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
async getLabels() {
return await repository.getEntities("SELECT * FROM labels WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
}
// WARNING: this doesn't take into account the possibility to have multi-valued attributes!
async getAttributeMap() {
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
async getLabelMap() {
const map = {};
for (const attr of await this.getAttributes()) {
map[attr.name] = attr.value;
for (const label of await this.getLabels()) {
map[label.name] = label.value;
}
return map;
}
async hasAttribute(name) {
const map = await this.getAttributeMap();
async hasLabel(name) {
const map = await this.getLabelMap();
return map.hasOwnProperty(name);
}
// WARNING: this doesn't take into account the possibility to have multi-valued attributes!
async getAttribute(name) {
return this.repository.getEntity("SELECT * FROM attributes WHERE noteId = ? AND name = ?", [this.noteId, name]);
// WARNING: this doesn't take into account the possibility to have multi-valued labels!
async getLabel(name) {
return await repository.getEntity("SELECT * FROM labels WHERE noteId = ? AND name = ?", [this.noteId, name]);
}
async getRevisions() {
return this.repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
return await repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
}
async getTrees() {
return this.repository.getEntities("SELECT * FROM note_tree WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
async getNoteImages() {
return await repository.getEntities("SELECT * FROM note_images WHERE noteId = ? AND isDeleted = 0", [this.noteId]);
}
async getChild(name) {
return this.repository.getEntity(`
async getBranches() {
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
}
async getChildNote(name) {
return await repository.getEntity(`
SELECT notes.*
FROM note_tree
FROM branches
JOIN notes USING(noteId)
WHERE notes.isDeleted = 0
AND note_tree.isDeleted = 0
AND note_tree.parentNoteId = ?
AND branches.isDeleted = 0
AND branches.parentNoteId = ?
AND notes.title = ?`, [this.noteId, name]);
}
async getChildren() {
return this.repository.getEntities(`
async getChildNotes() {
return await repository.getEntities(`
SELECT notes.*
FROM note_tree
FROM branches
JOIN notes USING(noteId)
WHERE notes.isDeleted = 0
AND note_tree.isDeleted = 0
AND note_tree.parentNoteId = ?
ORDER BY note_tree.notePosition`, [this.noteId]);
AND branches.isDeleted = 0
AND branches.parentNoteId = ?
ORDER BY branches.notePosition`, [this.noteId]);
}
async getParents() {
return this.repository.getEntities(`
async getChildBranches() {
return await repository.getEntities(`
SELECT branches.*
FROM branches
WHERE branches.isDeleted = 0
AND branches.parentNoteId = ?
ORDER BY branches.notePosition`, [this.noteId]);
}
async getParentNotes() {
return await repository.getEntities(`
SELECT parent_notes.*
FROM
note_tree AS child_tree
branches AS child_tree
JOIN notes AS parent_notes ON parent_notes.noteId = child_tree.parentNoteId
WHERE child_tree.noteId = ?
AND child_tree.isDeleted = 0
AND parent_notes.isDeleted = 0`, [this.noteId]);
}
async getNoteTree() {
return this.repository.getEntities(`
SELECT note_tree.*
FROM note_tree
JOIN notes USING(noteId)
WHERE notes.isDeleted = 0
AND note_tree.isDeleted = 0
AND note_tree.noteId = ?`, [this.noteId]);
}
beforeSaving() {
this.content = JSON.stringify(this.jsonContent, null, '\t');
super.beforeSaving();
if (this.isJson() && this.jsonContent) {
this.content = JSON.stringify(this.jsonContent, null, '\t');
}
if (this.isProtected) {
protected_session.encryptNote(this.dataKey, this);
protectedSessionService.encryptNote(this);
}
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}

View File

@@ -0,0 +1,35 @@
"use strict";
const Entity = require('./entity');
const repository = require('../services/repository');
const dateUtils = require('../services/date_utils');
class NoteImage extends Entity {
static get tableName() { return "note_images"; }
static get primaryKeyName() { return "noteImageId"; }
static get hashedProperties() { return ["noteImageId", "noteId", "imageId", "isDeleted", "dateModified", "dateCreated"]; }
async getNote() {
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
}
async getImage() {
return await repository.getEntity("SELECT * FROM images WHERE imageId = ?", [this.imageId]);
}
beforeSaving() {
super.beforeSaving();
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
this.dateModified = dateUtils.nowDate();
}
}
module.exports = NoteImage;

View File

@@ -1,13 +1,32 @@
"use strict";
const Entity = require('./entity');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
class NoteRevision extends Entity {
static get tableName() { return "note_revisions"; }
static get primaryKeyName() { return "noteRevisionId"; }
static get hashedProperties() { return ["noteRevisionId", "noteId", "title", "content", "dateModifiedFrom", "dateModifiedTo"]; }
constructor(row) {
super(row);
if (this.isProtected) {
protectedSessionService.decryptNoteRevision(this);
}
}
async getNote() {
return this.repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
}
beforeSaving() {
super.beforeSaving();
if (this.isProtected) {
protectedSessionService.encryptNoteRevision(this);
}
}
}

View File

@@ -1,18 +0,0 @@
"use strict";
const Entity = require('./entity');
class NoteTree extends Entity {
static get tableName() { return "note_tree"; }
static get primaryKeyName() { return "noteTreeId"; }
async getNote() {
return this.repository.getEntity("SELECT * FROM note_tree WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
}
async getParentNote() {
return this.repository.getEntity("SELECT * FROM note_tree WHERE isDeleted = 0 AND parentNoteId = ?", [this.parentNoteId]);
}
}
module.exports = NoteTree;

18
src/entities/option.js Normal file
View File

@@ -0,0 +1,18 @@
"use strict";
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
class Option extends Entity {
static get tableName() { return "options"; }
static get primaryKeyName() { return "name"; }
static get hashedProperties() { return ["name", "value"]; }
beforeSaving() {
super.beforeSaving();
this.dateModified = dateUtils.nowDate();
}
}
module.exports = Option;

View File

@@ -0,0 +1,24 @@
"use strict";
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
class RecentNote extends Entity {
static get tableName() { return "recent_notes"; }
static get primaryKeyName() { return "branchId"; }
static get hashedProperties() { return ["branchId", "notePath", "dateCreated", "isDeleted"]; }
beforeSaving() {
super.beforeSaving();
if (!this.isDeleted) {
this.isDeleted = false;
}
if (!this.dateCreated) {
this.dateCreated = dateUtils.nowDate();
}
}
}
module.exports = RecentNote;

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 245 B

View File

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

View File

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 463 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

View File

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 288 B

View File

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

View File

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

View File

Before

Width:  |  Height:  |  Size: 155 B

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 323 B

View File

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

View File

Before

Width:  |  Height:  |  Size: 252 B

After

Width:  |  Height:  |  Size: 252 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 431 B

View File

Before

Width:  |  Height:  |  Size: 419 B

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M63.966,45.043c0.008-0.009,0.021-0.021,0.027-0.029c0.938-1.156-0.823-13.453-5.063-20.125 c-1.389-2.186-2.239-3.423-3.219-4.719c-3.907-5.166-6-6.125-6-6.125S35.732,24.78,36.149,44.315 c-1.754,0.065-11.218,7.528-14.826,14.388c-1.206,2.291-1.856,3.645-2.493,5.141c-2.539,5.957-2.33,8.25-2.33,8.25 s16.271,6.79,33.014-3.294c0.007,0.021,0.013,0.046,0.02,0.063c0.537,1.389,12.08,5.979,19.976,5.621 c2.587-0.116,4.084-0.238,5.696-0.444c6.424-0.818,8.298-2.157,8.298-2.157S81.144,54.396,63.966,45.043z M50.787,65.343 c1.059-1.183,4.648-5.853,0.995-11.315c-0.253-0.377-0.496-0.236-0.496-0.236s0.063,10.822-5.162,12.359 c-5.225,1.537-13.886,4.4-20.427,0.455C25,66.186,26.924,53.606,38.544,47.229c0.546,1.599,2.836,6.854,9.292,6.409 c0.453-0.031,0.453-0.313,0.453-0.313s-9.422-5.328-8.156-10.625s3.089-14.236,9.766-17.948c0.714-0.397,10.746,7.593,10.417,20.94 c-1.606-0.319-7.377-1.004-10.226,4.864c-0.198,0.409,0.046,0.549,0.046,0.549s9.31-5.521,13.275-1.789 c3.965,3.733,10.813,9.763,10.71,17.4C74.111,67.533,62.197,72.258,50.787,65.343z M35.613,35.145c0,0-0.991,3.241-0.603,7.524 l-13.393-7.524C21.618,35.145,27.838,30.931,35.613,35.145z M21.193,36.03l13.344,7.612c-3.872,1.872-6.142,4.388-6.142,4.388 C20.78,43.531,21.193,36.03,21.193,36.03z M72.287,49.064c0,0-2.321-2.471-6.23-4.263l13.187-7.881 C79.243,36.92,79.808,44.413,72.287,49.064z M78.687,36.113l-13.237,7.794c0.3-4.291-0.754-7.511-0.754-7.511 C72.383,32.025,78.687,36.113,78.687,36.113z M42.076,73.778c0,0,3.309-0.737,6.845-3.185l0.056,15.361 C48.977,85.955,42.244,82.621,42.076,73.778z M49.956,85.888L50,70.526c3.539,2.445,6.846,3.181,6.846,3.181 C56.686,82.551,49.956,85.888,49.956,85.888z"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,33 +0,0 @@
"use strict";
const cloning = (function() {
async function cloneNoteTo(childNoteId, parentNoteId, prefix) {
const resp = await server.put('notes/' + childNoteId + '/clone-to/' + parentNoteId, {
prefix: prefix
});
if (!resp.success) {
alert(resp.message);
return;
}
await noteTree.reload();
}
// beware that first arg is noteId and second is noteTreeId!
async function cloneNoteAfter(noteId, afterNoteTreeId) {
const resp = await server.put('notes/' + noteId + '/clone-after/' + afterNoteTreeId);
if (!resp.success) {
alert(resp.message);
return;
}
await noteTree.reload();
}
return {
cloneNoteAfter,
cloneNoteTo
};
})();

View File

@@ -1,173 +0,0 @@
"use strict";
const contextMenu = (function() {
const $tree = $("#tree");
let clipboardIds = [];
let clipboardMode = null;
async function pasteAfter(node) {
if (clipboardMode === 'cut') {
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
await treeChanges.moveAfterNode(nodes, node);
clipboardIds = [];
clipboardMode = null;
}
else if (clipboardMode === 'copy') {
for (const noteId of clipboardIds) {
await cloning.cloneNoteAfter(noteId, node.data.noteTreeId);
}
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
}
else if (clipboardIds.length === 0) {
// just do nothing
}
else {
throwError("Unrecognized clipboard mode=" + clipboardMode);
}
}
async function pasteInto(node) {
if (clipboardMode === 'cut') {
const nodes = clipboardIds.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
await treeChanges.moveToNode(nodes, node);
clipboardIds = [];
clipboardMode = null;
}
else if (clipboardMode === 'copy') {
for (const noteId of clipboardIds) {
await cloning.cloneNoteTo(noteId, node.data.noteId);
}
// copy will keep clipboardIds and clipboardMode so it's possible to paste into multiple places
}
else if (clipboardIds.length === 0) {
// just do nothing
}
else {
throwError("Unrecognized clipboard mode=" + mode);
}
}
function copy(nodes) {
clipboardIds = nodes.map(node => node.data.noteId);
clipboardMode = 'copy';
showMessage("Note(s) have been copied into clipboard.");
}
function cut(nodes) {
clipboardIds = nodes.map(node => node.key);
clipboardMode = 'cut';
showMessage("Note(s) have been cut into clipboard.");
}
const contextMenuSettings = {
delegate: "span.fancytree-title",
autoFocus: true,
menu: [
{title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"},
{title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"},
{title: "Delete <kbd>Ctrl+Del</kbd>", cmd: "delete", uiIcon: "ui-icon-trash"},
{title: "----"},
{title: "Edit tree prefix <kbd>F2</kbd>", cmd: "editTreePrefix", uiIcon: "ui-icon-pencil"},
{title: "----"},
{title: "Protect sub-tree", cmd: "protectSubTree", uiIcon: "ui-icon-locked"},
{title: "Unprotect sub-tree", cmd: "unprotectSubTree", uiIcon: "ui-icon-unlocked"},
{title: "----"},
{title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
{title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
{title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
{title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
{title: "----"},
{title: "Export sub-tree", cmd: "exportSubTree", uiIcon: " ui-icon-arrowthick-1-ne"},
{title: "Import sub-tree into", cmd: "importSubTree", uiIcon: "ui-icon-arrowthick-1-sw"},
{title: "----"},
{title: "Collapse sub-tree <kbd>Alt+-</kbd>", cmd: "collapseSubTree", uiIcon: "ui-icon-minus"},
{title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"},
{title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"}
],
beforeOpen: (event, ui) => {
const node = $.ui.fancytree.getNode(ui.target);
// Modify menu entries depending on node status
$tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0);
$tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0);
// Activate node on right-click
node.setActive();
// Disable tree keyboard handling
ui.menu.prevKeyboard = node.tree.options.keyboard;
node.tree.options.keyboard = false;
},
close: (event, ui) => {},
select: (event, ui) => {
const node = $.ui.fancytree.getNode(ui.target);
if (ui.cmd === "insertNoteHere") {
const parentNoteId = node.data.parentNoteId;
const isProtected = treeUtils.getParentProtectedStatus(node);
noteTree.createNote(node, parentNoteId, 'after', isProtected);
}
else if (ui.cmd === "insertChildNote") {
noteTree.createNote(node, node.data.noteId, 'into');
}
else if (ui.cmd === "editTreePrefix") {
editTreePrefix.showDialog(node);
}
else if (ui.cmd === "protectSubTree") {
protected_session.protectSubTree(node.data.noteId, true);
}
else if (ui.cmd === "unprotectSubTree") {
protected_session.protectSubTree(node.data.noteId, false);
}
else if (ui.cmd === "copy") {
copy(noteTree.getSelectedNodes());
}
else if (ui.cmd === "cut") {
cut(noteTree.getSelectedNodes());
}
else if (ui.cmd === "pasteAfter") {
pasteAfter(node);
}
else if (ui.cmd === "pasteInto") {
pasteInto(node);
}
else if (ui.cmd === "delete") {
treeChanges.deleteNodes(noteTree.getSelectedNodes(true));
}
else if (ui.cmd === "exportSubTree") {
exportSubTree(node.data.noteId);
}
else if (ui.cmd === "importSubTree") {
importSubTree(node.data.noteId);
}
else if (ui.cmd === "collapseSubTree") {
noteTree.collapseTree(node);
}
else if (ui.cmd === "forceNoteSync") {
forceNoteSync(node.data.noteId);
}
else if (ui.cmd === "sortAlphabetically") {
noteTree.sortAlphabetically(node.data.noteId);
}
else {
messaging.logError("Unknown command: " + ui.cmd);
}
}
};
return {
pasteAfter,
pasteInto,
cut,
copy,
contextMenuSettings
}
})();

View File

@@ -1,137 +1,160 @@
"use strict";
import cloningService from '../services/cloning.js';
import linkService from '../services/link.js';
import noteDetailService from '../services/note_detail.js';
import treeUtils from '../services/tree_utils.js';
import server from "../services/server.js";
import noteDetailText from "../services/note_detail_text.js";
const addLink = (function() {
const $dialog = $("#add-link-dialog");
const $form = $("#add-link-form");
const $autoComplete = $("#note-autocomplete");
const $linkTitle = $("#link-title");
const $clonePrefix = $("#clone-prefix");
const $linkTitleFormGroup = $("#add-link-title-form-group");
const $prefixFormGroup = $("#add-link-prefix-form-group");
const $linkTypes = $("input[name='add-link-type']");
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
const $dialog = $("#add-link-dialog");
const $form = $("#add-link-form");
const $autoComplete = $("#note-autocomplete");
const $linkTitle = $("#link-title");
const $clonePrefix = $("#clone-prefix");
const $linkTitleFormGroup = $("#add-link-title-form-group");
const $prefixFormGroup = $("#add-link-prefix-form-group");
const $linkTypeDiv = $("#add-link-type-div");
const $linkTypes = $("input[name='add-link-type']");
const $linkTypeHtml = $linkTypes.filter('input[value="html"]');
function setLinkType(linkType) {
$linkTypes.each(function () {
$(this).prop('checked', $(this).val() === linkType);
});
linkTypeChanged();
}
function showDialog() {
glob.activeDialog = $dialog;
if (noteEditor.getCurrentNoteType() === 'text') {
$linkTypeHtml.prop('disabled', false);
setLinkType('html');
}
else {
$linkTypeHtml.prop('disabled', true);
setLinkType('selected-to-current');
}
$dialog.dialog({
modal: true,
width: 700
});
$autoComplete.val('').focus();
$clonePrefix.val('');
$linkTitle.val('');
function setDefaultLinkTitle(noteId) {
const noteTitle = noteTree.getNoteTitle(noteId);
$linkTitle.val(noteTitle);
}
$autoComplete.autocomplete({
source: noteTree.getAutocompleteItems(),
minLength: 0,
change: () => {
const val = $autoComplete.val();
const notePath = link.getNodePathFromLabel(val);
if (!notePath) {
return;
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (noteId) {
setDefaultLinkTitle(noteId);
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is
focus: (event, ui) => {
const notePath = link.getNodePathFromLabel(ui.item.value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
setDefaultLinkTitle(noteId);
}
});
}
$form.submit(() => {
const value = $autoComplete.val();
const notePath = link.getNodePathFromLabel(value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (notePath) {
const linkType = $("input[name='add-link-type']:checked").val();
if (linkType === 'html') {
const linkTitle = $linkTitle.val();
$dialog.dialog("close");
link.addLinkToEditor(linkTitle, '#' + notePath);
}
else if (linkType === 'selected-to-current') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteId, noteEditor.getCurrentNoteId(), prefix);
$dialog.dialog("close");
}
else if (linkType === 'current-to-selected') {
const prefix = $clonePrefix.val();
cloning.cloneNoteTo(noteEditor.getCurrentNoteId(), noteId, prefix);
$dialog.dialog("close");
}
}
return false;
function setLinkType(linkType) {
$linkTypes.each(function () {
$(this).prop('checked', $(this).val() === linkType);
});
function linkTypeChanged() {
const value = $linkTypes.filter(":checked").val();
linkTypeChanged();
}
if (value === 'html') {
$linkTitleFormGroup.show();
$prefixFormGroup.hide();
async function showDialog() {
glob.activeDialog = $dialog;
if (noteDetailService.getCurrentNoteType() === 'text') {
$linkTypeHtml.prop('disabled', false);
setLinkType('html');
}
else {
$linkTypeHtml.prop('disabled', true);
setLinkType('selected-to-current');
}
$dialog.dialog({
modal: true,
width: 700
});
$autoComplete.val('').focus();
$clonePrefix.val('');
$linkTitle.val('');
async function setDefaultLinkTitle(noteId) {
const noteTitle = await treeUtils.getNoteTitle(noteId);
$linkTitle.val(noteTitle);
}
$autoComplete.autocomplete({
source: async function(request, response) {
const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
if (result.length > 0) {
response(result);
}
else {
response([{
label: "No results",
value: "No results"
}]);
}
},
minLength: 2,
change: async () => {
const val = $autoComplete.val();
const notePath = linkService.getNodePathFromLabel(val);
if (!notePath) {
return;
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (noteId) {
await setDefaultLinkTitle(noteId);
}
},
// this is called when user goes through autocomplete list with keyboard
// at this point the item isn't selected yet so we use supplied ui.item to see WHERE the cursor is
focus: async (event, ui) => {
const notePath = linkService.getNodePathFromLabel(ui.item.value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
await setDefaultLinkTitle(noteId);
}
else {
$linkTitleFormGroup.hide();
$prefixFormGroup.show();
});
}
$form.submit(() => {
const value = $autoComplete.val();
const notePath = linkService.getNodePathFromLabel(value);
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
if (notePath) {
const linkType = $("input[name='add-link-type']:checked").val();
if (linkType === 'html') {
const linkTitle = $linkTitle.val();
$dialog.dialog("close");
const linkHref = '#' + notePath;
if (hasSelection()) {
const editor = noteDetailText.getEditor();
editor.execute('link', linkHref);
}
else {
linkService.addLinkToEditor(linkTitle, linkHref);
}
}
else if (linkType === 'selected-to-current') {
const prefix = $clonePrefix.val();
cloningService.cloneNoteTo(noteId, noteDetailService.getCurrentNoteId(), prefix);
$dialog.dialog("close");
}
else if (linkType === 'current-to-selected') {
const prefix = $clonePrefix.val();
cloningService.cloneNoteTo(noteDetailService.getCurrentNoteId(), noteId, prefix);
$dialog.dialog("close");
}
}
$linkTypes.change(linkTypeChanged);
return false;
});
$(document).bind('keydown', 'ctrl+l', e => {
showDialog();
// returns true if user selected some text, false if there's no selection
function hasSelection() {
const model = noteDetailText.getEditor().model;
const selection = model.document.selection;
e.preventDefault();
});
return !selection.isCollapsed;
}
return {
showDialog
};
})();
function linkTypeChanged() {
const value = $linkTypes.filter(":checked").val();
$linkTitleFormGroup.toggle(!hasSelection() && value === 'html');
$prefixFormGroup.toggle(!hasSelection() && value !== 'html');
$linkTypeDiv.toggle(!hasSelection());
}
$linkTypes.change(linkTypeChanged);
export default {
showDialog
};

View File

@@ -1,224 +0,0 @@
"use strict";
const attributesDialog = (function() {
const $dialog = $("#attributes-dialog");
const $saveAttributesButton = $("#save-attributes-button");
const $attributesBody = $('#attributes-table tbody');
const attributesModel = new AttributesModel();
let attributeNames = [];
function AttributesModel() {
const self = this;
this.attributes = ko.observableArray();
this.loadAttributes = async function() {
const noteId = noteEditor.getCurrentNoteId();
const attributes = await server.get('notes/' + noteId + '/attributes');
self.attributes(attributes.map(ko.observable));
addLastEmptyRow();
attributeNames = await server.get('attributes/names');
// attribute might not be rendered immediatelly so could not focus
setTimeout(() => $(".attribute-name:last").focus(), 100);
$attributesBody.sortable({
handle: '.handle',
containment: $attributesBody,
update: function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// attributes in the viewmodel (self.attributes()) stays the same
$attributesBody.find('input[name="position"]').each(function() {
const attr = self.getTargetAttribute(this);
attr().position = position++;
});
}
});
};
this.deleteAttribute = function(data, event) {
const attr = self.getTargetAttribute(event.target);
const attrData = attr();
if (attrData) {
attrData.isDeleted = 1;
attr(attrData);
addLastEmptyRow();
}
};
function isValid() {
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
if (self.isEmptyName(i)) {
return false;
}
}
return true;
}
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveAttributesButton.focus();
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
const noteId = noteEditor.getCurrentNoteId();
const attributesToSave = self.attributes()
.map(attr => attr())
.filter(attr => attr.attributeId !== "" || attr.name !== "");
const attributes = await server.put('notes/' + noteId + '/attributes', attributesToSave);
self.attributes(attributes.map(ko.observable));
addLastEmptyRow();
showMessage("Attributes have been saved.");
noteEditor.loadAttributeList();
};
function addLastEmptyRow() {
const attrs = self.attributes().filter(attr => attr().isDeleted === 0);
const last = attrs.length === 0 ? null : attrs[attrs.length - 1]();
if (!last || last.name.trim() !== "" || last.value !== "") {
self.attributes.push(ko.observable({
attributeId: '',
name: '',
value: '',
isDeleted: 0,
position: 0
}));
}
}
this.attributeChanged = function (data, event) {
addLastEmptyRow();
const attr = self.getTargetAttribute(event.target);
attr.valueHasMutated();
};
this.isNotUnique = function(index) {
const cur = self.attributes()[index]();
if (cur.name.trim() === "") {
return false;
}
for (let attrs = self.attributes(), i = 0; i < attrs.length; i++) {
const attr = attrs[i]();
if (index !== i && cur.name === attr.name) {
return true;
}
}
return false;
};
this.isEmptyName = function(index) {
const cur = self.attributes()[index]();
return cur.name.trim() === "" && (cur.attributeId !== "" || cur.value !== "");
};
this.getTargetAttribute = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
return self.attributes()[index];
}
}
async function showDialog() {
glob.activeDialog = $dialog;
await attributesModel.loadAttributes();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
}
$(document).bind('keydown', 'alt+a', e => {
showDialog();
e.preventDefault();
});
ko.applyBindings(attributesModel, document.getElementById('attributes-dialog'));
$(document).on('focus', '.attribute-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: attributeNames.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
$(document).on('focus', '.attribute-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const attributeName = $(this).parent().parent().find('.attribute-name').val();
if (attributeName.trim() === "") {
return;
}
const attributeValues = await server.get('attributes/values/' + encodeURIComponent(attributeName));
if (attributeValues.length === 0) {
return;
}
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in init.js
source: attributeValues.map(attr => {
return {
label: attr,
value: attr
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
return {
showDialog
};
})();

View File

@@ -0,0 +1,51 @@
import treeService from '../services/tree.js';
import server from '../services/server.js';
import treeCache from "../services/tree_cache.js";
import treeUtils from "../services/tree_utils.js";
const $dialog = $("#edit-tree-prefix-dialog");
const $form = $("#edit-tree-prefix-form");
const $treePrefixInput = $("#tree-prefix-input");
const $noteTitle = $('#tree-prefix-note-title');
let branchId;
async function showDialog() {
glob.activeDialog = $dialog;
await $dialog.dialog({
modal: true,
width: 500
});
const currentNode = treeService.getCurrentNode();
branchId = currentNode.data.branchId;
const branch = await treeCache.getBranch(branchId);
$treePrefixInput.val(branch.prefix).focus();
const noteTitle = await treeUtils.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
async function savePrefix() {
const prefix = $treePrefixInput.val();
await server.put('branches/' + branchId + '/set-prefix', { prefix: prefix });
await treeService.setPrefix(branchId, prefix);
$dialog.dialog("close");
}
$form.submit(() => {
savePrefix();
return false;
});
export default {
showDialog
};

View File

@@ -1,45 +0,0 @@
"use strict";
const editTreePrefix = (function() {
const $dialog = $("#edit-tree-prefix-dialog");
const $form = $("#edit-tree-prefix-form");
const $treePrefixInput = $("#tree-prefix-input");
const $noteTitle = $('#tree-prefix-note-title');
let noteTreeId;
async function showDialog() {
glob.activeDialog = $dialog;
await $dialog.dialog({
modal: true,
width: 500
});
const currentNode = noteTree.getCurrentNode();
noteTreeId = currentNode.data.noteTreeId;
$treePrefixInput.val(currentNode.data.prefix).focus();
const noteTitle = noteTree.getNoteTitle(currentNode.data.noteId);
$noteTitle.html(noteTitle);
}
$form.submit(() => {
const prefix = $treePrefixInput.val();
server.put('tree/' + noteTreeId + '/set-prefix', {
prefix: prefix
}).then(() => noteTree.setPrefix(noteTreeId, prefix));
$dialog.dialog("close");
return false;
});
return {
showDialog
};
})();

View File

@@ -1,38 +1,38 @@
"use strict";
import linkService from '../services/link.js';
import utils from '../services/utils.js';
import server from '../services/server.js';
const eventLog = (function() {
const $dialog = $("#event-log-dialog");
const $list = $("#event-log-list");
const $dialog = $("#event-log-dialog");
const $list = $("#event-log-list");
async function showDialog() {
glob.activeDialog = $dialog;
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
const result = await server.get('event-log');
const result = await server.get('event-log');
$list.html('');
$list.html('');
for (const event of result) {
const dateTime = formatDateTime(parseDate(event.dateAdded));
for (const event of result) {
const dateTime = utils.formatDateTime(utils.parseDate(event.dateCreated));
if (event.noteId) {
const noteLink = link.createNoteLink(event.noteId).prop('outerHTML');
if (event.noteId) {
const noteLink = await linkService.createNoteLink(event.noteId).prop('outerHTML');
event.comment = event.comment.replace('<note>', noteLink);
}
const eventEl = $('<li>').html(dateTime + " - " + event.comment);
$list.append(eventEl);
event.comment = event.comment.replace('<note>', noteLink);
}
}
return {
showDialog
};
})();
const eventEl = $('<li>').html(dateTime + " - " + event.comment);
$list.append(eventEl);
}
}
export default {
showDialog
};

View File

@@ -1,56 +1,86 @@
"use strict";
import treeService from '../services/tree.js';
import linkService from '../services/link.js';
import server from '../services/server.js';
import searchNotesService from '../services/search_notes.js';
const jumpToNote = (function() {
const $dialog = $("#jump-to-note-dialog");
const $autoComplete = $("#jump-to-note-autocomplete");
const $form = $("#jump-to-note-form");
const $dialog = $("#jump-to-note-dialog");
const $autoComplete = $("#jump-to-note-autocomplete");
const $form = $("#jump-to-note-form");
const $jumpToNoteButton = $("#jump-to-note-button");
const $showInFullTextButton = $("#show-in-full-text-button");
async function showDialog() {
glob.activeDialog = $dialog;
async function showDialog() {
glob.activeDialog = $dialog;
$autoComplete.val('');
$autoComplete.val('');
$dialog.dialog({
modal: true,
width: 800
});
await $autoComplete.autocomplete({
source: await stopWatch("building autocomplete", noteTree.getAutocompleteItems),
minLength: 0
});
}
function getSelectedNotePath() {
const val = $autoComplete.val();
return link.getNodePathFromLabel(val);
}
function goToNote() {
const notePath = getSelectedNotePath();
if (notePath) {
noteTree.activateNode(notePath);
$dialog.dialog('close');
}
}
$(document).bind('keydown', 'ctrl+j', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800
});
$form.submit(() => {
const action = $dialog.find("button:focus").val();
await $autoComplete.autocomplete({
source: async function(request, response) {
const result = await server.get('autocomplete?query=' + encodeURIComponent(request.term));
goToNote();
return false;
if (result.length > 0) {
response(result);
}
else {
response([{
label: "No results",
value: "No results"
}]);
}
},
focus: function(event, ui) {
return $(ui.item).val() !== 'No results';
},
minLength: 2
});
}
return {
showDialog
};
})();
function getSelectedNotePath() {
const val = $autoComplete.val();
return linkService.getNodePathFromLabel(val);
}
function goToNote() {
const notePath = getSelectedNotePath();
if (notePath) {
treeService.activateNode(notePath);
$dialog.dialog('close');
}
}
function showInFullText(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
const searchText = $autoComplete.val();
searchNotesService.resetSearch();
searchNotesService.showSearch();
searchNotesService.doSearch(searchText);
$dialog.dialog('close');
}
$form.submit(() => {
goToNote();
return false;
});
$jumpToNoteButton.click(goToNote);
$showInFullTextButton.click(showInFullText);
$dialog.bind('keydown', 'ctrl+return', showInFullText);
export default {
showDialog
};

View File

@@ -0,0 +1,223 @@
import noteDetailService from '../services/note_detail.js';
import utils from '../services/utils.js';
import server from '../services/server.js';
import infoService from "../services/info.js";
const $dialog = $("#labels-dialog");
const $saveLabelsButton = $("#save-labels-button");
const $labelsBody = $('#labels-table tbody');
const labelsModel = new LabelsModel();
let labelNames = [];
function LabelsModel() {
const self = this;
this.labels = ko.observableArray();
this.updateLabelPositions = function() {
let position = 0;
// we need to update positions by searching in the DOM, because order of the
// labels in the viewmodel (self.labels()) stays the same
$labelsBody.find('input[name="position"]').each(function() {
const label = self.getTargetLabel(this);
label().position = position++;
});
};
this.loadLabels = async function() {
const noteId = noteDetailService.getCurrentNoteId();
const labels = await server.get('notes/' + noteId + '/labels');
self.labels(labels.map(ko.observable));
addLastEmptyRow();
labelNames = await server.get('labels/names');
// label might not be rendered immediatelly so could not focus
setTimeout(() => $(".label-name:last").focus(), 100);
$labelsBody.sortable({
handle: '.handle',
containment: $labelsBody,
update: this.updateLabelPositions
});
};
this.deleteLabel = function(data, event) {
const label = self.getTargetLabel(event.target);
const labelData = label();
if (labelData) {
labelData.isDeleted = 1;
label(labelData);
addLastEmptyRow();
}
};
function isValid() {
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
if (self.isEmptyName(i)) {
return false;
}
}
return true;
}
this.save = async function() {
// we need to defocus from input (in case of enter-triggered save) because value is updated
// on blur event (because of conflict with jQuery UI Autocomplete). Without this, input would
// stay in focus, blur wouldn't be triggered and change wouldn't be updated in the viewmodel.
$saveLabelsButton.focus();
if (!isValid()) {
alert("Please fix all validation errors and try saving again.");
return;
}
self.updateLabelPositions();
const noteId = noteDetailService.getCurrentNoteId();
const labelsToSave = self.labels()
.map(label => label())
.filter(label => label.labelId !== "" || label.name !== "");
const labels = await server.put('notes/' + noteId + '/labels', labelsToSave);
self.labels(labels.map(ko.observable));
addLastEmptyRow();
infoService.showMessage("Labels have been saved.");
noteDetailService.loadLabelList();
};
function addLastEmptyRow() {
const labels = self.labels().filter(attr => attr().isDeleted === 0);
const last = labels.length === 0 ? null : labels[labels.length - 1]();
if (!last || last.name.trim() !== "" || last.value !== "") {
self.labels.push(ko.observable({
labelId: '',
name: '',
value: '',
isDeleted: 0,
position: 0
}));
}
}
this.labelChanged = function (data, event) {
addLastEmptyRow();
const label = self.getTargetLabel(event.target);
label.valueHasMutated();
};
this.isNotUnique = function(index) {
const cur = self.labels()[index]();
if (cur.name.trim() === "") {
return false;
}
for (let labels = self.labels(), i = 0; i < labels.length; i++) {
const label = labels[i]();
if (index !== i && cur.name === label.name) {
return true;
}
}
return false;
};
this.isEmptyName = function(index) {
const cur = self.labels()[index]();
return cur.name.trim() === "" && (cur.labelId !== "" || cur.value !== "");
};
this.getTargetLabel = function(target) {
const context = ko.contextFor(target);
const index = context.$index();
return self.labels()[index];
}
}
async function showDialog() {
glob.activeDialog = $dialog;
await labelsModel.loadLabels();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
}
ko.applyBindings(labelsModel, document.getElementById('labels-dialog'));
$(document).on('focus', '.label-name', function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in autocomplete.js
source: labelNames.map(label => {
return {
label: label,
value: label
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
$(document).on('focus', '.label-value', async function (e) {
if (!$(this).hasClass("ui-autocomplete-input")) {
const labelName = $(this).parent().parent().find('.label-name').val();
if (labelName.trim() === "") {
return;
}
const labelValues = await server.get('labels/values/' + encodeURIComponent(labelName));
if (labelValues.length === 0) {
return;
}
$(this).autocomplete({
// shouldn't be required and autocomplete should just accept array of strings, but that fails
// because we have overriden filter() function in autocomplete.js
source: labelValues.map(label => {
return {
label: label,
value: label
}
}),
minLength: 0
});
}
$(this).autocomplete("search", $(this).val());
});
export default {
showDialog
};

View File

@@ -1,78 +0,0 @@
"use strict";
const noteHistory = (function() {
const $dialog = $("#note-history-dialog");
const $list = $("#note-history-list");
const $content = $("#note-history-content");
const $title = $("#note-history-title");
let historyItems = [];
async function showCurrentNoteHistory() {
await showNoteHistoryDialog(noteEditor.getCurrentNoteId());
}
async function showNoteHistoryDialog(noteId, noteRevisionId) {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$list.empty();
$content.empty();
historyItems = await server.get('notes-history/' + noteId);
for (const item of historyItems) {
const dateModified = parseDate(item.dateModifiedFrom);
$list.append($('<option>', {
value: item.noteRevisionId,
text: formatDateTime(dateModified)
}));
}
if (historyItems.length > 0) {
if (!noteRevisionId) {
noteRevisionId = $list.find("option:first").val();
}
$list.val(noteRevisionId).trigger('change');
}
else {
$title.text("No history for this note yet...");
}
}
$(document).bind('keydown', 'alt+h', e => {
showCurrentNoteHistory();
e.preventDefault();
});
$list.on('change', () => {
const optVal = $list.find(":selected").val();
const historyItem = historyItems.find(r => r.noteRevisionId === optVal);
$title.html(historyItem.title);
$content.html(historyItem.content);
});
$(document).on('click', "a[action='note-history']", event => {
const linkEl = $(event.target);
const noteId = linkEl.attr('note-path');
const noteRevisionId = linkEl.attr('note-history-id');
showNoteHistoryDialog(noteId, noteRevisionId);
return false;
});
return {
showCurrentNoteHistory
};
})();

View File

@@ -0,0 +1,78 @@
import noteDetailService from '../services/note_detail.js';
import utils from '../services/utils.js';
import server from '../services/server.js';
const $dialog = $("#note-revisions-dialog");
const $list = $("#note-revision-list");
const $content = $("#note-revision-content");
const $title = $("#note-revision-title");
let revisionItems = [];
async function showCurrentNoteRevisions() {
await showNoteRevisionsDialog(noteDetailService.getCurrentNoteId());
}
async function showNoteRevisionsDialog(noteId, noteRevisionId) {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$list.empty();
$content.empty();
revisionItems = await server.get('notes/' + noteId + '/revisions');
for (const item of revisionItems) {
const dateModified = utils.parseDate(item.dateModifiedFrom);
$list.append($('<option>', {
value: item.noteRevisionId,
text: utils.formatDateTime(dateModified)
}));
}
if (revisionItems.length > 0) {
if (!noteRevisionId) {
noteRevisionId = $list.find("option:first").val();
}
$list.val(noteRevisionId).trigger('change');
}
else {
$title.text("No revisions for this note yet...");
}
}
$list.on('change', () => {
const optVal = $list.find(":selected").val();
const revisionItem = revisionItems.find(r => r.noteRevisionId === optVal);
$title.html(revisionItem.title);
if (revisionItem.type === 'text') {
$content.html(revisionItem.content);
}
else if (revisionItem.type === 'code') {
$content.html($("<pre>").text(revisionItem.content));
}
});
$(document).on('click', "a[action='note-revision']", event => {
const linkEl = $(event.target);
const noteId = linkEl.attr('note-path');
const noteRevisionId = linkEl.attr('note-revision-id');
showNoteRevisionsDialog(noteId, noteRevisionId);
return false;
});
export default {
showCurrentNoteRevisions
};

View File

@@ -1,57 +1,49 @@
"use strict";
import noteDetailService from '../services/note_detail.js';
const noteSource = (function() {
const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
function showDialog() {
glob.activeDialog = $dialog;
function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
const noteText = noteEditor.getCurrentNote().detail.content;
$noteSource.text(formatHtml(noteText));
}
function formatHtml(str) {
const div = document.createElement('div');
div.innerHTML = str.trim();
return formatNode(div, 0).innerHTML.trim();
}
function formatNode(node, level) {
const indentBefore = new Array(level++ + 1).join(' ');
const indentAfter = new Array(level - 1).join(' ');
let textNode;
for (let i = 0; i < node.children.length; i++) {
textNode = document.createTextNode('\n' + indentBefore);
node.insertBefore(textNode, node.children[i]);
formatNode(node.children[i], level);
if (node.lastElementChild === node.children[i]) {
textNode = document.createTextNode('\n' + indentAfter);
node.appendChild(textNode);
}
}
return node;
}
$(document).bind('keydown', 'ctrl+u', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800,
height: 500
});
return {
showDialog
};
})();
const noteText = noteDetailService.getCurrentNote().content;
$noteSource.text(formatHtml(noteText));
}
function formatHtml(str) {
const div = document.createElement('div');
div.innerHTML = str.trim();
return formatNode(div, 0).innerHTML.trim();
}
function formatNode(node, level) {
const indentBefore = new Array(level++ + 1).join(' ');
const indentAfter = new Array(level - 1).join(' ');
let textNode;
for (let i = 0; i < node.children.length; i++) {
textNode = document.createTextNode('\n' + indentBefore);
node.insertBefore(textNode, node.children[i]);
formatNode(node.children[i], level);
if (node.lastElementChild === node.children[i]) {
textNode = document.createTextNode('\n' + indentAfter);
node.appendChild(textNode);
}
}
return node;
}
export default {
showDialog
};

View File

@@ -0,0 +1,242 @@
"use strict";
import protectedSessionHolder from '../services/protected_session_holder.js';
import server from '../services/server.js';
import infoService from "../services/info.js";
import zoomService from "../services/zoom.js";
import utils from "../services/utils.js";
const $dialog = $("#options-dialog");
const $tabs = $("#options-tabs");
const tabHandlers = [];
function addTabHandler(handler) {
tabHandlers.push(handler);
}
async function showDialog() {
glob.activeDialog = $dialog;
const options = await server.get('options');
$dialog.dialog({
modal: true,
width: 900
});
$tabs.tabs();
for (const handler of tabHandlers) {
if (handler.optionsLoaded) {
handler.optionsLoaded(options);
}
}
}
async function saveOptions(optionName, optionValue) {
await server.put('options/' + encodeURIComponent(optionName) + '/' + encodeURIComponent(optionValue));
infoService.showMessage("Options change have been saved.");
}
export default {
showDialog,
saveOptions
};
addTabHandler((function() {
const $themeSelect = $("#theme-select");
const $zoomFactorSelect = $("#zoom-factor-select");
const $html = $("html");
function optionsLoaded(options) {
$themeSelect.val(options.theme);
if (utils.isElectron()) {
$zoomFactorSelect.val(options.zoomFactor);
}
else {
$zoomFactorSelect.prop('disabled', true);
}
}
$themeSelect.change(function() {
const newTheme = $(this).val();
$html.attr("class", "theme-" + newTheme);
server.put('options/theme/' + newTheme);
});
$zoomFactorSelect.change(function() {
const newZoomFactor = $(this).val();
zoomService.setZoomFactorAndSave(newZoomFactor);
});
return {
optionsLoaded
};
})());
addTabHandler((function() {
const $form = $("#change-password-form");
const $oldPassword = $("#old-password");
const $newPassword1 = $("#new-password1");
const $newPassword2 = $("#new-password2");
function optionsLoaded(options) {
}
$form.submit(() => {
const oldPassword = $oldPassword.val();
const newPassword1 = $newPassword1.val();
const newPassword2 = $newPassword2.val();
$oldPassword.val('');
$newPassword1.val('');
$newPassword2.val('');
if (newPassword1 !== newPassword2) {
alert("New passwords are not the same.");
return false;
}
server.post('password/change', {
'current_password': oldPassword,
'new_password': newPassword1
}).then(result => {
if (result.success) {
alert("Password has been changed. Trilium will be reloaded after you press OK.");
// password changed so current protected session is invalid and needs to be cleared
protectedSessionHolder.resetProtectedSession();
}
else {
infoService.showError(result.message);
}
});
return false;
});
return {
optionsLoaded
};
})());
addTabHandler((function() {
const $form = $("#protected-session-timeout-form");
const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
const optionName = 'protectedSessionTimeout';
function optionsLoaded(options) {
$protectedSessionTimeout.val(options[optionName]);
}
$form.submit(() => {
const protectedSessionTimeout = $protectedSessionTimeout.val();
saveOptions(optionName, protectedSessionTimeout).then(() => {
protectedSessionHolder.setProtectedSessionTimeout(protectedSessionTimeout);
});
return false;
});
return {
optionsLoaded
};
})());
addTabHandler((function () {
const $form = $("#note-revision-snapshot-time-interval-form");
const $timeInterval = $("#note-revision-snapshot-time-interval-in-seconds");
const optionName = 'noteRevisionSnapshotTimeInterval';
function optionsLoaded(options) {
$timeInterval.val(options[optionName]);
}
$form.submit(() => {
saveOptions(optionName, $timeInterval.val());
return false;
});
return {
optionsLoaded
};
})());
addTabHandler((async function () {
const $appVersion = $("#app-version");
const $dbVersion = $("#db-version");
const $syncVersion = $("#sync-version");
const $buildDate = $("#build-date");
const $buildRevision = $("#build-revision");
const appInfo = await server.get('app-info');
$appVersion.html(appInfo.appVersion);
$dbVersion.html(appInfo.dbVersion);
$syncVersion.html(appInfo.syncVersion);
$buildDate.html(appInfo.buildDate);
$buildRevision.html(appInfo.buildRevision);
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
return {};
})());
addTabHandler((async function () {
const $forceFullSyncButton = $("#force-full-sync-button");
const $fillSyncRowsButton = $("#fill-sync-rows-button");
const $anonymizeButton = $("#anonymize-button");
const $cleanupSoftDeletedButton = $("#cleanup-soft-deleted-items-button");
const $cleanupUnusedImagesButton = $("#cleanup-unused-images-button");
const $vacuumDatabaseButton = $("#vacuum-database-button");
$forceFullSyncButton.click(async () => {
await server.post('sync/force-full-sync');
infoService.showMessage("Full sync triggered");
});
$fillSyncRowsButton.click(async () => {
await server.post('sync/fill-sync-rows');
infoService.showMessage("Sync rows filled successfully");
});
$anonymizeButton.click(async () => {
await server.post('anonymization/anonymize');
infoService.showMessage("Created anonymized database");
});
$cleanupSoftDeletedButton.click(async () => {
if (confirm("Do you really want to clean up soft-deleted items?")) {
await server.post('cleanup/cleanup-soft-deleted-items');
infoService.showMessage("Soft deleted items have been cleaned up");
}
});
$cleanupUnusedImagesButton.click(async () => {
if (confirm("Do you really want to clean up unused images?")) {
await server.post('cleanup/cleanup-unused-images');
infoService.showMessage("Unused images have been cleaned up");
}
});
$vacuumDatabaseButton.click(async () => {
await server.post('cleanup/vacuum-database');
infoService.showMessage("Database has been vacuumed");
});
return {};
})());

View File

@@ -1,89 +1,87 @@
"use strict";
import linkService from '../services/link.js';
import utils from '../services/utils.js';
import server from '../services/server.js';
const recentChanges = (function() {
const $dialog = $("#recent-changes-dialog");
const $dialog = $("#recent-changes-dialog");
async function showDialog() {
glob.activeDialog = $dialog;
async function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
$dialog.dialog({
modal: true,
width: 800,
height: 700
});
const result = await server.get('recent-changes/');
const result = await server.get('recent-changes/');
$dialog.html('');
$dialog.html('');
const groupedByDate = groupByDate(result);
const groupedByDate = groupByDate(result);
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
const dayEl = $('<div>').append($('<b>').html(formatDate(dateDay))).append(changesListEl);
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append(changesListEl);
for (const change of dayChanges) {
const formattedTime = formatTime(parseDate(change.dateModifiedTo));
for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.dateModifiedTo));
const revLink = $("<a>", {
href: 'javascript:',
text: 'rev'
}).attr('action', 'note-history')
.attr('note-path', change.noteId)
.attr('note-history-id', change.noteRevisionId);
const revLink = $("<a>", {
href: 'javascript:',
text: 'rev'
}).attr('action', 'note-revision')
.attr('note-path', change.noteId)
.attr('note-revision-id', change.noteRevisionId);
let noteLink;
let noteLink;
if (change.current_isDeleted) {
noteLink = change.current_title;
}
else {
noteLink = link.createNoteLink(change.noteId, change.title);
}
changesListEl.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink)
.append(' (').append(revLink).append(')'));
}
$dialog.append(dayEl);
}
}
function groupByDate(result) {
const groupedByDate = new Map();
const dayCache = {};
for (const row of result) {
let dateDay = parseDate(row.dateModifiedTo);
dateDay.setHours(0);
dateDay.setMinutes(0);
dateDay.setSeconds(0);
dateDay.setMilliseconds(0);
// this stupidity is to make sure that we always use the same day object because Map uses only
// reference equality
if (dayCache[dateDay]) {
dateDay = dayCache[dateDay];
if (change.current_isDeleted) {
noteLink = change.current_title;
}
else {
dayCache[dateDay] = dateDay;
noteLink = await linkService.createNoteLink(change.noteId, change.title);
}
if (!groupedByDate.has(dateDay)) {
groupedByDate.set(dateDay, []);
}
groupedByDate.get(dateDay).push(row);
changesListEl.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink)
.append(' (').append(revLink).append(')'));
}
return groupedByDate;
$dialog.append(dayEl);
}
}
$(document).bind('keydown', 'alt+r', showDialog);
function groupByDate(result) {
const groupedByDate = new Map();
const dayCache = {};
return {
showDialog
};
})();
for (const row of result) {
let dateDay = utils.parseDate(row.dateModifiedTo);
dateDay.setHours(0);
dateDay.setMinutes(0);
dateDay.setSeconds(0);
dateDay.setMilliseconds(0);
// this stupidity is to make sure that we always use the same day object because Map uses only
// reference equality
if (dayCache[dateDay]) {
dateDay = dayCache[dateDay];
}
else {
dayCache[dateDay] = dateDay;
}
if (!groupedByDate.has(dateDay)) {
groupedByDate.set(dateDay, []);
}
groupedByDate.get(dateDay).push(row);
}
return groupedByDate;
}
export default {
showDialog
};

View File

@@ -1,102 +1,74 @@
"use strict";
import treeService from '../services/tree.js';
import server from '../services/server.js';
const recentNotes = (function() {
const $dialog = $("#recent-notes-dialog");
const $searchInput = $('#recent-notes-search-input');
const $dialog = $("#recent-notes-dialog");
const $searchInput = $('#recent-notes-search-input');
// list of recent note paths
let list = [];
function addRecentNote(branchId, notePath) {
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === treeService.getCurrentNotePath()) {
const result = await server.put('recent-notes/' + branchId + '/' + encodeURIComponent(notePath));
}
}, 1500);
}
async function reload() {
const result = await server.get('recent-notes');
async function showDialog() {
glob.activeDialog = $dialog;
list = result.map(r => r.notePath);
}
function addRecentNote(noteTreeId, notePath) {
setTimeout(async () => {
// we include the note into recent list only if the user stayed on the note at least 5 seconds
if (notePath && notePath === noteTree.getCurrentNotePath()) {
const result = await server.put('recent-notes/' + noteTreeId + '/' + encodeURIComponent(notePath));
list = result.map(r => r.notePath);
}
}, 1500);
}
function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: 800,
height: 100,
position: { my: "center top+100", at: "top", of: window }
});
$searchInput.val('');
// remove the current note
const recNotes = list.filter(note => note !== noteTree.getCurrentNotePath());
$searchInput.autocomplete({
source: recNotes.map(notePath => {
let noteTitle;
try {
noteTitle = noteTree.getNotePathTitle(notePath);
}
catch (e) {
noteTitle = "[error - can't find note title]";
messaging.logError("Could not find title for notePath=" + notePath + ", stack=" + e.stack);
}
return {
label: noteTitle,
value: notePath
}
}),
minLength: 0,
autoFocus: true,
select: function (event, ui) {
noteTree.activateNode(ui.item.value);
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
},
focus: function (event, ui) {
event.preventDefault();
},
close: function (event, ui) {
if (event.keyCode === 27) { // escape closes dialog
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
}
else {
// keep autocomplete open
// we're kind of abusing autocomplete to work in a way which it's not designed for
$searchInput.autocomplete("search", "");
}
},
create: () => $searchInput.autocomplete("search", ""),
classes: {
"ui-autocomplete": "recent-notes-autocomplete"
}
});
}
reload();
$(document).bind('keydown', 'ctrl+e', e => {
showDialog();
e.preventDefault();
$dialog.dialog({
modal: true,
width: 800,
height: 100,
position: { my: "center top+100", at: "top", of: window }
});
return {
showDialog,
addRecentNote,
reload
};
})();
$searchInput.val('');
const result = await server.get('recent-notes');
// remove the current note
const recNotes = result.filter(note => note.notePath !== treeService.getCurrentNotePath());
const items = recNotes.map(rn => {
return {
label: rn.title,
value: rn.notePath
};
});
$searchInput.autocomplete({
source: items,
minLength: 0,
autoFocus: true,
select: function (event, ui) {
treeService.activateNode(ui.item.value);
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
},
focus: function (event, ui) {
event.preventDefault();
},
close: function (event, ui) {
if (event.keyCode === 27) { // escape closes dialog
$searchInput.autocomplete('destroy');
$dialog.dialog('close');
}
else {
// keep autocomplete open
// we're kind of abusing autocomplete to work in a way which it's not designed for
$searchInput.autocomplete("search", "");
}
},
create: () => $searchInput.autocomplete("search", ""),
classes: {
"ui-autocomplete": "recent-notes-autocomplete"
}
});
}
export default {
showDialog,
addRecentNote
};

View File

@@ -1,205 +0,0 @@
"use strict";
const settings = (function() {
const $dialog = $("#settings-dialog");
const $tabs = $("#settings-tabs");
const settingModules = [];
function addModule(module) {
settingModules.push(module);
}
async function showDialog() {
glob.activeDialog = $dialog;
const settings = await server.get('settings');
$dialog.dialog({
modal: true,
width: 900
});
$tabs.tabs();
for (const module of settingModules) {
if (module.settingsLoaded) {
module.settingsLoaded(settings);
}
}
}
async function saveSettings(settingName, settingValue) {
await server.post('settings', {
name: settingName,
value: settingValue
});
showMessage("Settings change have been saved.");
}
return {
showDialog,
saveSettings,
addModule
};
})();
settings.addModule((function() {
const $form = $("#change-password-form");
const $oldPassword = $("#old-password");
const $newPassword1 = $("#new-password1");
const $newPassword2 = $("#new-password2");
function settingsLoaded(settings) {
}
$form.submit(() => {
const oldPassword = $oldPassword.val();
const newPassword1 = $newPassword1.val();
const newPassword2 = $newPassword2.val();
$oldPassword.val('');
$newPassword1.val('');
$newPassword2.val('');
if (newPassword1 !== newPassword2) {
alert("New passwords are not the same.");
return false;
}
server.post('password/change', {
'current_password': oldPassword,
'new_password': newPassword1
}).then(result => {
if (result.success) {
alert("Password has been changed. Trilium will be reloaded after you press OK.");
// password changed so current protected session is invalid and needs to be cleared
protected_session.resetProtectedSession();
}
else {
showError(result.message);
}
});
return false;
});
return {
settingsLoaded
};
})());
settings.addModule((function() {
const $form = $("#protected-session-timeout-form");
const $protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
const settingName = 'protected_session_timeout';
function settingsLoaded(settings) {
$protectedSessionTimeout.val(settings[settingName]);
}
$form.submit(() => {
const protectedSessionTimeout = $protectedSessionTimeout.val();
settings.saveSettings(settingName, protectedSessionTimeout).then(() => {
protected_session.setProtectedSessionTimeout(protectedSessionTimeout);
});
return false;
});
return {
settingsLoaded
};
})());
settings.addModule((function () {
const $form = $("#history-snapshot-time-interval-form");
const $timeInterval = $("#history-snapshot-time-interval-in-seconds");
const settingName = 'history_snapshot_time_interval';
function settingsLoaded(settings) {
$timeInterval.val(settings[settingName]);
}
$form.submit(() => {
settings.saveSettings(settingName, $timeInterval.val());
return false;
});
return {
settingsLoaded
};
})());
settings.addModule((async function () {
const $appVersion = $("#app-version");
const $dbVersion = $("#db-version");
const $buildDate = $("#build-date");
const $buildRevision = $("#build-revision");
const appInfo = await server.get('app-info');
$appVersion.html(appInfo.app_version);
$dbVersion.html(appInfo.db_version);
$buildDate.html(appInfo.build_date);
$buildRevision.html(appInfo.build_revision);
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.build_revision);
return {};
})());
settings.addModule((async function () {
const $forceFullSyncButton = $("#force-full-sync-button");
const $fillSyncRowsButton = $("#fill-sync-rows-button");
const $anonymizeButton = $("#anonymize-button");
const $cleanupSoftDeletedButton = $("#cleanup-soft-deleted-items-button");
const $cleanupUnusedImagesButton = $("#cleanup-unused-images-button");
const $vacuumDatabaseButton = $("#vacuum-database-button");
$forceFullSyncButton.click(async () => {
await server.post('sync/force-full-sync');
showMessage("Full sync triggered");
});
$fillSyncRowsButton.click(async () => {
await server.post('sync/fill-sync-rows');
showMessage("Sync rows filled successfully");
});
$anonymizeButton.click(async () => {
await server.post('anonymization/anonymize');
showMessage("Created anonymized database");
});
$cleanupSoftDeletedButton.click(async () => {
if (confirm("Do you really want to clean up soft-deleted items?")) {
await server.post('cleanup/cleanup-soft-deleted-items');
showMessage("Soft deleted items have been cleaned up");
}
});
$cleanupUnusedImagesButton.click(async () => {
if (confirm("Do you really want to clean up unused images?")) {
await server.post('cleanup/cleanup-unused-images');
showMessage("Unused images have been cleaned up");
}
});
$vacuumDatabaseButton.click(async () => {
await server.post('cleanup/vacuum-database');
showMessage("Database has been vacuumed");
});
return {};
})());

View File

@@ -1,106 +1,105 @@
"use strict";
import utils from '../services/utils.js';
import libraryLoader from '../services/library_loader.js';
import server from '../services/server.js';
import infoService from "../services/info.js";
const sqlConsole = (function() {
const $dialog = $("#sql-console-dialog");
const $query = $('#sql-console-query');
const $executeButton = $('#sql-console-execute');
const $resultHead = $('#sql-console-results thead');
const $resultBody = $('#sql-console-results tbody');
const $dialog = $("#sql-console-dialog");
const $query = $('#sql-console-query');
const $executeButton = $('#sql-console-execute');
const $resultHead = $('#sql-console-results thead');
const $resultBody = $('#sql-console-results tbody');
let codeEditor;
let codeEditor;
function showDialog() {
glob.activeDialog = $dialog;
function showDialog() {
glob.activeDialog = $dialog;
$dialog.dialog({
modal: true,
width: $(window).width(),
height: $(window).height(),
open: function() {
initEditor();
}
});
}
async function initEditor() {
if (!codeEditor) {
await requireLibrary(CODE_MIRROR);
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
// removing Escape binding so that Escape will propagate to the dialog (which will close on escape)
delete CodeMirror.keyMap.basic["Esc"];
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
codeEditor = CodeMirror($query[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}
});
codeEditor.setOption("mode", "text/x-sqlite");
CodeMirror.autoLoadMode(codeEditor, "sql");
$dialog.dialog({
modal: true,
width: $(window).width(),
height: $(window).height(),
open: function() {
initEditor();
}
});
}
codeEditor.focus();
}
async function initEditor() {
if (!codeEditor) {
await libraryLoader.requireLibrary(libraryLoader.CODE_MIRROR);
async function execute(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
CodeMirror.keyMap.default["Shift-Tab"] = "indentLess";
CodeMirror.keyMap.default["Tab"] = "indentMore";
const sqlQuery = codeEditor.getValue();
// removing Escape binding so that Escape will propagate to the dialog (which will close on escape)
delete CodeMirror.keyMap.basic["Esc"];
const result = await server.post("sql/execute", {
query: sqlQuery
CodeMirror.modeURL = 'libraries/codemirror/mode/%N/%N.js';
codeEditor = CodeMirror($query[0], {
value: "",
viewportMargin: Infinity,
indentUnit: 4,
highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}
});
if (!result.success) {
showError(result.error);
return;
}
else {
showMessage("Query was executed successfully.");
}
const rows = result.rows;
$resultHead.empty();
$resultBody.empty();
if (rows.length > 0) {
const result = rows[0];
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<th>").html(key));
}
$resultHead.append(rowEl);
}
for (const result of rows) {
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<td>").html(result[key]));
}
$resultBody.append(rowEl);
}
codeEditor.setOption("mode", "text/x-sqlite");
CodeMirror.autoLoadMode(codeEditor, "sql");
}
$(document).bind('keydown', 'alt+o', showDialog);
codeEditor.focus();
}
$query.bind('keydown', 'ctrl+return', execute);
async function execute(e) {
// stop from propagating upwards (dangerous especially with ctrl+enter executable javascript notes)
e.preventDefault();
e.stopPropagation();
$executeButton.click(execute);
const sqlQuery = codeEditor.getValue();
return {
showDialog
};
})();
const result = await server.post("sql/execute", {
query: sqlQuery
});
if (!result.success) {
infoService.showError(result.error);
return;
}
else {
infoService.showMessage("Query was executed successfully.");
}
const rows = result.rows;
$resultHead.empty();
$resultBody.empty();
if (rows.length > 0) {
const result = rows[0];
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<th>").html(key));
}
$resultHead.append(rowEl);
}
for (const result of rows) {
const rowEl = $("<tr>");
for (const key in result) {
rowEl.append($("<td>").html(result[key]));
}
$resultBody.append(rowEl);
}
}
$query.bind('keydown', 'ctrl+return', execute);
$executeButton.click(execute);
export default {
showDialog
};

View File

@@ -0,0 +1,26 @@
class Branch {
constructor(treeCache, row) {
this.treeCache = treeCache;
this.branchId = row.branchId;
this.noteId = row.noteId;
this.note = null;
this.parentNoteId = row.parentNoteId;
this.notePosition = row.notePosition;
this.prefix = row.prefix;
this.isExpanded = row.isExpanded;
}
async getNote() {
return await this.treeCache.getNote(this.noteId);
}
isTopLevel() {
return this.parentNoteId === 'root';
}
get toString() {
return `Branch(branchId=${this.branchId})`;
}
}
export default Branch;

View File

@@ -0,0 +1,18 @@
import NoteShort from './note_short.js';
class NoteFull extends NoteShort {
constructor(treeCache, row) {
super(treeCache, row);
this.content = row.content;
if (this.content !== "" && this.isJson()) {
try {
this.jsonContent = JSON.parse(this.content);
}
catch(e) {}
}
}
}
export default NoteFull;

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