diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html index eec8511d8..bcc2ef0fb 100644 --- a/docs/backend_api/BackendScriptApi.html +++ b/docs/backend_api/BackendScriptApi.html @@ -396,7 +396,7 @@ the backend.
Source:
@@ -534,7 +534,7 @@ the backend. -

createNote(parentNoteId, title, contentopt, extraOptionsopt) → {Promise.<{note: Note, branch: Branch}>}

+

createNote(extraOptionsopt) → {Promise.<{note: Note, branch: Branch}>}

@@ -576,115 +576,6 @@ the backend. - - - parentNoteId - - - - - -string - - - - - - - - - - - - - - - - - - - - - - create new note under this parent - - - - - - - title - - - - - -string - - - - - - - - - - - - - - - - - - - - - - - - - - - - - content - - - - - -string - - - - - - - - - <optional>
- - - - - - - - - - - - "" - - - - - - - - - extraOptions @@ -693,7 +584,7 @@ the backend. -CreateNoteExtraOptions +CreateNoteParams @@ -760,295 +651,7 @@ the backend.
Source:
- - - - - - - - - - - - - - - - - - - - - - - -
Returns:
- - -
- object contains newly created entities note and branch -
- - - -
-
- Type -
-
- -Promise.<{note: Note, branch: Branch}> - - -
-
- - - - - - - - - - - - - -

createNoteAndRefresh(parentNoteId, title, contentopt, extraOptionsopt) → {Promise.<{note: Note, branch: Branch}>}

- - - - - - -
- Creates new note according to given params and force all connected clients to refresh their tree. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeAttributesDefaultDescription
parentNoteId - - -string - - - - - - - - - - - - create new note under this parent
title - - -string - - - - - - - - - - - -
content - - -string - - - - - - <optional>
- - - - - -
- - "" - -
extraOptions - - -CreateNoteExtraOptions - - - - - - <optional>
- - - - - -
- - {} - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
@@ -1533,7 +1136,7 @@ the backend.
Source:
@@ -1997,7 +1600,7 @@ the backend.
Source:
@@ -2765,7 +2368,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3418,7 +3021,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3596,7 +3199,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3751,7 +3354,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -3901,7 +3504,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -4404,7 +4007,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -4537,7 +4140,7 @@ This method looks similar to toggleNoteInParent() but differs because we're look
Source:
@@ -4912,7 +4515,7 @@ transactional by default.
Source:
diff --git a/docs/backend_api/Note.html b/docs/backend_api/Note.html index cfc3cb5e9..e9a278742 100644 --- a/docs/backend_api/Note.html +++ b/docs/backend_api/Note.html @@ -509,7 +509,7 @@ -

(async) getAllNotePaths() → {Promise.<Array.<Array.<string>>>}

+

(async) addAttribute() → {Promise.<Attribute>}

@@ -557,7 +557,7 @@
Source:
@@ -585,10 +585,6 @@
Returns:
-
- - array of notePaths (each represented by array of noteIds constituting the particular note path) -
-
@@ -597,7 +593,7 @@
-Promise.<Array.<Array.<string>>> +Promise.<Attribute>
@@ -735,7 +731,7 @@
Source:
@@ -902,7 +898,7 @@
Source:
@@ -1080,7 +1076,7 @@
Source:
@@ -1186,7 +1182,7 @@
Source:
@@ -1288,7 +1284,7 @@
Source:
@@ -1394,7 +1390,7 @@
Source:
@@ -1602,7 +1598,7 @@
Source:
@@ -1835,7 +1831,7 @@
Source:
@@ -2033,7 +2029,7 @@
Source:
@@ -2231,7 +2227,7 @@
Source:
@@ -2484,7 +2480,7 @@
Source:
@@ -2651,7 +2647,7 @@
Source:
@@ -2818,7 +2814,7 @@
Source:
@@ -2973,7 +2969,7 @@
Source:
@@ -3085,7 +3081,7 @@
Source:
@@ -3187,7 +3183,7 @@
Source:
@@ -3295,7 +3291,7 @@ This method can be significantly faster than the getAttribute()
Source:
@@ -3403,7 +3399,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -3558,7 +3554,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -3725,7 +3721,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -3892,7 +3888,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4047,7 +4043,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4217,7 +4213,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4368,7 +4364,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4478,7 +4474,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4580,7 +4576,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4686,7 +4682,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4864,7 +4860,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -4970,7 +4966,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -5125,7 +5121,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -5280,7 +5276,7 @@ This method can be significantly faster than the getAttributes()
Source:
@@ -5391,7 +5387,7 @@ Cache is note instance scoped.
Source:
@@ -5475,7 +5471,7 @@ Cache is note instance scoped.
Source:
@@ -5581,7 +5577,7 @@ Cache is note instance scoped.
Source:
@@ -5687,7 +5683,7 @@ Cache is note instance scoped.
Source:
@@ -5793,7 +5789,7 @@ Cache is note instance scoped.
Source:
@@ -5899,7 +5895,7 @@ Cache is note instance scoped.
Source:
@@ -6005,7 +6001,7 @@ Cache is note instance scoped.
Source:
@@ -6234,7 +6230,7 @@ Cache is note instance scoped.
Source:
@@ -6432,7 +6428,7 @@ Cache is note instance scoped.
Source:
@@ -6630,7 +6626,7 @@ Cache is note instance scoped.
Source:
@@ -6859,7 +6855,7 @@ Cache is note instance scoped.
Source:
@@ -7063,7 +7059,7 @@ Cache is note instance scoped.
Source:
@@ -7261,7 +7257,7 @@ Cache is note instance scoped.
Source:
@@ -7459,7 +7455,7 @@ Cache is note instance scoped.
Source:
@@ -7719,7 +7715,7 @@ Cache is note instance scoped.
Source:
@@ -7948,7 +7944,7 @@ Cache is note instance scoped.
Source:
@@ -8177,7 +8173,7 @@ Cache is note instance scoped.
Source:
diff --git a/docs/backend_api/entities_attribute.js.html b/docs/backend_api/entities_attribute.js.html index 1e19b196b..aa9220e39 100644 --- a/docs/backend_api/entities_attribute.js.html +++ b/docs/backend_api/entities_attribute.js.html @@ -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 = ""; } diff --git a/docs/backend_api/entities_note.js.html b/docs/backend_api/entities_note.js.html index a62d38017..30c1f1c3c 100644 --- a/docs/backend_api/entities_note.js.html +++ b/docs/backend_api/entities_note.js.html @@ -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; @@ -500,6 +504,32 @@ 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) diff --git a/docs/backend_api/global.html b/docs/backend_api/global.html index 0759abe47..542cf911b 100644 --- a/docs/backend_api/global.html +++ b/docs/backend_api/global.html @@ -290,7 +290,7 @@ -

CreateNoteExtraOptions

+

CreateNoteParams

diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html index eb68860b6..0fbb86466 100644 --- a/docs/backend_api/services_backend_script_api.js.html +++ b/docs/backend_api/services_backend_script_api.js.html @@ -206,7 +206,7 @@ function BackendScriptApi(currentNote, apiParams) { */ /** - * @typedef {object} CreateNoteExtraOptions + * @typedef {object} CreateNoteParams * @property {boolean} [json=false] - should the note be JSON * @property {boolean} [isProtected=false] - should the note be protected * @property {string} [type='text'] - note type @@ -217,32 +217,10 @@ function BackendScriptApi(currentNote, apiParams) { /** * @method * - * @param {string} parentNoteId - create new note under this parent - * @param {string} title - * @param {string} [content=""] - * @param {CreateNoteExtraOptions} [extraOptions={}] + * @param {CreateNoteParams} [extraOptions={}] * @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch */ - this.createNote = noteService.createNote; - - /** - * 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<{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); - - ws.refreshTree(); - - return ret; - }; + this.createNote = noteService.createNewNote; /** * Log given message to trilium logs. diff --git a/docs/frontend_api/Branch.html b/docs/frontend_api/Branch.html index cc6fc3b03..0b7013743 100644 --- a/docs/frontend_api/Branch.html +++ b/docs/frontend_api/Branch.html @@ -719,7 +719,7 @@
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html index dc8fc5a64..5ce01216e 100644 --- a/docs/frontend_api/FrontendScriptApi.html +++ b/docs/frontend_api/FrontendScriptApi.html @@ -81,7 +81,7 @@
Source:
@@ -223,7 +223,7 @@
Source:
@@ -333,7 +333,113 @@
Source:
+ + + + + + + +
+ + + + + + + + +

keys

+ + + + + + + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +KeyboardAction + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -446,7 +552,7 @@
Source:
@@ -552,7 +658,7 @@
Source:
@@ -662,7 +768,7 @@
Source:
@@ -771,7 +877,7 @@
Source:
@@ -900,7 +1006,7 @@
Source:
@@ -1055,7 +1161,7 @@
Source:
@@ -1210,7 +1316,7 @@
Source:
@@ -1366,7 +1472,7 @@
Source:
@@ -1546,7 +1652,7 @@
Source:
@@ -1679,7 +1785,7 @@
Source:
@@ -1785,7 +1891,7 @@
Source:
@@ -1891,7 +1997,7 @@
Source:
@@ -2050,7 +2156,7 @@
Source:
@@ -2157,7 +2263,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2312,7 +2418,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2468,7 +2574,7 @@ if some action needs to happen on only one specific instance.
Source:
@@ -2669,7 +2775,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -2775,7 +2881,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -2930,7 +3036,7 @@ otherwise (by e.g. createNoteLink())
Source:
@@ -3039,7 +3145,7 @@ note.
Source:
@@ -3194,7 +3300,7 @@ note.
Source:
@@ -3327,7 +3433,7 @@ note.
Source:
@@ -3433,7 +3539,7 @@ note.
Source:
@@ -3521,7 +3627,7 @@ note.
Source:
@@ -3627,7 +3733,7 @@ note.
Source:
@@ -3733,7 +3839,7 @@ note.
Source:
@@ -3888,7 +3994,7 @@ note.
Source:
@@ -4049,7 +4155,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4209,7 +4315,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4365,7 +4471,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4520,7 +4626,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4671,7 +4777,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4808,7 +4914,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4945,7 +5051,7 @@ Internally this serializes the anonymous function into string and sends it to ba
Source:
@@ -4991,7 +5097,7 @@ Internally this serializes the anonymous function into string and sends it to ba
diff --git a/docs/frontend_api/KeyboardAction.html b/docs/frontend_api/KeyboardAction.html new file mode 100644 index 000000000..1877d3729 --- /dev/null +++ b/docs/frontend_api/KeyboardAction.html @@ -0,0 +1,2266 @@ + + + + + JSDoc: Class: KeyboardAction + + + + + + + + + + +
+ +

Class: KeyboardAction

+ + + + + + +
+ +
+ +

KeyboardAction()

+ +
blaa vlaa
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new KeyboardAction()

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(static) AddLinkToText

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) BackInNoteHistory

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ClipboardCopy

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ClipboardCut

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ClipboardPaste

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) CloneNotesTo

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) CloseTab

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) CollapseTree

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) CreateNoteAfter

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) CreateNoteInto

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) FindInText

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) FocusNote

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ForwardInNoteHistory

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) InsertDateTime

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) JumpToNote

+ + + + +
+ Open "Jump to note" dialog +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) MarkdownToHTML

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) MoveNotesTo

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) NewTab

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) NextTab

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) OpenDevTools

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) OpenSQLConsole

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) PreviousTab

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) Redo

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ReloadApp

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) RunCurrentNote

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) RunSQL

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ScrollToActiveNote

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) SearchNotes

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) SelectAllNotesInParent

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ShowAttributes

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ShowHelp

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ToggleFullscreen

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ToggleZenMode

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) Undo

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ZoomIn

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static) ZoomOut

+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/frontend_api/KeyboardActions.html b/docs/frontend_api/KeyboardActions.html new file mode 100644 index 000000000..0e5a93353 --- /dev/null +++ b/docs/frontend_api/KeyboardActions.html @@ -0,0 +1,280 @@ + + + + + JSDoc: Class: KeyboardActions + + + + + + + + + + +
+ +

Class: KeyboardActions

+ + + + + + +
+ +
+ +

KeyboardActions()

+ +
blaa vlaa
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new KeyboardActions()

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

JUMP_TO

+ + + + + + + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +string + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/frontend_api/NoteFull.html b/docs/frontend_api/NoteFull.html index 890782f59..113ac1492 100644 --- a/docs/frontend_api/NoteFull.html +++ b/docs/frontend_api/NoteFull.html @@ -449,7 +449,7 @@
diff --git a/docs/frontend_api/NoteShort.html b/docs/frontend_api/NoteShort.html index 6e3561286..110e0e051 100644 --- a/docs/frontend_api/NoteShort.html +++ b/docs/frontend_api/NoteShort.html @@ -346,7 +346,7 @@
Source:
@@ -414,7 +414,7 @@
Source:
@@ -548,64 +548,6 @@ -

iconClass

- - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - -

isDeleted

@@ -888,7 +830,7 @@
Source:
@@ -956,7 +898,7 @@
Source:
@@ -1220,7 +1162,7 @@
Source:
@@ -1387,7 +1329,7 @@
Source:
@@ -1561,7 +1503,7 @@
Source:
@@ -1667,7 +1609,7 @@
Source:
@@ -1769,7 +1711,7 @@
Source:
@@ -1871,7 +1813,7 @@
Source:
@@ -1973,7 +1915,7 @@
Source:
@@ -2124,7 +2066,7 @@
Source:
@@ -2291,7 +2233,7 @@
Source:
@@ -2458,7 +2400,7 @@
Source:
@@ -2613,7 +2555,7 @@
Source:
@@ -2719,7 +2661,7 @@
Source:
@@ -2821,7 +2763,7 @@
Source:
@@ -2972,7 +2914,7 @@
Source:
@@ -3139,7 +3081,7 @@
Source:
@@ -3306,7 +3248,7 @@
Source:
@@ -3461,7 +3403,7 @@
Source:
@@ -3631,7 +3573,7 @@
Source:
@@ -3782,7 +3724,7 @@
Source:
@@ -3892,7 +3834,7 @@
Source:
@@ -4066,7 +4008,7 @@
Source:
@@ -4172,7 +4114,7 @@
Source:
@@ -4323,7 +4265,7 @@
Source:
@@ -4478,7 +4420,7 @@
Source:
@@ -4589,7 +4531,7 @@ Cache is note instance scoped.
Source:
@@ -4673,7 +4615,7 @@ Cache is note instance scoped.
Source:
@@ -4737,7 +4679,7 @@ Cache is note instance scoped.
diff --git a/docs/frontend_api/entities_attribute.js.html b/docs/frontend_api/entities_attribute.js.html index 01956a8e7..2f85b38f8 100644 --- a/docs/frontend_api/entities_attribute.js.html +++ b/docs/frontend_api/entities_attribute.js.html @@ -71,7 +71,7 @@ export default Attribute;
diff --git a/docs/frontend_api/entities_branch.js.html b/docs/frontend_api/entities_branch.js.html index bf5d06879..cc008e83d 100644 --- a/docs/frontend_api/entities_branch.js.html +++ b/docs/frontend_api/entities_branch.js.html @@ -70,7 +70,7 @@ export default Branch;
diff --git a/docs/frontend_api/entities_note_full.js.html b/docs/frontend_api/entities_note_full.js.html index b66927b7b..9f141a2a7 100644 --- a/docs/frontend_api/entities_note_full.js.html +++ b/docs/frontend_api/entities_note_full.js.html @@ -68,7 +68,7 @@ export default NoteFull;
diff --git a/docs/frontend_api/entities_note_short.js.html b/docs/frontend_api/entities_note_short.js.html index ed9603318..736bb9ad0 100644 --- a/docs/frontend_api/entities_note_short.js.html +++ b/docs/frontend_api/entities_note_short.js.html @@ -365,7 +365,7 @@ export default NoteShort;
diff --git a/docs/frontend_api/global.html b/docs/frontend_api/global.html index 7f4006e31..043fc4c5f 100644 --- a/docs/frontend_api/global.html +++ b/docs/frontend_api/global.html @@ -303,7 +303,7 @@
Source:
@@ -333,7 +333,7 @@
diff --git a/docs/frontend_api/index.html b/docs/frontend_api/index.html index 310dca343..cd881639d 100644 --- a/docs/frontend_api/index.html +++ b/docs/frontend_api/index.html @@ -50,7 +50,7 @@
diff --git a/docs/frontend_api/module.exports.html b/docs/frontend_api/module.exports.html new file mode 100644 index 000000000..4050b4d16 --- /dev/null +++ b/docs/frontend_api/module.exports.html @@ -0,0 +1,280 @@ + + + + + JSDoc: Class: exports + + + + + + + + + + +
+ +

Class: exports

+ + + + + + +
+ +
+ +

exports()

+ +
blaa vlaa
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new exports()

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

JUMP_TO

+ + + + + + + + + + +
Properties:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDescription
+ + +string + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html index e76325720..d03a54856 100644 --- a/docs/frontend_api/services_frontend_script_api.js.html +++ b/docs/frontend_api/services_frontend_script_api.js.html @@ -39,6 +39,7 @@ import dateNotesService from './date_notes.js'; import StandardWidget from '../widgets/standard_widget.js'; import ws from "./ws.js"; import hoistedNoteService from "./hoisted_note.js"; +import KeyboardAction from "./keyboard_action.js"; /** * This is the main frontend API interface for scripts. It's published in the local "api" object. @@ -68,6 +69,11 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte /** @property {StandardWidget} */ this.StandardWidget = StandardWidget; + /** @property {KeyboardAction} */ + this.keys = new KeyboardAction(); + + this.bind = keys.bind; + /** * Activates note in the tree and in the note detail. * @@ -422,7 +428,7 @@ export default FrontendScriptApi;
diff --git a/docs/frontend_api/services_keyboard_action.js.html b/docs/frontend_api/services_keyboard_action.js.html new file mode 100644 index 000000000..f477850d8 --- /dev/null +++ b/docs/frontend_api/services_keyboard_action.js.html @@ -0,0 +1,280 @@ + + + + + JSDoc: Source: services/keyboard_action.js + + + + + + + + + + +
+ +

Source: services/keyboard_action.js

+ + + + + + +
+
+
/**
+ * 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;
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/docs/frontend_api/services_keyboard_actions.js.html b/docs/frontend_api/services_keyboard_actions.js.html new file mode 100644 index 000000000..6e24019bd --- /dev/null +++ b/docs/frontend_api/services_keyboard_actions.js.html @@ -0,0 +1,61 @@ + + + + + JSDoc: Source: services/keyboard_action.js + + + + + + + + + + +
+ +

Source: services/keyboard_action.js

+ + + + + + +
+
+
/**
+ * blaa vlaa
+ */
+class KeyboardActions {
+    constructor() {
+        /** @property {string} */
+        this.JUMP_TO = "";
+    }
+}
+
+export default KeyboardActions;
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/package-lock.json b/package-lock.json index 63c583348..506e6c846 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3112,9 +3112,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.1.tgz", - "integrity": "sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==" + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.2.tgz", + "integrity": "sha512-rHGwtpl67oih3xAHbZlpw5rQAt+YV1mSCu2fUZ9XNrfaGEhom7E+AUiMci+ByP4aSfuAWx7hE0BPuJLMrpXwOw==" }, "electron": { "version": "6.0.12", @@ -5990,9 +5990,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6546,12 +6546,13 @@ } }, "imagemin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.0.tgz", - "integrity": "sha512-TXvCSSIYl4KQUASur9S0+E4olVECzvxvZABU9rNqsza7vzIrUQMRTjyczGf8OmtcgvZ9jOYyinXW3epOpd/04A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-7.0.1.tgz", + "integrity": "sha512-33AmZ+xjZhg2JMCe+vDf6a9mzWukE7l+wAtesjE7KyteqqKjzxv7aVQeWnul1Ve26mWvEQqyPwl0OctNBfSR9w==", "requires": { "file-type": "^12.0.0", "globby": "^10.0.0", + "graceful-fs": "^4.2.2", "junk": "^3.1.0", "make-dir": "^3.0.0", "p-pipe": "^3.0.0", @@ -8130,11 +8131,18 @@ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.42.0" + }, + "dependencies": { + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + } } }, "mimic-fn": { @@ -9896,9 +9904,9 @@ "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "picomatch": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz", - "integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz", + "integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==" }, "pify": { "version": "4.0.1", diff --git a/package.json b/package.json index 002203a60..33abff9ea 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "start-server": "TRILIUM_ENV=dev node ./src/www", "start-electron": "TRILIUM_ENV=dev electron . --disable-gpu", "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", - "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js", + "build-frontend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/keyboard_action.js src/public/javascripts/services/frontend_script_api.js", "build-docs": "npm run build-backend-docs && npm run build-frontend-docs" }, "dependencies": { @@ -29,7 +29,7 @@ "csurf": "1.10.0", "dayjs": "1.8.17", "debug": "4.1.1", - "ejs": "2.7.1", + "ejs": "2.7.2", "electron-debug": "3.0.1", "electron-dl": "1.14.0", "electron-find": "1.0.6", @@ -45,13 +45,13 @@ "http-proxy-agent": "2.1.0", "https-proxy-agent": "3.0.1", "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", + "mime-types": "2.1.25", "moment": "2.24.0", "multer": "1.4.2", "node-abi": "2.12.0", diff --git a/src/entities/attribute.js b/src/entities/attribute.js index 0bc68afa2..6755f113f 100644 --- a/src/entities/attribute.js +++ b/src/entities/attribute.js @@ -79,6 +79,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 = ""; } diff --git a/src/entities/note.js b/src/entities/note.js index 7e0e544f1..0c8d18d25 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -114,6 +114,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; @@ -472,6 +476,32 @@ class Note extends Entity { } } + /** + * @return {Promise} + */ + 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} true if label exists (including inherited) diff --git a/src/public/javascripts/services/entrypoints.js b/src/public/javascripts/services/entrypoints.js index 8256ae96c..d0142e55d 100644 --- a/src/public/javascripts/services/entrypoints.js +++ b/src/public/javascripts/services/entrypoints.js @@ -92,12 +92,12 @@ function registerEntrypoints() { } } + // hide (toggle) everything except for the note content for zen mode utils.bindGlobalShortcut('alt+m', e => { - $(".hide-toggle").toggle(); - $("#container").toggleClass("distraction-free-mode"); + $(".hide-in-zen-mode").toggle(); + $("#container").toggleClass("zen-mode"); }); - // hide (toggle) everything except for the note content for distraction free writing utils.bindGlobalShortcut('alt+t', e => { const date = new Date(); const dateString = utils.formatDateTime(date); diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js index ea6d2dd01..9d45a3677 100644 --- a/src/public/javascripts/services/frontend_script_api.js +++ b/src/public/javascripts/services/frontend_script_api.js @@ -11,6 +11,7 @@ import dateNotesService from './date_notes.js'; import StandardWidget from '../widgets/standard_widget.js'; import ws from "./ws.js"; import hoistedNoteService from "./hoisted_note.js"; +import KeyboardAction from "./keyboard_action.js"; /** * This is the main frontend API interface for scripts. It's published in the local "api" object. @@ -40,6 +41,9 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte /** @property {StandardWidget} */ this.StandardWidget = StandardWidget; + /** @property {KeyboardAction} */ + this.KeyboardAction = KeyboardAction; + /** * Activates note in the tree and in the note detail. * diff --git a/src/public/javascripts/services/keyboard_action.js b/src/public/javascripts/services/keyboard_action.js new file mode 100644 index 000000000..2d74036ec --- /dev/null +++ b/src/public/javascripts/services/keyboard_action.js @@ -0,0 +1,261 @@ +/** + * blaa vlaa + */ +class KeyboardAction { + constructor(params) { + /** @property {string} */ + this.optionName = params.optionName; + /** @property {string[]} */ + this.defaultShortcuts = Array.isArray(params.defaultShortcuts) ? params.defaultShortcuts : [params.defaultShortcuts]; + /** @property {string[]} */ + this.activeShortcuts = this.defaultShortcuts.slice(); + /** @property {string} */ + this.description = params.description; + } + + addShortcut(shortcut) { + this.activeShortcuts.push(shortcut); + } + + /** + * @param {string|string[]} shortcuts + */ + replaceShortcuts(shortcuts) { + this.activeShortcuts = Array.isArray(shortcuts) ? shortcuts : [shortcuts]; + } + + /** @return {KeyboardAction[]} */ + static get allActions() { + return Object.keys(KeyboardAction) + .map(key => KeyboardAction[key]) + .filter(obj => obj instanceof KeyboardAction); + } +} + +const ELECTRON = 1; + +/** + * 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", + only: ELECTRON +}); + +/** @static */ +KeyboardAction.CloseTab = new KeyboardAction({ + optionName: "CloseTab", + defaultShortcuts: "mod+w", + only: ELECTRON +}); + +/** @static */ +KeyboardAction.NextTab = new KeyboardAction({ + optionName: "NextTab", + defaultShortcuts: "mod+tab", + only: ELECTRON +}); + +/** @static */ +KeyboardAction.PreviousTab = new KeyboardAction({ + optionName: "PreviousTab", + defaultShortcuts: "mod+shift+tab", + only: ELECTRON +}); + +/** @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; \ No newline at end of file diff --git a/src/public/javascripts/services/keys.js b/src/public/javascripts/services/keys.js new file mode 100644 index 000000000..64517f91b --- /dev/null +++ b/src/public/javascripts/services/keys.js @@ -0,0 +1,16 @@ +class Actions { + constructor() { + this.JUMP_TO = ""; + } +} + +const actions = new Actions(); + +function bind() { + +} + +export default { + actions, + bind +}; \ No newline at end of file diff --git a/src/public/javascripts/services/tab_row.js b/src/public/javascripts/services/tab_row.js index 1d5b4048b..870da284a 100644 --- a/src/public/javascripts/services/tab_row.js +++ b/src/public/javascripts/services/tab_row.js @@ -29,6 +29,7 @@ const tabTemplate = ` `; const newTabButtonTemplate = `
+
`; +const fillerTemplate = `
`; class TabRow { constructor(el) { @@ -40,9 +41,10 @@ class TabRow { this.setupStyleEl(); this.setupEvents(); - this.layoutTabs(); this.setupDraggabilly(); this.setupNewButton(); + this.setupFiller(); + this.layoutTabs(); this.setVisibility(); } @@ -109,12 +111,17 @@ class TabRow { const widths = []; let extraWidthRemaining = totalExtraWidthDueToFlooring; + for (let i = 0; i < numberOfTabs; i += 1) { const extraWidth = flooredClampedTargetWidth < TAB_CONTENT_MAX_WIDTH && extraWidthRemaining > 0 ? 1 : 0; widths.push(flooredClampedTargetWidth + extraWidth); if (extraWidthRemaining > 0) extraWidthRemaining -= 1; } + if (this.fillerEl) { + this.fillerEl.style.width = extraWidthRemaining + "px"; + } + return widths; } @@ -129,8 +136,9 @@ class TabRow { }); const newTabPosition = position; + const fillerPosition = position + 32; - return {tabPositions, newTabPosition}; + return {tabPositions, newTabPosition, fillerPosition}; } layoutTabs() { @@ -151,13 +159,14 @@ class TabRow { let styleHTML = ''; - const {tabPositions, newTabPosition} = this.getTabPositions(); + const {tabPositions, newTabPosition, fillerPosition} = this.getTabPositions(); tabPositions.forEach((position, i) => { styleHTML += `.note-tab:nth-child(${ i + 1 }) { transform: translate3d(${ position }px, 0, 0)} `; }); styleHTML += `.note-new-tab { transform: translate3d(${ newTabPosition }px, 0, 0) } `; + styleHTML += `.tab-row-filler { transform: translate3d(${ fillerPosition }px, 0, 0) } `; this.styleEl.innerHTML = styleHTML; } @@ -387,11 +396,18 @@ class TabRow { this.newTabEl = div.firstElementChild; this.tabContentEl.appendChild(this.newTabEl); - this.layoutTabs(); this.newTabEl.addEventListener('click', _ => this.emit('newTab')); } + setupFiller() { + const div = document.createElement('div'); + div.innerHTML = fillerTemplate; + this.fillerEl = div.firstElementChild; + + this.tabContentEl.appendChild(this.fillerEl); + } + closest(value, array) { let closest = Infinity; let closestIndex = -1; diff --git a/src/public/javascripts/services/tree.js b/src/public/javascripts/services/tree.js index dbbaa84be..d56f79108 100644 --- a/src/public/javascripts/services/tree.js +++ b/src/public/javascripts/services/tree.js @@ -368,7 +368,7 @@ async function treeInitialized() { const notePath = location.hash.substr(1); const noteId = treeUtils.getNoteIdFromNotePath(notePath); - if (await treeCache.noteExists(noteId)) { + if (noteId && await treeCache.noteExists(noteId)) { for (const tab of openTabs) { tab.active = false; } @@ -649,11 +649,9 @@ async function createNote(node, parentNoteId, target, extraOptions = {}) { const newNoteName = extraOptions.title || "new note"; - const {note, branch} = await server.post('notes/' + parentNoteId + '/children', { + const {note, branch} = await server.post(`notes/${parentNoteId}/children?target=${target}&targetBranchId=${node.data.branchId}`, { title: newNoteName, - content: extraOptions.content, - target: target, - target_branchId: node.data.branchId, + content: extraOptions.content || "", isProtected: extraOptions.isProtected, type: extraOptions.type }); diff --git a/src/public/stylesheets/desktop.css b/src/public/stylesheets/desktop.css index 9622e07da..206be72b1 100644 --- a/src/public/stylesheets/desktop.css +++ b/src/public/stylesheets/desktop.css @@ -18,7 +18,7 @@ body { grid-gap: 0; } -#container.distraction-free-mode { +#container.zen-mode { grid-template-areas: "tab-container" !important; grid-template-rows: auto @@ -59,7 +59,7 @@ body { background-color: var(--header-background-color); display: flex; align-items: center; - padding: 4px; + padding-top: 4px; } #header button { @@ -68,18 +68,26 @@ body { margin-bottom: 2px; margin-top: 2px; margin-right: 8px; + border-color: transparent !important; +} + +#header button.btn-sm .bx { + position: relative; + top: 1px; +} + +#header button:hover { + border-color: var(--button-border-color) !important; } #history-navigation { margin: 0 15px 0 5px; - position: relative; - top: 2px; } #global-buttons { display: flex; justify-content: space-around; - padding: 10px 0 10px 0; + padding: 3px 0 3px 0; border: 1px solid var(--main-border-color); border-radius: 7px; margin: 5px 15px 5px 5px; @@ -145,7 +153,7 @@ body { ::-webkit-scrollbar-thumb { border-radius: 3px; - border: 1px solid var(--main-border-color); + border: 1px solid var(--scrollbar-border-color); } ::-webkit-scrollbar-corner { @@ -165,6 +173,13 @@ body { cursor: pointer; position: relative; top: -1px; + border: 1px solid transparent; + padding: 2px; + border-radius: 2px; +} + +.refresh-search-button:hover { + border-color: var(--button-border-color); } .note-title-row { @@ -215,7 +230,7 @@ body { .note-new-tab { position: absolute; left: 0; - height: 32px; + height: 33px; width: 32px; border: 0; margin: 0; @@ -223,6 +238,7 @@ body { text-align: center; font-size: 24px; cursor: pointer; + border-bottom: 1px solid var(--button-border-color); } .note-new-tab:hover { @@ -230,6 +246,14 @@ body { border-radius: 5px; } +.tab-row-filler { + position: absolute; + left: 0; + background: linear-gradient(to right, var(--button-border-color), transparent); + height: 1px; + margin-top: 32px; +} + .note-tab-row .note-tab[active] { z-index: 5; } @@ -244,6 +268,7 @@ body { top: 10px; animation: note-tab-was-just-added 120ms forwards ease-in-out; } + .note-tab-row .note-tab .note-tab-wrapper { position: absolute; display: flex; @@ -256,11 +281,14 @@ body { border-top-right-radius: 8px; overflow: hidden; pointer-events: all; - background-image: linear-gradient(to bottom, var(--accented-background-color), var(--main-background-color)); + background-color: var(--accented-background-color); + border-bottom: 1px solid var(--button-border-color); } .note-tab-row .note-tab[active] .note-tab-wrapper { - background-image: linear-gradient(to bottom, var(--more-accented-background-color), var(--main-background-color)); + background-color: var(--main-background-color); + border: 1px solid var(--button-border-color); + border-bottom: 0; font-weight: bold; } @@ -268,6 +296,7 @@ body { padding-left: 2px; padding-right: 2px; } + .note-tab-row .note-tab .note-tab-title { flex: 1; vertical-align: top; @@ -275,12 +304,15 @@ body { white-space: nowrap; color: var(--muted-text-color); } + .note-tab-row .note-tab[is-small] .note-tab-title { margin-left: 0; } + .note-tab-row .note-tab[active] .note-tab-title { color: var(--main-text-color); } + .note-tab-row .note-tab .note-tab-drag-handle { position: absolute; top: 0; @@ -290,6 +322,7 @@ body { border-top-left-radius: 8px; border-top-right-radius: 8px; } + .note-tab-row .note-tab .note-tab-close { flex-grow: 0; flex-shrink: 0; @@ -348,6 +381,14 @@ body { .hide-sidebar-button { color: var(--main-text-color); + background: none; + border: 1px solid transparent; + padding: 2px 8px 2px 8px; + border-radius: 2px; +} + +.hide-sidebar-button:hover { + border-color: var(--button-border-color); } .note-detail-sidebar { @@ -380,6 +421,8 @@ body { border: 0; background: inherit; font-weight: bold; + text-transform: uppercase; + color: var(--muted-text-color) !important; } .note-detail-sidebar .widget-header-action { @@ -388,7 +431,9 @@ body { } .note-detail-sidebar .widget-help { - color: var(--main-text-color); + color: var(--muted-text-color); + position: relative; + top: 2px; } .note-detail-sidebar .widget-help.no-link:hover { diff --git a/src/public/stylesheets/mobile.css b/src/public/stylesheets/mobile.css index ab1a78969..b97088011 100644 --- a/src/public/stylesheets/mobile.css +++ b/src/public/stylesheets/mobile.css @@ -19,7 +19,7 @@ html, body { display: flex; flex-shrink: 0; justify-content: space-around; - padding: 10px 0 10px 0; + padding: 3px 0 3px 0; margin: 0 10px 0 16px; } diff --git a/src/public/stylesheets/style.css b/src/public/stylesheets/style.css index 97f0f6eac..00d4d2be6 100644 --- a/src/public/stylesheets/style.css +++ b/src/public/stylesheets/style.css @@ -144,9 +144,9 @@ ul.fancytree-container { margin-top: 0; } -/** we disable shield background when in distraction free mode because I couldn't get it to stay static +/** we disable shield background when in zen mode because I couldn't get it to stay static (it kept growing with content) */ -#container:not(.distraction-free-mode) .note-tab-content.protected { +#container:not(.zen-mode) .note-tab-content.protected { /* DON'T COLLAPSE THE RULES INTO SINGLE ONE, BACKGROUND WON'T DISPLAY */ background: url('../images/shield.svg') no-repeat; background-size: contain; @@ -227,9 +227,13 @@ span.fancytree-node.archived { .icon-action:hover { text-decoration: none; + border-color: var(--button-border-color); } .icon-action { + border: 1px solid transparent; + border-radius: 3px; + padding: 5px; cursor: pointer; font-size: 1.5em; } diff --git a/src/public/stylesheets/themes.css b/src/public/stylesheets/themes.css index b769d7362..032a38086 100644 --- a/src/public/stylesheets/themes.css +++ b/src/public/stylesheets/themes.css @@ -10,26 +10,27 @@ --main-text-color: black; --main-border-color: #ccc; --accented-background-color: #f5f5f5; - --more-accented-background-color: #ccc; - --header-background-color: #f8f8f8; - --button-background-color: #eee; - --button-disabled-background-color: #ccc; + --more-accented-background-color: #ddd; + --header-background-color: #fff; + --button-background-color: #fff; + --button-disabled-background-color: #ddd; --button-border-color: #ddd; --button-text-color: black; --button-border-radius: 5px; - --muted-text-color: #444; + --muted-text-color: #666; --input-text-color: black; --input-background-color: white; --hover-item-text-color: black; --hover-item-background-color: #eee; --active-item-text-color: black; - --active-item-background-color: #ccc; + --active-item-background-color: #ddd; --menu-text-color: black; --menu-background-color: white; --tooltip-background-color: #f8f8f8; --link-color: blue; --modal-background-color: white; --modal-backdrop-color: black; + --scrollbar-border-color: #ddd; } body.theme-black { @@ -56,6 +57,7 @@ body.theme-black { --link-color: lightskyblue; --modal-background-color: black; --modal-backdrop-color: #444; + --scrollbar-border-color: #888; } body.theme-black .CodeMirror { @@ -65,7 +67,7 @@ body.theme-black .CodeMirror { body.theme-dark { --main-background-color: #333; --main-text-color: white; - --main-border-color: #ddd; + --main-border-color: #aaa; --accented-background-color: #555; --more-accented-background-color: #777; --header-background-color: #333; @@ -86,6 +88,7 @@ body.theme-dark { --link-color: lightskyblue; --modal-background-color: #333; --modal-backdrop-color: #444; + --scrollbar-border-color: #888; } body.theme-dark .CodeMirror { diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 9bf806950..0344037a5 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -31,7 +31,11 @@ async function addClipping(req) { let clippingNote = await findClippingNote(todayNote, pageUrl); if (!clippingNote) { - clippingNote = (await noteService.createNote(todayNote.noteId, title, '')).note; + clippingNote = (await noteService.createNewNote({ + parentNoteId: todayNote.noteId, + title: title, + content: '' + })).note; await clippingNote.setLabel('clipType', 'clippings'); await clippingNote.setLabel('pageUrl', pageUrl); @@ -51,7 +55,11 @@ async function createNote(req) { const todayNote = await dateNoteService.getDateNote(dateUtils.localNowDate()); - const {note} = await noteService.createNote(todayNote.noteId, title, content); + const {note} = await noteService.createNewNote({ + parentNoteId: todayNote.noteId, + title, + content + }); await note.setLabel('clipType', clipType); diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index f0c7cb228..29b0b6849 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -53,10 +53,12 @@ async function getChildren(req) { } async function createNote(req) { - const parentNoteId = req.params.parentNoteId; - const newNote = req.body; + const params = Object.assign({}, req.body); // clone + params.parentNoteId = req.params.parentNoteId; - const { note, branch } = await noteService.createNewNote(parentNoteId, newNote, req); + const { target, targetBranchId } = req.query; + + const { note, branch } = await noteService.createNewNoteWithTarget(target, targetBranchId, params); await treeService.setCssClassesToNotes([note]); diff --git a/src/routes/api/sender.js b/src/routes/api/sender.js index 06d87d7b4..c60cab4ac 100644 --- a/src/routes/api/sender.js +++ b/src/routes/api/sender.js @@ -26,10 +26,10 @@ async function uploadImage(req) { async function saveNote(req) { const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']); - const {note, branch} = await noteService.createNewNote(parentNote.noteId, { + const {note, branch} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, title: req.body.title, content: req.body.content, - target: 'into', isProtected: false, type: 'text', mime: 'text/html' diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index c430c9f88..2e1f639fe 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -178,7 +178,7 @@ function BackendScriptApi(currentNote, apiParams) { */ /** - * @typedef {object} CreateNoteExtraOptions + * @typedef {object} CreateNoteParams * @property {boolean} [json=false] - should the note be JSON * @property {boolean} [isProtected=false] - should the note be protected * @property {string} [type='text'] - note type @@ -189,32 +189,10 @@ function BackendScriptApi(currentNote, apiParams) { /** * @method * - * @param {string} parentNoteId - create new note under this parent - * @param {string} title - * @param {string} [content=""] - * @param {CreateNoteExtraOptions} [extraOptions={}] + * @param {CreateNoteParams} [extraOptions={}] * @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch */ - this.createNote = noteService.createNote; - - /** - * 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<{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); - - ws.refreshTree(); - - return ret; - }; + this.createNote = noteService.createNewNote; /** * Log given message to trilium logs. diff --git a/src/services/date_notes.js b/src/services/date_notes.js index 7887b4b40..270258576 100644 --- a/src/services/date_notes.js +++ b/src/services/date_notes.js @@ -14,10 +14,10 @@ const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Satur const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December']; async function createNote(parentNoteId, noteTitle, noteText) { - return (await noteService.createNewNote(parentNoteId, { + return (await noteService.createNewNote({ + parentNoteId: parentNoteId, title: noteTitle, content: noteText, - target: 'into', isProtected: false })).note; } @@ -35,7 +35,8 @@ async function getRootCalendarNote() { let rootNote = await attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL); if (!rootNote) { - rootNote = (await noteService.createNewNote('root', { + rootNote = (await noteService.createNewNote({ + parentNoteId: 'root', title: 'Calendar', target: 'into', isProtected: false diff --git a/src/services/image.js b/src/services/image.js index 34cdb6ec7..21a3c3a3b 100644 --- a/src/services/image.js +++ b/src/services/image.js @@ -57,14 +57,17 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw const parentNote = await repository.getNote(parentNoteId); - const {note} = await noteService.createNote(parentNoteId, fileName, buffer, { - target: 'into', + const {note} = await noteService.createNewNote({ + parentNoteId, + title: fileName, + content: buffer, type: 'image', - isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), mime: 'image/' + imageFormat.ext.toLowerCase(), - attributes: [{ type: 'label', name: 'originalFileName', value: originalName }] + isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() }); + await note.addLabel('originalFileName', originalName); + return { fileName, note, diff --git a/src/services/import/enex.js b/src/services/import/enex.js index e3e38c857..0bf1b56a2 100644 --- a/src/services/import/enex.js +++ b/src/services/import/enex.js @@ -27,7 +27,10 @@ async function importEnex(taskContext, file, parentNote) { : file.originalname; // root note is new note into all ENEX/notebook's notes will be imported - const rootNote = (await noteService.createNote(parentNote.noteId, rootNoteTitle, "", { + const rootNote = (await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title: rootNoteTitle, + content: "", type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -193,14 +196,20 @@ async function importEnex(taskContext, file, parentNote) { content = extractContent(content); - const noteEntity = (await noteService.createNote(rootNote.noteId, title, content, { - attributes, + const noteEntity = (await noteService.createNewNote({ + parentNoteId: rootNote.noteId, + title, + content, utcDateCreated, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), })).note; + for (const attr of attributes) { + await noteEntity.addAttribute(attr.type, attr.name, attr.value); + } + taskContext.increaseProgressCount(); let noteContent = await noteEntity.getContent(); @@ -217,13 +226,19 @@ async function importEnex(taskContext, file, parentNote) { } const createFileNote = async () => { - const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, { - attributes: resource.attributes, + const resourceNote = (await noteService.createNewNote({ + parentNoteId: noteEntity.noteId, + title: resource.title, + content: resource.content, type: 'file', mime: resource.mime, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), })).note; + for (const attr of resource.attributes) { + await noteEntity.addAttribute(attr.type, attr.name, attr.value); + } + taskContext.increaseProgressCount(); const resourceLink = `${utils.escapeHtml(resource.title)}`; diff --git a/src/services/import/opml.js b/src/services/import/opml.js index 3b9e4f852..fc993dbf7 100644 --- a/src/services/import/opml.js +++ b/src/services/import/opml.js @@ -44,8 +44,12 @@ async function importOpml(taskContext, fileBuffer, parentNote) { throw new Error("Unrecognized OPML version " + opmlVersion); } - const {note} = await noteService.createNote(parentNoteId, title, content, { - isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), + const {note} = await noteService.createNewNote({ + parentNoteId, + title, + content, + type: 'text', + isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() }); taskContext.increaseProgressCount(); diff --git a/src/services/import/single.js b/src/services/import/single.js index 18c871fd0..983cca355 100644 --- a/src/services/import/single.js +++ b/src/services/import/single.js @@ -41,16 +41,18 @@ async function importImage(file, parentNote, taskContext) { async function importFile(taskContext, file, parentNote) { const originalName = file.originalname; - const size = file.size; - const {note} = await noteService.createNote(parentNote.noteId, originalName, file.buffer, { - target: 'into', + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title: originalName, + content: file.buffer, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), type: 'file', - mime: mimeService.getMime(originalName) || file.mimetype, - attributes: [{ type: "label", name: "originalFileName", value: originalName }] + mime: mimeService.getMime(originalName) || file.mimetype }); + await note.addLabel("originalFileName", originalName); + taskContext.increaseProgressCount(); return note; @@ -62,7 +64,10 @@ async function importCodeNote(taskContext, file, parentNote) { const detectedMime = mimeService.getMime(file.originalname) || file.mimetype; const mime = mimeService.normalizeMimeType(detectedMime); - const {note} = await noteService.createNote(parentNote.noteId, title, content, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content, type: 'code', mime: mime, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable() @@ -78,7 +83,10 @@ async function importPlainText(taskContext, file, parentNote) { const plainTextContent = file.buffer.toString("UTF-8"); const htmlContent = convertTextToHtml(plainTextContent); - const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content: htmlContent, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -118,7 +126,10 @@ async function importMarkdown(taskContext, file, parentNote) { const title = getFileNameWithoutExtension(file.originalname); - const {note} = await noteService.createNote(parentNote.noteId, title, htmlContent, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content: htmlContent, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), @@ -133,7 +144,10 @@ async function importHtml(taskContext, file, parentNote) { const title = getFileNameWithoutExtension(file.originalname); const content = file.buffer.toString("UTF-8"); - const {note} = await noteService.createNote(parentNote.noteId, title, content, { + const {note} = await noteService.createNewNote({ + parentNoteId: parentNote.noteId, + title, + content, type: 'text', mime: 'text/html', isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), diff --git a/src/services/import/tar.js b/src/services/import/tar.js index b459d8c17..d71a6c55b 100644 --- a/src/services/import/tar.js +++ b/src/services/import/tar.js @@ -177,8 +177,11 @@ async function importTar(taskContext, fileBuffer, importRootNote) { return; } - ({note} = await noteService.createNote(parentNoteId, noteTitle, '', { - noteId, + ({note} = await noteService.createNewNote({ + parentNoteId: parentNoteId, + title: noteTitle, + content: '', + noteId: noteId, type: noteMeta ? noteMeta.type : 'text', mime: noteMeta ? noteMeta.mime : 'text/html', prefix: noteMeta ? noteMeta.prefix : '', @@ -324,7 +327,10 @@ async function importTar(taskContext, fileBuffer, importRootNote) { await note.setContent(content); } else { - ({note} = await noteService.createNote(parentNoteId, noteTitle, content, { + ({note} = await noteService.createNewNote({ + parentNoteId: parentNoteId, + title: noteTitle, + content: content, noteId, type, mime, diff --git a/src/services/keyboard_actions.js b/src/services/keyboard_actions.js new file mode 100644 index 000000000..6a8d345b4 --- /dev/null +++ b/src/services/keyboard_actions.js @@ -0,0 +1,157 @@ +const ELECTRON = "electron"; + +const KEYBOARD_ACTIONS = [ + { + optionName: "JumpToNote", + defaultShortcuts: ["mod+j"], + description: 'Open "Jump to note" dialog' + }, + { + optionName: "MarkdownToHTML", + defaultShortcuts: ["mod+return"] + }, + { + optionName: "NewTab", + defaultShortcuts: ["mod+t"], + only: ELECTRON + }, + { + optionName: "CloseTab", + defaultShortcuts: ["mod+w"], + only: ELECTRON + }, + { + optionName: "NextTab", + defaultShortcuts: ["mod+tab"], + only: ELECTRON + }, + { + optionName: "PreviousTab", + defaultShortcuts: ["mod+shift+tab"], + only: ELECTRON + }, + { + optionName: "CreateNoteAfter", + defaultShortcuts: ["mod+o"] + }, + { + optionName: "CreateNoteInto", + defaultShortcuts: ["mod+p"] + }, + { + optionName: "ScrollToActiveNote", + defaultShortcuts: ["mod+."] + }, + { + optionName: "CollapseTree", + defaultShortcuts: ["alt+c"] + }, + { + optionName: "RunSQL", + defaultShortcuts: ["mod+return"] + }, + { + optionName: "FocusNote", + defaultShortcuts: ["return"] + }, + { + optionName: "RunCurrentNote", + defaultShortcuts: ["mod+return"] + }, + { + optionName: "ClipboardCopy", + defaultShortcuts: ["mod+c"] + }, + { + optionName: "ClipboardPaste", + defaultShortcuts: ["mod+v"] + }, + { + optionName: "ClipboardCut", + defaultShortcuts: ["mod+x"] + }, + { + optionName: "SelectAllNotesInParent", + defaultShortcuts: ["mod+a"] + }, + { + optionName: "Undo", + defaultShortcuts: ["mod+z"] + }, + { + optionName: "Redo", + defaultShortcuts: ["mod+y"] + }, + { + optionName: "AddLinkToText", + defaultShortcuts: ["mod+l"] + }, + { + optionName: "CloneNotesTo", + defaultShortcuts: ["mod+shift+c"] + }, + { + optionName: "MoveNotesTo", + defaultShortcuts: ["mod+shift+c"] + }, + { + optionName: "SearchNotes", + defaultShortcuts: ["mod+s"] + }, + { + optionName: "ShowAttributes", + defaultShortcuts: ["alt+a"] + }, + { + optionName: "ShowHelp", + defaultShortcuts: ["f1"] + }, + { + optionName: "OpenSQLConsole", + defaultShortcuts: ["alt+o"] + }, + { + optionName: "BackInNoteHistory", + defaultShortcuts: ["alt+left"] + }, + { + optionName: "ForwardInNoteHistory", + defaultShortcuts: ["alt+right"] + }, + { + optionName: "ToggleZenMode", + defaultShortcuts: ["alt+m"] + }, + { + optionName: "InsertDateTime", + defaultShortcuts: ["alt+t"] + }, + { + optionName: "ReloadApp", + defaultShortcuts: ["f5", "mod+r"] + }, + { + optionName: "OpenDevTools", + defaultShortcuts: ["mod+shift+i"] + }, + { + optionName: "FindInText", + defaultShortcuts: ["mod+f"] + }, + { + optionName: "ToggleFullscreen", + defaultShortcuts: ["f11"] + }, + { + optionName: "ZoomOut", + defaultShortcuts: ["mod+-"] + }, + { + optionName: "ZoomIn", + defaultShortcuts: ["mod+="] + } +]; + +module.exports = { + KEYBOARD_ACTIONS +}; \ No newline at end of file diff --git a/src/services/notes.js b/src/services/notes.js index cd516eaab..08d148291 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -16,29 +16,14 @@ const protectedSessionService = require('../services/protected_session'); const log = require('../services/log'); const noteRevisionService = require('../services/note_revisions'); -async function getNewNotePosition(parentNoteId, noteData) { - let newNotePos = 0; +async function getNewNotePosition(parentNoteId) { + const maxNotePos = await sql.getValue(` + SELECT MAX(notePosition) + FROM branches + WHERE parentNoteId = ? + AND isDeleted = 0`, [parentNoteId]); - if (noteData.target === 'into') { - const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentNoteId]); - - newNotePos = maxNotePos === null ? 0 : maxNotePos + 10; - } - else if (noteData.target === 'after') { - const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]); - - newNotePos = afterNote.notePosition + 10; - - // not updating utcDateModified to avoig having to sync whole rows - await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0', - [parentNoteId, afterNote.notePosition]); - - await syncTableService.addNoteReorderingSync(parentNoteId); - } - else { - throw new Error('Unknown target: ' + noteData.target); - } - return newNotePos; + return maxNotePos === null ? 0 : maxNotePos + 10; } async function triggerChildNoteCreated(childNote, parentNote) { @@ -49,88 +34,90 @@ async function triggerNoteTitleChanged(note) { await eventService.emit(eventService.NOTE_TITLE_CHANGED, note); } -/** - * FIXME: noteData has mandatory property "target", it might be better to add it as parameter to reflect this - */ -async function createNewNote(parentNoteId, noteData) { - let newNotePos; - - if (noteData.notePosition !== undefined) { - newNotePos = noteData.notePosition; - } - else { - newNotePos = await getNewNotePosition(parentNoteId, noteData); +function deriveMime(type, mime) { + if (!type) { + throw new Error(`Note type is a required param`); } - const parentNote = await repository.getNote(parentNoteId); - - if (!parentNote) { - throw new Error(`Parent note ${parentNoteId} not found.`); + if (mime) { + return mime; } - if (!noteData.type) { - if (parentNote.type === 'text' || parentNote.type === 'code') { - noteData.type = parentNote.type; - noteData.mime = parentNote.mime; - } - else { - // inheriting note type makes sense only for text and code - noteData.type = 'text'; - noteData.mime = 'text/html'; - } + if (type === 'text') { + mime = 'text/html'; + } else if (type === 'code') { + mime = 'text/plain'; + } else if (['relation-map', 'search'].includes(type)) { + mime = 'application/json'; } - if (!noteData.mime) { - if (noteData.type === 'text') { - noteData.mime = 'text/html'; - } - else if (noteData.type === 'code') { - noteData.mime = 'text/plain'; - } - else if (noteData.type === 'relation-map' || noteData.type === 'search') { - noteData.mime = 'application/json'; - } - } - - noteData.type = noteData.type || parentNote.type; - noteData.mime = noteData.mime || parentNote.mime; - - const note = await new Note({ - noteId: noteData.noteId, // optionally can force specific noteId - title: noteData.title, - isProtected: noteData.isProtected, - type: noteData.type || 'text', - mime: noteData.mime || 'text/html' - }).save(); - - if (note.isStringNote() || this.type === 'render') { // render to just make sure it's not null - noteData.content = noteData.content || ""; - } - - await note.setContent(noteData.content); - - const branch = await new Branch({ - noteId: note.noteId, - parentNoteId: parentNoteId, - notePosition: newNotePos, - prefix: noteData.prefix, - isExpanded: !!noteData.isExpanded - }).save(); + return mime; +} +async function copyChildAttributes(parentNote, childNote) { for (const attr of await parentNote.getAttributes()) { if (attr.name.startsWith("child:")) { await new Attribute({ - noteId: note.noteId, - type: attr.type, - name: attr.name.substr(6), - value: attr.value, - position: attr.position, - isInheritable: attr.isInheritable + noteId: childNote.noteId, + type: attr.type, + name: attr.name.substr(6), + value: attr.value, + position: attr.position, + isInheritable: attr.isInheritable }).save(); - note.invalidateAttributeCache(); + childNote.invalidateAttributeCache(); } } +} + +/** + * Following object properties are mandatory: + * - {string} parentNoteId + * - {string} title + * - {*} content + * - {string} type - text, code, file, image, search, book, relation-map + * + * Following are optional (have defaults) + * - {string} mime - value is derived from default mimes for type + * - {boolean} isProtected - default is false + * - {boolean} isExpanded - default is false + * - {string} prefix - default is empty string + * - {integer} notePosition - default is last existing notePosition in a parent + 10 + * + * @param params + * @return {Promise<{note: Note, branch: Branch}>} + */ +async function createNewNote(params) { + const parentNote = await repository.getNote(params.parentNoteId); + + if (!parentNote) { + throw new Error(`Parent note ${params.parentNoteId} not found.`); + } + + if (!params.title || params.title.trim().length === 0) { + throw new Error(`Note title must not be empty`); + } + + const note = await new Note({ + noteId: params.noteId, // optionally can force specific noteId + title: params.title, + isProtected: !!params.isProtected, + type: params.type, + mime: deriveMime(params.type, params.mime) + }).save(); + + await note.setContent(params.content); + + const branch = await new Branch({ + noteId: note.noteId, + parentNoteId: params.parentNoteId, + notePosition: params.notePosition !== undefined ? params.notePosition : await getNewNotePosition(params.parentNoteId), + prefix: params.prefix, + isExpanded: !!params.isExpanded + }).save(); + + await copyChildAttributes(parentNote, note); await triggerNoteTitleChanged(note); await triggerChildNoteCreated(note, parentNote); @@ -141,41 +128,59 @@ async function createNewNote(parentNoteId, noteData) { }; } -async function createNote(parentNoteId, title, content = "", extraOptions = {}) { - if (!parentNoteId) throw new Error("Empty parentNoteId"); - if (!title) throw new Error("Empty title"); +async function createNewNoteWithTarget(target, targetBranchId, params) { + if (!params.type) { + const parentNote = await repository.getNote(params.parentNoteId); - const noteData = { - title: title, - content: extraOptions.json ? JSON.stringify(content, null, '\t') : content, - target: 'into', - noteId: extraOptions.noteId, - isProtected: !!extraOptions.isProtected, - type: extraOptions.type, - mime: extraOptions.mime, - dateCreated: extraOptions.dateCreated, - isExpanded: extraOptions.isExpanded, - notePosition: extraOptions.notePosition - }; - - if (extraOptions.json && !noteData.type) { - noteData.type = "code"; - noteData.mime = "application/json"; + // code note type can be inherited, otherwise text is default + params.type = parentNote.type === 'code' ? 'code' : 'text'; + params.mime = parentNote.type === 'code' ? parentNote.mime : 'text/html'; } - const {note, branch} = await createNewNote(parentNoteId, noteData); - - for (const attr of extraOptions.attributes || []) { - await attributeService.createAttribute({ - noteId: note.noteId, - type: attr.type, - name: attr.name, - value: attr.value, - isInheritable: !!attr.isInheritable - }); + if (target === 'into') { + return await createNewNote(params); } + else if (target === 'after') { + const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [noteData.target_branchId]); - return {note, branch}; + // not updating utcDateModified to avoig having to sync whole rows + await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0', + [params.parentNoteId, afterNote.notePosition]); + + params.notePosition = afterNote.notePosition + 10; + + await createNewNote(params); + + await syncTableService.addNoteReorderingSync(params.parentNoteId); + } + else { + throw new Error(`Unknown target ${target}`); + } +} + +// methods below should be probably just backend API methods +async function createJsonNote(parentNoteId, title, content = {}, params = {}) { + params.parentNoteId = parentNoteId; + params.title = title; + + params.type = "code"; + params.mime = "application/json"; + + params.content = JSON.stringify(content, null, '\t'); + + return await createNewNote(params); +} + +async function createTextNote(parentNoteId, title, content = "", params = {}) { + params.parentNoteId = parentNoteId; + params.title = title; + + params.type = "text"; + params.mime = "text/html"; + + params.content = content; + + return await createNewNote(params); } async function protectNoteRecursively(note, protect, taskContext) { @@ -537,7 +542,7 @@ sqlInit.dbReady.then(() => { module.exports = { createNewNote, - createNote, + createNewNoteWithTarget, updateNote, deleteBranch, protectNoteRecursively, diff --git a/src/services/options_init.js b/src/services/options_init.js index a67063cc0..26efc4ca1 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -5,6 +5,7 @@ const appInfo = require('./app_info'); const utils = require('./utils'); const log = require('./log'); const dateUtils = require('./date_utils'); +const keyboardActions = require('./keyboard_actions'); async function initDocumentOptions() { await optionService.createOption('documentId', utils.randomSecureToken(16), false); @@ -87,7 +88,9 @@ const defaultOptions = [ async function initStartupOptions() { const optionsMap = await optionService.getOptionsMap(); - for (const {name, value, isSynced} of defaultOptions) { + const allDefaultOptions = defaultOptions.concat(getKeyboardDefaultOptions()); + + for (const {name, value, isSynced} of allDefaultOptions) { if (!(name in optionsMap)) { await optionService.createOption(name, value, isSynced); @@ -96,6 +99,16 @@ async function initStartupOptions() { } } +function getKeyboardDefaultOptions() { + return keyboardActions.KEYBOARD_ACTIONS.map(ka => { + return { + name: "keyboardShortcuts" + ka.optionName, + value: JSON.stringify(ka.defaultShortcuts), + isSynced: false + }; + }); +} + module.exports = { initDocumentOptions, initSyncedOptions, diff --git a/src/tools/generate_document.js b/src/tools/generate_document.js index 7c2fb6b1e..bf250ca77 100644 --- a/src/tools/generate_document.js +++ b/src/tools/generate_document.js @@ -50,7 +50,12 @@ async function start() { const content = loremIpsum({ count: paragraphCount, units: 'paragraphs', sentenceLowerBound: 1, sentenceUpperBound: 15, paragraphLowerBound: 3, paragraphUpperBound: 10, format: 'html' }); - const {note} = await noteService.createNote(getRandomParentNoteId(), title, content); + const {note} = await noteService.createNewNote({ + parentNoteId: getRandomParentNoteId(), + title, + content, + type: 'text' + }); console.log(`Created note ${i}: ${title}`); diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index e6a078476..09b1527dd 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -10,12 +10,10 @@