Compare commits

...

281 Commits

Author SHA1 Message Date
zadam
deb67d6275 release 0.40.5 2020-03-08 21:05:52 +01:00
zadam
e4039ea5e1 fixed printing relation map 2020-03-08 09:24:03 +01:00
zadam
78a50be663 don't print control buttons 2020-03-07 22:18:12 +01:00
zadam
3d3ad3b99b include themes.css for printing to have default CSS variables available 2020-03-07 22:14:07 +01:00
zadam
0d9cdcac85 load appropriate styles for printing book notes, closes #899 2020-03-07 22:01:34 +01:00
zadam
350331e2ef fix 500px constant height of relation maps, closes #882 2020-02-28 15:20:54 +01:00
zadam
e8a9e49e9e release 0.40.4 2020-02-24 22:59:22 +01:00
zadam
fb55cdaea6 release 0.40.4 2020-02-24 22:58:18 +01:00
zadam
b9b2cc8364 make sure $rendered is always jquery object 2020-02-24 22:55:12 +01:00
zadam
8dfdd090f5 use note's css class also in book and included note, closes #879 2020-02-24 22:46:27 +01:00
zadam
fe7705524a fix include note in mobile frontend, closes #878 2020-02-24 22:37:45 +01:00
zadam
2b1b7774f8 make images in text notes rendered responsively in a book, fixes #871 2020-02-19 22:24:31 +01:00
zadam
2d58019d6e allow to setup web version as a sync client 2020-02-19 22:10:40 +01:00
zadam
fe31f08c0d show setup window if DB is not initialized 2020-02-19 22:09:49 +01:00
zadam
ad7a55d305 always autofix note_contents.content = NULL sync issue after note erasion 2020-02-19 19:51:36 +01:00
zadam
4ce4ac9584 release 0.40.3 2020-02-09 10:48:23 +01:00
zadam
88bd65c679 external links are also parsed and label is created for them, closes #851 2020-02-09 10:45:07 +01:00
zadam
9eab3026bb display advanced item in the tree context menu in the middle to avoid being hidden, closes #853 2020-02-09 10:15:35 +01:00
zadam
7abaedbf31 add possibility to change clipper parent to a fixed note instead of day notes, fixes #854 2020-02-09 10:12:02 +01:00
zadam
402718d293 return focus to the previously focused element after closing the dialog, fixes #861 2020-02-09 10:00:13 +01:00
zadam
990a84c202 Merge remote-tracking branch 'origin/master' 2020-02-09 08:38:47 +01:00
zadam
d8e181a828 Merge branch 'stable'
# Conflicts:
#	package.json
#	src/services/build.js
2020-02-09 08:38:29 +01:00
jasontan056
adb8caa8a2 Fix corner case preventing notes from being created before ckeditor is initialized (#849)
* Pass deleteId to deleteBranch in ensureNoteIsAbsentFromParent

* Add checks for whether window.cutToNote is defined.

* check ckEditor initialized.
2020-02-02 09:28:19 +01:00
zadam
7651c53363 release 0.40.2 2020-02-01 10:17:51 +01:00
zadam
0f25c8a95f autobook should not be active on the mobile interface 2020-02-01 10:17:03 +01:00
zadam
1a49894adf fix tree loading on mobile interface, closes #839 2020-02-01 10:04:18 +01:00
zadam
bd8c078fb9 Merge remote-tracking branch 'origin/master' 2020-02-01 09:30:14 +01:00
zadam
6e060b87b8 fix date parsing in local timezone, closes #845 2020-02-01 09:29:56 +01:00
jasontan056
2375b170ba Pass deleteId to deleteBranch in ensureNoteIsAbsentFromParent (#846) 2020-02-01 09:05:23 +01:00
zadam
828cce0d78 release 0.40.1 2020-01-19 15:45:06 +01:00
zadam
ab535bf147 fixes of the new CopyWithoutFormatting 2020-01-19 09:25:35 +01:00
Heniker
1876664dfb add hotkey to copy contents with line breaks, fixes #349 (#831) 2020-01-19 09:16:36 +01:00
zadam
1690248e24 migration script to fix contentLength = -1 in new notes 2020-01-19 09:08:33 +01:00
zadam
cbeb8ea17e fix setting contentLength 2020-01-19 09:03:26 +01:00
zadam
9a13edd490 release 0.39.6 2020-01-18 20:52:14 +01:00
zadam
c9113ae752 Merge branch 'stable' 2020-01-18 09:24:39 +01:00
zadam
0ec11d29ba fix creating root calendar note when missing, #752 2020-01-18 08:59:46 +01:00
zadam
a6cd25071e more robust handling of sync error, fixes #830 2020-01-18 08:48:36 +01:00
zadam
20fdeee048 better error handling for search notes 2020-01-13 19:35:06 +01:00
zadam
a79a063d17 release 0.40.0-beta 2020-01-11 09:54:31 +01:00
zadam
5e91b1b5e0 package updates 2020-01-11 09:50:05 +01:00
zadam
7877443fb4 Merge branch 'stable' 2020-01-11 09:41:42 +01:00
zadam
759e47bfcf using included note should create a relation, closes #820 2020-01-10 21:41:00 +01:00
zadam
67bdffb27b expose text editor instance and method to add text to editor, closes #819 2020-01-10 20:10:17 +01:00
zadam
3386dace3b provide context menu in text editor also with disabled spellcheck 2020-01-10 19:56:27 +01:00
zadam
f3a2e2cbde Merge branch 'stable' 2020-01-08 21:39:17 +01:00
zadam
3cf3fc13b9 release 0.39.5 2020-01-08 21:01:24 +01:00
zadam
2b69abf8ab fix filter parser for >=, <=, *=* 2020-01-08 20:23:41 +01:00
zadam
3e49a7dbfa all consistency checkers have now fixers 2020-01-08 19:28:22 +01:00
zadam
4c7c3105e8 Merge branch 'stable'
# Conflicts:
#	src/services/notes.js
2020-01-07 22:29:54 +01:00
zadam
f782d2bef9 don't empty script area on save 2020-01-07 22:29:15 +01:00
zadam
ccaa9eae3a fix context submenus, closes #810 2020-01-07 20:53:41 +01:00
zadam
24c5388e0c protection against text note initialization race conditions 2020-01-07 19:48:26 +01:00
zadam
1cd2711097 Merge branch 'stable' 2020-01-05 20:02:19 +01:00
zadam
f0dfe7d552 release 0.39.4 2020-01-04 22:01:20 +01:00
zadam
3b8b4da149 task context progress fixes 2020-01-04 21:59:28 +01:00
zadam
2150619d62 activateNewNote does not reload whole tree anymore 2020-01-04 21:53:49 +01:00
zadam
acb76e0630 added notification to empty book notes otherwise they look suspiciously empty 2020-01-04 21:24:39 +01:00
zadam
fdb46f9329 fallback image saving without optimization will still compress image 2020-01-04 20:10:30 +01:00
zadam
ca587cccf6 book note type has automatically empty mime type 2020-01-04 19:34:01 +01:00
zadam
571772069a force SQLite to choose particular index for attribute search since it sometimes led to very inefficient query plans 2020-01-04 19:22:16 +01:00
zadam
79e7762c72 indent data notes 2020-01-04 18:44:54 +01:00
zadam
d025cfee1b empty note with just included note should be saved, closes #807 2020-01-04 13:22:07 +01:00
zadam
7793552443 fix display of show sidebar button 2020-01-04 10:05:03 +01:00
zadam
f377a84fa1 hide/show button z-index tweak 2020-01-04 09:21:42 +01:00
zadam
b8f2797abf better behavior of note info widget in tight width 2020-01-04 09:04:08 +01:00
zadam
54d89a9f47 allow configuring the time period before deleted notes are erased (and changed the default to 7 days) 2020-01-03 22:32:49 +01:00
zadam
1699646b39 fix undelete info messages 2020-01-03 21:32:41 +01:00
zadam
94a0a31f17 refactoring of tree handling to recommended fancytree 2020-01-03 21:15:45 +01:00
zadam
10219fb9dd Merge branch 'stable' 2020-01-03 20:13:13 +01:00
zadam
50431dd55a hide "show/hide sidebar" button in zen mode, fixes #805 2020-01-03 20:12:19 +01:00
zadam
17b23d92ef delete/undelete fixes 2020-01-03 13:41:44 +01:00
zadam
14f3c783f2 undelete note WIP 2020-01-03 13:14:43 +01:00
zadam
c1d0a1e07b undelete note WIP 2020-01-03 10:48:36 +01:00
zadam
1d3608b7bf fix recent note dialog for deleted notes 2020-01-03 09:04:38 +01:00
zadam
d0c655f66a fix removeAttribute to take into account attribute name, closes #804 2020-01-03 08:55:13 +01:00
zadam
ac75fd2ca3 focus and activate should work together 2020-01-02 19:07:50 +01:00
zadam
3b98428c8c change order of execution to mitigate race conditions 2020-01-02 19:03:54 +01:00
zadam
7d877d0fef release 0.39.3 2020-01-02 10:43:41 +01:00
zadam
cb79f2c7eb updated ckeditor build to support cuttonote 2020-01-02 10:38:29 +01:00
zadam
547a5714ae fancytree 2.34.0 2020-01-01 23:13:49 +01:00
zadam
82420fe5f6 reactivate "cut to note", closes #795 2020-01-01 22:59:51 +01:00
zadam
395913d1bb API docs update 2020-01-01 22:03:27 +01:00
zadam
f3a29b55ba remove @child🧒sorted label from calendar root in demo document since currently @sorted is added automatically in the code, closes #799 2020-01-01 20:58:59 +01:00
zadam
232321f3a4 fix searching multi-valued attributes, closes #800 2020-01-01 20:49:26 +01:00
zadam
51dddb0bbb fix scrolling behavior in firefox 2020-01-01 19:57:57 +01:00
zadam
8b9bf6e46f expand to note to be able to activate note 2020-01-01 19:46:27 +01:00
zadam
631a75deec release 0.39.2-beta 2019-12-30 19:48:54 +01:00
zadam
3f1d0e5872 small template refactoring 2019-12-30 19:46:48 +01:00
zadam
0fe91d0184 include note feature 2019-12-30 19:32:45 +01:00
zadam
2f711a12f8 added "include note" widget to text notes, WIP 2019-12-29 23:46:40 +01:00
zadam
64f32ba38f fix zen mode with new layout, closes #794 2019-12-29 20:48:24 +01:00
zadam
7db4859fb9 Merge remote-tracking branch 'origin/master' 2019-12-29 10:19:11 +01:00
zadam
eee9fcae5c set timeout for the initial sync seed request 2019-12-29 10:19:00 +01:00
zadam
9c4a976342 added some statistic scripts to demo document 2019-12-28 21:52:35 +01:00
zadam
e4a09c6207 fix left pane scrolling, closes #788 2019-12-28 21:37:13 +01:00
zadam
d467db2227 unify API for creating note links 2019-12-28 21:10:02 +01:00
zadam
b8d6ff0542 reset file inputs to allow reuploading the same file again 2019-12-28 19:29:52 +01:00
zadam
a9b8e65c9b force scrolling when width of the content overflows 2019-12-28 19:18:44 +01:00
zadam
bb8b563ece relaunch app after successful sync 2019-12-28 12:55:53 +01:00
zadam
05a8ffb944 small fixes 2019-12-28 10:28:12 +01:00
zadam
2502646a64 release 0.39.1-beta 2019-12-27 21:21:57 +01:00
zadam
3d95d69f80 styling hack for the design to look correct in both FF and chrome 2019-12-27 21:18:14 +01:00
zadam
df751f5d67 fix icon 2019-12-27 21:11:56 +01:00
zadam
4f06b6de78 fix registration of global shortcuts, fixes #786 2019-12-27 20:28:27 +01:00
zadam
d2177cd517 fix detection of desktop build in setup page, closes #787 2019-12-27 20:22:46 +01:00
zadam
0affcf5ad2 move electron-installer-debian to optional dependencies since it can't be installed on windows, #783 2019-12-26 10:02:55 +01:00
zadam
7a416b107b fix electron dep issue in the server version, closes #784 2019-12-25 19:38:28 +01:00
zadam
1ff124dab7 release 0.39.0-beta 2019-12-25 12:18:52 +01:00
zadam
4cb511bad0 fix import with style.css 2019-12-25 12:09:49 +01:00
zadam
73c8d145fa tar export fixes + added code block example to the demo document 2019-12-25 11:56:55 +01:00
zadam
ab79f24729 added content style to tar html export as well 2019-12-25 11:34:45 +01:00
zadam
cec71f65b3 load ckeditor content style for printing to have more similar look to the in-editor, #782 2019-12-25 10:59:45 +01:00
zadam
f75c008154 more sync recovery fixes 2019-12-24 17:49:44 +01:00
zadam
474baa7d95 sync recovery fixes 2019-12-24 16:00:31 +01:00
zadam
a155b6e8d5 create separate window for setup and then main window 2019-12-24 14:42:03 +01:00
zadam
229974e543 added option to enable native title bar (disabled by default) 2019-12-24 12:10:32 +01:00
zadam
6fc19bfb93 Merge branch 'stable' 2019-12-24 11:55:27 +01:00
zadam
ccaa108faa release 0.38.3 2019-12-24 10:51:51 +01:00
zadam
0a72383495 make opening new links from ckeditor more consistent for internal and external links, closes #779 2019-12-24 10:49:16 +01:00
zadam
d389100611 properly cleanup sidebar content after closing tab 2019-12-23 21:46:37 +01:00
zadam
ea7257a5b2 cleaned up experimental attribute pane 2019-12-23 21:13:56 +01:00
zadam
0ebc947fbd visual tweaks 2019-12-23 21:05:47 +01:00
zadam
c89514f9bb saving size and visibility state of the panes 2019-12-23 20:34:29 +01:00
zadam
e0368e395c removed left and right sidebar sizing options 2019-12-23 19:45:59 +01:00
zadam
6986c201dd removed hideTabRowForOneTab option 2019-12-23 19:39:48 +01:00
zadam
bcf163f8a1 css alignment 2019-12-23 17:12:17 +01:00
zadam
15aaead7b9 show/hide switcher for the sidebar 2019-12-23 16:48:34 +01:00
zadam
d29c5c4758 sidebar pulled outside of the tab content and added splitter 2019-12-23 15:50:24 +01:00
zadam
81e2baeee5 blur title buttons after clicking 2019-12-23 13:46:26 +01:00
zadam
4cececafc9 added title bar buttons 2019-12-23 13:34:54 +01:00
zadam
7c8e7a3f4b frameless design with tabs on top, split for left panel 2019-12-23 11:52:45 +01:00
zadam
613d5f93e8 convert css grid design to flex based one 2019-12-23 08:52:57 +01:00
zadam
4f5b23fbf8 tar file export now sets mtime to tar records based on utcDateCreated of a note, closes #487 2019-12-22 10:57:55 +01:00
zadam
a37b9cfc7b steel blue tweaks in demo document 2019-12-21 15:48:36 +01:00
zadam
2bc18bc214 use main border color for tab border 2019-12-21 15:37:51 +01:00
zadam
f31a998c5d path list contains a button to add a new path, closes #611 2019-12-21 13:55:13 +01:00
zadam
5552917533 Merge branch 'stable' 2019-12-21 13:39:12 +01:00
zadam
a9702aa6a2 fix empty checkbox visibility, closes #775 2019-12-21 12:37:24 +01:00
zadam
d1941cc650 Merge branch 'stable' 2019-12-20 21:00:30 +01:00
zadam
f98fa4098f clearer WS error message 2019-12-20 20:17:58 +01:00
zadam
5350496ed4 fix creating note from global ctrl+alt+p shortcut, closes #773 2019-12-20 20:13:21 +01:00
zadam
b62d79044a fix creating note from relation map, closes #771 2019-12-20 19:02:52 +01:00
zadam
0db3722ec2 package updates 2019-12-20 18:04:05 +01:00
zadam
d47403c0e7 implemented sync hash check recovery process 2019-12-18 22:58:30 +01:00
zadam
77311954a1 added sectors for contect check computation 2019-12-18 22:24:13 +01:00
zadam
b7cf4fe96b more general filter parsing 2019-12-18 20:51:48 +01:00
zadam
6d9b702d4c Merge branch 'master' into stable 2019-12-18 20:49:24 +01:00
zadam
6e4c30571c release 0.38.2 2019-12-18 20:21:06 +01:00
zadam
5988776b7e styling of active button 2019-12-18 20:16:11 +01:00
zadam
384da60953 fix regex for parsing out the filters 2019-12-18 19:56:53 +01:00
zadam
21fab412cb sync error mitigation 2019-12-17 22:17:03 +01:00
zadam
eb4dfbad92 sync fixes 2019-12-16 22:47:07 +01:00
zadam
aff9ce97ee small sync fixes 2019-12-16 22:00:44 +01:00
zadam
b0a3f828fb release 0.38.1-beta 2019-12-10 23:09:02 +01:00
zadam
76f5736255 update demo document 2019-12-10 23:08:50 +01:00
zadam
a82066d899 ability to define a keyboard shortcut for paste markdown, closes #761 2019-12-10 23:04:18 +01:00
zadam
45c5287d53 protection against note switching race conditions 2019-12-10 22:35:24 +01:00
zadam
dce54c7af3 run consistency checks on demand 2019-12-10 22:03:00 +01:00
zadam
ee15db0ae1 for title/content search does not make sense to search for protected notes 2019-12-10 21:40:53 +01:00
zadam
c48dbb0913 more debugging info for problems after sync 2019-12-10 21:31:24 +01:00
zadam
882ebdbd8f revert unicode regex since it's still not supported by ff 2019-12-09 23:08:32 +01:00
zadam
6f32d6fabe new mechanism to wait for sync after interaction with backend in Script API using api.waitForMaxKnownSyncId() 2019-12-09 23:07:45 +01:00
zadam
1e123f2390 sql console tweaks 2019-12-09 21:31:38 +01:00
zadam
b29155775e Merge branch 'stable'
# Conflicts:
#	package-lock.json
2019-12-09 20:23:22 +01:00
zadam
b821ed28fc refactor mention setup 2019-12-09 20:09:55 +01:00
zadam
fdb8959aa1 move mime types loading to avoid race conditions 2019-12-09 20:06:36 +01:00
zadam
7554cb057b support unicode characters in filters without quotes, fixes #757 2019-12-09 19:38:48 +01:00
zadam
fab959539a ability to run multiple queries (and get multiple result sets) from SQL console 2019-12-08 11:20:44 +01:00
zadam
afe44a6fe8 Merge branch 'stable' 2019-12-08 10:37:19 +01:00
zadam
7ed526beb7 fix clipper, closes #752 2019-12-08 09:41:31 +01:00
zadam
af695802e3 codeNotesMimeTypes option has not been created for new documents, fixes #755 2019-12-08 09:12:42 +01:00
zadam
156f040880 added "backend log" dialog 2019-12-05 21:25:36 +01:00
zadam
cf6f04defb better port error logging 2019-12-04 22:59:26 +01:00
zadam
a890b91079 release 0.38.0-beta 2019-12-04 22:40:31 +01:00
zadam
e0aabe4f9c fix docs 2019-12-04 22:40:20 +01:00
zadam
01cd9d8fb3 ckeditor 16 with code blocks plugin 2019-12-04 21:21:07 +01:00
zadam
af10f0f52a API docs updates 2019-12-03 22:53:17 +01:00
zadam
aa5ede5039 Merge branch 'stable' 2019-12-03 22:52:37 +01:00
zadam
21e77c83fc release 0.37.8 2019-12-03 22:31:20 +01:00
zadam
b0310e34e2 fix CSS loading from subdomain, #741 2019-12-03 22:13:02 +01:00
zadam
9812b9c272 (mostly) backwards compatible .createNote() backend API 2019-12-03 21:31:46 +01:00
zadam
92d5f91aa6 Merge branch 'stable' 2019-12-03 20:33:42 +01:00
zadam
b0368c7f17 Merge remote-tracking branch 'origin/master' 2019-12-03 20:32:01 +01:00
zadam
8e1f8c869b new @parentCount virtual search attribute, closes #738 2019-12-03 20:31:34 +01:00
nil0x42
4688cda493 [DOC] createNewNote(): Add missing render type (#740) 2019-12-03 19:49:27 +01:00
zadam
761c51069a use max-height for promoted attributes, #739 2019-12-03 19:47:53 +01:00
zadam
4dc285d84f serve favicon from relative path 2019-12-03 19:31:58 +01:00
zadam
0e2f8b5734 don't set date modified on erasing 2019-12-03 19:10:40 +01:00
zadam
a1402c7c66 release 0.37.7 2019-12-02 23:09:42 +01:00
zadam
6ba3e5ab7f backport fix from master to avoid doubled attributes inherited from multiple paths 2019-12-02 23:07:19 +01:00
zadam
f740e52ebf correctly respect label @disableVersioning
(cherry picked from commit dc063983ea)
2019-12-02 23:06:06 +01:00
zadam
e9454e4db7 fix SQL console scrolling
(cherry picked from commit 749bb90713)
2019-12-02 23:05:05 +01:00
zadam
749bb90713 fix SQL console scrolling 2019-12-02 23:04:22 +01:00
zadam
eb8c296e62 attempt to make updating clients via websocket faster 2019-12-02 22:27:06 +01:00
zadam
dc063983ea correctly respect label @disableVersioning 2019-12-02 20:21:52 +01:00
zadam
2595c3ac31 fix attribute loading CTE + don't duplicate attributes in case of cloning, fixes #735 2019-12-02 20:10:10 +01:00
zadam
9cb8bc5dd8 generate document now creates note revisions as well 2019-12-02 19:52:58 +01:00
zadam
3b690f5456 mini optimizations 2019-12-01 15:01:09 +01:00
zadam
7ef2e7769f added index to source_ids 2019-12-01 14:39:24 +01:00
zadam
4c07ac4c4c optimized edited notes on day query 2019-12-01 14:30:59 +01:00
zadam
35cd7f3261 optimization of recursive CTE attribute query 2019-12-01 13:29:39 +01:00
zadam
8c3e2e5eb7 better placement of slow query detection 2019-12-01 12:51:47 +01:00
zadam
d57caee0d3 drop unnecessary indexes 2019-12-01 12:38:07 +01:00
zadam
6e83980784 aligning frontend attributes API with the backend one 2019-12-01 12:22:22 +01:00
zadam
295af1f43e adding file length limit to tar export 2019-12-01 11:49:14 +01:00
zadam
ed2afe5c20 Merge branch 'stable' 2019-12-01 11:32:33 +01:00
zadam
bfc7570e14 don't convert MD to HTML if "import markdown as text" is not selected, closes #733 2019-12-01 11:27:22 +01:00
zadam
d9b9d730bb moving from inherited attribute queries to owned one where it makes sense 2019-12-01 11:10:04 +01:00
zadam
ba8a8dca7b adding more "owned" attribute methods to Note entity 2019-12-01 10:57:28 +01:00
zadam
29eb5a8435 Merge branch 'stable'
# Conflicts:
#	src/services/notes.js
#	src/tools/generate_document.js
2019-12-01 10:32:30 +01:00
zadam
5de92171a7 use owned attributes where it's a better fit 2019-12-01 10:28:05 +01:00
zadam
29c5e394ab generate document now creates also labels and relations 2019-12-01 10:20:18 +01:00
zadam
07b3d11fe5 fix generate new document script 2019-12-01 09:19:16 +01:00
zadam
67663fba50 fixes 2019-11-30 11:36:36 +01:00
zadam
995ebbf577 removed foreign keys PRAGMAs since foreign key constraints are not used anymore 2019-11-30 10:41:53 +01:00
zadam
d0e6be3e0c entity stat as part of consistency checks 2019-11-30 09:15:08 +01:00
zadam
01370a5968 fix anonymization according to latest schema 2019-11-29 21:42:24 +01:00
zadam
6c561b5764 rename API .createNote() to .createNewNote() to allow future backwards compatibility 2019-11-28 22:35:55 +01:00
zadam
2953f1bdb8 adding some standard classes for tree items 2019-11-28 22:05:27 +01:00
zadam
1c057cac75 small script API additions 2019-11-27 23:07:10 +01:00
zadam
0415efd33b create year/month/date labels/relation only when the note is created 2019-11-27 19:42:10 +01:00
zadam
e58dc829f5 bootstrap 4.4.0 2019-11-27 18:59:42 +01:00
zadam
90d10c1ff3 Merge branch 'stable'
# Conflicts:
#	package-lock.json
#	src/public/javascripts/services/tree_context_menu.js
#	src/services/import/enex.js
2019-11-27 18:54:49 +01:00
zadam
5b30291601 release 0.37.6 2019-11-26 22:50:08 +01:00
zadam
5193f073e9 if there's no updated field use created #725 2019-11-26 22:47:54 +01:00
zadam
6c7d8a9667 preserve dateCreated and dateModified in ENEX import, fixes #725 2019-11-26 22:02:21 +01:00
zadam
5e9bedd903 fixed sidebar switch in the options dialog 2019-11-26 20:46:49 +01:00
zadam
e712990c03 don't remove active tab after deleting note to preserve tab state, fixes #727 2019-11-26 20:42:34 +01:00
zadam
91487b338a make the context menu scrollable when exceeding total window height, closes #723 2019-11-26 19:55:52 +01:00
zadam
3ff24d53e5 fix decrypting note titles on the server installation, closes #724 2019-11-26 19:49:52 +01:00
zadam
94c904fb40 fix context menu over root, closes #726 2019-11-26 19:42:47 +01:00
zadam
56c7b7f5bd API docs update 2019-11-25 22:45:09 +01:00
zadam
19206d1e0d backend API note creation updates 2019-11-25 21:24:41 +01:00
zadam
420be6d8c6 fix demo document relation map and shortcuts before db initialization 2019-11-24 22:42:54 +01:00
zadam
dbd2040bee shortcut fixes 2019-11-24 22:15:33 +01:00
zadam
1e979d71c7 changed note selection in tree using keyboard - now only sibling nodes are selected 2019-11-24 22:01:10 +01:00
zadam
6bbd4c59bc reduce flicker of "create new day note" 2019-11-24 21:40:50 +01:00
zadam
3a54d00e2b added shortcut filter in the options dialog 2019-11-24 20:20:13 +01:00
zadam
499c9a7381 separated some context menu items into "advanced" 2019-11-24 20:00:54 +01:00
zadam
cd139bdd76 note can be activated through 'keyboardShortcut' label 2019-11-24 18:32:18 +01:00
zadam
60c3b5cccc Merge remote-tracking branch 'origin/master' 2019-11-24 14:10:19 +01:00
zadam
540f9f933a update demo.tar with correct system links 2019-11-24 14:10:11 +01:00
Benoit Stahl
a59943094e this input is text (#719) 2019-11-24 10:44:09 +01:00
zadam
c400a7143c reorganization of shortcuts in the options 2019-11-24 10:40:18 +01:00
zadam
1f37d00e42 updating also shortcuts in element titles 2019-11-24 10:14:30 +01:00
zadam
d21e824343 binding remaining actions 2019-11-24 09:50:19 +01:00
zadam
ff3f0ee0a0 Merge branch 'stable' 2019-11-23 23:07:07 +01:00
zadam
01ff34b5d4 moving edit branch prefix, search in subtree and toggle note hoisting to global entrypoints instead of being tree specific 2019-11-23 23:06:25 +01:00
zadam
0cde7ede24 updated note menu with current shortcuts 2019-11-23 22:56:35 +01:00
zadam
92cb723d0c updated inapp help with current shortcuts 2019-11-23 22:17:08 +01:00
zadam
e4bec265c1 system links (internal, image, relation map) should follow camelCase convention used for other attributes 2019-11-23 20:54:49 +01:00
zadam
4da6234911 Merge branch 'stable' 2019-11-22 22:59:08 +01:00
zadam
ff0245f05f dynamically translating kbd based on actions 2019-11-22 22:35:59 +01:00
zadam
98c81faedb Merge branch 'stable' 2019-11-22 20:36:47 +01:00
zadam
465c3b87a7 tree keyboard shortcuts 2019-11-21 22:24:07 +01:00
zadam
0e5028acd3 support global shortcuts with global: prefix 2019-11-21 21:12:07 +01:00
zadam
00c295e4bf upgrade to electron 8.0.0-beta.3 2019-11-21 19:36:34 +01:00
zadam
7084ed4fb1 Merge branch 'stable' 2019-11-20 23:11:21 +01:00
zadam
587134c2f8 separators and fixes 2019-11-20 23:10:41 +01:00
zadam
5fac2c7633 saving options keyboard shortcuts 2019-11-20 22:48:32 +01:00
zadam
08a518479b keyboard shortcuts options pane 2019-11-20 21:35:18 +01:00
zadam
bcdfb47939 Merge branch 'stable' 2019-11-19 23:34:35 +01:00
zadam
667471e7bb toggle zen mode in the global menu 2019-11-19 23:33:07 +01:00
zadam
3c4ec7fe1a keyboard shortcuts WIP 2019-11-19 23:02:54 +01:00
zadam
4bd7438fca shortcuts WIP 2019-11-19 20:53:04 +01:00
zadam
0ae9c8da17 Merge branch 'stable' 2019-11-19 19:32:49 +01:00
zadam
f921562346 Merge branch 'm38'
# Conflicts:
#	docs/backend_api/Note.html
#	docs/frontend_api/NoteShort.html
#	src/services/import/enex.js
2019-11-18 23:26:29 +01:00
zadam
643d9077fc configurable keyboard shortcuts WIP 2019-11-18 23:01:31 +01:00
zadam
b4709e8ee5 "distraction free mode" renamed to more standard "zen mode" 2019-11-18 19:32:27 +01:00
zadam
a1181623b7 hide sidebar button styling 2019-11-17 11:58:05 +01:00
zadam
73a6c66379 header styling changes 2019-11-17 11:39:06 +01:00
zadam
1d5daa8dfd action icons now have hover border as well 2019-11-17 11:30:11 +01:00
zadam
c141f4b2c0 tab row styling change 2019-11-17 10:22:26 +01:00
zadam
767aaa18f4 fix OPML import 2019-11-16 18:04:13 +01:00
zadam
8a7228146c simpler ENEX parsing 2019-11-16 17:56:49 +01:00
zadam
3d294c5163 refactoring of note creation APIs WIP 2019-11-16 17:00:22 +01:00
zadam
60231de0ed refactoring of note creation APIs WIP 2019-11-16 12:28:47 +01:00
zadam
13c0411533 refactoring of note creation APIs WIP 2019-11-16 11:09:52 +01:00
zadam
de02e9e889 redesign of createNote APIs, WIP 2019-11-14 23:10:56 +01:00
zadam
e143becb7a styling changes 2019-11-14 20:27:28 +01:00
207 changed files with 13807 additions and 7845 deletions

3
.idea/.gitignore generated vendored
View File

@@ -2,4 +2,5 @@
/workspace.xml
# Datasource local storage ignored files
/dataSources.local.xml
/dataSources.local.xml
/dataSources/

3
.idea/dataSources.xml generated
View File

@@ -6,9 +6,6 @@
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/../trilium-data/document.db</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
</component>
</project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<dataSource name="document.db">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.16">
<database-model serializer="dbm" dbms="SQLITE" family-id="SQLITE" format-version="4.17">
<root id="1">
<ServerVersion>3.25.1</ServerVersion>
</root>
@@ -111,32 +111,34 @@
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="35" parent="7" name="hash">
<column id="35" parent="7" name="deleteId">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="36" parent="7" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="36" parent="7" name="isInheritable">
<Position>11</Position>
<column id="37" parent="7" name="isInheritable">
<Position>12</Position>
<DataType>int|0s</DataType>
<DefaultExpression>0</DefaultExpression>
</column>
<index id="37" parent="7" name="sqlite_autoindex_attributes_1">
<index id="38" parent="7" name="sqlite_autoindex_attributes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>attributeId</ColNames>
<Unique>1</Unique>
</index>
<index id="38" parent="7" name="IDX_attributes_noteId_index">
<index id="39" parent="7" name="IDX_attributes_noteId_index">
<ColNames>noteId</ColNames>
</index>
<index id="39" parent="7" name="IDX_attributes_name_value">
<index id="40" parent="7" name="IDX_attributes_name_value">
<ColNames>name
value</ColNames>
</index>
<index id="40" parent="7" name="IDX_attributes_name_index">
<ColNames>name</ColNames>
</index>
<index id="41" parent="7" name="IDX_attributes_value_index">
<ColNames>value</ColNames>
</index>
@@ -181,34 +183,36 @@ value</ColNames>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="50" parent="8" name="utcDateModified">
<column id="50" parent="8" name="deleteId">
<Position>8</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="51" parent="8" name="utcDateCreated">
<column id="51" parent="8" name="utcDateModified">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="52" parent="8" name="hash">
<column id="52" parent="8" name="utcDateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="53" parent="8" name="hash">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<index id="53" parent="8" name="sqlite_autoindex_branches_1">
<index id="54" parent="8" name="sqlite_autoindex_branches_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>branchId</ColNames>
<Unique>1</Unique>
</index>
<index id="54" parent="8" name="IDX_branches_noteId_parentNoteId">
<index id="55" parent="8" name="IDX_branches_noteId_parentNoteId">
<ColNames>noteId
parentNoteId</ColNames>
</index>
<index id="55" parent="8" name="IDX_branches_noteId">
<ColNames>noteId</ColNames>
</index>
<index id="56" parent="8" name="IDX_branches_parentNoteId">
<ColNames>parentNoteId</ColNames>
</index>
@@ -423,220 +427,228 @@ parentNoteId</ColNames>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="99" parent="12" name="isErased">
<column id="99" parent="12" name="deleteId">
<Position>9</Position>
<DataType>TEXT|0s</DataType>
<DefaultExpression>NULL</DefaultExpression>
</column>
<column id="100" parent="12" name="isErased">
<Position>10</Position>
<DataType>INT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="100" parent="12" name="dateCreated">
<Position>10</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="101" parent="12" name="dateModified">
<column id="101" parent="12" name="dateCreated">
<Position>11</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="102" parent="12" name="utcDateCreated">
<column id="102" parent="12" name="dateModified">
<Position>12</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="103" parent="12" name="utcDateModified">
<column id="103" parent="12" name="utcDateCreated">
<Position>13</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="104" parent="12" name="sqlite_autoindex_notes_1">
<column id="104" parent="12" name="utcDateModified">
<Position>14</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="105" parent="12" name="sqlite_autoindex_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<Unique>1</Unique>
</index>
<index id="105" parent="12" name="IDX_notes_title">
<index id="106" parent="12" name="IDX_notes_title">
<ColNames>title</ColNames>
</index>
<index id="106" parent="12" name="IDX_notes_type">
<index id="107" parent="12" name="IDX_notes_type">
<ColNames>type</ColNames>
</index>
<index id="107" parent="12" name="IDX_notes_isDeleted">
<index id="108" parent="12" name="IDX_notes_isDeleted">
<ColNames>isDeleted</ColNames>
</index>
<index id="108" parent="12" name="IDX_notes_dateCreated">
<index id="109" parent="12" name="IDX_notes_dateCreated">
<ColNames>dateCreated</ColNames>
</index>
<index id="109" parent="12" name="IDX_notes_dateModified">
<index id="110" parent="12" name="IDX_notes_dateModified">
<ColNames>dateModified</ColNames>
</index>
<index id="110" parent="12" name="IDX_notes_utcDateCreated">
<index id="111" parent="12" name="IDX_notes_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
</index>
<index id="111" parent="12" name="IDX_notes_utcDateModified">
<index id="112" parent="12" name="IDX_notes_utcDateModified">
<ColNames>utcDateModified</ColNames>
</index>
<key id="112" parent="12">
<key id="113" parent="12">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_notes_1</UnderlyingIndexName>
</key>
<column id="113" parent="13" name="name">
<column id="114" parent="13" name="name">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="114" parent="13" name="value">
<column id="115" parent="13" name="value">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
</column>
<column id="115" parent="13" name="isSynced">
<column id="116" parent="13" name="isSynced">
<Position>3</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>0</DefaultExpression>
</column>
<column id="116" parent="13" name="hash">
<column id="117" parent="13" name="hash">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="117" parent="13" name="utcDateCreated">
<column id="118" parent="13" name="utcDateCreated">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="118" parent="13" name="utcDateModified">
<column id="119" parent="13" name="utcDateModified">
<Position>6</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="119" parent="13" name="sqlite_autoindex_options_1">
<index id="120" parent="13" name="sqlite_autoindex_options_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>name</ColNames>
<Unique>1</Unique>
</index>
<key id="120" parent="13">
<key id="121" parent="13">
<ColNames>name</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_options_1</UnderlyingIndexName>
</key>
<column id="121" parent="14" name="noteId">
<column id="122" parent="14" name="noteId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="122" parent="14" name="notePath">
<column id="123" parent="14" name="notePath">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="123" parent="14" name="hash">
<column id="124" parent="14" name="hash">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
<DefaultExpression>&quot;&quot;</DefaultExpression>
</column>
<column id="124" parent="14" name="utcDateCreated">
<column id="125" parent="14" name="utcDateCreated">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="125" parent="14" name="isDeleted">
<column id="126" parent="14" name="isDeleted">
<Position>5</Position>
<DataType>INT|0s</DataType>
</column>
<index id="126" parent="14" name="sqlite_autoindex_recent_notes_1">
<index id="127" parent="14" name="sqlite_autoindex_recent_notes_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>noteId</ColNames>
<Unique>1</Unique>
</index>
<key id="127" parent="14">
<key id="128" parent="14">
<ColNames>noteId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_recent_notes_1</UnderlyingIndexName>
</key>
<column id="128" parent="15" name="sourceId">
<column id="129" parent="15" name="sourceId">
<Position>1</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="129" parent="15" name="utcDateCreated">
<column id="130" parent="15" name="utcDateCreated">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="130" parent="15" name="sqlite_autoindex_source_ids_1">
<index id="131" parent="15" name="sqlite_autoindex_source_ids_1">
<NameSurrogate>1</NameSurrogate>
<ColNames>sourceId</ColNames>
<Unique>1</Unique>
</index>
<key id="131" parent="15">
<index id="132" parent="15" name="IDX_source_ids_utcDateCreated">
<ColNames>utcDateCreated</ColNames>
</index>
<key id="133" parent="15">
<ColNames>sourceId</ColNames>
<Primary>1</Primary>
<UnderlyingIndexName>sqlite_autoindex_source_ids_1</UnderlyingIndexName>
</key>
<column id="132" parent="16" name="type">
<column id="134" parent="16" name="type">
<Position>1</Position>
<DataType>text|0s</DataType>
</column>
<column id="133" parent="16" name="name">
<column id="135" parent="16" name="name">
<Position>2</Position>
<DataType>text|0s</DataType>
</column>
<column id="134" parent="16" name="tbl_name">
<column id="136" parent="16" name="tbl_name">
<Position>3</Position>
<DataType>text|0s</DataType>
</column>
<column id="135" parent="16" name="rootpage">
<column id="137" parent="16" name="rootpage">
<Position>4</Position>
<DataType>int|0s</DataType>
</column>
<column id="136" parent="16" name="sql">
<column id="138" parent="16" name="sql">
<Position>5</Position>
<DataType>text|0s</DataType>
</column>
<column id="137" parent="17" name="name">
<column id="139" parent="17" name="name">
<Position>1</Position>
</column>
<column id="138" parent="17" name="seq">
<column id="140" parent="17" name="seq">
<Position>2</Position>
</column>
<column id="139" parent="18" name="id">
<column id="141" parent="18" name="id">
<Position>1</Position>
<DataType>INTEGER|0s</DataType>
<NotNull>1</NotNull>
<SequenceIdentity>1</SequenceIdentity>
</column>
<column id="140" parent="18" name="entityName">
<column id="142" parent="18" name="entityName">
<Position>2</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="141" parent="18" name="entityId">
<column id="143" parent="18" name="entityId">
<Position>3</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="142" parent="18" name="sourceId">
<column id="144" parent="18" name="sourceId">
<Position>4</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<column id="143" parent="18" name="utcSyncDate">
<column id="145" parent="18" name="utcSyncDate">
<Position>5</Position>
<DataType>TEXT|0s</DataType>
<NotNull>1</NotNull>
</column>
<index id="144" parent="18" name="IDX_sync_entityName_entityId">
<index id="146" parent="18" name="IDX_sync_entityName_entityId">
<ColNames>entityName
entityId</ColNames>
<Unique>1</Unique>
</index>
<index id="145" parent="18" name="IDX_sync_utcSyncDate">
<index id="147" parent="18" name="IDX_sync_utcSyncDate">
<ColNames>utcSyncDate</ColNames>
</index>
<key id="146" parent="18">
<key id="148" parent="18">
<ColNames>id</ColNames>
<Primary>1</Primary>
</key>

View File

@@ -1,4 +1,4 @@
FROM node:12.13.0-alpine
FROM node:12.14.0-alpine
# Create app directory
WORKDIR /usr/src/app

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
PKG_DIR=dist/trilium-linux-x64-server
NODE_VERSION=12.13.0
NODE_VERSION=12.14.0
if [ "$1" != "DONTCOPY" ]
then

View File

@@ -2,6 +2,9 @@
# Instance name can be used to distinguish between different instances
instanceName=
# Disable automatically generating desktop icon
# noDesktopIcon=true
[Network]
# host setting is relevant only for web deployments - set the host on which the server will listen
# host=0.0.0.0

Binary file not shown.

View File

@@ -0,0 +1,3 @@
UPDATE attributes SET name = 'internalLink' WHERE name = 'internal-link';
UPDATE attributes SET name = 'imageLink' WHERE name = 'image-link';
UPDATE attributes SET name = 'relationMapLink' WHERE name = 'relation-map-link';

View File

@@ -0,0 +1,5 @@
DROP INDEX IF EXISTS IDX_attributes_name_index;
DROP INDEX IF EXISTS IDX_branches_noteId;
CREATE INDEX IDX_source_ids_utcDateCreated
on source_ids (utcDateCreated);

View File

@@ -0,0 +1,81 @@
CREATE TABLE IF NOT EXISTS "notes_mig" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
INSERT INTO notes_mig (noteId, title, contentLength, isProtected, type, mime, hash, isDeleted, isErased, dateCreated, dateModified, utcDateCreated, utcDateModified)
SELECT noteId, title, -1, isProtected, type, mime, hash, isDeleted, isErased, dateCreated, dateModified, utcDateCreated, utcDateModified FROM notes;
DROP TABLE notes;
ALTER TABLE notes_mig RENAME TO notes;
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "branches_mig" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
INSERT INTO branches_mig (branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated, hash)
SELECT branchId, noteId, parentNoteId, notePosition, prefix, isExpanded, isDeleted, utcDateModified, utcDateCreated, hash FROM branches;
DROP TABLE branches;
ALTER TABLE branches_mig RENAME TO branches;
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "attributes_mig"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
INSERT INTO attributes_mig (attributeId, noteId, type, name, value, position, utcDateCreated, utcDateModified, isDeleted, hash, isInheritable)
SELECT attributeId, noteId, type, name, value, position, utcDateCreated, utcDateModified, isDeleted, hash, isInheritable FROM attributes;
DROP TABLE attributes;
ALTER TABLE attributes_mig RENAME TO attributes;
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);

View File

@@ -0,0 +1 @@
UPDATE notes SET contentLength = COALESCE((SELECT COALESCE(LENGTH(content), 0) FROM note_contents WHERE note_contents.noteId = notes.noteId), -1);

View File

@@ -9,6 +9,8 @@ CREATE TABLE IF NOT EXISTS "source_ids" (
`utcDateCreated` TEXT NOT NULL,
PRIMARY KEY(`sourceId`)
);
CREATE INDEX IDX_source_ids_utcDateCreated
on source_ids (utcDateCreated);
CREATE TABLE IF NOT EXISTS "api_tokens"
(
apiTokenId TEXT PRIMARY KEY NOT NULL,
@@ -25,19 +27,6 @@ CREATE TABLE IF NOT EXISTS "options"
utcDateCreated TEXT not null,
utcDateModified TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "attributes"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
`entityName`,
`entityId`
@@ -45,14 +34,6 @@ CREATE UNIQUE INDEX `IDX_sync_entityName_entityId` ON `sync` (
CREATE INDEX `IDX_sync_utcSyncDate` ON `sync` (
`utcSyncDate`
);
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_name_index
on attributes (name);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);
CREATE TABLE IF NOT EXISTS "note_contents" (
`noteId` TEXT NOT NULL,
`content` TEXT NULL DEFAULT NULL,
@@ -68,47 +49,10 @@ CREATE TABLE recent_notes
utcDateCreated TEXT not null,
isDeleted INT
);
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` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
CREATE INDEX `IDX_branches_noteId` ON `branches` (`noteId`);
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "note_revision_contents" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`content` TEXT,
hash TEXT DEFAULT '' NOT NULL,
`utcDateModified` TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
`noteId` TEXT NOT NULL,
`title` TEXT,
@@ -128,3 +72,61 @@ CREATE INDEX `IDX_note_revisions_utcDateCreated` ON `note_revisions` (`utcDateCr
CREATE INDEX `IDX_note_revisions_utcDateLastEdited` ON `note_revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_note_revisions_dateCreated` ON `note_revisions` (`dateCreated`);
CREATE INDEX `IDX_note_revisions_dateLastEdited` ON `note_revisions` (`dateLastEdited`);
CREATE TABLE IF NOT EXISTS "notes" (
`noteId` TEXT NOT NULL,
`title` TEXT NOT NULL DEFAULT "note",
`contentLength` INT NOT NULL,
`isProtected` INT NOT NULL DEFAULT 0,
`type` TEXT NOT NULL DEFAULT 'text',
`mime` TEXT NOT NULL DEFAULT 'text/html',
`hash` TEXT DEFAULT "" NOT NULL,
`isDeleted` INT NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`isErased` INT NOT NULL DEFAULT 0,
`dateCreated` TEXT NOT NULL,
`dateModified` TEXT NOT NULL,
`utcDateCreated` TEXT NOT NULL,
`utcDateModified` TEXT NOT NULL,
PRIMARY KEY(`noteId`));
CREATE INDEX `IDX_notes_isDeleted` ON `notes` (`isDeleted`);
CREATE INDEX `IDX_notes_title` ON `notes` (`title`);
CREATE INDEX `IDX_notes_type` ON `notes` (`type`);
CREATE INDEX `IDX_notes_dateCreated` ON `notes` (`dateCreated`);
CREATE INDEX `IDX_notes_dateModified` ON `notes` (`dateModified`);
CREATE INDEX `IDX_notes_utcDateModified` ON `notes` (`utcDateModified`);
CREATE INDEX `IDX_notes_utcDateCreated` ON `notes` (`utcDateCreated`);
CREATE TABLE IF NOT EXISTS "branches" (
`branchId` TEXT NOT NULL,
`noteId` TEXT NOT NULL,
`parentNoteId` TEXT NOT NULL,
`notePosition` INTEGER NOT NULL,
`prefix` TEXT,
`isExpanded` INTEGER NOT NULL DEFAULT 0,
`isDeleted` INTEGER NOT NULL DEFAULT 0,
`deleteId` TEXT DEFAULT NULL,
`utcDateModified` TEXT NOT NULL,
utcDateCreated TEXT NOT NULL,
hash TEXT DEFAULT "" NOT NULL,
PRIMARY KEY(`branchId`));
CREATE INDEX `IDX_branches_noteId_parentNoteId` ON `branches` (`noteId`,`parentNoteId`);
CREATE INDEX IDX_branches_parentNoteId ON branches (parentNoteId);
CREATE TABLE IF NOT EXISTS "attributes"
(
attributeId TEXT not null primary key,
noteId TEXT not null,
type TEXT not null,
name TEXT not null,
value TEXT default '' not null,
position INT default 0 not null,
utcDateCreated TEXT not null,
utcDateModified TEXT not null,
isDeleted INT not null,
`deleteId` TEXT DEFAULT NULL,
hash TEXT default "" not null,
isInheritable int DEFAULT 0 NULL);
CREATE INDEX IDX_attributes_name_value
on attributes (name, value);
CREATE INDEX IDX_attributes_noteId_index
on attributes (noteId);
CREATE INDEX IDX_attributes_value_index
on attributes (value);

View File

@@ -396,7 +396,7 @@ the backend.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line313">line 313</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line394">line 394</a>
</li></ul></dd>
@@ -534,6 +534,375 @@ the backend.
<h4 class="name" id="createDataNote"><span class="type-signature"></span>createDataNote<span class="signature">(parentNoteId, title, content)</span><span class="type-signature"> &rarr; {Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
<div class="description">
Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
JSON MIME type. See also createNewNote() for more options.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>parentNoteId</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>title</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>content</code></td>
<td class="type">
<span class="param-type">object</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line204">line 204</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}></span>
</dd>
</dl>
<h4 class="name" id="createNewNote"><span class="type-signature"></span>createNewNote<span class="signature">(params<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>params</code></td>
<td class="type">
<span class="param-type"><a href="global.html#CreateNewNoteParams">CreateNewNoteParams</a></span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line231">line 231</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
object contains newly created entities note and branch
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}></span>
</dd>
</dl>
<h4 class="name" id="createNote"><span class="type-signature"></span>createNote<span class="signature">(parentNoteId, title, content<span class="signature-attributes">opt</span>, extraOptions<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
@@ -760,7 +1129,7 @@ the backend.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line198">line 198</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line258">line 258</a>
</li></ul></dd>
@@ -818,7 +1187,7 @@ the backend.
<h4 class="name" id="createNoteAndRefresh"><span class="type-signature"></span>createNoteAndRefresh<span class="signature">(parentNoteId, title, content<span class="signature-attributes">opt</span>, extraOptions<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
<h4 class="name" id="createTextNote"><span class="type-signature"></span>createTextNote<span class="signature">(parentNoteId, title, content)</span><span class="type-signature"> &rarr; {Promise.&lt;{note: <a href="Note.html">Note</a>, branch: <a href="Branch.html">Branch</a>}>}</span></h4>
@@ -826,7 +1195,7 @@ the backend.
<div class="description">
Creates new note according to given params and force all connected clients to refresh their tree.
Create text note. See also createNewNote() for more options.
</div>
@@ -850,12 +1219,8 @@ the backend.
<th>Type</th>
<th>Attributes</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
@@ -879,22 +1244,10 @@ the backend.
</td>
<td class="attributes">
</td>
<td class="default">
</td>
<td class="description last">create new note under this parent</td>
<td class="description last"></td>
</tr>
@@ -914,19 +1267,7 @@ the backend.
</td>
<td class="attributes">
</td>
<td class="default">
</td>
<td class="description last"></td>
@@ -949,62 +1290,7 @@ the backend.
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="default">
""
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>extraOptions</code></td>
<td class="type">
<span class="param-type"><a href="global.html#CreateNoteExtraOptions">CreateNoteExtraOptions</a></span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="default">
{}
</td>
<td class="description last"></td>
@@ -1048,7 +1334,7 @@ the backend.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line211">line 211</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line188">line 188</a>
</li></ul></dd>
@@ -1076,10 +1362,6 @@ the backend.
<h5>Returns:</h5>
<div class="param-desc">
object contains newly created entities note and branch
</div>
<dl>
@@ -1533,7 +1815,7 @@ the backend.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line318">line 318</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line399">line 399</a>
</li></ul></dd>
@@ -1997,7 +2279,7 @@ the backend.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line241">line 241</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line314">line 314</a>
</li></ul></dd>
@@ -2765,7 +3047,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line260">line 260</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line341">line 341</a>
</li></ul></dd>
@@ -3418,7 +3700,113 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line232">line 232</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line305">line 305</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Promise.&lt;(<a href="Note.html">Note</a>|null)></span>
</dd>
</dl>
<h4 class="name" id="getTodayNote"><span class="type-signature"></span>getTodayNote<span class="signature">()</span><span class="type-signature"> &rarr; {Promise.&lt;(<a href="Note.html">Note</a>|null)>}</span></h4>
<div class="description">
Returns today's day note. If such note doesn't exist, it is created.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line322">line 322</a>
</li></ul></dd>
@@ -3596,7 +3984,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line251">line 251</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line332">line 332</a>
</li></ul></dd>
@@ -3751,7 +4139,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line269">line 269</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line350">line 350</a>
</li></ul></dd>
@@ -3901,7 +4289,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line224">line 224</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line297">line 297</a>
</li></ul></dd>
@@ -4404,7 +4792,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line290">line 290</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line371">line 371</a>
</li></ul></dd>
@@ -4537,7 +4925,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line276">line 276</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line357">line 357</a>
</li></ul></dd>
@@ -4912,7 +5300,7 @@ transactional by default.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line303">line 303</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line384">line 384</a>
</li></ul></dd>

File diff suppressed because it is too large Load Diff

View File

@@ -107,6 +107,10 @@ class Attribute extends Entity {
async beforeSaving() {
if (!this.value) {
if (this.type === 'relation') {
throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
}
// null value isn't allowed
this.value = "";
}
@@ -137,6 +141,7 @@ class Attribute extends Entity {
// cannot be static!
updatePojo(pojo) {
delete pojo.isOwned;
delete pojo.__note;
}
}

View File

@@ -142,6 +142,10 @@ class Note extends Entity {
/** @returns {Promise} */
async setContent(content) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note ${this.noteId}`);
}
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.length;
@@ -220,26 +224,36 @@ class Note extends Entity {
return null;
}
async loadOwnedAttributesToCache() {
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
return this.__ownedAttributeCache;
}
/**
* @returns {Promise&lt;Attribute[]>} attributes belonging to this specific note (excludes inherited attributes)
* This method is a faster variant of getAttributes() which looks for only owned attributes.
* Use when inheritance is not needed and/or in batch/performance sensitive operations.
*
* This method can be significantly faster than the getAttributes()
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise&lt;Attribute[]>} note's "owned" attributes - excluding inherited ones
*/
async getOwnedAttributes(type, name) {
let query = `SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`;
const params = [this.noteId];
if (type) {
query += ` AND type = ?`;
params.push(type);
if (!this.__ownedAttributeCache) {
await this.loadOwnedAttributesToCache();
}
if (name) {
query += ` AND name = ?`;
params.push(name);
if (type &amp;&amp; name) {
return this.__ownedAttributeCache.filter(attr => attr.type === type &amp;&amp; attr.name === name);
}
else if (type) {
return this.__ownedAttributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__ownedAttributeCache.filter(attr => attr.name === name);
}
else {
return this.__ownedAttributeCache.slice();
}
return await repository.getEntities(query, params);
}
/**
@@ -261,19 +275,26 @@ class Note extends Entity {
}
/**
* @param {string} [name] - attribute name to filter
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise&lt;Attribute[]>} all note's attributes, including inherited ones
*/
async getAttributes(name) {
async getAttributes(type, name) {
if (!this.__attributeCache) {
await this.loadAttributesToCache();
}
if (name) {
if (type &amp;&amp; name) {
return this.__attributeCache.filter(attr => attr.type === type &amp;&amp; attr.name === name);
}
else if (type) {
return this.__attributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
return this.__attributeCache;
return this.__attributeCache.slice();
}
}
@@ -282,7 +303,15 @@ class Note extends Entity {
* @returns {Promise&lt;Attribute[]>} all note's labels (attributes with type label), including inherited ones
*/
async getLabels(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
return await this.getAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {Promise&lt;Attribute[]>} all note's labels (attributes with type label), excluding inherited ones
*/
async getOwnedLabels(name) {
return await this.getOwnedAttributes(LABEL, name);
}
/**
@@ -290,7 +319,7 @@ class Note extends Entity {
* @returns {Promise&lt;Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
return await this.getAttributes(LABEL_DEFINITION, name);
}
/**
@@ -298,7 +327,15 @@ class Note extends Entity {
* @returns {Promise&lt;Attribute[]>} all note's relations (attributes with type relation), including inherited ones
*/
async getRelations(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
return await this.getAttributes(RELATION, name);
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise&lt;Attribute[]>} all note's relations (attributes with type relation), excluding inherited ones
*/
async getOwnedRelations(name) {
return await this.getOwnedAttributes(RELATION, name);
}
/**
@@ -321,7 +358,7 @@ class Note extends Entity {
* @returns {Promise&lt;Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
return await this.getAttributes(RELATION_DEFINITION, name);
}
/**
@@ -330,6 +367,7 @@ class Note extends Entity {
*/
invalidateAttributeCache() {
this.__attributeCache = null;
this.__ownedAttributeCache = null;
}
/** @returns {Promise&lt;void>} */
@@ -339,11 +377,10 @@ class Note extends Entity {
tree(noteId, level) AS (
SELECT ?, 0
UNION
SELECT branches.parentNoteId, tree.level + 1 FROM branches
SELECT branches.parentNoteId, tree.level + 1
FROM branches
JOIN tree ON branches.noteId = tree.noteId
JOIN notes ON notes.noteId = branches.parentNoteId
WHERE notes.isDeleted = 0
AND branches.isDeleted = 0
WHERE branches.isDeleted = 0
),
treeWithAttrs(noteId, level) AS (
SELECT * FROM tree
@@ -362,6 +399,11 @@ class Note extends Entity {
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
const filteredAttributes = attributes.filter((attr, index) => {
// if this exact attribute already appears then don't include it (can happen via cloning)
if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
return false;
}
if (attr.isDefinition()) {
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type &amp;&amp; el.name === attr.name);
@@ -405,6 +447,15 @@ class Note extends Entity {
return !!await this.getAttribute(type, name);
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise&lt;boolean>} true if note has an attribute with given type and name (excluding inherited)
*/
async hasOwnedAttribute(type, name) {
return !!await this.getOwnedAttribute(type, name);
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
@@ -419,7 +470,7 @@ class Note extends Entity {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise&lt;string>} attribute value of given type and name or null if no such attribute exists.
* @returns {Promise&lt;string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getAttributeValue(type, name) {
const attr = await this.getAttribute(type, name);
@@ -427,6 +478,17 @@ class Note extends Entity {
return attr ? attr.value : null;
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise&lt;string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getOwnedAttributeValue(type, name) {
const attr = await this.getOwnedAttribute(type, name);
return attr ? attr.value : null;
}
/**
* Based on enabled, attribute is either set or removed.
*
@@ -454,7 +516,7 @@ class Note extends Entity {
* @returns {Promise&lt;void>}
*/
async setAttribute(type, name, value) {
const attributes = await this.getOwnedAttributes();
const attributes = await this.loadOwnedAttributesToCache();
let attr = attributes.find(attr => attr.type === type &amp;&amp; attr.name === name);
if (attr) {
@@ -488,10 +550,10 @@ class Note extends Entity {
* @returns {Promise&lt;void>}
*/
async removeAttribute(type, name, value) {
const attributes = await this.getOwnedAttributes();
const attributes = await this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
if (attribute.type === type &amp;&amp; (value === undefined || value === attribute.value)) {
if (attribute.type === type &amp;&amp; attribute.name === name &amp;&amp; (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
@@ -500,42 +562,104 @@ class Note extends Entity {
}
}
/**
* @return {Promise&lt;Attribute>}
*/
async addAttribute(type, name, value = "") {
const attr = new Attribute({
noteId: this.noteId,
type: type,
name: name,
value: value
});
await attr.save();
this.invalidateAttributeCache();
return attr;
}
async addLabel(name, value = "") {
return await this.addAttribute(LABEL, name, value);
}
async addRelation(name, targetNoteId) {
return await this.addAttribute(RELATION, name, targetNoteId);
}
/**
* @param {string} name - label name
* @returns {Promise&lt;boolean>} true if label exists (including inherited)
*/
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise&lt;boolean>} true if label exists (excluding inherited)
*/
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;boolean>} true if relation exists (including inherited)
*/
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;boolean>} true if relation exists (excluding inherited)
*/
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise&lt;Attribute>} label if it exists, null otherwise
* @returns {Promise&lt;Attribute|null>} label if it exists, null otherwise
*/
async getLabel(name) { return await this.getAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise&lt;Attribute|null>} label if it exists, null otherwise
*/
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;Attribute>} relation if it exists, null otherwise
* @returns {Promise&lt;Attribute|null>} relation if it exists, null otherwise
*/
async getRelation(name) { return await this.getAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;Attribute|null>} relation if it exists, null otherwise
*/
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise&lt;string>} label value if label exists, null otherwise
* @returns {Promise&lt;string|null>} label value if label exists, null otherwise
*/
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise&lt;string|null>} label value if label exists, null otherwise
*/
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;string>} relation value if relation exists, null otherwise
* @returns {Promise&lt;string|null>} relation value if relation exists, null otherwise
*/
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise&lt;string|null>} relation value if relation exists, null otherwise
*/
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
/**
* @param {string} name
* @returns {Promise&lt;Note>|null} target note of the relation or null (if target is empty or note was not found)
@@ -546,6 +670,16 @@ class Note extends Entity {
return relation ? await repository.getNote(relation.value) : null;
}
/**
* @param {string} name
* @returns {Promise&lt;Note>|null} target note of the relation or null (if target is empty or note was not found)
*/
async getOwnedRelationTarget(name) {
const relation = await this.getOwnedRelation(name);
return relation ? await repository.getNote(relation.value) : null;
}
/**
* Based on enabled, label is either set or removed.
*
@@ -699,7 +833,7 @@ class Note extends Entity {
WHERE noteId = ? AND
isDeleted = 0 AND
type = 'relation' AND
name IN ('internal-link', 'image-link', 'relation-map-link')`, [this.noteId]);
name IN ('internalLink', 'imageLink', 'relationMapLink')`, [this.noteId]);
}
/**
@@ -776,6 +910,16 @@ class Note extends Entity {
return notePaths;
}
/**
* @param ancestorNoteId
* @return {Promise&lt;boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
*/
async isDescendantOfNote(ancestorNoteId) {
const notePaths = await this.getAllNotePaths();
return notePaths.some(path => path.includes(ancestorNoteId));
}
beforeSaving() {
if (!this.isDeleted) {
this.isDeleted = false;
@@ -815,7 +959,9 @@ class Note extends Entity {
delete pojo.isContentAvailable;
delete pojo.__attributeCache;
delete pojo.__ownedAttributeCache;
delete pojo.content;
/** zero references to contentHash, probably can be removed */
delete pojo.contentHash;
}
}

View File

@@ -102,6 +102,313 @@
<h4 class="name" id="CreateNewNoteParams">CreateNewNoteParams</h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">object</span>
</li>
</ul>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>parentNoteId</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">MANDATORY</td>
</tr>
<tr>
<td class="name"><code>title</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">MANDATORY</td>
</tr>
<tr>
<td class="name"><code>content</code></td>
<td class="type">
<span class="param-type">string</span>
|
<span class="param-type">buffer</span>
</td>
<td class="description last">MANDATORY</td>
</tr>
<tr>
<td class="name"><code>type</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">text, code, file, image, search, book, relation-map - MANDATORY</td>
</tr>
<tr>
<td class="name"><code>mime</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">value is derived from default mimes for type</td>
</tr>
<tr>
<td class="name"><code>isProtected</code></td>
<td class="type">
<span class="param-type">boolean</span>
</td>
<td class="description last">default is false</td>
</tr>
<tr>
<td class="name"><code>isExpanded</code></td>
<td class="type">
<span class="param-type">boolean</span>
</td>
<td class="description last">default is false</td>
</tr>
<tr>
<td class="name"><code>prefix</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last">default is empty string</td>
</tr>
<tr>
<td class="name"><code>notePosition</code></td>
<td class="type">
<span class="param-type">int</span>
</td>
<td class="description last">default is last existing notePosition in a parent + 10</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line212">line 212</a>
</li></ul></dd>
</dl>
<h4 class="name" id="CreateNoteAttribute">CreateNoteAttribute</h4>
@@ -290,6 +597,194 @@
<h4 class="name" id="CreateNoteAttribute">CreateNoteAttribute</h4>
<h5>Type:</h5>
<ul>
<li>
<span class="param-type">object</span>
</li>
</ul>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Attributes</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>type</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
</td>
<td class="description last">attribute type - label, relation etc.</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
</td>
<td class="description last">attribute name</td>
</tr>
<tr>
<td class="name"><code>value</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="description last">attribute value</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line233">line 233</a>
</li></ul></dd>
</dl>
<h4 class="name" id="CreateNoteExtraOptions">CreateNoteExtraOptions</h4>
@@ -558,7 +1053,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line180">line 180</a>
<a href="services_backend_script_api.js.html">services/backend_script_api.js</a>, <a href="services_backend_script_api.js.html#line240">line 240</a>
</li></ul></dd>

View File

@@ -198,6 +198,66 @@ function BackendScriptApi(currentNote, apiParams) {
*/
this.toggleNoteInParent = cloningService.toggleNoteInParent;
/**
* @typedef {object} CreateNoteAttribute
* @property {string} type - attribute type - label, relation etc.
* @property {string} name - attribute name
* @property {string} [value] - attribute value
*/
/**
* Create text note. See also createNewNote() for more options.
*
* @param {string} parentNoteId
* @param {string} title
* @param {string} content
* @return {Promise&lt;{note: Note, branch: Branch}>}
*/
this.createTextNote = async (parentNoteId, title, content = '') => await noteService.createNewNote({
parentNoteId,
title,
content,
type: 'text'
});
/**
* Create data note - data in this context means object serializable to JSON. Created note will be of type 'code' and
* JSON MIME type. See also createNewNote() for more options.
*
* @param {string} parentNoteId
* @param {string} title
* @param {object} content
* @return {Promise&lt;{note: Note, branch: Branch}>}
*/
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content, null, '\t'),
type: 'code',
mime: 'application/json'
});
/**
* @typedef {object} CreateNewNoteParams
* @property {string} parentNoteId - MANDATORY
* @property {string} title - MANDATORY
* @property {string|buffer} content - MANDATORY
* @property {string} type - text, code, file, image, search, book, relation-map - MANDATORY
* @property {string} mime - value is derived from default mimes for type
* @property {boolean} isProtected - default is false
* @property {boolean} isExpanded - default is false
* @property {string} prefix - default is empty string
* @property {int} notePosition - default is last existing notePosition in a parent + 10
*/
/**
* @method
*
* @param {CreateNewNoteParams} [params]
* @returns {Promise&lt;{note: Note, branch: Branch}>} object contains newly created entities note and branch
*/
this.createNewNote = noteService.createNewNote;
/**
* @typedef {object} CreateNoteAttribute
* @property {string} type - attribute type - label, relation etc.
@@ -223,25 +283,38 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {CreateNoteExtraOptions} [extraOptions={}]
* @returns {Promise&lt;{note: Note, branch: Branch}>} object contains newly created entities note and branch
*/
this.createNote = noteService.createNote;
this.createNote = async (parentNoteId, title, content = "", extraOptions= {}) => {
extraOptions.parentNoteId = parentNoteId;
extraOptions.title = title;
/**
* Creates new note according to given params and force all connected clients to refresh their tree.
*
* @method
*
* @param {string} parentNoteId - create new note under this parent
* @param {string} title
* @param {string} [content=""]
* @param {CreateNoteExtraOptions} [extraOptions={}]
* @returns {Promise&lt;{note: Note, branch: Branch}>} object contains newly created entities note and branch
*/
this.createNoteAndRefresh = async function(parentNoteId, title, content, extraOptions) {
const ret = await noteService.createNote(parentNoteId, title, content, extraOptions);
const parentNote = await repository.getNote(parentNoteId);
ws.refreshTree();
return ret;
// code note type can be inherited, otherwise text is default
extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
extraOptions.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html';
if (extraOptions.json) {
extraOptions.content = JSON.stringify(content || {}, null, '\t');
extraOptions.type = 'code';
extraOptions.mime = 'application/json';
}
else {
extraOptions.content = content;
}
const {note, branch} = await noteService.createNewNote(extraOptions);
for (const attr of extraOptions.attributes || []) {
await attributeService.createAttribute({
noteId: note.noteId,
type: attr.type,
name: attr.name,
value: attr.value,
isInheritable: !!attr.isInheritable
});
}
return {note, branch};
};
/**
@@ -268,6 +341,14 @@ function BackendScriptApi(currentNote, apiParams) {
*/
this.getDateNote = dateNoteService.getDateNote;
/**
* Returns today's day note. If such note doesn't exist, it is created.
*
* @method
* @returns {Promise&lt;Note|null>}
*/
this.getTodayNote = dateNoteService.getTodayNote;
/**
* Returns note for the first date of the week of the given date.
*

View File

@@ -241,7 +241,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line17">line 17</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line16">line 16</a>
</li></ul></dd>
@@ -357,7 +357,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line13">line 13</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line12">line 12</a>
</li></ul></dd>
@@ -415,7 +415,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line11">line 11</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line10">line 10</a>
</li></ul></dd>
@@ -473,7 +473,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line15">line 15</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line14">line 14</a>
</li></ul></dd>
@@ -549,7 +549,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line21">line 21</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line20">line 20</a>
</li></ul></dd>
@@ -651,7 +651,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line26">line 26</a>
<a href="entities_branch.js.html">entities/branch.js</a>, <a href="entities_branch.js.html#line25">line 25</a>
</li></ul></dd>

View File

@@ -807,7 +807,7 @@
<div class="description">
Activates newly created note. Compared to this.activateNote() also refreshes tree.
Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
</div>
@@ -1240,6 +1240,143 @@
<h4 class="name" id="addTextToActiveTabEditor"><span class="type-signature"></span>addTextToActiveTabEditor<span class="signature">(text)</span><span class="type-signature"></span></h4>
<div class="description">
Adds given text to the editor cursor
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>text</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line305">line 305</a>
</li></ul></dd>
</dl>
@@ -1366,7 +1503,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line384">line 384</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line406">line 406</a>
</li></ul></dd>
@@ -1546,7 +1683,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line291">line 291</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line297">line 297</a>
</li></ul></dd>
@@ -1679,7 +1816,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line235">line 235</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line241">line 241</a>
</li></ul></dd>
@@ -1785,7 +1922,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line297">line 297</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line311">line 311</a>
</li></ul></dd>
@@ -1891,7 +2028,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line303">line 303</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line325">line 325</a>
</li></ul></dd>
@@ -1949,6 +2086,119 @@
<h4 class="name" id="getActiveTabTextEditor"><span class="type-signature"></span>getActiveTabTextEditor<span class="signature">()</span><span class="type-signature"> &rarr; {Editor|null}</span></h4>
<div class="description">
See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line319">line 319</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
CKEditor instance or null (e.g. if active note is not a text note)
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Editor</span>
|
<span class="param-type">null</span>
</dd>
</dl>
<h4 class="name" id="getDateNote"><span class="type-signature"></span>getDateNote<span class="signature">(date)</span><span class="type-signature"> &rarr; {Promise.&lt;<a href="NoteShort.html">NoteShort</a>>}</span></h4>
@@ -2050,7 +2300,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line350">line 350</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line372">line 372</a>
</li></ul></dd>
@@ -2157,7 +2407,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line228">line 228</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line234">line 234</a>
</li></ul></dd>
@@ -2312,7 +2562,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line359">line 359</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line381">line 381</a>
</li></ul></dd>
@@ -2468,7 +2718,7 @@ if some action needs to happen on only one specific instance.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line200">line 200</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line206">line 206</a>
</li></ul></dd>
@@ -2669,7 +2919,7 @@ otherwise (by e.g. createNoteLink())
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line212">line 212</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line218">line 218</a>
</li></ul></dd>
@@ -2775,7 +3025,7 @@ otherwise (by e.g. createNoteLink())
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line341">line 341</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line363">line 363</a>
</li></ul></dd>
@@ -2930,7 +3180,7 @@ otherwise (by e.g. createNoteLink())
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line368">line 368</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line390">line 390</a>
</li></ul></dd>
@@ -3039,7 +3289,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line314">line 314</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line336">line 336</a>
</li></ul></dd>
@@ -3194,7 +3444,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line322">line 322</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line344">line 344</a>
</li></ul></dd>
@@ -3327,7 +3577,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line242">line 242</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line248">line 248</a>
</li></ul></dd>
@@ -3433,7 +3683,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line333">line 333</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line355">line 355</a>
</li></ul></dd>
@@ -3521,7 +3771,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line274">line 274</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line280">line 280</a>
</li></ul></dd>
@@ -3627,7 +3877,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line282">line 282</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line288">line 288</a>
</li></ul></dd>
@@ -3733,7 +3983,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line266">line 266</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line272">line 272</a>
</li></ul></dd>
@@ -3888,7 +4138,7 @@ note.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line220">line 220</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line226">line 226</a>
</li></ul></dd>
@@ -3924,7 +4174,7 @@ note.
<h4 class="name" id="runOnServer"><span class="type-signature"></span>runOnServer<span class="signature">(script, params)</span><span class="type-signature"> &rarr; {Promise.&lt;*>}</span></h4>
<h4 class="name" id="runOnBackend"><span class="type-signature"></span>runOnBackend<span class="signature">(script, params)</span><span class="type-signature"> &rarr; {Promise.&lt;*>}</span></h4>
@@ -3932,7 +4182,7 @@ note.
<div class="description">
Executes given anonymous function on the server.
Executes given anonymous function on the backend.
Internally this serializes the anonymous function into string and sends it to backend via AJAX.
</div>
@@ -4101,6 +4351,92 @@ Internally this serializes the anonymous function into string and sends it to ba
<h4 class="name" id="runOnServer"><span class="type-signature"></span>runOnServer<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="important tag-deprecated">Deprecated:</dt><dd><ul class="dummy"><li>new name of this API call is runOnBackend so use that</li></ul></dd>
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line166">line 166</a>
</li></ul></dd>
</dl>
@@ -4209,7 +4545,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line188">line 188</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line194">line 194</a>
</li></ul></dd>
@@ -4365,7 +4701,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line170">line 170</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line176">line 176</a>
</li></ul></dd>
@@ -4520,7 +4856,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line377">line 377</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line399">line 399</a>
</li></ul></dd>
@@ -4671,7 +5007,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line328">line 328</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line350">line 350</a>
</li></ul></dd>
@@ -4808,7 +5144,7 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line258">line 258</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line264">line 264</a>
</li></ul></dd>
@@ -4945,7 +5281,99 @@ Internally this serializes the anonymous function into string and sends it to ba
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line250">line 250</a>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line256">line 256</a>
</li></ul></dd>
</dl>
<h4 class="name" id="waitUntilSynced"><span class="type-signature"></span>waitUntilSynced<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Trilium runs in backend and frontend process, when something is changed on the backend from script,
frontend will get asynchronously synchronized.
This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line417">line 417</a>
</li></ul></dd>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: KeyboardActions</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: KeyboardActions</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>KeyboardActions<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">blaa vlaa</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="KeyboardActions"><span class="type-signature"></span>new KeyboardActions<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line5">line 5</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="JUMP_TO"><span class="type-signature"></span>JUMP_TO<span class="type-signature"></span></h4>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line7">line 7</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardActions.html">KeyboardActions</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@@ -1220,7 +1220,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line220">line 220</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line227">line 227</a>
</li></ul></dd>
@@ -1278,7 +1278,7 @@
<h4 class="name" id="getAttributes"><span class="type-signature">(async) </span>getAttributes<span class="signature">(name<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {Promise.&lt;Array.&lt;<a href="Attribute.html">Attribute</a>>>}</span></h4>
<h4 class="name" id="getAttributes"><span class="type-signature">(async) </span>getAttributes<span class="signature">(type<span class="signature-attributes">opt</span>, name<span class="signature-attributes">opt</span>)</span><span class="type-signature"> &rarr; {Promise.&lt;Array.&lt;<a href="Attribute.html">Attribute</a>>>}</span></h4>
@@ -1318,6 +1318,39 @@
<tbody>
<tr>
<td class="name"><code>type</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="attributes">
&lt;optional><br>
</td>
<td class="description last">(optional) attribute type to filter</td>
</tr>
<tr>
<td class="name"><code>name</code></td>
@@ -1346,7 +1379,7 @@
<td class="description last">attribute name to filter</td>
<td class="description last">(optional) attribute name to filter</td>
</tr>
@@ -1387,7 +1420,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line160">line 160</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line161">line 161</a>
</li></ul></dd>
@@ -1415,6 +1448,10 @@
<h5>Returns:</h5>
<div class="param-desc">
all note's attributes, including inherited ones
</div>
<dl>
@@ -1561,7 +1598,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line231">line 231</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line238">line 238</a>
</li></ul></dd>
@@ -2124,7 +2161,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line253">line 253</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line260">line 260</a>
</li></ul></dd>
@@ -2291,7 +2328,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line186">line 186</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line193">line 193</a>
</li></ul></dd>
@@ -2458,7 +2495,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line178">line 178</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line185">line 185</a>
</li></ul></dd>
@@ -2613,7 +2650,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line265">line 265</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line272">line 272</a>
</li></ul></dd>
@@ -2972,7 +3009,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line259">line 259</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line266">line 266</a>
</li></ul></dd>
@@ -3139,7 +3176,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line202">line 202</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line209">line 209</a>
</li></ul></dd>
@@ -3306,7 +3343,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line194">line 194</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line201">line 201</a>
</li></ul></dd>
@@ -3461,7 +3498,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line277">line 277</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line284">line 284</a>
</li></ul></dd>
@@ -3631,7 +3668,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line287">line 287</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line294">line 294</a>
</li></ul></dd>
@@ -3782,7 +3819,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line271">line 271</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line278">line 278</a>
</li></ul></dd>
@@ -3892,7 +3929,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line311">line 311</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line318">line 318</a>
</li></ul></dd>
@@ -4066,7 +4103,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line211">line 211</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line218">line 218</a>
</li></ul></dd>
@@ -4323,7 +4360,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line241">line 241</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line248">line 248</a>
</li></ul></dd>
@@ -4478,7 +4515,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line247">line 247</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line254">line 254</a>
</li></ul></dd>
@@ -4536,7 +4573,7 @@
<h4 class="name" id="invalidateAttributeCache"><span class="type-signature"></span>invalidateAttributeCache<span class="signature">()</span><span class="type-signature"></span></h4>
<h4 class="name" id="invalidate__attributeCache"><span class="type-signature"></span>invalidate__attributeCache<span class="signature">()</span><span class="type-signature"></span></h4>
@@ -4589,7 +4626,7 @@ Cache is note instance scoped.
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line302">line 302</a>
<a href="entities_note_short.js.html">entities/note_short.js</a>, <a href="entities_note_short.js.html#line309">line 309</a>
</li></ul></dd>

View File

@@ -34,7 +34,6 @@ class Branch {
this.branchId = row.branchId;
/** @param {string} */
this.noteId = row.noteId;
this.note = null;
/** @param {string} */
this.parentNoteId = row.parentNoteId;
/** @param {int} */
@@ -47,7 +46,7 @@ class Branch {
/** @returns {NoteShort} */
async getNote() {
return await this.treeCache.getNote(this.noteId);
return this.treeCache.getNote(this.noteId);
}
/** @returns {boolean} true if it's top level, meaning its parent is root note */

View File

@@ -182,20 +182,27 @@ class NoteShort {
}
/**
* @param {string} [name] - attribute name to filter
* @returns {Promise&lt;Attribute[]>}
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise&lt;Attribute[]>} all note's attributes, including inherited ones
*/
async getAttributes(name) {
if (!this.attributeCache) {
this.attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
async getAttributes(type, name) {
if (!this.__attributeCache) {
this.__attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
.map(attrRow => new Attribute(this.treeCache, attrRow));
}
if (name) {
return this.attributeCache.filter(attr => attr.name === name);
if (type &amp;&amp; name) {
return this.__attributeCache.filter(attr => attr.type === type &amp;&amp; attr.name === name);
}
else if (type) {
return this.__attributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
return this.attributeCache;
return this.__attributeCache.slice();
}
}
@@ -204,7 +211,7 @@ class NoteShort {
* @returns {Promise&lt;Attribute[]>} all note's labels (attributes with type label), including inherited ones
*/
async getLabels(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
return await this.getAttributes(LABEL, name);
}
/**
@@ -212,7 +219,7 @@ class NoteShort {
* @returns {Promise&lt;Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
return await this.getAttributes(LABEL_DEFINITION, name);
}
/**
@@ -220,7 +227,7 @@ class NoteShort {
* @returns {Promise&lt;Attribute[]>} all note's relations (attributes with type relation), including inherited ones
*/
async getRelations(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
return await this.getAttributes(RELATION, name);
}
/**
@@ -228,7 +235,7 @@ class NoteShort {
* @returns {Promise&lt;Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
return await this.getAttributes(RELATION_DEFINITION, name);
}
/**
@@ -327,8 +334,8 @@ class NoteShort {
* Clear note's attributes cache to force fresh reload for next attribute request.
* Cache is note instance scoped.
*/
invalidateAttributeCache() {
this.attributeCache = null;
invalidate__attributeCache() {
this.__attributeCache = null;
}
/**
@@ -349,7 +356,7 @@ class NoteShort {
const dto = Object.assign({}, this);
delete dto.treeCache;
delete dto.archived;
delete dto.attributeCache;
delete dto.__attributeCache;
return dto;
}

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: exports</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: exports</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>exports<span class="signature">()</span><span class="type-signature"></span></h2>
<div class="class-description">blaa vlaa</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="exports"><span class="type-signature"></span>new exports<span class="signature">()</span><span class="type-signature"></span></h4>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line5">line 5</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="JUMP_TO"><span class="type-signature"></span>JUMP_TO<span class="type-signature"></span></h4>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="services_keyboard_actions.js.html">services/keyboard_action.js</a>, <a href="services_keyboard_actions.js.html#line7">line 7</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="module.exports.html">exports</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@@ -86,13 +86,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
};
/**
* Activates newly created note. Compared to this.activateNote() also refreshes tree.
* Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
*
* @param {string} notePath (or noteId)
* @return {Promise&lt;void>}
*/
this.activateNewNote = async notePath => {
await treeService.reload();
await ws.waitForMaxKnownSyncId();
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
@@ -153,14 +153,14 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
}
/**
* Executes given anonymous function on the server.
* Executes given anonymous function on the backend.
* Internally this serializes the anonymous function into string and sends it to backend via AJAX.
*
* @param {string} script - script to be executed on the backend
* @param {Array.&lt;?>} params - list of parameters to the anonymous function to be send to backend
* @return {Promise&lt;*>} return value of the executed function on the backend
*/
this.runOnServer = async (script, params = []) => {
this.runOnBackend = async (script, params = []) => {
if (typeof script === "function") {
script = script.toString();
}
@@ -187,6 +187,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
}
};
/**
* @deprecated new name of this API call is runOnBackend so use that
* @method
*/
this.runOnServer = this.runOnBackend;
/**
* This is a powerful search method - you can search by attributes and their values, e.g.:
* "@dateModified =* MONTH AND @log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search
@@ -318,12 +324,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/
this.createNoteLink = linkService.createNoteLink;
/**
* Adds given text to the editor cursor
*
* @param {string} text
* @method
*/
this.addTextToActiveTabEditor = linkService.addTextToEditor;
/**
* @method
* @returns {NoteFull} active note (loaded into right pane)
*/
this.getActiveTabNote = noteDetailService.getActiveTabNote;
/**
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
*
* @method
* @returns {Editor|null} CKEditor instance or null (e.g. if active note is not a text note)
*/
this.getActiveTabTextEditor = noteDetailService.getActiveEditor;
/**
* @method
* @returns {Promise&lt;string|null>} returns note path of active note or null if there isn't active note
@@ -410,6 +432,17 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
* @param {function} handler
*/
this.bindGlobalShortcut = utils.bindGlobalShortcut;
/**
* Trilium runs in backend and frontend process, when something is changed on the backend from script,
* frontend will get asynchronously synchronized.
*
* This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
* Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
*
* @method
*/
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
}
export default FrontendScriptApi;</code></pre>

View File

@@ -0,0 +1,280 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: services/keyboard_action.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: services/keyboard_action.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* blaa vlaa
*/
class KeyboardAction {
constructor(params) {
this.optionName = params.optionName;
}
}
/**
* Open "Jump to note" dialog
* @static
*/
KeyboardAction.JumpToNote = new KeyboardAction({
optionName: "JumpToNote",
defaultShortcuts: "mod+j",
description: 'Open "Jump to note" dialog'
});
/** @static */
KeyboardAction.MarkdownToHTML = new KeyboardAction({
optionName: "MarkdownToHTML",
defaultShortcuts: "mod+return"
});
/** @static */
KeyboardAction.NewTab = new KeyboardAction({
optionName: "NewTab",
defaultShortcuts: "mod+t"
});
/** @static */
KeyboardAction.CloseTab = new KeyboardAction({
optionName: "CloseTab",
defaultShortcuts: "mod+w"
});
/** @static */
KeyboardAction.NextTab = new KeyboardAction({
optionName: "NextTab",
defaultShortcuts: "mod+tab"
});
/** @static */
KeyboardAction.PreviousTab = new KeyboardAction({
optionName: "PreviousTab",
defaultShortcuts: "mod+shift+tab"
});
/** @static */
KeyboardAction.CreateNoteAfter = new KeyboardAction({
optionName: "CreateNoteAfter",
defaultShortcuts: "mod+o"
});
/** @static */
KeyboardAction.CreateNoteInto = new KeyboardAction({
optionName: "CreateNoteInto",
defaultShortcuts: "mod+p"
});
/** @static */
KeyboardAction.ScrollToActiveNote = new KeyboardAction({
optionName: "ScrollToActiveNote",
defaultShortcuts: "mod+."
});
/** @static */
KeyboardAction.CollapseTree = new KeyboardAction({
optionName: "CollapseTree",
defaultShortcuts: "alt+c"
});
/** @static */
KeyboardAction.RunSQL = new KeyboardAction({
optionName: "RunSQL",
defaultShortcuts: "mod+return"
});
/** @static */
KeyboardAction.FocusNote = new KeyboardAction({
optionName: "FocusNote",
defaultShortcuts: "return"
});
/** @static */
KeyboardAction.RunCurrentNote = new KeyboardAction({
optionName: "RunCurrentNote",
defaultShortcuts: "mod+return"
});
/** @static */
KeyboardAction.ClipboardCopy = new KeyboardAction({
optionName: "ClipboardCopy",
defaultShortcuts: "mod+c"
});
/** @static */
KeyboardAction.ClipboardPaste = new KeyboardAction({
optionName: "ClipboardPaste",
defaultShortcuts: "mod+v"
});
/** @static */
KeyboardAction.ClipboardCut = new KeyboardAction({
optionName: "ClipboardCut",
defaultShortcuts: "mod+x"
});
/** @static */
KeyboardAction.SelectAllNotesInParent = new KeyboardAction({
optionName: "SelectAllNotesInParent",
defaultShortcuts: "mod+a"
});
/** @static */
KeyboardAction.Undo = new KeyboardAction({
optionName: "Undo",
defaultShortcuts: "mod+z"
});
/** @static */
KeyboardAction.Redo = new KeyboardAction({
optionName: "Redo",
defaultShortcuts: "mod+y"
});
/** @static */
KeyboardAction.AddLinkToText = new KeyboardAction({
optionName: "AddLinkToText",
defaultShortcuts: "mod+l"
});
/** @static */
KeyboardAction.CloneNotesTo = new KeyboardAction({
optionName: "CloneNotesTo",
defaultShortcuts: "mod+shift+c"
});
/** @static */
KeyboardAction.MoveNotesTo = new KeyboardAction({
optionName: "MoveNotesTo",
defaultShortcuts: "mod+shift+c"
});
/** @static */
KeyboardAction.SearchNotes = new KeyboardAction({
optionName: "SearchNotes",
defaultShortcuts: "mod+s"
});
/** @static */
KeyboardAction.ShowAttributes = new KeyboardAction({
optionName: "ShowAttributes",
defaultShortcuts: "alt+a"
});
/** @static */
KeyboardAction.ShowHelp = new KeyboardAction({
optionName: "ShowHelp",
defaultShortcuts: "f1"
});
/** @static */
KeyboardAction.OpenSQLConsole = new KeyboardAction({
optionName: "OpenSQLConsole",
defaultShortcuts: "alt+o"
});
/** @static */
KeyboardAction.BackInNoteHistory = new KeyboardAction({
optionName: "BackInNoteHistory",
defaultShortcuts: "alt+left"
});
/** @static */
KeyboardAction.ForwardInNoteHistory = new KeyboardAction({
optionName: "ForwardInNoteHistory",
defaultShortcuts: "alt+right"
});
/** @static */
KeyboardAction.ToggleZenMode = new KeyboardAction({
optionName: "ToggleZenMode",
defaultShortcuts: "alt+m"
});
/** @static */
KeyboardAction.InsertDateTime = new KeyboardAction({
optionName: "InsertDateTime",
defaultShortcuts: "alt+t"
});
/** @static */
KeyboardAction.ReloadApp = new KeyboardAction({
optionName: "ReloadApp",
defaultShortcuts: ["f5", "mod+r"]
});
/** @static */
KeyboardAction.OpenDevTools = new KeyboardAction({
optionName: "OpenDevTools",
defaultShortcuts: "mod+shift+i"
});
/** @static */
KeyboardAction.FindInText = new KeyboardAction({
optionName: "FindInText",
defaultShortcuts: "mod+f"
});
/** @static */
KeyboardAction.ToggleFullscreen = new KeyboardAction({
optionName: "ToggleFullscreen",
defaultShortcuts: "f11"
});
/** @static */
KeyboardAction.ZoomOut = new KeyboardAction({
optionName: "ZoomOut",
defaultShortcuts: "mod+-"
});
/** @static */
KeyboardAction.ZoomIn = new KeyboardAction({
optionName: "ZoomIn",
defaultShortcuts: "mod+="
});
export default KeyboardAction;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardAction.html">KeyboardAction</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: services/keyboard_action.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: services/keyboard_action.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* blaa vlaa
*/
class KeyboardActions {
constructor() {
/** @property {string} */
this.JUMP_TO = "";
}
}
export default KeyboardActions;</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Branch.html">Branch</a></li><li><a href="FrontendScriptApi.html">FrontendScriptApi</a></li><li><a href="KeyboardActions.html">KeyboardActions</a></li><li><a href="NoteFull.html">NoteFull</a></li><li><a href="NoteShort.html">NoteShort</a></li></ul><h3><a href="global.html">Global</a></h3>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a>
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@@ -1,85 +1,17 @@
'use strict';
const {app, globalShortcut, BrowserWindow} = require('electron');
const path = require('path');
const log = require('./src/services/log');
const {app, globalShortcut} = require('electron');
const sqlInit = require('./src/services/sql_init');
const cls = require('./src/services/cls');
const url = require("url");
const port = require('./src/services/port');
const env = require('./src/services/env');
const appIconService = require('./src/services/app_icon');
const windowStateKeeper = require('electron-window-state');
const windowService = require('./src/services/window');
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
appIconService.installLocalAppIcon();
// Prevent window being garbage collected
let mainWindow;
require('electron-dl')({ saveAs: true });
function onClosed() {
// Dereference the window
// For multiple windows store them in an array
mainWindow = null;
}
async function createMainWindow() {
await sqlInit.dbConnection;
// if schema doesn't exist -> setup process
// if schema exists, then we need to wait until the migration process is finished
if (await sqlInit.schemaExists()) {
await sqlInit.dbReady;
}
const mainWindowState = windowStateKeeper({
// default window width & height so it's usable on 1600 * 900 display (including some extra panels etc.)
defaultWidth: 1200,
defaultHeight: 800
});
const win = new BrowserWindow({
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
title: 'Trilium Notes',
webPreferences: {
nodeIntegration: true
},
icon: path.join(__dirname, 'images/app-icons/png/256x256' + (env.isDev() ? '-dev' : '') + '.png')
});
mainWindowState.manage(win);
win.setMenuBarVisibility(false);
win.loadURL('http://127.0.0.1:' + await port);
win.on('closed', onClosed);
win.webContents.on('new-window', (e, url) => {
if (url !== win.webContents.getURL()) {
e.preventDefault();
require('electron').shell.openExternal(url);
}
});
// prevent drag & drop to navigate away from trilium
win.webContents.on('will-navigate', (ev, targetUrl) => {
const parsedUrl = url.parse(targetUrl);
// we still need to allow internal redirects from setup and migration pages
if (!['localhost', '127.0.0.1'].includes(parsedUrl.hostname) || (parsedUrl.path && parsedUrl.path !== '/')) {
ev.preventDefault();
}
});
return win;
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
@@ -89,27 +21,23 @@ app.on('window-all-closed', () => {
}
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
}
});
app.on('ready', async () => {
app.setAppUserModelId('com.github.zadam.trilium');
mainWindow = await createMainWindow();
await sqlInit.dbConnection;
const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => {
// window may be hidden / not in focus
mainWindow.focus();
// if db is not initialized -> setup process
// if db is initialized, then we need to wait until the migration process is finished
if (await sqlInit.isDbInitialized()) {
await sqlInit.dbReady;
mainWindow.webContents.send('create-day-sub-note');
}));
if (!result) {
log.error("Could not register global shortcut CTRL+ALT+P");
await windowService.createMainWindow();
}
else {
await windowService.createSetupWindow();
}
await windowService.registerGlobalShortcuts();
});
app.on('will-quit', () => {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,310 @@
/*
* !!!!!!! This stylesheet is heavily modified compared to the original for similarity with in-editor look !!!!!!!
* This is used for printing and tar HTML export
*
* CKEditor 5 (v15.0.0) content styles.
* Generated on Wed, 27 Nov 2019 13:26:13 GMT.
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/content-styles.html
*/
:root {
--ck-highlight-marker-blue: #72cdfd;
--ck-highlight-marker-green: #63f963;
--ck-highlight-marker-pink: #fc7999;
--ck-highlight-marker-yellow: #fdfd77;
--ck-highlight-pen-green: #118800;
--ck-highlight-pen-red: #e91313;
--ck-image-style-spacing: 1.5em;
--ck-todo-list-checkmark-size: 16px;
font-family: Arial, Sans-Serif;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list {
list-style: none;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list li {
margin-bottom: 5px;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list li .todo-list {
margin-top: 5px;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label > input {
-webkit-appearance: none;
display: inline-block;
position: relative;
width: var(--ck-todo-list-checkmark-size);
height: var(--ck-todo-list-checkmark-size);
vertical-align: middle;
border: 0;
left: -25px;
margin-right: -15px;
right: 0;
margin-left: 0;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label > input::before {
display: block;
position: absolute;
box-sizing: border-box;
content: '';
width: 100%;
height: 100%;
border: 1px solid hsl(0, 0%, 20%);
border-radius: 2px;
transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label > input::after {
display: block;
position: absolute;
box-sizing: content-box;
pointer-events: none;
content: '';
left: calc( var(--ck-todo-list-checkmark-size) / 3 );
top: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
width: calc( var(--ck-todo-list-checkmark-size) / 5.3 );
height: calc( var(--ck-todo-list-checkmark-size) / 2.6 );
border-style: solid;
border-color: transparent;
border-width: 0 calc( var(--ck-todo-list-checkmark-size) / 8 ) calc( var(--ck-todo-list-checkmark-size) / 8 ) 0;
transform: rotate(45deg);
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label > input[checked]::before {
border-color: hsl(126, 64%, 41%);
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label > input[checked]::after {
border-color: hsl(126, 64%, 41%);
}
/* ckeditor5-list/theme/todolist.css */
.todo-list .todo-list__label .todo-list__label__description {
vertical-align: middle;
}
/* ckeditor5-image/theme/image.css */
.image {
display: table;
clear: both;
text-align: center;
margin: 1em auto;
}
/* ckeditor5-image/theme/image.css */
.image > img {
display: block;
margin: 0 auto;
max-width: 100%;
min-width: 50px;
}
/* ckeditor5-block-quote/theme/blockquote.css */
blockquote {
overflow: hidden;
padding-right: 1.5em;
padding-left: 1.5em;
margin-left: 0;
margin-right: 0;
font-style: italic;
border-left: solid 5px hsl(0, 0%, 80%);
}
/* ckeditor5-block-quote/theme/blockquote.css */
.ck-content[dir="rtl"] blockquote {
border-left: 0;
border-right: solid 5px hsl(0, 0%, 80%);
}
/* ckeditor5-image/theme/imageresize.css */
.image.image_resized {
max-width: 100%;
display: block;
box-sizing: border-box;
}
/* ckeditor5-image/theme/imageresize.css */
.image.image_resized img {
width: 100%;
}
/* ckeditor5-image/theme/imageresize.css */
.image.image_resized > figcaption {
display: block;
}
/* ckeditor5-image/theme/imagestyle.css */
.image-style-side,
.image-style-align-left,
.image-style-align-center,
.image-style-align-right {
max-width: 50%;
}
/* ckeditor5-image/theme/imagestyle.css */
.image-style-side {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
.image-style-align-left {
float: left;
margin-right: var(--ck-image-style-spacing);
}
/* ckeditor5-image/theme/imagestyle.css */
.image-style-align-center {
margin-left: auto;
margin-right: auto;
}
/* ckeditor5-image/theme/imagestyle.css */
.image-style-align-right {
float: right;
margin-left: var(--ck-image-style-spacing);
}
/* ckeditor5-media-embed/theme/mediaembed.css */
.media {
clear: both;
margin: 1em 0;
display: block;
min-width: 15em;
}
/* ckeditor5-table/theme/table.css */
.table {
display: table;
margin: 0;
}
/* ckeditor5-table/theme/table.css */
.table table {
border-collapse: collapse;
border-spacing: 0;
border: 1px double hsl(0, 0%, 70%);
}
/* ckeditor5-table/theme/table.css */
.table table td,
.table table th {
min-width: 2em;
padding: .4em;
border: 1px solid #d9d9d9;
}
/* ckeditor5-table/theme/table.css */
.table table th {
font-weight: bold;
background-color: #f5f5f5;
text-align: left;
}
/* ckeditor5-highlight/theme/highlight.css */
.marker-yellow {
background-color: var(--ck-highlight-marker-yellow);
}
/* ckeditor5-highlight/theme/highlight.css */
.marker-green {
background-color: var(--ck-highlight-marker-green);
}
/* ckeditor5-highlight/theme/highlight.css */
.marker-pink {
background-color: var(--ck-highlight-marker-pink);
}
/* ckeditor5-highlight/theme/highlight.css */
.marker-blue {
background-color: var(--ck-highlight-marker-blue);
}
/* ckeditor5-highlight/theme/highlight.css */
.pen-red {
color: var(--ck-highlight-pen-red);
background-color: transparent;
}
/* ckeditor5-highlight/theme/highlight.css */
.pen-green {
color: var(--ck-highlight-pen-green);
background-color: transparent;
}
/* ckeditor5-page-break/theme/pagebreak.css */
.page-break {
position: relative;
clear: both;
padding: 5px 0;
display: flex;
align-items: center;
justify-content: center;
}
/* ckeditor5-page-break/theme/pagebreak.css */
.page-break::after {
content: '';
position: absolute;
border-bottom: 2px dashed hsl(0, 0%, 77%);
width: 100%;
}
/* ckeditor5-page-break/theme/pagebreak.css */
.page-break__label {
position: relative;
z-index: 1;
padding: .3em .6em;
display: block;
text-transform: uppercase;
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
font-size: 0.75em;
font-weight: bold;
color: hsl(0, 0%, 20%);
background: #fff;
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* ckeditor5-basic-styles/theme/code.css */
code {
background-color: hsla(0, 0%, 78%, 0.3);
padding: .15em;
border-radius: 2px;
}
/* ckeditor5-image/theme/imagecaption.css */
.image > figcaption {
display: table-caption;
caption-side: bottom;
word-break: break-word;
color: hsl(0, 0%, 20%);
background-color: hsl(0, 0%, 97%);
padding: .6em;
font-size: .75em;
outline-offset: -1px;
}
/* ckeditor5-horizontal-line/theme/horizontalline.css */
hr {
border-width: 1px 0 0;
border-style: solid;
border-color: hsl(0, 0%, 37%);
margin: 0;
}
/* ckeditor5-code-block/theme/codeblock.css */
pre {
padding: 1em;
color: #353535;
background: hsla(0, 0%, 78%, 0.3);
border: 1px solid hsl(0, 0%, 77%);
border-radius: 2px;
text-align: left;
direction: ltr;
tab-size: 4;
white-space: pre-wrap;
font-style: normal;
min-width: 200px;
}
/* ckeditor5-code-block/theme/codeblock.css */
pre code {
background: unset;
padding: 0;
border-radius: 0;
}
.ck-widget__selection-handle {
display: none;
}
@media print {
/* ckeditor5-page-break/theme/pagebreak.css */
.page-break {
padding: 0;
}
/* ckeditor5-page-break/theme/pagebreak.css */
.page-break::after {
display: none;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
/*! jQuery Fancytree Plugin - 2.33.0 - 2019-10-29T08:00:07Z
/*! jQuery Fancytree Plugin - 2.34.0 - 2019-12-26T14:16:19Z
* https://github.com/mar10/fancytree
* Copyright (c) 2019 Martin Wendt; Licensed MIT
*/
@@ -1365,8 +1365,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
/** Core Fancytree module.
@@ -4156,11 +4156,18 @@ var uniqueId = $.fn.extend( {
delete this._tempCache[key];
return null;
},
/* Check if this tree has extension `name` enabled.
*
* @param {string} name name of the required extension
*/
_usesExtension: function(name) {
return $.inArray(name, this.options.extensions) >= 0;
},
/* Check if current extensions dependencies are met and throw an error if not.
*
* This method may be called inside the `treeInit` hook for custom extensions.
*
* @param {string} extension name of the required extension
* @param {string} name name of the required extension
* @param {boolean} [required=true] pass `false` if the extension is optional, but we want to check for order if it is present
* @param {boolean} [before] `true` if `name` must be included before this, `false` otherwise (use `null` if order doesn't matter)
* @param {string} [message] optional error message (defaults to a descriptve error message)
@@ -4417,6 +4424,13 @@ var uniqueId = $.fn.extend( {
consoleApply("log", arguments);
}
},
/** Destroy this widget, restore previous markup and cleanup resources.
*
* @since 2.34
*/
destroy: function() {
this.widget.destroy();
},
/** Enable (or disable) the tree control.
*
* @param {boolean} [flag=true] pass false to disable
@@ -4795,6 +4809,20 @@ var uniqueId = $.fn.extend( {
* @returns {boolean}
*/
hasFocus: function() {
// var ae = document.activeElement,
// hasFocus = !!(
// ae && $(ae).closest(".fancytree-container").length
// );
// if (hasFocus !== !!this._hasFocus) {
// this.warn(
// "hasFocus(): fix inconsistent container state, now: " +
// hasFocus
// );
// this._hasFocus = hasFocus;
// this.$container.toggleClass("fancytree-treefocus", hasFocus);
// }
// return hasFocus;
return !!this._hasFocus;
},
/** Write to browser console if debugLevel >= 3 (prepending tree name)
@@ -5120,6 +5148,26 @@ var uniqueId = $.fn.extend( {
setOption: function(optionName, value) {
return this.widget.option(optionName, value);
},
/**
* Call console.time() when in debug mode (verbose >= 4).
*
* @param {string} label
*/
debugTime: function(label) {
if (this.options.debugLevel >= 4) {
window.console.time(this + " - " + label);
}
},
/**
* Call console.timeEnd() when in debug mode (verbose >= 4).
*
* @param {string} label
*/
debugTimeEnd: function(label) {
if (this.options.debugLevel >= 4) {
window.console.timeEnd(this + " - " + label);
}
},
/**
* Return all nodes as nested list of {@link NodeData}.
*
@@ -5185,7 +5233,7 @@ var uniqueId = $.fn.extend( {
* @since 2.28
*/
visitRows: function(fn, opts) {
if (!this.rootNode.children) {
if (!this.rootNode.hasChildren()) {
return false;
}
if (opts && opts.reverse) {
@@ -5314,7 +5362,8 @@ var uniqueId = $.fn.extend( {
/**
* These additional methods of the {@link Fancytree} class are 'hook functions'
* that can be used and overloaded by extensions.
* (See <a href="https://github.com/mar10/fancytree/wiki/TutorialExtensions">writing extensions</a>.)
*
* @see [writing extensions](https://github.com/mar10/fancytree/wiki/TutorialExtensions)
* @mixin Fancytree_Hooks
*/
$.extend(
@@ -6005,6 +6054,7 @@ var uniqueId = $.fn.extend( {
* Call this method to create new nodes, or after the strucuture
* was changed (e.g. after moving this node or adding/removing children)
* nodeRenderTitle() and nodeRenderStatus() are implied.
*
* ```html
* <li id='KEY' ftnode=NODE>
* <span class='fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e'>
@@ -7493,14 +7543,28 @@ var uniqueId = $.fn.extend( {
*/
/**
* The plugin (derrived from <a href=" http://api.jqueryui.com/jQuery.widget/">jQuery.Widget</a>).<br>
* This constructor is not called directly. Use `$(selector).fancytree({})`
* to initialize the plugin instead.<br>
* <pre class="sh_javascript sunlight-highlight-javascript">// Access widget methods and members:
* var tree = $("#tree").fancytree("getTree");
* var node = $("#tree").fancytree("getActiveNode", "1234");
* </pre>
* The plugin (derrived from [jQuery.Widget](http://api.jqueryui.com/jQuery.widget/)).
*
* **Note:**
* These methods implement the standard jQuery UI widget API.
* It is recommended to use methods of the {Fancytree} instance instead
*
* @example
* // DEPRECATED: Access jQuery UI widget methods and members:
* var tree = $("#tree").fancytree("getTree", "#myTree");
* var node = $.ui.fancytree.getTree("#tree").getActiveNode();
*
* // RECOMMENDED: Use the Fancytree object API
* var tree = $.ui.fancytree.getTree("#myTree");
* var node = tree.getActiveNode();
*
* // or you may already have stored the tree instance upon creation:
* import {createTree, version} from 'jquery.fancytree'
* const tree = createTree('#tree', { ... });
* var node = tree.getActiveNode();
*
* @see {Fancytree_Static#getTree}
* @deprecated Use methods of the {Fancytree} instance instead
* @mixin Fancytree_Widget
*/
@@ -7584,6 +7648,17 @@ var uniqueId = $.fn.extend( {
lazyLoad: null,
postProcess: null,
},
_deprecationWarning: function(name) {
var tree = this.tree;
if (tree && tree.options.debugLevel >= 3) {
tree.warn(
"$().fancytree('" +
name +
"') is deprecated (see https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Widget.html"
);
}
},
/* Set up the widget, Called on first $().fancytree() */
_create: function() {
this.tree = new Fancytree(this);
@@ -7701,11 +7776,11 @@ var uniqueId = $.fn.extend( {
},
/** Use the destroy method to clean up any modifications your widget has made to the DOM */
destroy: function() {
_destroy: function() {
this._unbind();
this.tree._callHook("treeDestroy", this.tree);
// In jQuery UI 1.8, you must invoke the destroy method from the base widget
$.Widget.prototype.destroy.call(this);
// $.Widget.prototype.destroy.call(this);
// TODO: delete tree and nodes to make garbage collect easier?
// TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
},
@@ -7898,27 +7973,35 @@ var uniqueId = $.fn.extend( {
},
/** Return the active node or null.
* @returns {FancytreeNode}
* @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
*/
getActiveNode: function() {
this._deprecationWarning("getActiveNode");
return this.tree.activeNode;
},
/** Return the matching node or null.
* @param {string} key
* @returns {FancytreeNode}
* @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
*/
getNodeByKey: function(key) {
this._deprecationWarning("getNodeByKey");
return this.tree.getNodeByKey(key);
},
/** Return the invisible system root node.
* @returns {FancytreeNode}
* @deprecated Use methods of the Fancytree instance instead (<a href="Fancytree_Widget.html">example above</a>).
*/
getRootNode: function() {
this._deprecationWarning("getRootNode");
return this.tree.rootNode;
},
/** Return the current tree instance.
* @returns {Fancytree}
* @deprecated Use `$.ui.fancytree.getTree()` instead (<a href="Fancytree_Widget.html">example above</a>).
*/
getTree: function() {
this._deprecationWarning("getTree");
return this.tree;
},
}
@@ -7928,12 +8011,14 @@ var uniqueId = $.fn.extend( {
FT = $.ui.fancytree;
/**
* Static members in the `$.ui.fancytree` namespace.<br>
* <br>
* <pre class="sh_javascript sunlight-highlight-javascript">// Access static members:
* Static members in the `$.ui.fancytree` namespace.
* This properties and methods can be accessed without instantiating a concrete
* Fancytree instance.
*
* @example
* // Access static members:
* var node = $.ui.fancytree.getNode(element);
* alert($.ui.fancytree.version);
* </pre>
*
* @mixin Fancytree_Static
*/
@@ -7941,11 +8026,14 @@ var uniqueId = $.fn.extend( {
$.ui.fancytree,
/** @lends Fancytree_Static# */
{
/** @type {string} */
version: "2.33.0", // Set to semver by 'grunt release'
/** @type {string} */
/** Version number `"MAJOR.MINOR.PATCH"`
* @type {string} */
version: "2.34.0", // Set to semver by 'grunt release'
/** @type {string}
* @description `"production" for release builds` */
buildType: "production", // Set to 'production' by 'grunt build'
/** @type {int} */
/** @type {int}
* @description 0: silent .. 5: verbose (default: 3 for release builds). */
debugLevel: 3, // Set to 3 by 'grunt build'
// Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
@@ -7954,9 +8042,15 @@ var uniqueId = $.fn.extend( {
_extensions: {},
// focusTree: null,
/** Expose class object as $.ui.fancytree._FancytreeClass */
/** Expose class object as `$.ui.fancytree._FancytreeClass`.
* Useful to extend `$.ui.fancytree._FancytreeClass.prototype`.
* @type {Fancytree}
*/
_FancytreeClass: Fancytree,
/** Expose class object as $.ui.fancytree._FancytreeNodeClass */
/** Expose class object as $.ui.fancytree._FancytreeNodeClass
* Useful to extend `$.ui.fancytree._FancytreeNodeClass.prototype`.
* @type {FancytreeNode}
*/
_FancytreeNodeClass: FancytreeNode,
/* Feature checks to provide backwards compatibility */
jquerySupports: {
@@ -7983,10 +8077,8 @@ var uniqueId = $.fn.extend( {
* @since 2.25
*/
createTree: function(el, opts) {
var tree = $(el)
.fancytree(opts)
.fancytree("getTree");
return tree;
var $tree = $(el).fancytree(opts);
return FT.getTree($tree);
},
/** Return a function that executes *fn* at most every *timeout* ms.
* @param {integer} timeout
@@ -8191,11 +8283,17 @@ var uniqueId = $.fn.extend( {
if (!el.length) {
el = $(orgEl).eq(0); // el was a selector: use first match
}
} else if (
el instanceof Element ||
el instanceof HTMLDocument
) {
el = $(el);
} else if (el instanceof $) {
el = el.eq(0); // el was a jQuery object: use the first DOM element
el = el.eq(0); // el was a jQuery object: use the first
} else if (el.originalEvent !== undefined) {
el = $(el.target); // el was an Event
}
// el is a jQuery object wit one element here
el = el.closest(":ui-fancytree");
widget = el.data("ui-fancytree") || el.data("fancytree"); // the latter is required by jQuery <= 1.8
return widget ? widget.tree : null;
@@ -8203,11 +8301,11 @@ var uniqueId = $.fn.extend( {
/** Return an option value that has a default, but may be overridden by a
* callback or a node instance attribute.
*
* Evaluation sequence:<br>
* Evaluation sequence:
*
* If tree.options.<optionName> is a callback that returns something, use that.<br>
* Else if node.<optionName> is defined, use that.<br>
* Else if tree.options.<optionName> is a value, use that.<br>
* If `tree.options.<optionName>` is a callback that returns something, use that.
* Else if `node.<optionName>` is defined, use that.
* Else if `tree.options.<optionName>` is a value, use that.
* Else use `defaultValue`.
*
* @param {string} optionName name of the option property (on node and tree)
@@ -8565,8 +8663,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
// To keep the global namespace clean, we wrap everything in a closure.
@@ -8602,7 +8700,7 @@ var uniqueId = $.fn.extend( {
// New member functions can be added to the `Fancytree` class.
// This function will be available for every tree instance:
//
// var tree = $("#tree").fancytree("getTree");
// var tree = $.ui.fancytree.getTree("#tree");
// tree.countSelected(false);
$.ui.fancytree._FancytreeClass.prototype.countSelected = function(topOnly) {
@@ -8685,7 +8783,7 @@ var uniqueId = $.fn.extend( {
// Every extension must be registered by a unique name.
name: "childcounter",
// Version information should be compliant with [semver](http://semver.org)
version: "2.33.0",
version: "2.34.0",
// Extension specific options and their defaults.
// This options will be available as `tree.options.childcounter.hideExpanded`
@@ -8796,8 +8894,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -9155,7 +9253,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "clones",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
highlightActiveClones: true, // set 'fancytree-active-clone' on active clones and all peers
@@ -9317,8 +9415,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
/*
@@ -10322,7 +10420,7 @@ var uniqueId = $.fn.extend( {
$.ui.fancytree.registerExtension({
name: "dnd5",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering
@@ -10455,8 +10553,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -10748,7 +10846,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "edit",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
adjustWidthOfs: 4, // null: don't adjust input size to content
@@ -10859,8 +10957,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -11198,7 +11296,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "filter",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
autoApply: true, // Re-apply last filter if lazy data is loaded
@@ -11318,8 +11416,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -11502,7 +11600,7 @@ var uniqueId = $.fn.extend( {
$.ui.fancytree.registerExtension({
name: "glyph",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
preset: null, // 'awesome3', 'awesome4', 'bootstrap3', 'material'
@@ -11656,8 +11754,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -11774,7 +11872,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "gridnav",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
autofocusInput: false, // Focus first embedded input if node gets activated
@@ -11881,8 +11979,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -11911,7 +12009,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "multi",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
allowNoSelect: false, //
@@ -12013,8 +12111,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -12154,7 +12252,7 @@ var uniqueId = $.fn.extend( {
/**
* [ext-persist] Remove persistence data of the given type(s).
* Called like
* $("#tree").fancytree("getTree").clearCookies("active expanded focus selected");
* $.ui.fancytree.getTree("#tree").clearCookies("active expanded focus selected");
*
* @alias Fancytree#clearPersistData
* @requires jquery.fancytree.persist.js
@@ -12191,7 +12289,7 @@ var uniqueId = $.fn.extend( {
* [ext-persist] Return persistence information from cookies
*
* Called like
* $("#tree").fancytree("getTree").getPersistData();
* $.ui.fancytree.getTree("#tree").getPersistData();
*
* @alias Fancytree#getPersistData
* @requires jquery.fancytree.persist.js
@@ -12214,7 +12312,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "persist",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
cookieDelimiter: "~",
@@ -12507,8 +12605,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -12591,7 +12689,7 @@ var uniqueId = $.fn.extend( {
$.ui.fancytree.registerExtension({
name: "table",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
@@ -13057,8 +13155,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -13081,7 +13179,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "themeroller",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
activeClass: "ui-state-active", // Class added to active node
@@ -13177,8 +13275,8 @@ var uniqueId = $.fn.extend( {
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
*/
(function(factory) {
@@ -13308,7 +13406,7 @@ var uniqueId = $.fn.extend( {
*/
$.ui.fancytree.registerExtension({
name: "wide",
version: "2.33.0",
version: "2.34.0",
// Default options for this extension.
options: {
iconWidth: null, // Adjust this if @fancy-icon-width != "16px"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -13,8 +13,8 @@
* Released under the MIT license
* https://github.com/mar10/fancytree/wiki/LicenseInfo
*
* @version 2.33.0
* @date 2019-10-29T08:00:07Z
* @version 2.34.0
* @date 2019-12-26T14:16:19Z
******************************************************************************/
/*------------------------------------------------------------------------------
* Helpers

3
libraries/split.min.js vendored Normal file

File diff suppressed because one or more lines are too long

5146
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.37.5",
"version": "0.40.5",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -21,40 +21,40 @@
},
"dependencies": {
"async-mutex": "0.1.4",
"axios": "0.19.0",
"axios": "0.19.1",
"body-parser": "1.19.0",
"cls-hooked": "4.2.2",
"commonmark": "0.29.0",
"commonmark": "0.29.1",
"cookie-parser": "1.4.4",
"csurf": "1.10.0",
"dayjs": "1.8.17",
"dayjs": "1.8.19",
"debug": "4.1.1",
"ejs": "2.7.1",
"ejs": "2.7.4",
"electron-debug": "3.0.1",
"electron-dl": "1.14.0",
"electron-dl": "2.0.0",
"electron-find": "1.0.6",
"electron-spellchecker": "2.2.0",
"electron-spellchecker": "2.2.1",
"electron-window-state": "5.0.3",
"express": "4.17.1",
"express-session": "1.17.0",
"file-type": "12.4.0",
"file-type": "13.0.3",
"fs-extra": "8.1.0",
"helmet": "3.21.2",
"html": "1.0.0",
"html2plaintext": "2.1.2",
"http-proxy-agent": "2.1.0",
"https-proxy-agent": "3.0.1",
"http-proxy-agent": "3.0.0",
"https-proxy-agent": "4.0.0",
"image-type": "4.1.0",
"imagemin": "7.0.0",
"imagemin": "7.0.1",
"imagemin-giflossy": "5.1.10",
"imagemin-mozjpeg": "8.0.0",
"imagemin-pngquant": "8.0.0",
"ini": "1.3.5",
"jimp": "0.8.5",
"mime-types": "2.1.24",
"jimp": "0.9.3",
"mime-types": "2.1.26",
"moment": "2.24.0",
"multer": "1.4.2",
"node-abi": "2.12.0",
"node-abi": "2.13.0",
"open": "7.0.0",
"pngjs": "3.4.0",
"portscanner": "2.2.0",
@@ -63,34 +63,28 @@
"rimraf": "3.0.0",
"sanitize-filename": "1.6.3",
"sax": "1.2.4",
"semver": "6.3.0",
"semver": "7.1.1",
"serve-favicon": "2.5.0",
"session-file-store": "1.3.1",
"simple-node-logger": "18.12.23",
"sqlite": "3.0.3",
"sqlite3": "4.1.0",
"string-similarity": "3.0.0",
"sqlite3": "4.1.1",
"string-similarity": "4.0.1",
"tar-stream": "2.1.0",
"turndown": "5.0.3",
"turndown-plugin-gfm": "1.0.2",
"unescape": "1.0.1",
"ws": "7.2.0"
"ws": "7.2.1"
},
"devDependencies": {
"electron": "6.0.12",
"electron-builder": "22.1.0",
"electron-compile": "6.4.4",
"electron-installer-debian": "2.0.1",
"electron-packager": "14.1.0",
"electron-rebuild": "1.8.6",
"electron-builder": "21.2.0",
"electron-packager": "14.1.1",
"electron-rebuild": "1.8.8",
"jsdoc": "3.6.3",
"lorem-ipsum": "2.0.3",
"xo": "0.25.3"
"lorem-ipsum": "2.0.3"
},
"xo": {
"envs": [
"node",
"browser"
]
"optionalDependencies": {
"electron-installer-debian": "2.0.1"
}
}

View File

@@ -16,6 +16,7 @@ const sql = require('../services/sql');
* @property {int} position
* @property {boolean} isInheritable
* @property {boolean} isDeleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {string} utcDateCreated
* @property {string} utcDateModified
*
@@ -79,6 +80,10 @@ class Attribute extends Entity {
async beforeSaving() {
if (!this.value) {
if (this.type === 'relation') {
throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
}
// null value isn't allowed
this.value = "";
}
@@ -109,6 +114,7 @@ class Attribute extends Entity {
// cannot be static!
updatePojo(pojo) {
delete pojo.isOwned;
delete pojo.__note;
}
}

View File

@@ -16,6 +16,7 @@ const sql = require('../services/sql');
* @property {string} prefix
* @property {boolean} isExpanded
* @property {boolean} isDeleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {string} utcDateModified
* @property {string} utcDateCreated
*
@@ -25,7 +26,7 @@ class Branch extends Entity {
static get entityName() { 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", "isDeleted", "prefix"]; }
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "isDeleted", "deleteId", "prefix"]; }
constructor(row = {}) {
super(row);

View File

@@ -24,6 +24,7 @@ const RELATION_DEFINITION = 'relation-definition';
* @property {int} contentLength - length of content
* @property {boolean} isProtected - true if note is protected
* @property {boolean} isDeleted - true if note is deleted
* @property {string|null} deleteId - ID identifying delete transaction
* @property {boolean} isErased - true if note's content is erased after it has been deleted
* @property {string} dateCreated - local date time (with offset)
* @property {string} dateModified - local date time (with offset)
@@ -35,7 +36,7 @@ const RELATION_DEFINITION = 'relation-definition';
class Note extends Entity {
static get entityName() { return "notes"; }
static get primaryKeyName() { return "noteId"; }
static get hashedProperties() { return ["noteId", "title", "type", "isProtected", "isDeleted"]; }
static get hashedProperties() { return ["noteId", "title", "type", "isProtected", "isDeleted", "deleteId"]; }
/**
* @param row - object containing database row from "notes" table
@@ -109,14 +110,24 @@ class Note extends Entity {
async getJsonContent() {
const content = await this.getContent();
if (!content || !content.trim()) {
return null;
}
return JSON.parse(content);
}
/** @returns {Promise} */
async setContent(content) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note ${this.noteId}`);
}
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.length;
this.contentLength = content.byteLength;
await this.save();
this.content = content;
@@ -125,7 +136,7 @@ class Note extends Entity {
noteId: this.noteId,
content: content,
utcDateModified: dateUtils.utcNowDateTime(),
hash: utils.hash(this.noteId + "|" + content)
hash: utils.hash(this.noteId + "|" + content.toString())
};
if (this.isProtected) {
@@ -192,26 +203,36 @@ class Note extends Entity {
return null;
}
async loadOwnedAttributesToCache() {
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
return this.__ownedAttributeCache;
}
/**
* @returns {Promise<Attribute[]>} attributes belonging to this specific note (excludes inherited attributes)
* This method is a faster variant of getAttributes() which looks for only owned attributes.
* Use when inheritance is not needed and/or in batch/performance sensitive operations.
*
* This method can be significantly faster than the getAttributes()
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise<Attribute[]>} note's "owned" attributes - excluding inherited ones
*/
async getOwnedAttributes(type, name) {
let query = `SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`;
const params = [this.noteId];
if (type) {
query += ` AND type = ?`;
params.push(type);
if (!this.__ownedAttributeCache) {
await this.loadOwnedAttributesToCache();
}
if (name) {
query += ` AND name = ?`;
params.push(name);
if (type && name) {
return this.__ownedAttributeCache.filter(attr => attr.type === type && attr.name === name);
}
else if (type) {
return this.__ownedAttributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__ownedAttributeCache.filter(attr => attr.name === name);
}
else {
return this.__ownedAttributeCache.slice();
}
return await repository.getEntities(query, params);
}
/**
@@ -233,19 +254,26 @@ class Note extends Entity {
}
/**
* @param {string} [name] - attribute name to filter
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
*/
async getAttributes(name) {
async getAttributes(type, name) {
if (!this.__attributeCache) {
await this.loadAttributesToCache();
}
if (name) {
if (type && name) {
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
}
else if (type) {
return this.__attributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
return this.__attributeCache;
return this.__attributeCache.slice();
}
}
@@ -254,7 +282,15 @@ class Note extends Entity {
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
*/
async getLabels(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
return await this.getAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), excluding inherited ones
*/
async getOwnedLabels(name) {
return await this.getOwnedAttributes(LABEL, name);
}
/**
@@ -262,7 +298,7 @@ class Note extends Entity {
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
return await this.getAttributes(LABEL_DEFINITION, name);
}
/**
@@ -270,7 +306,15 @@ class Note extends Entity {
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
*/
async getRelations(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
return await this.getAttributes(RELATION, name);
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), excluding inherited ones
*/
async getOwnedRelations(name) {
return await this.getOwnedAttributes(RELATION, name);
}
/**
@@ -293,7 +337,7 @@ class Note extends Entity {
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
return await this.getAttributes(RELATION_DEFINITION, name);
}
/**
@@ -302,6 +346,7 @@ class Note extends Entity {
*/
invalidateAttributeCache() {
this.__attributeCache = null;
this.__ownedAttributeCache = null;
}
/** @returns {Promise<void>} */
@@ -311,11 +356,10 @@ class Note extends Entity {
tree(noteId, level) AS (
SELECT ?, 0
UNION
SELECT branches.parentNoteId, tree.level + 1 FROM branches
SELECT branches.parentNoteId, tree.level + 1
FROM branches
JOIN tree ON branches.noteId = tree.noteId
JOIN notes ON notes.noteId = branches.parentNoteId
WHERE notes.isDeleted = 0
AND branches.isDeleted = 0
WHERE branches.isDeleted = 0
),
treeWithAttrs(noteId, level) AS (
SELECT * FROM tree
@@ -334,6 +378,11 @@ class Note extends Entity {
// we order by noteId so that attributes from same note stay together. Actual noteId ordering doesn't matter.
const filteredAttributes = attributes.filter((attr, index) => {
// if this exact attribute already appears then don't include it (can happen via cloning)
if (attributes.findIndex(it => it.attributeId === attr.attributeId) !== index) {
return false;
}
if (attr.isDefinition()) {
const firstDefinitionIndex = attributes.findIndex(el => el.type === attr.type && el.name === attr.name);
@@ -377,6 +426,15 @@ class Note extends Entity {
return !!await this.getAttribute(type, name);
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise<boolean>} true if note has an attribute with given type and name (excluding inherited)
*/
async hasOwnedAttribute(type, name) {
return !!await this.getOwnedAttribute(type, name);
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
@@ -391,7 +449,7 @@ class Note extends Entity {
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise<string>} attribute value of given type and name or null if no such attribute exists.
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getAttributeValue(type, name) {
const attr = await this.getAttribute(type, name);
@@ -399,6 +457,17 @@ class Note extends Entity {
return attr ? attr.value : null;
}
/**
* @param {string} type - attribute type (label, relation, etc.)
* @param {string} name - attribute name
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getOwnedAttributeValue(type, name) {
const attr = await this.getOwnedAttribute(type, name);
return attr ? attr.value : null;
}
/**
* Based on enabled, attribute is either set or removed.
*
@@ -426,7 +495,7 @@ class Note extends Entity {
* @returns {Promise<void>}
*/
async setAttribute(type, name, value) {
const attributes = await this.getOwnedAttributes();
const attributes = await this.loadOwnedAttributesToCache();
let attr = attributes.find(attr => attr.type === type && attr.name === name);
if (attr) {
@@ -460,10 +529,10 @@ class Note extends Entity {
* @returns {Promise<void>}
*/
async removeAttribute(type, name, value) {
const attributes = await this.getOwnedAttributes();
const attributes = await this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
if (attribute.type === type && (value === undefined || value === attribute.value)) {
if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
@@ -472,42 +541,104 @@ class Note extends Entity {
}
}
/**
* @return {Promise<Attribute>}
*/
async addAttribute(type, name, value = "") {
const attr = new Attribute({
noteId: this.noteId,
type: type,
name: name,
value: value
});
await attr.save();
this.invalidateAttributeCache();
return attr;
}
async addLabel(name, value = "") {
return await this.addAttribute(LABEL, name, value);
}
async addRelation(name, targetNoteId) {
return await this.addAttribute(RELATION, name, targetNoteId);
}
/**
* @param {string} name - label name
* @returns {Promise<boolean>} true if label exists (including inherited)
*/
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<boolean>} true if label exists (excluding inherited)
*/
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<boolean>} true if relation exists (including inherited)
*/
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<boolean>} true if relation exists (excluding inherited)
*/
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise<Attribute>} label if it exists, null otherwise
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
*/
async getLabel(name) { return await this.getAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
*/
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<Attribute>} relation if it exists, null otherwise
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
*/
async getRelation(name) { return await this.getAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
*/
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise<string>} label value if label exists, null otherwise
* @returns {Promise<string|null>} label value if label exists, null otherwise
*/
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<string|null>} label value if label exists, null otherwise
*/
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<string>} relation value if relation exists, null otherwise
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
*/
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
*/
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
/**
* @param {string} name
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
@@ -518,6 +649,16 @@ class Note extends Entity {
return relation ? await repository.getNote(relation.value) : null;
}
/**
* @param {string} name
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
*/
async getOwnedRelationTarget(name) {
const relation = await this.getOwnedRelation(name);
return relation ? await repository.getNote(relation.value) : null;
}
/**
* Based on enabled, label is either set or removed.
*
@@ -670,8 +811,10 @@ class Note extends Entity {
FROM attributes
WHERE noteId = ? AND
isDeleted = 0 AND
type = 'relation' AND
name IN ('internal-link', 'image-link', 'relation-map-link')`, [this.noteId]);
((type = 'relation' AND
name IN ('internalLink', 'imageLink', 'relationMapLink', 'includeNoteLink'))
OR
(type = 'label' AND name = 'externalLink'))`, [this.noteId]);
}
/**
@@ -748,6 +891,16 @@ class Note extends Entity {
return notePaths;
}
/**
* @param ancestorNoteId
* @return {Promise<boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
*/
async isDescendantOfNote(ancestorNoteId) {
const notePaths = await this.getAllNotePaths();
return notePaths.some(path => path.includes(ancestorNoteId));
}
beforeSaving() {
if (!this.isDeleted) {
this.isDeleted = false;
@@ -787,7 +940,9 @@ class Note extends Entity {
delete pojo.isContentAvailable;
delete pojo.__attributeCache;
delete pojo.__ownedAttributeCache;
delete pojo.content;
/** zero references to contentHash, probably can be removed */
delete pojo.contentHash;
}
}

View File

@@ -30,6 +30,10 @@ import cssLoader from './services/css_loader.js';
import dateNoteService from './services/date_notes.js';
import sidebarService from './services/sidebar.js';
import importService from './services/import.js';
import keyboardActionService from "./services/keyboard_actions.js";
import splitService from "./services/split.js";
import optionService from "./services/options.js";
import noteContentRenderer from "./services/note_content_renderer.js";
window.glob.isDesktop = utils.isDesktop;
window.glob.isMobile = utils.isMobile;
@@ -38,6 +42,20 @@ window.glob.isMobile = utils.isMobile;
window.glob.getActiveNode = treeService.getActiveNode;
window.glob.getHeaders = server.getHeaders;
window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog());
window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb));
window.glob.loadIncludedNote = async (noteId, el) => {
const note = await treeCache.getNote(noteId);
if (note) {
$(el).empty().append($("<h3>").append(await linkService.createNoteLink(note.noteId, {
showTooltip: false
})));
const {renderedContent} = await noteContentRenderer.getRenderedContent(note);
$(el).append(renderedContent);
}
};
// this is required by CKEditor when uploading images
window.glob.noteChanged = noteDetailService.noteChanged;
window.glob.refreshTree = treeService.reload;
@@ -78,7 +96,7 @@ window.onerror = function (msg, url, lineNo, columnNo, error) {
};
for (const appCssNoteId of window.appCssNoteIds) {
cssLoader.requireCss(`/api/notes/download/${appCssNoteId}`);
cssLoader.requireCss(`api/notes/download/${appCssNoteId}`);
}
const wikiBaseUrl = "https://github.com/zadam/trilium/wiki/";
@@ -108,18 +126,8 @@ $("body").on("click", "a.external", function () {
});
if (utils.isElectron()) {
require('electron').ipcRenderer.on('create-day-sub-note', async function(event) {
const todayNote = await dateNoteService.getTodayNote();
const notePath = await treeService.getSomeNotePath(todayNote);
const node = await treeService.expandToNote(notePath);
await noteDetailService.openEmptyTab(false);
await treeService.createNote(node, todayNote.noteId, 'into', {
type: "text",
isProtected: node.data.isProtected
});
require('electron').ipcRenderer.on('globalShortcut', async function(event, actionName) {
keyboardActionService.triggerAction(actionName);
});
}
@@ -136,7 +144,7 @@ $noteTabContainer.on("click", ".export-note-button", function () {
$noteTabContainer.on("click", ".import-files-button",
() => import('./dialogs/import.js').then(d => d.showDialog(treeService.getActiveNode())));
$noteTabContainer.on("click", ".print-note-button", async function () {
async function printActiveNote() {
if ($(this).hasClass("disabled")) {
return;
}
@@ -151,10 +159,23 @@ $noteTabContainer.on("click", ".print-note-button", async function () {
$tabContext.$tabContent.find('.note-detail-component:visible').printThis({
header: $("<h2>").text($tabContext.note && $tabContext.note.title).prop('outerHTML') ,
importCSS: false,
loadCSS: "libraries/codemirror/codemirror.css",
loadCSS: [
"libraries/codemirror/codemirror.css",
"libraries/ckeditor/ckeditor-content.css",
"libraries/ckeditor/ckeditor-content.css",
"libraries/bootstrap/css/bootstrap.min.css",
"stylesheets/print.css",
"stylesheets/relation_map.css",
"stylesheets/themes.css",
"stylesheets/detail.css"
],
debug: true
});
});
}
keyboardActionService.setGlobalActionHandler("PrintActiveNote", printActiveNote);
$noteTabContainer.on("click", ".print-note-button", printActiveNote);
$('[data-toggle="tooltip"]').tooltip({
html: true
@@ -181,4 +202,34 @@ noteAutocompleteService.init();
if (utils.isElectron()) {
import("./services/spell_check.js").then(spellCheckService => spellCheckService.initSpellCheck());
}
}
optionService.waitForOptions().then(options => {
if (utils.isElectron() && !options.is('nativeTitleBarVisible')) {
$("#title-bar-buttons").show();
$("#minimize-btn").on('click', () => {
$("#minimize-btn").trigger('blur');
const {remote} = require('electron');
remote.BrowserWindow.getFocusedWindow().minimize();
});
$("#maximize-btn").on('click', () => {
$("#maximize-btn").trigger('blur');
const {remote} = require('electron');
const focusedWindow = remote.BrowserWindow.getFocusedWindow();
if (focusedWindow.isMaximized()) {
focusedWindow.unmaximize();
} else {
focusedWindow.maximize();
}
});
$("#close-btn").on('click', () => {
$("#close-btn").trigger('blur');
const {remote} = require('electron');
remote.BrowserWindow.getFocusedWindow().close();
});
}
});

View File

@@ -10,8 +10,6 @@ const $buildRevision = $("#build-revision");
const $dataDirectory = $("#data-directory");
export async function showDialog() {
utils.closeActiveDialog();
const appInfo = await server.get('app-info');
$appVersion.text(appInfo.appVersion);
@@ -22,7 +20,5 @@ export async function showDialog() {
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
$dataDirectory.text(appInfo.dataDirectory);
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}

View File

@@ -11,13 +11,9 @@ const $linkTitle = $("#link-title");
const $addLinkTitleFormGroup = $("#add-link-title-form-group");
export async function showDialog() {
utils.closeActiveDialog();
$addLinkTitleFormGroup.toggle(!hasSelection());
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$autoComplete.val('').trigger('focus');
$linkTitle.val('');

View File

@@ -287,8 +287,6 @@ function initKoPlugins() {
}
export async function showDialog() {
utils.closeActiveDialog();
await libraryLoader.requireLibrary(libraryLoader.KNOCKOUT);
// lazily apply bindings on first use
@@ -300,11 +298,9 @@ export async function showDialog() {
ko.applyBindings(attributesModel, $dialog[0]);
}
glob.activeDialog = $dialog;
await attributesModel.loadAttributes();
$dialog.modal();
utils.openDialog($dialog);
}
$dialog.on('focus', '.attribute-name', function (e) {

View File

@@ -0,0 +1,28 @@
import server from "../services/server.js";
import utils from "../services/utils.js";
const $dialog = $("#backend-log-dialog");
const $backendLogTextArea = $("#backend-log-textarea");
const $refreshBackendLog = $("#refresh-backend-log-button");
export async function showDialog() {
utils.openDialog($dialog);
load();
}
function scrollToBottom() {
$backendLogTextArea.scrollTop($backendLogTextArea[0].scrollHeight);
}
async function load() {
const backendLog = await server.get('backend-log');
$backendLogTextArea.text(backendLog);
scrollToBottom();
}
$refreshBackendLog.on('click', load);
$dialog.on('shown.bs.modal', scrollToBottom);

View File

@@ -13,15 +13,21 @@ const $noteTitle = $('#branch-prefix-note-title');
let branchId;
export async function showDialog(node) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
branchId = node.data.branchId;
const branch = treeCache.getBranch(branchId);
if (branch.noteId === 'root') {
return;
}
const parentNote = await treeCache.getNote(branch.parentNoteId);
if (parentNote.type === 'search') {
return;
}
utils.openDialog($dialog);
$treePrefixInput.val(branch.prefix);
const noteTitle = await treeUtils.getNoteTitle(node.data.noteId);

View File

@@ -22,11 +22,7 @@ export async function showDialog(noteIds) {
}
}
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$noteAutoComplete.val('').trigger('focus');

View File

@@ -17,8 +17,6 @@ let taskId = '';
let branchId = null;
export async function showDialog(node, defaultType) {
utils.closeActiveDialog();
// each opening of the dialog resets the taskId so we don't associate it with previous exports anymore
taskId = '';
$exportButton.removeAttr("disabled");
@@ -38,9 +36,7 @@ export async function showDialog(node, defaultType) {
$("#opml-v2").prop("checked", true); // setting default
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
branchId = node.data.branchId;

View File

@@ -3,9 +3,5 @@ import utils from "../services/utils.js";
const $dialog = $("#help-dialog");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}

View File

@@ -16,8 +16,6 @@ const $explodeArchivesCheckbox = $("#explode-archives-checkbox");
let parentNoteId = null;
export async function showDialog(node) {
utils.closeActiveDialog();
$fileUploadInput.val('').trigger('change'); // to trigger Import button disabling listener below
$safeImportCheckbox.prop("checked", true);
@@ -26,13 +24,11 @@ export async function showDialog(node) {
$codeImportedAsCodeCheckbox.prop("checked", true);
$explodeArchivesCheckbox.prop("checked", true);
glob.activeDialog = $dialog;
parentNoteId = node.data.noteId;
$noteTitle.text(await treeUtils.getNoteTitle(parentNoteId));
$dialog.modal();
utils.openDialog($dialog);
}
$form.on('submit', () => {

View File

@@ -0,0 +1,36 @@
import treeUtils from '../services/tree_utils.js';
import noteAutocompleteService from '../services/note_autocomplete.js';
import utils from "../services/utils.js";
const $dialog = $("#include-note-dialog");
const $form = $("#include-note-form");
const $autoComplete = $("#include-note-autocomplete");
let callback = null;
export async function showDialog(cb) {
callback = cb;
$autoComplete.val('');
utils.openDialog($dialog);
noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true });
noteAutocompleteService.showRecentNotes($autoComplete);
}
$form.on('submit', () => {
const notePath = $autoComplete.getSelectedPath();
if (notePath) {
$dialog.modal('hide');
if (callback) {
callback(treeUtils.getNoteIdFromNotePath(notePath));
}
}
else {
console.error("No noteId to include.");
}
return false;
});

View File

@@ -10,13 +10,9 @@ let $originallyFocused; // element focused before the dialog was opened so we ca
export function info(message) {
$originallyFocused = $(':focus');
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$infoContent.text(message);
$dialog.modal();
utils.openDialog($dialog);
return new Promise((res, rej) => { resolve = res; });
}

View File

@@ -8,13 +8,9 @@ const $autoComplete = $("#jump-to-note-autocomplete");
const $showInFullTextButton = $("#show-in-full-text-button");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$autoComplete.val('');
$dialog.modal();
utils.openDialog($dialog);
noteAutocompleteService.initNoteAutocomplete($autoComplete, { hideGoToSelectedNoteButton: true })
.on('autocomplete:selected', function(event, suggestion, dataset) {

View File

@@ -16,10 +16,6 @@ function getOptions() {
}
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
// set default settings
$maxNotesInput.val(20);
@@ -27,7 +23,7 @@ export async function showDialog() {
$linkMapContainer.empty();
$dialog.modal();
utils.openDialog($dialog);
}
$dialog.on('shown.bs.modal', () => {

View File

@@ -26,6 +26,10 @@ async function convertMarkdownToHtml(text) {
}
export async function importMarkdownInline() {
if (noteDetailService.getActiveTabNoteType() !== 'text') {
return;
}
if (utils.isElectron()) {
const {clipboard} = require('electron');
const text = clipboard.readText();
@@ -33,9 +37,7 @@ export async function importMarkdownInline() {
convertMarkdownToHtml(text);
}
else {
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
}
}

View File

@@ -1,7 +1,5 @@
import noteAutocompleteService from "../services/note_autocomplete.js";
import utils from "../services/utils.js";
import cloningService from "../services/cloning.js";
import treeUtils from "../services/tree_utils.js";
import toastService from "../services/toast.js";
import treeCache from "../services/tree_cache.js";
import treeChangesService from "../services/branches.js";
@@ -18,11 +16,7 @@ let movedNodes;
export async function showDialog(nodes) {
movedNodes = nodes;
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
$noteAutoComplete.val('').trigger('focus');

View File

@@ -10,11 +10,7 @@ const $mime = $("#note-info-mime");
const $okButton = $("#note-info-ok-button");
export function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const activeNote = noteDetailService.getActiveTabNote();

View File

@@ -29,11 +29,7 @@ export async function showCurrentNoteRevisions() {
}
export async function showNoteRevisionsDialog(noteId, noteRevisionId) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
await loadNoteRevisions(noteId, noteRevisionId);
}

View File

@@ -5,11 +5,7 @@ const $dialog = $("#note-source-dialog");
const $noteSource = $("#note-source");
export function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const noteText = noteDetailService.getActiveTabNote().content;

View File

@@ -6,13 +6,9 @@ import utils from "../services/utils.js";
const $dialog = $("#options-dialog");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
const options = await server.get('options');
$dialog.modal();
utils.openDialog($dialog);
(await Promise.all([
import('./options/advanced.js'),
@@ -22,6 +18,7 @@ export async function showDialog() {
import('./options/other.js'),
import('./options/sidebar.js'),
import('./options/sync.js'),
import('./options/keyboard_shortcuts.js'),
]))
.map(m => new m.default)
.forEach(tab => {

View File

@@ -3,19 +3,23 @@ import toastService from "../../services/toast.js";
const TPL = `
<h4 style="margin-top: 0;">Sync</h4>
<button id="force-full-sync-button" class="btn btn-secondary">Force full sync</button>
<button id="force-full-sync-button" class="btn">Force full sync</button>
<br/>
<br/>
<button id="fill-sync-rows-button" class="btn btn-secondary">Fill sync rows</button>
<button id="fill-sync-rows-button" class="btn">Fill sync rows</button>
<br/>
<br/>
<h4>Consistency checks</h4>
<button id="find-and-fix-consistency-issues-button" class="btn">Find and fix consistency issues</button><br/><br/>
<h4>Debugging</h4>
<button id="anonymize-button" class="btn btn-secondary">Save anonymized database</button><br/><br/>
<button id="anonymize-button" class="btn">Save anonymized database</button><br/><br/>
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
for sharing online for debugging purposes without fear of leaking your personal data.</p>
@@ -24,7 +28,7 @@ const TPL = `
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
<button id="vacuum-database-button" class="btn btn-secondary">Vacuum database</button>`;
<button id="vacuum-database-button" class="btn">Vacuum database</button>`;
export default class AdvancedOptions {
constructor() {
@@ -33,9 +37,8 @@ export default class AdvancedOptions {
this.$forceFullSyncButton = $("#force-full-sync-button");
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
this.$anonymizeButton = $("#anonymize-button");
this.$cleanupSoftDeletedButton = $("#cleanup-soft-deleted-items-button");
this.$cleanupUnusedImagesButton = $("#cleanup-unused-images-button");
this.$vacuumDatabaseButton = $("#vacuum-database-button");
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
this.$forceFullSyncButton.on('click', async () => {
await server.post('sync/force-full-sync');
@@ -55,26 +58,16 @@ export default class AdvancedOptions {
toastService.showMessage("Created anonymized database");
});
this.$cleanupSoftDeletedButton.on('click', async () => {
if (confirm("Do you really want to clean up soft-deleted items?")) {
await server.post('cleanup/cleanup-soft-deleted-items');
toastService.showMessage("Soft deleted items have been cleaned up");
}
});
this.$cleanupUnusedImagesButton.on('click', async () => {
if (confirm("Do you really want to clean up unused images?")) {
await server.post('cleanup/cleanup-unused-images');
toastService.showMessage("Unused images have been cleaned up");
}
});
this.$vacuumDatabaseButton.on('click', async () => {
await server.post('cleanup/vacuum-database');
toastService.showMessage("Database has been vacuumed");
});
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
await server.post('cleanup/find-and-fix-consistency-issues');
toastService.showMessage("Consistency issues should be fixed.");
});
}
}

View File

@@ -19,12 +19,13 @@ const TPL = `
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
</div>
<div class="col-4">
<label for="one-tab-display-select">If there's only one tab, then...</label>
<select class="form-control" id="one-tab-display-select">
<option value="show">show the tab bar</option>
<option value="hide">hide the tab bar</option>
<label for="native-title-bar-select">Native title bar (requires app restart)</label>
<select class="form-control" id="native-title-bar-select">
<option value="show">enabled</option>
<option value="hide">disabled</option>
</select>
</div>
</div>
@@ -69,37 +70,6 @@ const TPL = `
</div>
<p>Note that tree and detail font sizing is relative to the main font size setting.</p>
<h4>Left pane sizing</h4>
<div class="form-group row">
<div class="col-6">
<label for="left-pane-min-width">Left pane minimum width (in pixels)</label>
<div class="input-group">
<input type="number" class="form-control" id="left-pane-min-width" min="100" max="2000" step="1"/>
<div class="input-group-append">
<span class="input-group-text">px</span>
</div>
</div>
</div>
<div class="col-6">
<label for="left-pane-min-width">Left pane width percent of window size</label>
<div class="input-group">
<input type="number" class="form-control" id="left-pane-width-percent" min="0" max="99" step="1"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
</div>
</div>
</div>
<p>Left pane width is calculated from the percent of window size, if this is smaller than minimum width, then minimum width is used. If you want to have fixed width left pane, set minimum width to the desired width and set percent to 0.</p>
</form>`;
export default class ApperanceOptions {
@@ -108,9 +78,7 @@ export default class ApperanceOptions {
this.$themeSelect = $("#theme-select");
this.$zoomFactorSelect = $("#zoom-factor-select");
this.$oneTabDisplaySelect = $("#one-tab-display-select");
this.$leftPaneMinWidth = $("#left-pane-min-width");
this.$leftPaneWidthPercent = $("#left-pane-width-percent");
this.$nativeTitleBarSelect = $("#native-title-bar-select");
this.$mainFontSize = $("#main-font-size");
this.$treeFontSize = $("#tree-font-size");
this.$detailFontSize = $("#detail-font-size");
@@ -131,7 +99,7 @@ export default class ApperanceOptions {
if (noteId) {
// make sure the CSS is loaded
// if the CSS has been loaded and then updated then the changes won't take effect though
cssLoader.requireCss(`/api/notes/download/${noteId}`);
cssLoader.requireCss(`api/notes/download/${noteId}`);
}
this.$body.addClass("theme-" + newTheme);
@@ -141,23 +109,10 @@ export default class ApperanceOptions {
this.$zoomFactorSelect.on('change', () => { zoomService.setZoomFactorAndSave(this.$zoomFactorSelect.val()); });
this.$oneTabDisplaySelect.on('change', () => {
const hideTabRowForOneTab = this.$oneTabDisplaySelect.val() === 'hide' ? 'true' : 'false';
this.$nativeTitleBarSelect.on('change', () => {
const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false';
server.put('options/hideTabRowForOneTab/' + hideTabRowForOneTab)
.then(optionsService.reloadOptions);
});
this.$leftPaneMinWidth.on('change', async () => {
await server.put('options/leftPaneMinWidth/' + this.$leftPaneMinWidth.val());
this.resizeLeftPanel();
});
this.$leftPaneWidthPercent.on('change', async () => {
await server.put('options/leftPaneWidthPercent/' + this.$leftPaneWidthPercent.val());
this.resizeLeftPanel();
server.put('options/nativeTitleBarVisible/' + nativeTitleBarVisible);
});
this.$mainFontSize.on('change', async () => {
@@ -204,24 +159,13 @@ export default class ApperanceOptions {
this.$zoomFactorSelect.prop('disabled', true);
}
this.$oneTabDisplaySelect.val(options.hideTabRowForOneTab === 'true' ? 'hide' : 'show');
this.$leftPaneMinWidth.val(options.leftPaneMinWidth);
this.$leftPaneWidthPercent.val(options.leftPaneWidthPercent);
this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide');
this.$mainFontSize.val(options.mainFontSize);
this.$treeFontSize.val(options.treeFontSize);
this.$detailFontSize.val(options.detailFontSize);
}
resizeLeftPanel() {
const leftPanePercent = parseInt(this.$leftPaneWidthPercent.val());
const rightPanePercent = 100 - leftPanePercent;
const leftPaneMinWidth = this.$leftPaneMinWidth.val();
this.$container.css("grid-template-columns", `minmax(${leftPaneMinWidth}px, ${leftPanePercent}fr) ${rightPanePercent}fr`);
}
applyFontSizes() {
this.$body.get(0).style.setProperty("--main-font-size", this.$mainFontSize.val() + "%");
this.$body.get(0).style.setProperty("--tree-font-size", this.$treeFontSize.val() + "%");

View File

@@ -0,0 +1,141 @@
import server from "../../services/server.js";
import utils from "../../services/utils.js";
const TPL = `
<h4>Keyboard shortcuts</h4>
<p>Multiple shortcuts for the same action can be separated by comma.</p>
<div class="form-group">
<input type="text" class="form-control" id="keyboard-shortcut-filter" placeholder="Type text to filter shortcuts...">
</div>
<div style="overflow: auto; height: 500px;">
<table id="keyboard-shortcut-table" cellpadding="10">
<thead>
<tr>
<th>Action name</th>
<th>Shortcuts</th>
<th>Default shortcuts</th>
<th>Description</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<div style="display: flex; justify-content: space-between">
<button class="btn btn-primary" id="options-keyboard-shortcuts-reload-app">Reload app to apply changes</button>
<button class="btn" id="options-keyboard-shortcuts-set-all-to-default">Set all shortcuts to the default</button>
</div>
`;
let globActions;
export default class KeyboardShortcutsOptions {
constructor() {
$("#options-keyboard-shortcuts").html(TPL);
$("#options-keyboard-shortcuts-reload-app").on("click", () => utils.reloadApp());
const $table = $("#keyboard-shortcut-table tbody");
server.get('keyboard-actions').then(actions => {
globActions = actions;
for (const action of actions) {
const $tr = $("<tr>");
if (action.separator) {
$tr.append(
$('<td colspan="4">')
.attr("style","background-color: var(--accented-background-color); font-weight: bold;")
.text(action.separator)
)
}
else {
$tr.append($("<td>").text(action.actionName))
.append($("<td>").append(
$(`<input type="text" class="form-control">`)
.val(action.effectiveShortcuts.join(", "))
.attr('data-keyboard-action-name', action.actionName)
.attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", "))
)
)
.append($("<td>").text(action.defaultShortcuts.join(", ")))
.append($("<td>").text(action.description));
}
$table.append($tr);
}
});
$table.on('change', 'input.form-control', e => {
const $input = $(e.target);
const actionName = $input.attr('data-keyboard-action-name');
const shortcuts = $input.val()
.replace('+,', "+Comma")
.split(",")
.map(shortcut => shortcut.replace("+Comma", "+,"))
.filter(shortcut => !!shortcut);
const opts = {};
opts['keyboardShortcuts' + actionName] = JSON.stringify(shortcuts);
server.put('options', opts);
});
$("#options-keyboard-shortcuts-set-all-to-default").on('click', async () => {
const confirmDialog = await import('../confirm.js');
if (!await confirmDialog.confirm("Do you really want to reset all keyboard shortcuts to the default?")) {
return;
}
$table.find('input.form-control').each(function() {
const defaultShortcuts = $(this).attr('data-default-keyboard-shortcuts');
if ($(this).val() !== defaultShortcuts) {
$(this)
.val(defaultShortcuts)
.trigger('change');
}
});
});
const $filter = $("#keyboard-shortcut-filter");
$filter.on('keyup', () => {
const filter = $filter.val().trim().toLowerCase();
$table.find("tr").each((i, el) => {
if (!filter) {
$(el).show();
return;
}
const actionName = $(el).find('input').attr('data-keyboard-action-name');
if (!actionName) {
$(el).hide();
return;
}
const action = globActions.find(act => act.actionName === actionName);
if (!action) {
$(el).hide();
return;
}
$(el).toggle(!!( // !! to avoid toggle overloads with different behavior
action.actionName.toLowerCase().includes(filter)
|| action.defaultShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|| action.effectiveShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|| (action.description && action.description.toLowerCase().includes(filter))
));
});
});
}
}

View File

@@ -6,7 +6,7 @@ const TPL = `
<div>
<h4>Spell check</h4>
<p>These options apply only for desktop builds, browsers will use their own native spell check.</p>
<p>These options apply only for desktop builds, browsers will use their own native spell check. App restart is required after change.</p>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="spell-check-enabled">
@@ -17,7 +17,7 @@ const TPL = `
<div class="form-group">
<label for="spell-check-language-code">Language code</label>
<input type="email" class="form-control" id="spell-check-language-code" placeholder="for example &quot;en-US&quot;, &quot;de-AT&quot;">
<input type="text" class="form-control" id="spell-check-language-code" placeholder="for example &quot;en-US&quot;, &quot;de-AT&quot;">
</div>
<p>Changes to the spell check options will take effect after application restart.</p>
@@ -37,6 +37,20 @@ const TPL = `
</div>
</div>
<div>
<h4>Note erasure timeout</h4>
<p>Deleted notes are at first only marked as deleted and it is possible to recover them
from Recent Notes dialog. After period of time, deleted notes are "erased" which means
their content is not recoverable anymore. This setting allows you to configure the length
of the period between deleting and erasing the note.</p>
<div class="form-group">
<label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label>
<input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0">
</div>
</div>
<div>
<h4>Protected session timeout</h4>
@@ -81,6 +95,20 @@ export default class ProtectedSessionOptions {
return false;
});
this.$eraseNotesAfterTimeInSeconds = $("#erase-notes-after-time-in-seconds");
this.$eraseNotesAfterTimeInSeconds.on('change', () => {
const eraseNotesAfterTimeInSeconds = this.$eraseNotesAfterTimeInSeconds.val();
server.put('options', { 'eraseNotesAfterTimeInSeconds': eraseNotesAfterTimeInSeconds }).then(() => {
optionsService.reloadOptions();
toastService.showMessage("Options change have been saved.");
});
return false;
});
this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
this.$protectedSessionTimeout.on('change', () => {
@@ -126,10 +154,11 @@ export default class ProtectedSessionOptions {
this.$spellCheckEnabled.prop("checked", options['spellCheckEnabled'] === 'true');
this.$spellCheckLanguageCode.val(options['spellCheckLanguageCode']);
this.$eraseNotesAfterTimeInSeconds.val(options['eraseNotesAfterTimeInSeconds']);
this.$protectedSessionTimeout.val(options['protectedSessionTimeout']);
this.$noteRevisionsTimeInterval.val(options['noteRevisionSnapshotTimeInterval']);
this.$imageMaxWidthHeight.val(options['imageMaxWidthHeight']);
this.$imageJpegQuality.val(options['imageJpegQuality']);
}
}
}

View File

@@ -3,45 +3,6 @@ import server from "../../services/server.js";
import optionsService from "../../services/options.js";
const TPL = `
<h4>Show sidebar in new tab</h4>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="show-sidebar-in-new-tab">
<label class="form-check-label" for="show-sidebar-in-new-tab">Show sidebar in new tab</label>
</div>
<br>
<h4>Sidebar sizing</h4>
<div class="form-group row">
<div class="col-6">
<label for="sidebar-min-width">Sidebar minimum width (in pixels)</label>
<div class="input-group">
<input type="number" class="form-control" id="sidebar-min-width" min="100" max="2000" step="1"/>
<div class="input-group-append">
<span class="input-group-text">px</span>
</div>
</div>
</div>
<div class="col-6">
<label for="left-pane-min-width">Sidebar width percent of the detail pane</label>
<div class="input-group">
<input type="number" class="form-control" id="sidebar-width-percent" min="0" max="70" step="1"/>
<div class="input-group-append">
<span class="input-group-text">%</span>
</div>
</div>
</div>
</div>
<p>Sidebar width is calculated from the percent of the detail pane, if this is smaller than minimum width, then minimum width is used. If you want to have fixed width sidebar, set minimum width to the desired width and set percent to 0.</p>
<h4>Widgets</h4>
<div id="widgets-configuration" class="row">
@@ -58,48 +19,15 @@ export default class SidebarOptions {
constructor() {
$("#options-sidebar").html(TPL);
this.$sidebarMinWidth = $("#sidebar-min-width");
this.$sidebarWidthPercent = $("#sidebar-width-percent");
this.$showSidebarInNewTab = $("#show-sidebar-in-new-tab");
this.$widgetsConfiguration = $("#widgets-configuration");
this.$widgetsEnabled = $("#widgets-enabled");
this.$widgetsDisabled = $("#widgets-disabled");
this.$sidebarMinWidth.on('change', async () => {
await server.put('options/sidebarMinWidth/' + this.$sidebarMinWidth.val());
this.resizeSidebar();
});
this.$sidebarWidthPercent.on('change', async () => {
await server.put('options/sidebarWidthPercent/' + this.$sidebarWidthPercent.val());
this.resizeSidebar();
});
this.$showSidebarInNewTab.on('change', async () => {
const flag = this.$showSidebarInNewTab.is(":checked") ? 'true' : 'false';
await server.put('options/showSidebarInNewTab/' + flag);
optionsService.reloadOptions();
});
}
async optionsLoaded(options) {
this.$widgetsEnabled.empty();
this.$widgetsDisabled.empty();
this.$sidebarMinWidth.val(options.sidebarMinWidth);
this.$sidebarWidthPercent.val(options.sidebarWidthPercent);
if (parseInt(options.showSidebarInNewTab)) {
this.$showSidebarInNewTab.attr("checked", "checked");
}
else {
this.$showSidebarInNewTab.removeAttr("checked");
}
const widgets = [
{name: 'attributes', title: 'Attributes'},
{name: 'linkMap', title: 'Link map'},
@@ -188,19 +116,4 @@ export default class SidebarOptions {
return null;
}
}
resizeSidebar() {
const sidebarWidthPercent = parseInt(this.$sidebarWidthPercent.val());
const sidebarMinWidth = this.$sidebarMinWidth.val();
// need to find them dynamically since they change
const $sidebar = $(".note-detail-sidebar");
const $content = $(".note-detail-content");
$sidebar.css("width", sidebarWidthPercent + '%');
$sidebar.css("min-width", sidebarMinWidth + 'px');
$content.css("width", (100 - sidebarWidthPercent) + '%');
}
}

View File

@@ -12,7 +12,7 @@ const TPL = `
<div class="form-group">
<label for="sync-server-timeout">Sync timeout (milliseconds)</label>
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number">
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number" style="text-align: left;">
</div>
<div class="form-group">
@@ -25,7 +25,7 @@ const TPL = `
<div style="display: flex; justify-content: space-between;">
<button class="btn btn-primary">Save</button>
<button class="btn btn-secondary" type="button" data-help-page="Synchronization">Help</button>
<button class="btn" type="button" data-help-page="Synchronization">Help</button>
</div>
</form>
@@ -35,7 +35,7 @@ const TPL = `
<p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p>
<button id="test-sync-button" class="btn btn-secondary">Test sync</button>`;
<button id="test-sync-button" class="btn">Test sync</button>`;
export default class SyncOptions {
constructor() {

View File

@@ -12,10 +12,6 @@ let resolve;
let shownCb;
export function ask({ message, defaultValue, shown }) {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
shownCb = shown;
$question = $("<label>")
@@ -34,7 +30,7 @@ export function ask({ message, defaultValue, shown }) {
.append($question)
.append($answer));
$dialog.modal();
utils.openDialog($dialog);
return new Promise((res, rej) => { resolve = res; });
}

View File

@@ -1,11 +1,12 @@
import protectedSessionService from "../services/protected_session.js";
import utils from "../services/utils.js";
const $dialog = $("#protected-session-password-dialog");
const $passwordForm = $dialog.find(".protected-session-password-form");
const $passwordInput = $dialog.find(".protected-session-password");
export function show() {
$dialog.modal();
utils.openDialog($dialog);
$passwordInput.trigger('focus');
}

View File

@@ -8,11 +8,7 @@ const $dialog = $("#recent-changes-dialog");
const $content = $("#recent-changes-content");
export async function showDialog() {
utils.closeActiveDialog();
glob.activeDialog = $dialog;
$dialog.modal();
utils.openDialog($dialog);
const result = await server.get('recent-changes');
@@ -28,28 +24,60 @@ export async function showDialog() {
const groupedByDate = groupByDate(result);
for (const [dateDay, dayChanges] of groupedByDate) {
const changesListEl = $('<ul>');
const $changesList = $('<ul>');
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append(changesListEl);
const dayEl = $('<div>').append($('<b>').html(utils.formatDate(dateDay))).append($changesList);
for (const change of dayChanges) {
const formattedTime = utils.formatTime(utils.parseDate(change.date));
let noteLink;
let $noteLink;
if (change.current_isDeleted) {
noteLink = change.current_title;
$noteLink = $("<span>").text(change.current_title);
if (change.canBeUndeleted) {
const $undeleteLink = $(`<a href="javascript:">`)
.text("undelete")
.on('click', async () => {
const confirmDialog = await import('../dialogs/confirm.js');
const text = 'Do you want to undelete this note and its sub-notes?';
if (await confirmDialog.confirm(text)) {
await server.put(`notes/${change.noteId}/undelete`);
$dialog.modal('hide');
await treeCache.reloadNotes([change.noteId]);
treeService.activateNote(change.noteId);
}
});
$noteLink
.append(' (')
.append($undeleteLink)
.append(')');
}
}
else {
const note = await treeCache.getNote(change.noteId);
const notePath = await treeService.getSomeNotePath(note);
noteLink = await linkService.createNoteLinkWithPath(notePath, change.title);
if (notePath) {
$noteLink = await linkService.createNoteLink(notePath, {
title: change.title,
showNotePath: true
});
}
else {
$noteLink = $("<span>").text(note.title);
}
}
changesListEl.append($('<li>')
$changesList.append($('<li>')
.append(formattedTime + ' - ')
.append(noteLink));
.append($noteLink));
}
$content.append(dayEl);
@@ -82,5 +110,6 @@ function groupByDate(result) {
groupedByDate.get(dateDay).push(row);
}
return groupedByDate;
}

View File

@@ -6,22 +6,17 @@ import utils from "../services/utils.js";
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 $tables = $("#sql-console-tables");
const $tableSchemas = $("#sql-console-table-schemas");
const $resultContainer = $("#result-container");
let codeEditor;
$dialog.on("shown.bs.modal", e => initEditor());
export async function showDialog() {
utils.closeActiveDialog();
await showTableSchemas();
glob.activeDialog = $dialog;
await showTables();
$dialog.modal();
utils.openDialog($dialog);
}
async function initEditor() {
@@ -45,6 +40,10 @@ async function initEditor() {
codeEditor.setOption("mode", "text/x-sqlite");
CodeMirror.autoLoadMode(codeEditor, "sql");
codeEditor.setValue(`SELECT title, isProtected, type, mime FROM notes WHERE noteId = 'root';
---
SELECT noteId, parentNoteId, notePosition, prefix FROM branches WHERE branchId = 'root';`);
}
codeEditor.focus();
@@ -70,55 +69,66 @@ async function execute() {
toastService.showMessage("Query was executed successfully.");
}
const rows = result.rows;
const results = result.results;
$resultHead.empty();
$resultBody.empty();
$resultContainer.empty();
for (const rows of results) {
if (rows.length === 0) {
continue;
}
const $table = $('<table class="table table-striped">');
$resultContainer.append($table);
if (rows.length > 0) {
const result = rows[0];
const rowEl = $("<tr>");
const $row = $("<tr>");
for (const key in result) {
rowEl.append($("<th>").html(key));
$row.append($("<th>").html(key));
}
$resultHead.append(rowEl);
}
$table.append($row);
for (const result of rows) {
const rowEl = $("<tr>");
for (const result of rows) {
const $row = $("<tr>");
for (const key in result) {
rowEl.append($("<td>").html(result[key]));
for (const key in result) {
$row.append($("<td>").html(result[key]));
}
$table.append($row);
}
$resultBody.append(rowEl);
}
}
async function showTables() {
async function showTableSchemas() {
const tables = await server.get('sql/schema');
$tables.empty();
$tableSchemas.empty();
for (const table of tables) {
const $tableLink = $('<button class="btn">').text(table.name);
const $columns = $("<table>");
const $columns = $("<ul>");
for (const column of table.columns) {
$columns.append(
$("<tr>")
.append($("<td>").text(column.name))
.append($("<td>").text(column.type))
$("<li>")
.append($("<span>").text(column.name))
.append($("<span>").text(column.type))
);
}
$tables.append($tableLink).append(" ");
$tableSchemas.append($tableLink).append(" ");
$tableLink
.tooltip({html: true, title: $columns.html()})
.tooltip({
html: true,
placement: 'bottom',
boundary: 'window',
title: $columns[0].outerHTML
})
.on('click', () => codeEditor.setValue("SELECT * FROM " + table.name + " LIMIT 100"));
}
}

View File

@@ -6,7 +6,6 @@ class Branch {
this.branchId = row.branchId;
/** @param {string} */
this.noteId = row.noteId;
this.note = null;
/** @param {string} */
this.parentNoteId = row.parentNoteId;
/** @param {int} */
@@ -19,7 +18,7 @@ class Branch {
/** @returns {NoteShort} */
async getNote() {
return await this.treeCache.getNote(this.noteId);
return this.treeCache.getNote(this.noteId);
}
/** @returns {boolean} true if it's top level, meaning its parent is root note */

View File

@@ -154,20 +154,27 @@ class NoteShort {
}
/**
* @param {string} [name] - attribute name to filter
* @returns {Promise<Attribute[]>}
* @param {string} [type] - (optional) attribute type to filter
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
*/
async getAttributes(name) {
if (!this.attributeCache) {
this.attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
async getAttributes(type, name) {
if (!this.__attributeCache) {
this.__attributeCache = (await server.get('notes/' + this.noteId + '/attributes'))
.map(attrRow => new Attribute(this.treeCache, attrRow));
}
if (name) {
return this.attributeCache.filter(attr => attr.name === name);
if (type && name) {
return this.__attributeCache.filter(attr => attr.type === type && attr.name === name);
}
else if (type) {
return this.__attributeCache.filter(attr => attr.type === type);
}
else if (name) {
return this.__attributeCache.filter(attr => attr.name === name);
}
else {
return this.attributeCache;
return this.__attributeCache.slice();
}
}
@@ -176,7 +183,7 @@ class NoteShort {
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
*/
async getLabels(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL);
return await this.getAttributes(LABEL, name);
}
/**
@@ -184,7 +191,7 @@ class NoteShort {
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === LABEL_DEFINITION);
return await this.getAttributes(LABEL_DEFINITION, name);
}
/**
@@ -192,7 +199,7 @@ class NoteShort {
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
*/
async getRelations(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION);
return await this.getAttributes(RELATION, name);
}
/**
@@ -200,7 +207,7 @@ class NoteShort {
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return (await this.getAttributes(name)).filter(attr => attr.type === RELATION_DEFINITION);
return await this.getAttributes(RELATION_DEFINITION, name);
}
/**
@@ -299,8 +306,8 @@ class NoteShort {
* Clear note's attributes cache to force fresh reload for next attribute request.
* Cache is note instance scoped.
*/
invalidateAttributeCache() {
this.attributeCache = null;
invalidate__attributeCache() {
this.__attributeCache = null;
}
/**
@@ -321,7 +328,7 @@ class NoteShort {
const dto = Object.assign({}, this);
delete dto.treeCache;
delete dto.archived;
delete dto.attributeCache;
delete dto.__attributeCache;
return dto;
}

View File

@@ -7,9 +7,26 @@ import contextMenuWidget from "./services/context_menu.js";
import treeChangesService from "./services/branches.js";
import utils from "./services/utils.js";
import treeUtils from "./services/tree_utils.js";
import linkService from "./services/link.js";
import noteContentRenderer from "./services/note_content_renderer.js";
window.glob.isDesktop = utils.isDesktop;
window.glob.isMobile = utils.isMobile;
window.glob.showAddLinkDialog = () => import('./dialogs/add_link.js').then(d => d.showDialog());
window.glob.showIncludeNoteDialog = cb => import('./dialogs/include_note.js').then(d => d.showDialog(cb));
window.glob.loadIncludedNote = async (noteId, el) => {
const note = await treeCache.getNote(noteId);
if (note) {
$(el).empty().append($("<h3>").append(await linkService.createNoteLink(note.noteId, {
showTooltip: false
})));
const {renderedContent} = await noteContentRenderer.getRenderedContent(note);
$(el).append(renderedContent);
}
};
const $leftPane = $("#left-pane");
const $tree = $("#tree");
@@ -37,12 +54,12 @@ $detail.on("click", ".close-detail-button",() => {
});
async function showTree() {
const tree = await treeService.loadTree();
const treeData = await treeService.loadTreeData();
$tree.fancytree({
autoScroll: true,
extensions: ["dnd5", "clones"],
source: tree,
source: treeData,
scrollParent: $tree,
minExpandLevel: 2, // root can't be collapsed
click: (event, data) => {
@@ -87,6 +104,8 @@ async function showTree() {
});
}
});
treeService.setTree($.ui.fancytree.getTree("#tree"));
}
$detail.on("click", ".note-menu-button", async e => {

View File

@@ -265,6 +265,24 @@ ws.subscribeToMessages(async message => {
}
});
ws.subscribeToMessages(async message => {
if (message.taskType !== 'undelete-notes') {
return;
}
if (message.type === 'task-error') {
toastService.closePersistent(message.taskId);
toastService.showError(message.message);
} else if (message.type === 'task-progress-count') {
toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount));
} else if (message.type === 'task-succeeded') {
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
toast.closeAfter = 5000;
toastService.showPersistent(toast);
}
});
export default {
moveBeforeNode,
moveAfterNode,

View File

@@ -1,4 +1,4 @@
import treeUtils from "./tree_utils.js";
import treeService from "./tree.js";
import treeChangesService from "./branches.js";
import cloningService from "./cloning.js";
import toastService from "./toast.js";
@@ -19,7 +19,7 @@ async function pasteAfter(afterNode) {
}
if (clipboardMode === 'cut') {
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
await treeChangesService.moveAfterNode(nodes, afterNode);
@@ -28,7 +28,7 @@ async function pasteAfter(afterNode) {
}
else if (clipboardMode === 'copy') {
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
const clipNode = treeService.getNodeByKey(nodeKey);
await cloningService.cloneNoteAfter(clipNode.data.noteId, afterNode.data.branchId);
}
@@ -46,7 +46,7 @@ async function pasteInto(parentNode) {
}
if (clipboardMode === 'cut') {
const nodes = clipboardNodeKeys.map(nodeKey => treeUtils.getNodeByKey(nodeKey));
const nodes = clipboardNodeKeys.map(nodeKey => treeService.getNodeByKey(nodeKey));
await treeChangesService.moveToNode(nodes, parentNode);
@@ -57,7 +57,7 @@ async function pasteInto(parentNode) {
}
else if (clipboardMode === 'copy') {
for (const nodeKey of clipboardNodeKeys) {
const clipNode = treeUtils.getNodeByKey(nodeKey);
const clipNode = treeService.getNodeByKey(nodeKey);
await cloningService.cloneNoteTo(clipNode.data.noteId, parentNode.data.noteId);
}
@@ -92,7 +92,7 @@ function cut(nodes) {
}
function isClipboardEmpty() {
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeUtils.getNodeByKey(key));
clipboardNodeKeys = clipboardNodeKeys.filter(key => !!treeService.getNodeByKey(key));
return clipboardNodeKeys.length === 0;
}

View File

@@ -1,3 +1,4 @@
import keyboardActionService from './keyboard_actions.js';
const $contextMenuContainer = $("#context-menu-container");
let dateContextMenuOpenedMs = 0;
@@ -69,12 +70,13 @@ async function initContextMenu(event, contextMenu) {
addItems($contextMenuContainer, await contextMenu.getContextMenuItems());
keyboardActionService.updateDisplayedShortcuts($contextMenuContainer);
// code below tries to detect when dropdown would overflow from page
// in such case we'll position it above click coordinates so it will fit into client
const clickPosition = event.pageY;
const clientHeight = document.documentElement.clientHeight;
const contextMenuHeight = $contextMenuContainer.height();
const contextMenuHeight = $contextMenuContainer.outerHeight() + 30;
let top;
if (clickPosition + contextMenuHeight > clientHeight) {

View File

@@ -4,6 +4,12 @@ import zoomService from "./zoom.js";
import protectedSessionService from "./protected_session.js";
import searchNotesService from "./search_notes.js";
import treeService from "./tree.js";
import dateNoteService from "./date_notes.js";
import noteDetailService from "./note_detail.js";
import keyboardActionService from "./keyboard_actions.js";
import hoistedNoteService from "./hoisted_note.js";
import treeCache from "./tree_cache.js";
import server from "./server.js";
const NOTE_REVISIONS = "../dialogs/note_revisions.js";
const OPTIONS = "../dialogs/options.js";
@@ -12,6 +18,7 @@ const JUMP_TO_NOTE = "../dialogs/jump_to_note.js";
const NOTE_SOURCE = "../dialogs/note_source.js";
const RECENT_CHANGES = "../dialogs/recent_changes.js";
const SQL_CONSOLE = "../dialogs/sql_console.js";
const BACKEND_LOG = "../dialogs/backend_log.js";
const ATTRIBUTES = "../dialogs/attributes.js";
const HELP = "../dialogs/help.js";
const NOTE_INFO = "../dialogs/note_info.js";
@@ -26,92 +33,123 @@ function registerEntrypoints() {
jQuery.hotkeys.options.filterContentEditable = false;
jQuery.hotkeys.options.filterTextInputs = false;
utils.bindGlobalShortcut('ctrl+l', () => import(ADD_LINK).then(d => d.showDialog()));
utils.bindGlobalShortcut('ctrl+shift+l', () => import(ADD_LINK).then(d => d.showDialogForClone()));
keyboardActionService.setGlobalActionHandler("AddLinkToText", () => import(ADD_LINK).then(d => d.showDialog()));
$("#jump-to-note-dialog-button").on('click', () => import(JUMP_TO_NOTE).then(d => d.showDialog()));
utils.bindGlobalShortcut('ctrl+j', () => import(JUMP_TO_NOTE).then(d => d.showDialog()));
const showJumpToNoteDialog = () => import(JUMP_TO_NOTE).then(d => d.showDialog());
$("#jump-to-note-dialog-button").on('click', showJumpToNoteDialog);
keyboardActionService.setGlobalActionHandler("JumpToNote", showJumpToNoteDialog);
$("#recent-changes-button").on('click', () => import(RECENT_CHANGES).then(d => d.showDialog()));
const showRecentChanges = () => import(RECENT_CHANGES).then(d => d.showDialog());
$("#recent-changes-button").on('click', showRecentChanges);
keyboardActionService.setGlobalActionHandler("ShowRecentChanges", showRecentChanges);
$("#enter-protected-session-button").on('click', protectedSessionService.enterProtectedSession);
$("#leave-protected-session-button").on('click', protectedSessionService.leaveProtectedSession);
$("#toggle-search-button").on('click', searchNotesService.toggleSearch);
utils.bindGlobalShortcut('ctrl+s', searchNotesService.toggleSearch);
keyboardActionService.setGlobalActionHandler('SearchNotes', searchNotesService.toggleSearch);
const $noteTabContainer = $("#note-tab-container");
$noteTabContainer.on("click", ".show-attributes-button", () => import(ATTRIBUTES).then(d => d.showDialog()));
utils.bindGlobalShortcut('alt+a', () => import(ATTRIBUTES).then(d => d.showDialog()));
$noteTabContainer.on("click", ".show-note-info-button", () => import(NOTE_INFO).then(d => d.showDialog()));
const showAttributesDialog = () => import(ATTRIBUTES).then(d => d.showDialog());
$noteTabContainer.on("click", ".show-attributes-button", showAttributesDialog);
keyboardActionService.setGlobalActionHandler("ShowAttributes", showAttributesDialog);
$noteTabContainer.on("click", ".show-note-revisions-button", function() {
const showNoteInfoDialog = () => import(NOTE_INFO).then(d => d.showDialog());
$noteTabContainer.on("click", ".show-note-info-button", showNoteInfoDialog);
keyboardActionService.setGlobalActionHandler("ShowNoteInfo", showNoteInfoDialog);
const showNoteRevisionsDialog = function() {
if ($(this).hasClass("disabled")) {
return;
}
import(NOTE_REVISIONS).then(d => d.showCurrentNoteRevisions());
});
};
$noteTabContainer.on("click", ".show-source-button", function() {
$noteTabContainer.on("click", ".show-note-revisions-button", showNoteRevisionsDialog);
keyboardActionService.setGlobalActionHandler("ShowNoteRevisions", showNoteRevisionsDialog);
const showNoteSourceDialog = function() {
if ($(this).hasClass("disabled")) {
return;
}
import(NOTE_SOURCE).then(d => d.showDialog());
});
};
$noteTabContainer.on("click", ".show-link-map-button", function() {
import(LINK_MAP).then(d => d.showDialog());
});
$noteTabContainer.on("click", ".show-source-button", showNoteSourceDialog);
keyboardActionService.setGlobalActionHandler("ShowNoteSource", showNoteSourceDialog);
$("#options-button").on('click', () => import(OPTIONS).then(d => d.showDialog()));
const showLinkMapDialog = () => import(LINK_MAP).then(d => d.showDialog());
$noteTabContainer.on("click", ".show-link-map-button", showLinkMapDialog);
keyboardActionService.setGlobalActionHandler("ShowLinkMap", showLinkMapDialog);
$("#show-help-button").on('click', () => import(HELP).then(d => d.showDialog()));
utils.bindGlobalShortcut('f1', () => import(HELP).then(d => d.showDialog()));
const showOptionsDialog = () => import(OPTIONS).then(d => d.showDialog());
$("#options-button").on('click', showOptionsDialog);
keyboardActionService.setGlobalActionHandler("ShowOptions", showOptionsDialog);
$("#open-sql-console-button").on('click', () => import(SQL_CONSOLE).then(d => d.showDialog()));
utils.bindGlobalShortcut('alt+o', () => import(SQL_CONSOLE).then(d => d.showDialog()));
const showHelpDialog = () => import(HELP).then(d => d.showDialog());
$("#show-help-button").on('click', showHelpDialog);
keyboardActionService.setGlobalActionHandler("ShowHelp", showHelpDialog);
const showSqlConsoleDialog = () => import(SQL_CONSOLE).then(d => d.showDialog());
$("#open-sql-console-button").on('click', showSqlConsoleDialog);
keyboardActionService.setGlobalActionHandler("ShowSQLConsole", showSqlConsoleDialog);
const showBackendLogDialog = () => import(BACKEND_LOG).then(d => d.showDialog());
$("#show-backend-log-button").on('click', showBackendLogDialog);
keyboardActionService.setGlobalActionHandler("ShowBackendLog", showBackendLogDialog);
$("#show-about-dialog-button").on('click', () => import(ABOUT).then(d => d.showDialog()));
if (utils.isElectron()) {
$("#history-navigation").show();
$("#history-back-button").on('click', window.history.back);
$("#history-forward-button").on('click', window.history.forward);
keyboardActionService.setGlobalActionHandler("BackInNoteHistory", window.history.back);
if (utils.isMac()) {
// Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376
utils.bindGlobalShortcut('meta+left', window.history.back);
utils.bindGlobalShortcut('meta+right', window.history.forward);
}
else {
utils.bindGlobalShortcut('alt+left', window.history.back);
utils.bindGlobalShortcut('alt+right', window.history.forward);
}
$("#history-forward-button").on('click', window.history.forward);
keyboardActionService.setGlobalActionHandler("ForwardInNoteHistory", window.history.forward);
}
utils.bindGlobalShortcut('alt+m', e => {
$(".hide-toggle").toggle();
$("#container").toggleClass("distraction-free-mode");
});
let zenModeActive = false;
// hide (toggle) everything except for the note content for distraction free writing
utils.bindGlobalShortcut('alt+t', e => {
// hide (toggle) everything except for the note content for zen mode
const toggleZenMode = () => {
if (!zenModeActive) {
$(".hide-in-zen-mode,.gutter").addClass("hidden-by-zen-mode");
$("#container").addClass("zen-mode");
zenModeActive = true;
}
else {
// not hiding / showing explicitly since element might be hidden also for other reasons
$(".hide-in-zen-mode,.gutter").removeClass("hidden-by-zen-mode");
$("#container").removeClass("zen-mode");
zenModeActive = false;
}
};
$("#toggle-zen-mode-button").on('click', toggleZenMode);
keyboardActionService.setGlobalActionHandler("ToggleZenMode", toggleZenMode);
keyboardActionService.setGlobalActionHandler("InsertDateTimeToText", () => {
const date = new Date();
const dateString = utils.formatDateTime(date);
linkService.addTextToEditor(dateString);
});
utils.bindGlobalShortcut('f5', utils.reloadApp);
$("#reload-frontend-button").on('click', utils.reloadApp);
utils.bindGlobalShortcut('ctrl+r', utils.reloadApp);
keyboardActionService.setGlobalActionHandler("ReloadFrontendApp", utils.reloadApp);
$("#open-dev-tools-button").toggle(utils.isElectron());
keyboardActionService.setGlobalActionHandler("PasteMarkdownIntoText", async () => {
const dialog = await import("../dialogs/markdown_import.js");
dialog.importMarkdownInline();
});
if (utils.isElectron()) {
const openDevTools = () => {
require('electron').remote.getCurrentWindow().toggleDevTools();
@@ -119,8 +157,8 @@ function registerEntrypoints() {
return false;
};
utils.bindGlobalShortcut('ctrl+shift+i', openDevTools);
$("#open-dev-tools-button").on('click', openDevTools);
keyboardActionService.setGlobalActionHandler("OpenDevTools", openDevTools);
}
let findInPage;
@@ -141,18 +179,16 @@ function registerEntrypoints() {
textHoverBgColor: '#555',
caseSelectedColor: 'var(--main-border-color)'
});
}
if (utils.isElectron()) {
utils.bindGlobalShortcut('ctrl+f', () => {
findInPage.openFindWindow();
return false;
keyboardActionService.setGlobalActionHandler("FindInText", () => {
if (!glob.activeDialog || !glob.activeDialog.is(":visible")) {
findInPage.openFindWindow();
}
});
}
if (utils.isElectron()) {
const toggleFullscreen = function() {
const toggleFullscreen = () => {
const win = require('electron').remote.getCurrentWindow();
if (win.isFullScreenable()) {
@@ -164,7 +200,7 @@ function registerEntrypoints() {
$("#toggle-fullscreen-button").on('click', toggleFullscreen);
utils.bindGlobalShortcut('f11', toggleFullscreen);
keyboardActionService.setGlobalActionHandler("ToggleFullscreen", toggleFullscreen);
}
else {
// outside of electron this is handled by the browser
@@ -172,8 +208,8 @@ function registerEntrypoints() {
}
if (utils.isElectron()) {
utils.bindGlobalShortcut('ctrl+-', zoomService.decreaseZoomFactor);
utils.bindGlobalShortcut('ctrl+=', zoomService.increaseZoomFactor);
keyboardActionService.setGlobalActionHandler("ZoomOut", zoomService.decreaseZoomFactor);
keyboardActionService.setGlobalActionHandler("ZoomIn", zoomService.increaseZoomFactor);
}
$(document).on('click', "a[data-action='note-revision']", async event => {
@@ -188,7 +224,7 @@ function registerEntrypoints() {
return false;
});
utils.bindGlobalShortcut('ctrl+shift+c', () => import(CLONE_TO).then(d => {
keyboardActionService.setGlobalActionHandler("CloneNotesTo", () => import(CLONE_TO).then(d => {
const activeNode = treeService.getActiveNode();
const selectedOrActiveNodes = treeService.getSelectedOrActiveNodes(activeNode);
@@ -198,7 +234,7 @@ function registerEntrypoints() {
d.showDialog(noteIds);
}));
utils.bindGlobalShortcut('ctrl+shift+x', () => import(MOVE_TO).then(d => {
keyboardActionService.setGlobalActionHandler("MoveNotesTo", () => import(MOVE_TO).then(d => {
const activeNode = treeService.getActiveNode();
const selectedOrActiveNodes = treeService.getSelectedOrActiveNodes(activeNode);
@@ -206,6 +242,54 @@ function registerEntrypoints() {
d.showDialog(selectedOrActiveNodes);
}));
keyboardActionService.setGlobalActionHandler("CreateNoteIntoDayNote", async () => {
const todayNote = await dateNoteService.getTodayNote();
const {note} = await server.post(`notes/${todayNote.noteId}/children?target=into`, {
title: 'new note',
content: '',
type: 'text',
isProtected: todayNote.isProtected
});
await treeService.expandToNote(note.noteId);
await noteDetailService.openInTab(note.noteId, true);
noteDetailService.focusAndSelectTitle();
});
keyboardActionService.setGlobalActionHandler("EditBranchPrefix", async () => {
const node = treeService.getActiveNode();
const editBranchPrefixDialog = await import("../dialogs/branch_prefix.js");
editBranchPrefixDialog.showDialog(node);
});
keyboardActionService.setGlobalActionHandler("ToggleNoteHoisting", async () => {
const node = treeService.getActiveNode();
hoistedNoteService.getHoistedNoteId().then(async hoistedNoteId => {
if (node.data.noteId === hoistedNoteId) {
hoistedNoteService.unhoist();
}
else {
const note = await treeCache.getNote(node.data.noteId);
if (note.type !== 'search') {
hoistedNoteService.setHoistedNoteId(node.data.noteId);
}
}
});
});
keyboardActionService.setGlobalActionHandler("SearchInSubtree", () => {
const node = treeService.getActiveNode();
searchNotesService.searchInSubtree(node.data.noteId);
});
keyboardActionService.setGlobalActionHandler("CopyWithoutFormatting", utils.copySelectionToClipboard);
}
export default {

View File

@@ -58,13 +58,13 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
};
/**
* Activates newly created note. Compared to this.activateNote() also refreshes tree.
* Activates newly created note. Compared to this.activateNote() also makes sure that frontend has been fully synced.
*
* @param {string} notePath (or noteId)
* @return {Promise<void>}
*/
this.activateNewNote = async notePath => {
await treeService.reload();
await ws.waitForMaxKnownSyncId();
await treeService.activateNote(notePath, noteDetailService.focusAndSelectTitle);
};
@@ -125,14 +125,14 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
}
/**
* Executes given anonymous function on the server.
* Executes given anonymous function on the backend.
* Internally this serializes the anonymous function into string and sends it to backend via AJAX.
*
* @param {string} script - script to be executed on the backend
* @param {Array.<?>} params - list of parameters to the anonymous function to be send to backend
* @return {Promise<*>} return value of the executed function on the backend
*/
this.runOnServer = async (script, params = []) => {
this.runOnBackend = async (script, params = []) => {
if (typeof script === "function") {
script = script.toString();
}
@@ -159,6 +159,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
}
};
/**
* @deprecated new name of this API call is runOnBackend so use that
* @method
*/
this.runOnServer = this.runOnBackend;
/**
* This is a powerful search method - you can search by attributes and their values, e.g.:
* "@dateModified =* MONTH AND @log". See full documentation for all options at: https://github.com/zadam/trilium/wiki/Search
@@ -290,12 +296,28 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
*/
this.createNoteLink = linkService.createNoteLink;
/**
* Adds given text to the editor cursor
*
* @param {string} text - this must be clear text, HTML is not supported.
* @method
*/
this.addTextToActiveTabEditor = linkService.addTextToEditor;
/**
* @method
* @returns {NoteFull} active note (loaded into right pane)
*/
this.getActiveTabNote = noteDetailService.getActiveTabNote;
/**
* See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
*
* @method
* @returns {Editor|null} CKEditor instance or null (e.g. if active note is not a text note)
*/
this.getActiveTabTextEditor = noteDetailService.getActiveEditor;
/**
* @method
* @returns {Promise<string|null>} returns note path of active note or null if there isn't active note
@@ -382,6 +404,17 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
* @param {function} handler
*/
this.bindGlobalShortcut = utils.bindGlobalShortcut;
/**
* Trilium runs in backend and frontend process, when something is changed on the backend from script,
* frontend will get asynchronously synchronized.
*
* This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
* Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
*
* @method
*/
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
}
export default FrontendScriptApi;

View File

@@ -0,0 +1,117 @@
import server from "./server.js";
import utils from "./utils.js";
const keyboardActionRepo = {};
const keyboardActionsLoaded = server.get('keyboard-actions').then(actions => {
for (const action of actions) {
keyboardActionRepo[action.actionName] = action;
}
});
server.get('keyboard-shortcuts-for-notes').then(shortcutForNotes => {
for (const shortcut in shortcutForNotes) {
utils.bindGlobalShortcut(shortcut, async () => {
const treeService = (await import("./tree.js")).default;
treeService.activateNote(shortcutForNotes[shortcut]);
});
}
});
function setGlobalActionHandler(actionName, handler) {
keyboardActionsLoaded.then(() => {
const action = keyboardActionRepo[actionName];
if (!action) {
throw new Error(`Cannot find keyboard action '${actionName}'`);
}
action.handler = handler;
for (const shortcut of action.effectiveShortcuts) {
if (shortcut && !shortcut.startsWith("global:")) { // global shortcuts should be handled in the electron code
utils.bindGlobalShortcut(shortcut, handler);
}
}
});
}
function setElementActionHandler($el, actionName, handler) {
keyboardActionsLoaded.then(() => {
const action = keyboardActionRepo[actionName];
if (!action) {
throw new Error(`Cannot find keyboard action '${actionName}'`);
}
// not setting action.handler since this is not global
for (const shortcut of action.effectiveShortcuts) {
if (shortcut) {
utils.bindElShortcut($el, shortcut, handler);
}
}
});
}
async function triggerAction(actionName) {
const action = await getAction(actionName);
if (!action.handler) {
throw new Error(`Action ${actionName} has no handler`);
}
await action.handler();
}
async function getAction(actionName, silent = false) {
await keyboardActionsLoaded;
const action = keyboardActionRepo[actionName];
if (!action) {
if (silent) {
console.log(`Cannot find action ${actionName}`);
}
else {
throw new Error(`Cannot find action ${actionName}`);
}
}
return action;
}
function updateDisplayedShortcuts($container) {
$container.find('kbd[data-kb-action]').each(async (i, el) => {
const actionName = $(el).attr('data-kb-action');
const action = await getAction(actionName, true);
if (action) {
$(el).text(action.effectiveShortcuts.join(', '));
}
});
$container.find('button[data-kb-action],a.icon-action[data-kb-action]').each(async (i, el) => {
const actionName = $(el).attr('data-kb-action');
const action = await getAction(actionName, true);
if (action) {
const title = $(el).attr('title');
const shortcuts = action.effectiveShortcuts.join(', ');
const newTitle = !title || !title.trim() ? shortcuts : `${title} (${shortcuts})`;
$(el).attr('title', newTitle);
}
});
}
$(() => updateDisplayedShortcuts($(document)));
export default {
setGlobalActionHandler,
setElementActionHandler,
triggerAction,
getAction,
updateDisplayedShortcuts
};

View File

@@ -0,0 +1,16 @@
class Actions {
constructor() {
this.JUMP_TO = "";
}
}
const actions = new Actions();
function bind() {
}
export default {
actions,
bind
};

View File

@@ -6,15 +6,20 @@ import noteDetailService from "./note_detail.js";
function getNotePathFromUrl(url) {
const notePathMatch = /#(root[A-Za-z0-9/]*)$/.exec(url);
if (notePathMatch === null) {
return null;
}
else {
return notePathMatch[1];
}
return notePathMatch === null ? null : notePathMatch[1];
}
async function createNoteLink(notePath, noteTitle = null, tooltip = true) {
async function createNoteLink(notePath, options = {}) {
if (!notePath || !notePath.trim()) {
console.error("Missing note path");
return $("<span>").text("[missing note]");
}
let noteTitle = options.title;
const showTooltip = options.showTooltip === undefined ? true : options.showTooltip;
const showNotePath = options.showNotePath === undefined ? false : options.showNotePath;
if (!noteTitle) {
const {noteId, parentNoteId} = treeUtils.getNoteIdAndParentIdFromNotePath(notePath);
@@ -27,30 +32,28 @@ async function createNoteLink(notePath, noteTitle = null, tooltip = true) {
}).attr('data-action', 'note')
.attr('data-note-path', notePath);
if (!tooltip) {
if (!showTooltip) {
$noteLink.addClass("no-tooltip-preview");
}
return $noteLink;
}
const $container = $("<span>").append($noteLink);
async function createNoteLinkWithPath(notePath, noteTitle = null) {
const $link = await createNoteLink(notePath, noteTitle);
if (showNotePath) {
notePath = await treeService.resolveNotePath(notePath);
const $res = $("<span>").append($link);
if (notePath) {
const noteIds = notePath.split("/");
noteIds.pop(); // remove last element
if (notePath.includes("/")) {
const noteIds = notePath.split("/");
noteIds.pop(); // remove last element
const parentNotePath = noteIds.join("/").trim();
const parentNotePath = noteIds.join("/").trim();
if (parentNotePath) {
$res.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
if (parentNotePath) {
$container.append($("<small>").text(" (" + await treeUtils.getNotePathTitle(parentNotePath) + ")"));
}
}
}
return $res;
return $container;
}
function getNotePathFromLink($link) {
@@ -66,13 +69,16 @@ function getNotePathFromLink($link) {
}
function goToLink(e) {
const $link = $(e.target);
e.preventDefault();
e.stopPropagation();
const $link = $(e.target).closest("a");
const notePath = getNotePathFromLink($link);
if (notePath) {
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
noteDetailService.openInTab(notePath);
noteDetailService.openInTab(notePath, false);
}
else if (e.which === 1) {
treeService.activateNote(notePath);
@@ -89,9 +95,6 @@ function goToLink(e) {
}
}
e.preventDefault();
e.stopPropagation();
return true;
}
@@ -118,7 +121,7 @@ function addTextToEditor(text) {
}
function newTabContextMenu(e) {
const $link = $(e.target);
const $link = $(e.target).closest("a");
const notePath = getNotePathFromLink($link);
@@ -142,22 +145,27 @@ function newTabContextMenu(e) {
});
}
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu);
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu);
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu);
// when click on link popup, in case of internal link, just go the the referenced note instead of default behavior
// of opening the link in new window/tab
$(document).on('mousedown', "a[data-action='note']", goToLink);
$(document).on('mousedown', 'div.popover-content a, div.ui-tooltip-content a', goToLink);
$(document).on('dblclick', '.note-detail-text a', goToLink);
$(document).on('mousedown', '.note-detail-text a', function (e) {
const notePath = getNotePathFromLink($(e.target));
if (notePath && ((e.which === 1 && e.ctrlKey) || e.which === 2)) {
const $link = $(e.target).closest("a");
const notePath = getNotePathFromLink($link);
if ((e.which === 1 && e.ctrlKey) || e.which === 2) {
// if it's a ctrl-click, then we open on new tab, otherwise normal flow (CKEditor opens link-editing dialog)
e.preventDefault();
noteDetailService.loadNoteDetail(notePath, { newTab: true });
if (notePath) {
noteDetailService.loadNoteDetail(notePath, {newTab: true});
}
else {
const address = $link.attr('href');
window.open(address, '_blank');
}
return true;
}
@@ -166,24 +174,20 @@ $(document).on('mousedown', '.note-detail-text a', function (e) {
$(document).on('mousedown', '.note-detail-book a', goToLink);
$(document).on('mousedown', '.note-detail-render a', goToLink);
$(document).on('mousedown', '.note-detail-text.ck-read-only a', goToLink);
$(document).on('mousedown', 'span.ck-button__label', e => {
// this is a link preview dialog from CKEditor link editing
// for some reason clicked element is span
const url = $(e.target).text();
const notePath = getNotePathFromUrl(url);
if (notePath) {
treeService.activateNote(notePath);
e.preventDefault();
}
$(document).on('mousedown', 'a.ck-link-actions__preview', goToLink);
$(document).on('click', 'a.ck-link-actions__preview', e => {
e.preventDefault();
e.stopPropagation();
});
$(document).on('contextmenu', 'a.ck-link-actions__preview', newTabContextMenu);
$(document).on('contextmenu', '.note-detail-text a', newTabContextMenu);
$(document).on('contextmenu', "a[data-action='note']", newTabContextMenu);
$(document).on('contextmenu', ".note-detail-render a", newTabContextMenu);
export default {
getNotePathFromUrl,
createNoteLink,
createNoteLinkWithPath,
addLinkToEditor,
addTextToEditor,
goToLink

View File

@@ -88,7 +88,7 @@ export default class LinkMap {
.addClass("note-box")
.prop("id", noteBoxId);
linkService.createNoteLink(noteId, note.title).then($link => {
linkService.createNoteLink(noteId, {title: note.title}).then($link => {
$link.on('click', e => {
try {
$link.tooltip('dispose');

View File

@@ -1,6 +1,7 @@
import optionsService from "./options.js";
const MIME_TYPES_DICT = [
{ default: true, title: "Plain text", mime: "text/plain" },
{ title: "APL", mime: "text/apl" },
{ title: "PGP", mime: "application/pgp" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
@@ -91,7 +92,6 @@ const MIME_TYPES_DICT = [
{ default: true, title: "Perl", mime: "text/x-perl" },
{ default: true, title: "PHP", mime: "text/x-php" },
{ title: "Pig", mime: "text/x-pig" },
{ title: "Plain Text", mime: "text/plain" },
{ title: "PLSQL", mime: "text/x-plsql" },
{ title: "PostgreSQL", mime: "text/x-pgsql" },
{ title: "PowerShell", mime: "application/x-powershell" },
@@ -168,7 +168,7 @@ function loadMimeTypes(options) {
|| MIME_TYPES_DICT.filter(mt => mt.default).map(mt => mt.mime);
for (const mt of mimeTypes) {
mt.enabled = enabledMimeTypes.includes(mt.mime);
mt.enabled = enabledMimeTypes.includes(mt.mime) || mt.mime === 'text/plain'; // text/plain is always enabled
}
}

View File

@@ -0,0 +1,98 @@
import server from "./server.js";
import utils from "./utils.js";
import renderService from "./render.js";
import protectedSessionService from "./protected_session.js";
import protectedSessionHolder from "./protected_session_holder.js";
async function getRenderedContent(note) {
const type = getRenderingType(note);
let $rendered;
if (type === 'text') {
const fullNote = await server.get('notes/' + note.noteId);
$rendered = $("<div>").html(fullNote.content);
}
else if (type === 'code') {
const fullNote = await server.get('notes/' + note.noteId);
$rendered = $("<pre>").text(fullNote.content);
}
else if (type === 'image') {
$rendered = $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`);
}
else if (type === 'file') {
function getFileUrl() {
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
}
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
$downloadButton.on('click', () => utils.download(getFileUrl()));
$openButton.on('click', () => {
if (utils.isElectron()) {
const open = require("open");
open(getFileUrl(), {url: true});
}
else {
window.location.href = getFileUrl();
}
});
// open doesn't work for protected notes since it works through browser which isn't in protected session
$openButton.toggle(!note.isProtected);
$rendered = $('<div>')
.append($downloadButton)
.append(' &nbsp; ')
.append($openButton);
}
else if (type === 'render') {
$rendered = $('<div>');
await renderService.render(note, $rendered, this.ctx);
}
else if (type === 'protected-session') {
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
.on('click', protectedSessionService.enterProtectedSession);
$rendered = $("<div>")
.append("<div>This note is protected and to access it you need to enter password.</div>")
.append("<br/>")
.append($button);
}
else {
$rendered = $("<em>Content of this note cannot be displayed in the book format</em>");
}
if (note.cssClass) {
$rendered.addClass(note.cssClass);
}
return {
renderedContent: $rendered,
type
};
}
function getRenderingType(note) {
let type = note.type;
if (note.isProtected) {
if (protectedSessionHolder.isProtectedSessionAvailable()) {
protectedSessionHolder.touchProtectedSession();
}
else {
type = 'protected-session';
}
}
return type;
}
export default {
getRenderedContent
};

View File

@@ -8,6 +8,7 @@ import utils from "./utils.js";
import contextMenuService from "./context_menu.js";
import treeUtils from "./tree_utils.js";
import tabRow from "./tab_row.js";
import keyboardActionService from "./keyboard_actions.js";
const $tabContentsContainer = $("#note-tab-container");
const $savedIndicator = $(".saved-indicator");
@@ -34,8 +35,8 @@ async function reloadAllTabs() {
}
}
async function openInTab(notePath) {
await loadNoteDetail(notePath, { newTab: true });
async function openInTab(notePath, activate) {
await loadNoteDetail(notePath, { newTab: true, activate });
}
async function switchToNote(notePath) {
@@ -173,7 +174,11 @@ async function showTab(tabId) {
if (newActiveTabContext && newActiveTabContext.notePath) {
const newActiveNode = await treeService.getNodeFromPath(newActiveTabContext.notePath);
if (newActiveNode && newActiveNode.isVisible()) {
if (newActiveNode) {
if (!newActiveNode.isVisible()) {
await treeService.expandToNote(newActiveTabContext.notePath);
}
newActiveNode.setActive(true, {noEvents: true});
}
}
@@ -196,17 +201,13 @@ async function loadNoteDetail(origNotePath, options = {}) {
const newTab = !!options.newTab;
const activate = !!options.activate;
const notePath = await treeService.resolveNotePath(origNotePath);
let notePath = await treeService.resolveNotePath(origNotePath);
if (!notePath) {
console.error(`Cannot resolve note path ${origNotePath}`);
// fallback to display something
if (tabContexts.length === 0) {
await openEmptyTab();
}
return;
notePath = 'root';
}
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
@@ -273,7 +274,9 @@ async function filterTabs(noteId) {
async function noteDeleted(noteId) {
for (const tc of tabContexts) {
if (tc.notePath && tc.notePath.split("/").includes(noteId)) {
// not removing active even if it contains deleted note since that one will move to another note (handled by deletion logic)
// and we would lose tab context state (e.g. sidebar visibility)
if (!tc.isActive() && tc.notePath && tc.notePath.split("/").includes(noteId)) {
await tabRow.removeTab(tc.$tab[0]);
}
}
@@ -419,33 +422,31 @@ $(tabRow.el).on('contextmenu', '.note-tab', e => {
});
});
if (utils.isElectron()) {
utils.bindGlobalShortcut('ctrl+t', () => {
openEmptyTab();
});
keyboardActionService.setGlobalActionHandler('OpenNewTab', () => {
openEmptyTab();
});
utils.bindGlobalShortcut('ctrl+w', () => {
if (tabRow.activeTabEl) {
tabRow.removeTab(tabRow.activeTabEl);
}
});
keyboardActionService.setGlobalActionHandler('CloseActiveTab', () => {
if (tabRow.activeTabEl) {
tabRow.removeTab(tabRow.activeTabEl);
}
});
utils.bindGlobalShortcut('ctrl+tab', () => {
const nextTab = tabRow.nextTabEl;
keyboardActionService.setGlobalActionHandler('ActivateNextTab', () => {
const nextTab = tabRow.nextTabEl;
if (nextTab) {
tabRow.activateTab(nextTab);
}
});
if (nextTab) {
tabRow.activateTab(nextTab);
}
});
utils.bindGlobalShortcut('ctrl+shift+tab', () => {
const prevTab = tabRow.previousTabEl;
keyboardActionService.setGlobalActionHandler('ActivatePreviousTab', () => {
const prevTab = tabRow.previousTabEl;
if (prevTab) {
tabRow.activateTab(prevTab);
}
});
}
if (prevTab) {
tabRow.activateTab(prevTab);
}
});
tabRow.addListener('activeTabChange', openTabsChanged);
tabRow.addListener('tabRemove', openTabsChanged);

View File

@@ -1,10 +1,6 @@
import server from "./server.js";
import linkService from "./link.js";
import utils from "./utils.js";
import treeCache from "./tree_cache.js";
import renderService from "./render.js";
import protectedSessionHolder from "./protected_session_holder.js";
import protectedSessionService from "./protected_session.js";
import noteContentRenderer from "./note_content_renderer.js";
const MIN_ZOOM_LEVEL = 1;
const MAX_ZOOM_LEVEL = 6;
@@ -47,6 +43,7 @@ class NoteDetailBook {
this.$zoomInButton = this.$component.find('.book-zoom-in-button');
this.$zoomOutButton = this.$component.find('.book-zoom-out-button');
this.$expandChildrenButton = this.$component.find('.expand-children-button');
this.$help = this.$component.find('.note-detail-book-help');
this.$zoomInButton.on('click', () => this.setZoom(this.zoomLevel - 1));
this.$zoomOutButton.on('click', () => this.setZoom(this.zoomLevel + 1));
@@ -109,6 +106,7 @@ class NoteDetailBook {
async render() {
this.$content.empty();
this.$help.hide();
if (this.isAutoBook()) {
const $addTextLink = $('<a href="javascript:">here</a>').on('click', () => {
@@ -128,19 +126,21 @@ class NoteDetailBook {
}
async renderIntoElement(note, $container) {
for (const childNote of await note.getChildNotes()) {
const type = this.getRenderingType(childNote);
const childNotes = await note.getChildNotes();
for (const childNote of childNotes) {
const childNotePath = this.ctx.notePath + '/' + childNote.noteId;
const {type, renderedContent} = await noteContentRenderer.getRenderedContent(childNote);
const $card = $('<div class="note-book-card">')
.attr('data-note-id', childNote.noteId)
.css("flex-basis", ZOOMS[this.zoomLevel].width)
.addClass("type-" + type)
.append($('<h5 class="note-book-title">').append(await linkService.createNoteLink(childNotePath, null, false)))
.append($('<h5 class="note-book-title">').append(await linkService.createNoteLink(childNotePath, {showTooltip: false})))
.append($('<div class="note-book-content">')
.css("max-height", ZOOMS[this.zoomLevel].height)
.append(await this.getNoteContent(type, childNote)));
.append(renderedContent));
const childCount = childNote.getChildNoteIds().length;
@@ -148,87 +148,17 @@ class NoteDetailBook {
const label = `${childCount} child${childCount > 1 ? 'ren' : ''}`;
$card.append($('<div class="note-book-children">')
.append($(`<a class="note-book-open-children-button" href="javascript:">+ Show ${label}</a>`))
.append($(`<a class="note-book-hide-children-button" href="javascript:">- Hide ${label}</a>`).hide())
.append($(`<a class="note-book-open-children-button no-print" href="javascript:">+ Show ${label}</a>`))
.append($(`<a class="note-book-hide-children-button no-print" href="javascript:">- Hide ${label}</a>`).hide())
.append($('<div class="note-book-children-content">'))
);
}
$container.append($card);
}
}
async getNoteContent(type, note) {
if (type === 'text') {
const fullNote = await server.get('notes/' + note.noteId);
const $content = $("<div>").html(fullNote.content);
if (utils.isHtmlEmpty(fullNote.content)) {
return "";
}
else {
return $content;
}
}
else if (type === 'code') {
const fullNote = await server.get('notes/' + note.noteId);
if (fullNote.content.trim() === "") {
return "";
}
return $("<pre>").text(fullNote.content);
}
else if (type === 'image') {
return $("<img>").attr("src", `api/images/${note.noteId}/${note.title}`);
}
else if (type === 'file') {
function getFileUrl() {
return utils.getUrlForDownload("api/notes/" + note.noteId + "/download");
}
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
$downloadButton.on('click', () => utils.download(getFileUrl()));
$openButton.on('click', () => {
if (utils.isElectron()) {
const open = require("open");
open(getFileUrl(), {url: true});
}
else {
window.location.href = getFileUrl();
}
});
// open doesn't work for protected notes since it works through browser which isn't in protected session
$openButton.toggle(!note.isProtected);
return $('<div>')
.append($downloadButton)
.append(' &nbsp; ')
.append($openButton);
}
else if (type === 'render') {
const $el = $('<div>');
await renderService.render(note, $el, this.ctx);
return $el;
}
else if (type === 'protected-session') {
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
.on('click', protectedSessionService.enterProtectedSession);
return $("<div>")
.append("<div>This note is protected and to access it you need to enter password.</div>")
.append("<br/>")
.append($button);
}
else {
return "<em>Content of this note cannot be displayed in the book format</em>";
if (childNotes.length === 0) {
this.$help.show();
}
}
@@ -256,21 +186,6 @@ class NoteDetailBook {
}
}
getRenderingType(childNote) {
let type = childNote.type;
if (childNote.isProtected) {
if (protectedSessionHolder.isProtectedSessionAvailable()) {
protectedSessionHolder.touchProtectedSession();
}
else {
type = 'protected-session';
}
}
return type;
}
getContent() {
// for auto-book cases when renaming title there should be content
return "";

View File

@@ -3,7 +3,7 @@ import bundleService from "./bundle.js";
import toastService from "./toast.js";
import server from "./server.js";
import noteDetailService from "./note_detail.js";
import utils from "./utils.js";
import keyboardActionService from "./keyboard_actions.js";
class NoteDetailCode {
@@ -17,7 +17,7 @@ class NoteDetailCode {
this.$editorEl = this.$component.find('.note-detail-code-editor');
this.$executeScriptButton = ctx.$tabContent.find(".execute-script-button");
utils.bindElShortcut(ctx.$tabContent, "ctrl+return", () => this.executeCurrentNote());
keyboardActionService.setElementActionHandler(ctx.$tabContent, 'RunActiveNote', () => this.executeCurrentNote());
this.$executeScriptButton.on('click', () => this.executeCurrentNote());
}
@@ -55,18 +55,21 @@ class NoteDetailCode {
this.onNoteChange(() => this.ctx.noteChanged());
}
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
// we provide fallback
this.codeEditor.setValue(this.ctx.note.content || "");
// lazy loading above can take time and tab might have been already switched to another note
if (this.ctx.note && this.ctx.note.type === 'code') {
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
// we provide fallback
this.codeEditor.setValue(this.ctx.note.content || "");
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
if (info) {
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
if (info) {
this.codeEditor.setOption("mode", info.mime);
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
}
this.show();
}
this.show();
}
show() {

View File

@@ -39,8 +39,11 @@ class NoteDetailFile {
});
this.$uploadNewRevisionInput.on('change', async () => {
const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below
this.$uploadNewRevisionInput.val('');
const formData = new FormData();
formData.append('upload', this.$uploadNewRevisionInput[0].files[0]);
formData.append('upload', fileToUpload);
const result = await $.ajax({
url: baseApiUrl + 'notes/' + this.ctx.note.noteId + '/file',

View File

@@ -48,8 +48,11 @@ class NoteDetailImage {
});
this.$uploadNewRevisionInput.on('change', async () => {
const fileToUpload = this.$uploadNewRevisionInput[0].files[0]; // copy to allow reset below
this.$uploadNewRevisionInput.val('');
const formData = new FormData();
formData.append('upload', this.$uploadNewRevisionInput[0].files[0]);
formData.append('upload', fileToUpload);
const result = await $.ajax({
url: baseApiUrl + 'images/' + this.ctx.note.noteId,

View File

@@ -129,9 +129,10 @@ class NoteDetailRelationMap {
return;
}
const {note} = await server.post(`notes/${this.ctx.note.noteId}/children`, {
const {note} = await server.post(`notes/${this.ctx.note.noteId}/children?target=into`, {
title,
target: 'into'
content: '',
type: 'text'
});
toastService.showMessage("Click on canvas to place new note");
@@ -159,7 +160,7 @@ class NoteDetailRelationMap {
const noteId = this.idToNoteId($noteBox.prop("id"));
if (cmd === "open-in-new-tab") {
noteDetailService.openInTab(noteId);
noteDetailService.openInTab(noteId, false);
}
else if (cmd === "remove") {
const confirmDialog = await import('../dialogs/confirm.js');
@@ -238,14 +239,17 @@ class NoteDetailRelationMap {
await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
this.loadMapData();
jsPlumb.ready(() => {
this.initJsPlumbInstance();
// lazy loading above can take time and tab might have been already switched to another note
if (this.ctx.note && this.ctx.note.type === 'relation-map') {
this.loadMapData();
this.initPanZoom();
this.initJsPlumbInstance();
this.loadNotesAndRelations();
this.initPanZoom();
this.loadNotesAndRelations();
}
});
}
@@ -490,7 +494,7 @@ class NoteDetailRelationMap {
}
async createNoteBox(noteId, title, x, y) {
const $link = await linkService.createNoteLink(noteId, title);
const $link = await linkService.createNoteLink(noteId, {title});
$link.mousedown(e => {
console.log(e);

View File

@@ -1,6 +1,43 @@
import libraryLoader from "./library_loader.js";
import treeService from './tree.js';
import noteAutocompleteService from './note_autocomplete.js';
import mimeTypesService from './mime_types.js';
const ENABLE_INSPECTOR = false;
const mentionSetup = {
feeds: [
{
marker: '@',
feed: queryText => {
return new Promise((res, rej) => {
noteAutocompleteService.autocompleteSource(queryText, rows => {
if (rows.length === 1 && rows[0].title === 'No results') {
rows = [];
}
for (const row of rows) {
row.text = row.name = row.noteTitle;
row.id = '@' + row.text;
row.link = '#' + row.path;
}
res(rows);
});
});
},
itemRenderer: item => {
const itemElement = document.createElement('span');
itemElement.classList.add('mentions-item');
itemElement.innerHTML = `${item.highlightedTitle} `;
return itemElement;
},
minimumCharacters: 0
}
]
};
class NoteDetailText {
/**
@@ -10,6 +47,7 @@ class NoteDetailText {
this.ctx = ctx;
this.$component = ctx.$tabContent.find('.note-detail-text');
this.$editorEl = this.$component.find('.note-detail-text-editor');
this.textEditorPromise = null;
this.textEditor = null;
this.$component.on("dblclick", "img", e => {
@@ -26,81 +64,76 @@ class NoteDetailText {
else {
window.open(src, '_blank');
}
})
});
}
async render() {
if (!this.textEditor) {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
if (!this.textEditorPromise) {
this.textEditorPromise = this.initEditor();
}
await this.textEditorPromise;
// lazy loading above can take time and tab might have been already switched to another note
if (this.ctx.note && this.ctx.note.type === 'text') {
this.textEditor.isReadOnly = await this.isReadOnly();
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
// display of $component in both branches.
this.$component.show();
// textEditor might have been initialized during previous await so checking again
// looks like double initialization can freeze CKEditor pretty badly
if (!this.textEditor) {
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
placeholder: "Type the content of your note here ...",
mention: {
feeds: [
{
marker: '@',
feed: queryText => {
return new Promise((res, rej) => {
noteAutocompleteService.autocompleteSource(queryText, rows => {
if (rows.length === 1 && rows[0].title === 'No results') {
rows = [];
}
this.textEditor.setData(this.ctx.note.content);
}
}
for (const row of rows) {
row.text = row.name = row.noteTitle;
row.id = '@' + row.text;
row.link = '#' + row.path;
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
console.log(rows.slice(0, Math.min(5, rows.length)));
res(rows);
});
});
},
itemRenderer: item => {
const itemElement = document.createElement('span');
itemElement.classList.add('mentions-item');
itemElement.innerHTML = `${item.highlightedTitle} `;
return itemElement;
},
minimumCharacters: 0
}
]
const codeBlockLanguages =
(await mimeTypesService.getMimeTypes())
.filter(mt => mt.enabled)
.map(mt => {
return {
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
label: mt.title
}
});
this.onNoteChange(() => this.ctx.noteChanged());
}
}
this.textEditor.isReadOnly = await this.isReadOnly();
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
// display of $component in both branches.
this.$component.show();
this.textEditor.setData(this.ctx.note.content);
const textEditorInstance = await BalloonEditor.create(this.$editorEl[0], {
placeholder: "Type the content of your note here ...",
mention: mentionSetup,
codeBlock: {
languages: codeBlockLanguages
}
});
if (glob.isDev && ENABLE_INSPECTOR) {
await import('../../libraries/ckeditor/inspector.js');
CKEditorInspector.attach(textEditorInstance);
}
this.textEditor = textEditorInstance;
this.onNoteChange(() => this.ctx.noteChanged());
}
getContent() {
let content = this.textEditor.getData();
const content = this.textEditor.getData();
// if content is only tags/whitespace (typically <p>&nbsp;</p>), then just make it empty
// this is important when setting new note to code
if (jQuery(content).text().trim() === '' && !content.includes("<img")) {
content = '';
}
return this.isContentEmpty(content) ? '' : content;
}
return content;
isContentEmpty(content) {
content = content.toLowerCase();
return jQuery(content).text().trim() === ''
&& !content.includes("<img")
&& !content.includes("<section")
}
async isReadOnly() {

View File

@@ -13,6 +13,10 @@ class Options {
return this.arr[key];
}
getNames() {
return Object.keys(this.arr);
}
getJson(key) {
try {
return JSON.parse(this.arr[key]);

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