diff --git a/docs/backend_api/BackendScriptApi.html b/docs/backend_api/BackendScriptApi.html
index d99d0c219..8e1204851 100644
--- a/docs/backend_api/BackendScriptApi.html
+++ b/docs/backend_api/BackendScriptApi.html
@@ -131,6 +131,78 @@
 
         
             
+
__private :Object
+
+
+
+
+
+    This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
+
+
+
+
+    Type:
+    
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+        
+axios
 
 
diff --git a/docs/backend_api/Note.html b/docs/backend_api/Note.html
index c5eea4f2f..2afe54588 100644
--- a/docs/backend_api/Note.html
+++ b/docs/backend_api/Note.html
@@ -93,7 +93,7 @@
     
     Source:
     
     
 
@@ -204,7 +204,7 @@
     
     Source:
     
     
 
@@ -279,7 +279,7 @@
     
     Source:
     
     
 
@@ -347,7 +347,7 @@
     
     Source:
     
     
 
@@ -415,7 +415,7 @@
     
     Source:
     
     
 
@@ -486,7 +486,7 @@
     
     Source:
     
     
 
@@ -554,7 +554,7 @@
     
     Source:
     
     
 
@@ -622,7 +622,7 @@
     
     Source:
     
     
 
@@ -690,7 +690,7 @@
     
     Source:
     
     
 
@@ -758,7 +758,7 @@
     
     Source:
     
     
 
@@ -833,7 +833,7 @@
     
     Source:
     
     
 
@@ -901,7 +901,7 @@
     
     Source:
     
     
 
@@ -969,7 +969,7 @@
     
     Source:
     
     
 
@@ -1037,7 +1037,7 @@
     
     Source:
     
     
 
@@ -1112,7 +1112,7 @@
     
     Source:
     
     
 
@@ -1180,7 +1180,7 @@
     
     Source:
     
     
 
@@ -1248,7 +1248,7 @@
     
     Source:
     
     
 
@@ -1316,7 +1316,7 @@
     
     Source:
     
     
 
@@ -1384,7 +1384,7 @@
     
     Source:
     
     
 
@@ -1452,7 +1452,7 @@
     
     Source:
     
     
 
@@ -1528,7 +1528,7 @@
     
     Source:
     
     
 
@@ -1630,7 +1630,7 @@
     
     Source:
     
     
 
@@ -1830,7 +1830,7 @@
     
     Source:
     
     
 
@@ -1914,7 +1914,7 @@
     
     Source:
     
     
 
@@ -2020,7 +2020,7 @@
     
     Source:
     
     
 
@@ -2194,7 +2194,7 @@
     
     Source:
     
     
 
@@ -2394,7 +2394,7 @@
     
     Source:
     
     
 
@@ -2572,7 +2572,7 @@
     
     Source:
     
     
 
@@ -2683,7 +2683,7 @@
     
     Source:
     
     
 
@@ -2785,7 +2785,7 @@
     
     Source:
     
     
 
@@ -2887,7 +2887,7 @@
     
     Source:
     
     
 
@@ -2989,7 +2989,7 @@
     
     Source:
     
     
 
@@ -3091,7 +3091,7 @@
     
     Source:
     
     
 
@@ -3199,7 +3199,7 @@
     
     Source:
     
     
 
@@ -3305,7 +3305,7 @@
     
     Source:
     
     
 
@@ -3456,7 +3456,7 @@
     
     Source:
     
     
 
@@ -3626,7 +3626,7 @@
     
     Source:
     
     
 
@@ -3781,7 +3781,7 @@
     
     Source:
     
     
 
@@ -3951,7 +3951,7 @@
     
     Source:
     
     
 
@@ -4057,7 +4057,7 @@
     
     Source:
     
     
 
@@ -4259,7 +4259,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -4437,7 +4437,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -4595,7 +4595,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -4765,7 +4765,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -4920,7 +4920,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5090,7 +5090,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5245,7 +5245,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5415,7 +5415,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5570,7 +5570,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5679,7 +5679,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5781,7 +5781,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -5932,7 +5932,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6102,7 +6102,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6257,7 +6257,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6366,7 +6366,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6475,7 +6475,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6577,7 +6577,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6679,7 +6679,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6781,7 +6781,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6888,7 +6888,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -6990,7 +6990,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7141,7 +7141,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7319,7 +7319,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7474,7 +7474,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7629,7 +7629,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7784,7 +7784,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -7934,7 +7934,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8040,7 +8040,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8146,7 +8146,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8252,7 +8252,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8358,7 +8358,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8464,7 +8464,7 @@ This method can be significantly faster than the getAttribute()
     
     Source:
     
     
 
@@ -8856,7 +8856,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -9036,7 +9036,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -9216,7 +9216,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -9363,6 +9363,111 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
 
     
+    saveNoteRevision() → {NoteRevision|null}
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+
+
+    - 
+        Type
+    +
- 
+        
+NoteRevision
+|
+
+null
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+setAttribute(type, name, valueopt)
     
 
@@ -9538,7 +9643,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -9718,7 +9823,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -9878,7 +9983,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -10120,7 +10225,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -10331,7 +10436,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
@@ -10542,7 +10647,7 @@ This is a low level method, for notes and branches use `note.deleteNote()` and '
     
     Source:
     
     
 
diff --git a/docs/backend_api/becca_entities_note.js.html b/docs/backend_api/becca_entities_note.js.html
index 3af1da710..57d0527e7 100644
--- a/docs/backend_api/becca_entities_note.js.html
+++ b/docs/backend_api/becca_entities_note.js.html
@@ -37,6 +37,8 @@ const entityChangesService = require('../../services/entity_changes');
 const AbstractEntity = require("./abstract_entity");
 const NoteRevision = require("./note_revision");
 const TaskContext = require("../../services/task_context.js");
+const optionService = require("../../services/options.js");
+const noteRevisionService = require("../../services/note_revisions.js");
 
 const LABEL = 'label';
 const RELATION = 'relation';
@@ -1192,6 +1194,41 @@ class Note extends AbstractEntity {
         return !(this.noteId in this.becca.notes);
     }
 
+    /**
+     * @return {NoteRevision|null}
+     */
+    saveNoteRevision() {
+        const content = this.getContent();
+
+        if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
+            return null;
+        }
+
+        const contentMetadata = this.getContentMetadata();
+
+        const noteRevision = new NoteRevision({
+            noteId: this.noteId,
+            // title and text should be decrypted now
+            title: this.title,
+            type: this.type,
+            mime: this.mime,
+            isProtected: false, // will be fixed in the protectNoteRevisions() call
+            utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
+                ? this.utcDateModified
+                : contentMetadata.utcDateModified,
+            utcDateCreated: dateUtils.utcNowDateTime(),
+            utcDateModified: dateUtils.utcNowDateTime(),
+            dateLastEdited: this.dateModified > contentMetadata.dateModified
+                ? this.dateModified
+                : contentMetadata.dateModified,
+            dateCreated: dateUtils.localNowDateTime()
+        }).save();
+
+        noteRevision.setContent(content);
+
+        return noteRevision;
+    }
+
     beforeSaving() {
         super.beforeSaving();
 
diff --git a/docs/backend_api/services_backend_script_api.js.html b/docs/backend_api/services_backend_script_api.js.html
index 3c73eee9f..23c98b39e 100644
--- a/docs/backend_api/services_backend_script_api.js.html
+++ b/docs/backend_api/services_backend_script_api.js.html
@@ -454,6 +454,15 @@ function BackendScriptApi(currentNote, apiParams) {
      * @return {{syncVersion, appVersion, buildRevision, dbVersion, dataDirectory, buildDate}|*} - object representing basic info about running Trilium version
      */
     this.getAppInfo = () => appInfo
+
+    /**
+     * This object contains "at your risk" and "no BC guarantees" objects for advanced use cases.
+     *
+     * @type {{becca: Becca}}
+     */
+    this.__private = {
+        becca
+    }
 }
 
 module.exports = BackendScriptApi;
diff --git a/docs/frontend_api/FrontendScriptApi.html b/docs/frontend_api/FrontendScriptApi.html
index 9ac977093..a6b3044b7 100644
--- a/docs/frontend_api/FrontendScriptApi.html
+++ b/docs/frontend_api/FrontendScriptApi.html
@@ -1635,7 +1635,144 @@
     
     Source:
     
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+        
+            
+
+    
+
+    
+    addTextToActiveContextEditor(text)
+    
+
+    
+
+
+
+
+    Adds given text to the editor cursor
+
+
+
+
+
+
+
+
+
+
+    Parameters:
+    
+
+
+    
+    
+        
+        | Name+        
+
+ | Type+
+        
+
+        
+
+ | Description+ | 
+    
+
+    
+    
+
+        
+            
+                | +            
+
+text | +            
+                
+string
+
+
+            
++
+            
+
+            
+
+ | this must be clear text, HTML is not supported.+ | 
+
+    
+    
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
     
 
@@ -1760,6 +1897,8 @@
     
 
     
+
- Deprecated:
- use addTextToActiveContextEditor() instead
 
+    
 
     
 
@@ -1772,7 +1911,7 @@- Source:@@ -1928,7 +2067,7 @@
- Source:@@ -2288,7 +2427,7 @@
- Source:@@ -2421,7 +2560,7 @@
- Source:@@ -2479,6 +2618,438 @@
     
 
     
+
getActiveContextCodeEditor() → {Promise.<CodeMirror>}
+    
+
+    
+
+
+
+
+    See https://codemirror.net/doc/manual.html#api
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+    instance of CodeMirror
+
+
+
+
+
+    - 
+        Type
+    +
- 
+        
+Promise.<CodeMirror>
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+    
+getActiveContextNote() → {NoteShort}
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+    active note (loaded into right pane)
+
+
+
+
+
+    - 
+        Type
+    +
- 
+        
+NoteShort
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+    
+getActiveContextNotePath() → {Promise.<(string|null)>}
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+    returns note path of active note or null if there isn't active note
+
+
+
+
+
+    - 
+        Type
+    +
- 
+        
+Promise.<(string|null)>
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+    
+getActiveContextTextEditor() → {Promise.<CKEditor>}
+    
+
+    
+
+
+
+
+    See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+    instance of CKEditor
+
+
+
+
+
+    - 
+        Type
+    +
- 
+        
+Promise.<CKEditor>
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+    
     
     
 
@@ -2532,7 +3103,7 @@ implementation of actual widget type.- Source:@@ -2586,116 +3157,6 @@ implementation of actual widget type.
     
 
     
-
getActiveTabCodeEditor() → {Promise.<CodeMirror>}
-    
-
-    
-
-
-
-
-    See https://codemirror.net/doc/manual.html#api
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-
-    
-    - Source:-    
-    
-
-    
-
-    
-
-    
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Returns:
-
-        
-
-    instance of CodeMirror
-
-
-
-
-
-    - 
-        Type
-    -
- 
-        
-Promise.<CodeMirror>
-
-
-    -
-
-    
-
-
-
-
-
-        
-            
-
-    
-
-getActiveTabNote() → {NoteShort}
     
 
@@ -2732,6 +3193,8 @@ implementation of actual widget type.
     
 
     
+        - Deprecated:
- use getActiveContextNote() instead
 
+    
 
     
 
@@ -2744,7 +3207,7 @@ implementation of actual widget type.- Source:@@ -2838,6 +3301,8 @@ implementation of actual widget type.
     
 
     
+
- Deprecated:
- use getActiveContextNotePath() instead
 
+    
 
     
 
@@ -2850,7 +3315,7 @@ implementation of actual widget type.- Source:@@ -2908,7 +3373,7 @@ implementation of actual widget type.
     
 
     
-
getActiveTabTextEditor(callbackopt) → {Promise.<CKEditor>}
+    getActiveTabTextEditor(callbackopt)
     
 
     
@@ -2975,7 +3440,7 @@ implementation of actual widget type.
 
             
 
-            deprecated (use returned promise): callback receiving "textEditor" instance+ | callback receiving "textEditor" instance@@ -3004,6 +3469,8 @@ implementation of actual widget type.
     
 
     
+ | - Deprecated:
- use getActiveContextTextEditor()
 
+    
 
     
 
@@ -3016,7 +3483,7 @@ implementation of actual widget type.- Source:@@ -3041,28 +3508,6 @@ implementation of actual widget type.
 
 
 
-
Returns:
-
-        
-
-    instance of CKEditor
-
-
-
-
-
-    - 
-        Type
-    -
- 
-        
-Promise.<CKEditor>
-
-
-    -
-
-    
 
 
 
@@ -3175,7 +3620,7 @@ implementation of actual widget type.- Source:@@ -3332,7 +3777,7 @@ implementation of actual widget type.
- Source:@@ -3487,7 +3932,7 @@ implementation of actual widget type.
- Source:@@ -3594,7 +4039,7 @@ if some action needs to happen on only one specific instance.
- Source:@@ -3749,7 +4194,7 @@ if some action needs to happen on only one specific instance.
- Source:@@ -3905,7 +4350,7 @@ if some action needs to happen on only one specific instance.
- Source:@@ -4106,7 +4551,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -4212,7 +4657,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -4367,7 +4812,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -4522,7 +4967,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -4576,6 +5021,184 @@ otherwise (by e.g. createNoteLink())
     
 
     
+
openSplitWithNote(notePath, activate) → {Promise.<void>}
+    
+
+    
+
+
+
+
+    Open a note in a new split.
+
+
+
+
+
+
+
+
+
+
+    Parameters:
+    
+
+
+    
+    
+        
+        | Name+        
+
+ | Type+
+        
+
+        
+
+ | Description+ | 
+    
+
+    
+    
+
+        
+            
+                | +            
+
+notePath | +            
+                
+string
+
+
+            
++
+            
+
+            
+
+ | (or noteId)+ | 
+
+    
+
+        
+            
+                | +            
+
+activate | +            
+                
+boolean
+
+
+            
++
+            
+
+            
+
+ | set to true to activate the new split, false to stay on the current split+ | 
+
+    
+    
+
+
+
+
+
+
+
+
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+
+    
+    - Source:+    
+    
+
+    
+
+    
+
+    
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Returns:
+
+        
+
+
+
+    - 
+        Type
+    +
- 
+        
+Promise.<void>
+
+
+    +
+
+    
+
+
+
+
+
+        
+            
+
+    
+
+openTabWithNote(notePath, activate) → {Promise.<void>}
     
 
@@ -4851,7 +5474,7 @@ otherwise (by e.g. createNoteLink())
     
     - Source:@@ -4959,7 +5582,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5115,7 +5738,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5271,7 +5894,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5408,7 +6031,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5562,7 +6185,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5648,7 +6271,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5785,7 +6408,7 @@ otherwise (by e.g. createNoteLink())
- Source:@@ -5946,7 +6569,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6054,7 +6677,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6192,7 +6815,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6348,7 +6971,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6503,7 +7126,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6654,7 +7277,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6791,7 +7414,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -6928,7 +7551,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -7088,7 +7711,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -7248,7 +7871,7 @@ Internally this serializes the anonymous function into string and sends it to ba
- Source:@@ -7340,7 +7963,7 @@ Typical use case is when new note has been created, we should wait until it is s
- Source:diff --git a/docs/frontend_api/global.html b/docs/frontend_api/global.html
index e3bb5cbf0..8879721e8 100644
--- a/docs/frontend_api/global.html
+++ b/docs/frontend_api/global.html
@@ -395,7 +395,7 @@
- Source:diff --git a/docs/frontend_api/services_frontend_script_api.js.html b/docs/frontend_api/services_frontend_script_api.js.html
index fe896a184..da00296e1 100644
--- a/docs/frontend_api/services_frontend_script_api.js.html
+++ b/docs/frontend_api/services_frontend_script_api.js.html
@@ -129,6 +129,26 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
         }
     };
 
+    /**
+     * Open a note in a new split.
+     *
+     * @param {string} notePath (or noteId)
+     * @param {boolean} activate - set to true to activate the new split, false to stay on the current split
+     * @return {Promise<void>}
+     */
+    this.openSplitWithNote = async (notePath, activate) => {
+        await ws.waitForMaxKnownEntityChangeId();
+
+        const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
+        const {ntxId} = subContexts[subContexts.length - 1];
+
+        appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath});
+
+        if (activate) {
+            appContext.triggerEvent('focusAndSelectTitle');
+        }
+    };
+
     /**
      * @typedef {Object} ToolbarButtonOptions
      * @property {string} title
@@ -412,7 +432,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
     this.getActiveTabTextEditor = callback => {
         console.warn("api.getActiveTabTextEditor() is deprecated, use getActiveContextTextEditor() instead.");
 
-        return appContext.tabManager.getActiveContextTextEditor(callback);
+        return appContext.tabManager.getActiveContext()?.getTextEditor(callback);
     };
 
     /**
@@ -421,7 +441,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
      * @method
      * @returns {Promise<CKEditor>} instance of CKEditor
      */
-    this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContextTextEditor();
+    this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor();
 
     /**
      * See https://codemirror.net/doc/manual.html#api
@@ -429,7 +449,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
      * @method
      * @returns {Promise<CodeMirror>} instance of CodeMirror
      */
-    this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContextCodeEditor();
+    this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor();
 
     /**
      * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the
diff --git a/src/becca/entities/note.js b/src/becca/entities/note.js
index 595e41052..a815df1c3 100644
--- a/src/becca/entities/note.js
+++ b/src/becca/entities/note.js
@@ -9,6 +9,8 @@ const entityChangesService = require('../../services/entity_changes');
 const AbstractEntity = require("./abstract_entity");
 const NoteRevision = require("./note_revision");
 const TaskContext = require("../../services/task_context.js");
+const optionService = require("../../services/options.js");
+const noteRevisionService = require("../../services/note_revisions.js");
 
 const LABEL = 'label';
 const RELATION = 'relation';
@@ -1164,6 +1166,41 @@ class Note extends AbstractEntity {
         return !(this.noteId in this.becca.notes);
     }
 
+    /**
+     * @return {NoteRevision|null}
+     */
+    saveNoteRevision() {
+        const content = this.getContent();
+
+        if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
+            return null;
+        }
+
+        const contentMetadata = this.getContentMetadata();
+
+        const noteRevision = new NoteRevision({
+            noteId: this.noteId,
+            // title and text should be decrypted now
+            title: this.title,
+            type: this.type,
+            mime: this.mime,
+            isProtected: false, // will be fixed in the protectNoteRevisions() call
+            utcDateLastEdited: this.utcDateModified > contentMetadata.utcDateModified
+                ? this.utcDateModified
+                : contentMetadata.utcDateModified,
+            utcDateCreated: dateUtils.utcNowDateTime(),
+            utcDateModified: dateUtils.utcNowDateTime(),
+            dateLastEdited: this.dateModified > contentMetadata.dateModified
+                ? this.dateModified
+                : contentMetadata.dateModified,
+            dateCreated: dateUtils.localNowDateTime()
+        }).save();
+
+        noteRevision.setContent(content);
+
+        return noteRevision;
+    }
+
     beforeSaving() {
         super.beforeSaving();
 
diff --git a/src/public/app/services/frontend_script_api.js b/src/public/app/services/frontend_script_api.js
index b2eabdc13..5ae0eaa05 100644
--- a/src/public/app/services/frontend_script_api.js
+++ b/src/public/app/services/frontend_script_api.js
@@ -101,6 +101,26 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
         }
     };
 
+    /**
+     * Open a note in a new split.
+     *
+     * @param {string} notePath (or noteId)
+     * @param {boolean} activate - set to true to activate the new split, false to stay on the current split
+     * @return {Promise}
+     */
+    this.openSplitWithNote = async (notePath, activate) => {
+        await ws.waitForMaxKnownEntityChangeId();
+
+        const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
+        const {ntxId} = subContexts[subContexts.length - 1];
+
+        appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath});
+
+        if (activate) {
+            appContext.triggerEvent('focusAndSelectTitle');
+        }
+    };
+
     /**
      * @typedef {Object} ToolbarButtonOptions
      * @property {string} title
diff --git a/src/public/app/services/tab_manager.js b/src/public/app/services/tab_manager.js
index 15f939bfe..59318d6e3 100644
--- a/src/public/app/services/tab_manager.js
+++ b/src/public/app/services/tab_manager.js
@@ -306,7 +306,8 @@ export default class TabManager extends Component {
                 const mainNoteContexts = this.getNoteContexts().filter(nc => nc.isMainContext());
 
                 if (mainNoteContexts.length === 1) {
-                    mainNoteContexts[0].setEmpty();
+                    await this.clearLastMainNoteContext(noteContextToRemove);
+
                     return;
                 }
             }
@@ -317,7 +318,7 @@ export default class TabManager extends Component {
             const noteContextsToRemove = noteContextToRemove.getSubContexts();
             const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId);
 
-            await this.triggerEvent('beforeTabRemove', { ntxIds: ntxIdsToRemove });
+            await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove });
 
             if (!noteContextToRemove.isMainContext()) {
                 await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId);
@@ -336,16 +337,39 @@ export default class TabManager extends Component {
                 }
             }
 
-            this.children = this.children.filter(nc => !ntxIdsToRemove.includes(nc.ntxId));
-
-            this.recentlyClosedTabs.push(noteContextsToRemove);
-
-            this.triggerEvent('noteContextRemoved', {ntxIds: ntxIdsToRemove});
-
-            this.tabsUpdate.scheduleUpdate();
+            this.removeNoteContexts(noteContextsToRemove);
         });
     }
 
+    async clearLastMainNoteContext(noteContextToClear) {
+        noteContextToClear.setEmpty();
+
+        // activate main split
+        await this.activateNoteContext(noteContextToClear.ntxId);
+
+        // remove all other splits
+        const noteContextsToRemove = noteContextToClear.getSubContexts()
+            .filter(ntx => ntx.ntxId !== noteContextToClear.ntxId);
+
+        const ntxIdsToRemove = noteContextsToRemove.map(ntx => ntx.ntxId);
+
+        await this.triggerEvent('beforeNoteContextRemove', {ntxIds: ntxIdsToRemove});
+
+        this.removeNoteContexts(noteContextsToRemove);
+    }
+
+    removeNoteContexts(noteContextsToRemove) {
+        const ntxIdsToRemove = noteContextsToRemove.map(nc => nc.ntxId);
+
+        this.children = this.children.filter(nc => !ntxIdsToRemove.includes(nc.ntxId));
+
+        this.recentlyClosedTabs.push(noteContextsToRemove);
+
+        this.triggerEvent('noteContextRemoved', {ntxIds: ntxIdsToRemove});
+
+        this.tabsUpdate.scheduleUpdate();
+    }
+
     tabReorderEvent({ntxIdsInOrder}) {
         const order = {};
 
diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js
index f5dc0093b..d6bf8fe16 100644
--- a/src/public/app/widgets/note_detail.js
+++ b/src/public/app/widgets/note_detail.js
@@ -219,7 +219,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
         }
     }
 
-    async beforeTabRemoveEvent({ntxIds}) {
+    async beforeNoteContextRemoveEvent({ntxIds}) {
         if (this.isNoteContext(ntxIds)) {
             await this.spacedUpdate.updateNowIfNecessary();
         }
diff --git a/src/public/app/widgets/note_title.js b/src/public/app/widgets/note_title.js
index 59007faa6..2cbd9b5a1 100644
--- a/src/public/app/widgets/note_title.js
+++ b/src/public/app/widgets/note_title.js
@@ -87,7 +87,7 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
         }
     }
 
-    async beforeTabRemoveEvent({ntxIds}) {
+    async beforeNoteContextRemoveEvent({ntxIds}) {
         if (this.isNoteContext(ntxIds)) {
             await this.spacedUpdate.updateNowIfNecessary();
         }
diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js
index 2b7ccf595..a2aef1dfa 100644
--- a/src/routes/api/notes.js
+++ b/src/routes/api/notes.js
@@ -206,7 +206,7 @@ function changeTitle(req) {
     const noteTitleChanged = note.title !== title;
 
     if (noteTitleChanged) {
-        noteService.saveNoteRevision(note);
+        noteService.saveNoteRevisionIfNeeded(note);
     }
 
     note.title = title;
diff --git a/src/services/note_revisions.js b/src/services/note_revisions.js
index 3ff9b692a..3759d2d2e 100644
--- a/src/services/note_revisions.js
+++ b/src/services/note_revisions.js
@@ -30,46 +30,6 @@ function protectNoteRevisions(note) {
     }
 }
 
-/**
- * @param {Note} note
- * @return {NoteRevision|null}
- */
-function createNoteRevision(note) {
-    if (note.hasLabel("disableVersioning")) {
-        return null;
-    }
-
-    const content = note.getContent();
-
-    if (!content || (Buffer.isBuffer(content) && content.byteLength === 0)) {
-        return null;
-    }
-
-    const contentMetadata = note.getContentMetadata();
-
-    const noteRevision = new NoteRevision({
-        noteId: note.noteId,
-        // title and text should be decrypted now
-        title: note.title,
-        type: note.type,
-        mime: note.mime,
-        isProtected: false, // will be fixed in the protectNoteRevisions() call
-        utcDateLastEdited: note.utcDateModified > contentMetadata.utcDateModified
-            ? note.utcDateModified
-            : contentMetadata.utcDateModified,
-        utcDateCreated: dateUtils.utcNowDateTime(),
-        utcDateModified: dateUtils.utcNowDateTime(),
-        dateLastEdited: note.dateModified > contentMetadata.dateModified
-            ? note.dateModified
-            : contentMetadata.dateModified,
-        dateCreated: dateUtils.localNowDateTime()
-    }).save();
-
-    noteRevision.setContent(content);
-
-    return noteRevision;
-}
-
 function eraseNoteRevisions(noteRevisionIdsToErase) {
     if (noteRevisionIdsToErase.length === 0) {
         return;
@@ -86,6 +46,5 @@ function eraseNoteRevisions(noteRevisionIdsToErase) {
 
 module.exports = {
     protectNoteRevisions,
-    createNoteRevision,
     eraseNoteRevisions
 };
diff --git a/src/services/notes.js b/src/services/notes.js
index 8168dc0de..69633c681 100644
--- a/src/services/notes.js
+++ b/src/services/notes.js
@@ -499,7 +499,7 @@ function saveLinks(note, content) {
     return content;
 }
 
-function saveNoteRevision(note) {
+function saveNoteRevisionIfNeeded(note) {
     // files and images are versioned separately
     if (note.type === 'file' || note.type === 'image' || note.hasLabel('disableVersioning')) {
         return;
@@ -516,7 +516,7 @@ function saveNoteRevision(note) {
     const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.utcDateCreated).getTime();
 
     if (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) {
-        noteRevisionService.createNoteRevision(note);
+        note.saveNoteRevision();
     }
 }
 
@@ -527,7 +527,7 @@ function updateNote(noteId, noteUpdates) {
         throw new Error(`Note '${noteId}' is not available for change!`);
     }
 
-    saveNoteRevision(note);
+    saveNoteRevisionIfNeeded(note);
 
     // if protected status changed, then we need to encrypt/decrypt the content anyway
     if (['file', 'image'].includes(note.type) && note.isProtected !== noteUpdates.isProtected) {
@@ -918,6 +918,6 @@ module.exports = {
     triggerNoteTitleChanged,
     eraseDeletedNotesNow,
     eraseNotesWithDeleteId,
-    saveNoteRevision,
+    saveNoteRevisionIfNeeded,
     downloadImages
 };
diff --git a/src/services/search/services/lex.js b/src/services/search/services/lex.js
index c6bdc2dfd..470ba5526 100644
--- a/src/services/search/services/lex.js
+++ b/src/services/search/services/lex.js
@@ -83,7 +83,7 @@ function lex(str) {
             continue;
         }
         else if (!quotes) {
-            if (!fulltextEnded && currentWord === 'note' && chr === '.') {
+            if (!fulltextEnded && currentWord === 'note' && chr === '.' && i + 1 < str.length) {
                 fulltextEnded = true;
             }