mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-02 19:36:12 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/next58' into next58
# Conflicts: # src/public/app/components/note_context.js
This commit is contained in:
		@@ -30,6 +30,7 @@ rm -r $BUILD_DIR/swiftshader
 | 
				
			|||||||
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
					cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp -r dump-db $BUILD_DIR/
 | 
					cp -r dump-db $BUILD_DIR/
 | 
				
			||||||
 | 
					rm -rf $BUILD_DIR/dump-db/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp bin/tpl/trilium-portable.sh $BUILD_DIR/
 | 
					cp bin/tpl/trilium-portable.sh $BUILD_DIR/
 | 
				
			||||||
chmod 755 $BUILD_DIR/trilium-portable.sh
 | 
					chmod 755 $BUILD_DIR/trilium-portable.sh
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
 | 
				
			|||||||
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
					cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp -r dump-db $BUILD_DIR/
 | 
					cp -r dump-db $BUILD_DIR/
 | 
				
			||||||
 | 
					rm -rf $BUILD_DIR/dump-db/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "Zipping mac x64 electron distribution..."
 | 
					echo "Zipping mac x64 electron distribution..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,7 @@ chmod 755 $PKG_DIR/trilium.sh
 | 
				
			|||||||
cp bin/tpl/anonymize-database.sql $PKG_DIR/
 | 
					cp bin/tpl/anonymize-database.sql $PKG_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp -r dump-db $PKG_DIR/
 | 
					cp -r dump-db $PKG_DIR/
 | 
				
			||||||
 | 
					rm -rf $PKG_DIR/dump-db/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VERSION=`jq -r ".version" package.json`
 | 
					VERSION=`jq -r ".version" package.json`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,6 +28,7 @@ rm -r $BUILD_DIR/swiftshader
 | 
				
			|||||||
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
					cp bin/tpl/anonymize-database.sql $BUILD_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp -r dump-db $BUILD_DIR/
 | 
					cp -r dump-db $BUILD_DIR/
 | 
				
			||||||
 | 
					rm -rf $BUILD_DIR/dump-db/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp bin/tpl/trilium-{portable,no-cert-check,safe-mode}.{bat,ps1} $BUILD_DIR/
 | 
					cp bin/tpl/trilium-{portable,no-cert-check,safe-mode}.{bat,ps1} $BUILD_DIR/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,9 +14,6 @@ mkdir $DIR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
echo "Copying Trilium to build directory $DIR"
 | 
					echo "Copying Trilium to build directory $DIR"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cp -r dump-db $DIR/
 | 
					 | 
				
			||||||
rm -rf $DIR/dump-db/node_modules
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cp -r images $DIR/
 | 
					cp -r images $DIR/
 | 
				
			||||||
cp -r libraries $DIR/
 | 
					cp -r libraries $DIR/
 | 
				
			||||||
cp -r src $DIR/
 | 
					cp -r src $DIR/
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
module.exports = () => {
 | 
					module.exports = () => {
 | 
				
			||||||
    const hiddenSubtreeService = require('../../src/services/hidden_subtree.js');
 | 
					    const hiddenSubtreeService = require('../../src/services/hidden_subtree');
 | 
				
			||||||
    const cls = require("../../src/services/cls");
 | 
					    const cls = require("../../src/services/cls");
 | 
				
			||||||
    const beccaLoader = require("../../src/becca/becca_loader");
 | 
					    const beccaLoader = require("../../src/becca/becca_loader");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const yargs = require('yargs/yargs')
 | 
					const yargs = require('yargs/yargs')
 | 
				
			||||||
const { hideBin } = require('yargs/helpers')
 | 
					const { hideBin } = require('yargs/helpers')
 | 
				
			||||||
const dumpService = require("./inc/dump.js");
 | 
					const dumpService = require("./inc/dump");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
yargs(hideBin(process.argv))
 | 
					yargs(hideBin(process.argv))
 | 
				
			||||||
    .command('$0 <path_to_document> <target_directory>', 'dump the contents of document.db into the target directory', (yargs) => {
 | 
					    .command('$0 <path_to_document> <target_directory>', 'dump the contents of document.db into the target directory', (yargs) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
const crypto = require("crypto");
 | 
					const crypto = require("crypto");
 | 
				
			||||||
const sql = require("./sql.js");
 | 
					const sql = require("./sql");
 | 
				
			||||||
const decryptService = require("./decrypt.js");
 | 
					const decryptService = require("./decrypt");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getDataKey(password) {
 | 
					function getDataKey(password) {
 | 
				
			||||||
    if (!password) {
 | 
					    if (!password) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,9 @@
 | 
				
			|||||||
const fs = require("fs");
 | 
					const fs = require("fs");
 | 
				
			||||||
const sanitize = require("sanitize-filename");
 | 
					const sanitize = require("sanitize-filename");
 | 
				
			||||||
const sql = require("./sql.js");
 | 
					const sql = require("./sql");
 | 
				
			||||||
const decryptService = require("./decrypt.js");
 | 
					const decryptService = require("./decrypt");
 | 
				
			||||||
const dataKeyService = require("./data_key.js");
 | 
					const dataKeyService = require("./data_key");
 | 
				
			||||||
const extensionService = require("./extension.js");
 | 
					const extensionService = require("./extension");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function dumpDocument(documentPath, targetPath, options) {
 | 
					function dumpDocument(documentPath, targetPath, options) {
 | 
				
			||||||
    const stats = {
 | 
					    const stats = {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								libraries/boxicons/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								libraries/boxicons/LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2015-2021 Aniket Suvarna
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB  | 
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -5,7 +5,6 @@
 | 
				
			|||||||
  "requires": true,
 | 
					  "requires": true,
 | 
				
			||||||
  "packages": {
 | 
					  "packages": {
 | 
				
			||||||
    "": {
 | 
					    "": {
 | 
				
			||||||
      "name": "trilium",
 | 
					 | 
				
			||||||
      "version": "0.57.3",
 | 
					      "version": "0.57.3",
 | 
				
			||||||
      "hasInstallScript": true,
 | 
					      "hasInstallScript": true,
 | 
				
			||||||
      "license": "AGPL-3.0-only",
 | 
					      "license": "AGPL-3.0-only",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ const Branch = require('../../src/becca/entities/branch');
 | 
				
			|||||||
const SearchContext = require('../../src/services/search/search_context');
 | 
					const SearchContext = require('../../src/services/search/search_context');
 | 
				
			||||||
const dateUtils = require('../../src/services/date_utils');
 | 
					const dateUtils = require('../../src/services/date_utils');
 | 
				
			||||||
const becca = require('../../src/becca/becca');
 | 
					const becca = require('../../src/becca/becca');
 | 
				
			||||||
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');
 | 
					const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("Search", () => {
 | 
					describe("Search", () => {
 | 
				
			||||||
    let rootNote;
 | 
					    let rootNote;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const {note} = require('./becca_mocking.js');
 | 
					const {note} = require('./becca_mocking');
 | 
				
			||||||
const ValueExtractor = require('../../src/services/search/value_extractor');
 | 
					const ValueExtractor = require('../../src/services/search/value_extractor');
 | 
				
			||||||
const becca = require('../../src/becca/becca');
 | 
					const becca = require('../../src/becca/becca');
 | 
				
			||||||
const SearchContext = require("../../src/services/search/search_context");
 | 
					const SearchContext = require("../../src/services/search/search_context");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const becca = require('./becca.js');
 | 
					const becca = require('./becca');
 | 
				
			||||||
const cls = require('../services/cls');
 | 
					const cls = require('../services/cls');
 | 
				
			||||||
const protectedSessionService = require('../services/protected_session');
 | 
					const protectedSessionService = require('../services/protected_session');
 | 
				
			||||||
const log = require('../services/log');
 | 
					const log = require('../services/log');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ const Note = require('./note');
 | 
				
			|||||||
const AbstractEntity = require("./abstract_entity");
 | 
					const AbstractEntity = require("./abstract_entity");
 | 
				
			||||||
const sql = require("../../services/sql");
 | 
					const sql = require("../../services/sql");
 | 
				
			||||||
const dateUtils = require("../../services/date_utils");
 | 
					const dateUtils = require("../../services/date_utils");
 | 
				
			||||||
const utils = require("../../services/utils.js");
 | 
					const utils = require("../../services/utils");
 | 
				
			||||||
const TaskContext = require("../../services/task_context");
 | 
					const TaskContext = require("../../services/task_context");
 | 
				
			||||||
const cls = require("../../services/cls");
 | 
					const cls = require("../../services/cls");
 | 
				
			||||||
const log = require("../../services/log");
 | 
					const log = require("../../services/log");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1329,7 +1329,7 @@ class Note extends AbstractEntity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isLaunchBarConfig() {
 | 
					    isLaunchBarConfig() {
 | 
				
			||||||
        return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'];
 | 
					        return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isOptions() {
 | 
					    isOptions() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
const becca = require('./becca');
 | 
					const becca = require('./becca');
 | 
				
			||||||
const log = require('../services/log');
 | 
					const log = require('../services/log');
 | 
				
			||||||
const beccaService = require('./becca_service.js');
 | 
					const beccaService = require('./becca_service');
 | 
				
			||||||
const dateUtils = require('../services/date_utils');
 | 
					const dateUtils = require('../services/date_utils');
 | 
				
			||||||
const { JSDOM } = require("jsdom");
 | 
					const { JSDOM } = require("jsdom");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src/errors/not_found_error.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/errors/not_found_error.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					class NotFoundError {
 | 
				
			||||||
 | 
					    constructor(message) {
 | 
				
			||||||
 | 
					        this.message = message;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = NotFoundError;
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/errors/validation_error.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/errors/validation_error.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					class ValidationError {
 | 
				
			||||||
 | 
					    constructor(message) {
 | 
				
			||||||
 | 
					        this.message = message;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = ValidationError;
 | 
				
			||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
const appInfo = require('../services/app_info');
 | 
					const appInfo = require('../services/app_info');
 | 
				
			||||||
const eu = require("./etapi_utils.js");
 | 
					const eu = require("./etapi_utils");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function register(router) {
 | 
					function register(router) {
 | 
				
			||||||
    eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
 | 
					    eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,10 @@ class NoteContext extends Component {
 | 
				
			|||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.hoistedNoteId === 'root' && this.notePath.startsWith("root/hidden")) {
 | 
					        if (this.hoistedNoteId === 'root'
 | 
				
			||||||
 | 
					            && this.notePath.startsWith("root/hidden")
 | 
				
			||||||
 | 
					            && !this.note.hasLabel("keepCurrentHoisting")
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
            // hidden subtree displays only when hoisted so it doesn't make sense to keep root as hoisted note
 | 
					            // hidden subtree displays only when hoisted so it doesn't make sense to keep root as hoisted note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let hoistedNoteId = 'hidden';
 | 
					            let hoistedNoteId = 'hidden';
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -828,7 +828,7 @@ class NoteShort {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isLaunchBarConfig() {
 | 
					    isLaunchBarConfig() {
 | 
				
			||||||
        return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'].includes(this.noteId);
 | 
					        return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isOptions() {
 | 
					    isOptions() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,27 +90,27 @@ function getNotePathFromLink($link) {
 | 
				
			|||||||
    return url ? getNotePathFromUrl(url) : null;
 | 
					    return url ? getNotePathFromUrl(url) : null;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function goToLink(e) {
 | 
					function goToLink(evt) {
 | 
				
			||||||
    const $link = $(e.target).closest("a,.block-link");
 | 
					    const $link = $(evt.target).closest("a,.block-link");
 | 
				
			||||||
    const address = $link.attr('href');
 | 
					    const address = $link.attr('href');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (address?.startsWith("data:")) {
 | 
					    if (address?.startsWith("data:")) {
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    e.preventDefault();
 | 
					    evt.preventDefault();
 | 
				
			||||||
    e.stopPropagation();
 | 
					    evt.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const notePath = getNotePathFromLink($link);
 | 
					    const notePath = getNotePathFromLink($link);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ctrlKey = (!utils.isMac() && e.ctrlKey) || (utils.isMac() && e.metaKey);
 | 
					    const ctrlKey = utils.isCtrlKey(evt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (notePath) {
 | 
					    if (notePath) {
 | 
				
			||||||
        if ((e.which === 1 && ctrlKey) || e.which === 2) {
 | 
					        if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
 | 
				
			||||||
            appContext.tabManager.openTabWithNoteWithHoisting(notePath);
 | 
					            appContext.tabManager.openTabWithNoteWithHoisting(notePath);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (e.which === 1) {
 | 
					        else if (evt.which === 1) {
 | 
				
			||||||
            const ntxId = $(e.target).closest("[data-ntx-id]").attr("data-ntx-id");
 | 
					            const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const noteContext = ntxId
 | 
					            const noteContext = ntxId
 | 
				
			||||||
                ? appContext.tabManager.getNoteContextById(ntxId)
 | 
					                ? appContext.tabManager.getNoteContextById(ntxId)
 | 
				
			||||||
@@ -124,7 +124,7 @@ function goToLink(e) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        if ((e.which === 1 && ctrlKey) || e.which === 2
 | 
					        if ((evt.which === 1 && ctrlKey) || evt.which === 2
 | 
				
			||||||
            || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
 | 
					            || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
 | 
				
			||||||
            || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
 | 
					            || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
import utils from './utils.js';
 | 
					import utils from './utils.js';
 | 
				
			||||||
 | 
					import ValidationError from "./validation_error.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const REQUEST_LOGGING_ENABLED = false;
 | 
					const REQUEST_LOGGING_ENABLED = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,10 +103,15 @@ async function call(method, url, data, headers = {}) {
 | 
				
			|||||||
    return resp.body;
 | 
					    return resp.body;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function reportError(method, url, status, error) {
 | 
					async function reportError(method, url, status, response) {
 | 
				
			||||||
    const message = "Error when calling " + method + " " + url + ": " + status + " - " + error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const toastService = (await import("./toast.js")).default;
 | 
					    const toastService = (await import("./toast.js")).default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ([400, 404].includes(status) && response && typeof response === 'object') {
 | 
				
			||||||
 | 
					        toastService.showError(response.message);
 | 
				
			||||||
 | 
					        throw new ValidationError(response);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const message = "Error when calling " + method + " " + url + ": " + status + " - " + responseText;
 | 
				
			||||||
    toastService.showError(message);
 | 
					    toastService.showError(message);
 | 
				
			||||||
    toastService.throwError(message);
 | 
					    toastService.throwError(message);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,11 @@ function isMac() {
 | 
				
			|||||||
    return navigator.platform.indexOf('Mac') > -1;
 | 
					    return navigator.platform.indexOf('Mac') > -1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isCtrlKey(evt) {
 | 
				
			||||||
 | 
					    return (!isMac() && evt.ctrlKey)
 | 
				
			||||||
 | 
					        || (isMac() && evt.metaKey);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function assertArguments() {
 | 
					function assertArguments() {
 | 
				
			||||||
    for (const i in arguments) {
 | 
					    for (const i in arguments) {
 | 
				
			||||||
        if (!arguments[i]) {
 | 
					        if (!arguments[i]) {
 | 
				
			||||||
@@ -362,6 +367,7 @@ export default {
 | 
				
			|||||||
    now,
 | 
					    now,
 | 
				
			||||||
    isElectron,
 | 
					    isElectron,
 | 
				
			||||||
    isMac,
 | 
					    isMac,
 | 
				
			||||||
 | 
					    isCtrlKey,
 | 
				
			||||||
    assertArguments,
 | 
					    assertArguments,
 | 
				
			||||||
    escapeHtml,
 | 
					    escapeHtml,
 | 
				
			||||||
    stopWatch,
 | 
					    stopWatch,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								src/public/app/services/validation_error.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/public/app/services/validation_error.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					export default class ValidationError {
 | 
				
			||||||
 | 
					    constructor(resp) {
 | 
				
			||||||
 | 
					        for (const key in resp) {
 | 
				
			||||||
 | 
					            this[key] = resp[key];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -357,7 +357,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        // disable spellcheck for attribute editor
 | 
					        // disable spellcheck for attribute editor
 | 
				
			||||||
        this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot()));
 | 
					        this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js');
 | 
					        //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector');
 | 
				
			||||||
        //CKEditorInspector.attach(this.textEditor);
 | 
					        //CKEditorInspector.attach(this.textEditor);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,55 @@
 | 
				
			|||||||
import AbstractLauncher from "./abstract_launcher.js";
 | 
					import AbstractLauncher from "./abstract_launcher.js";
 | 
				
			||||||
import dialogService from "../../../services/dialog.js";
 | 
					import dialogService from "../../../services/dialog.js";
 | 
				
			||||||
import appContext from "../../../components/app_context.js";
 | 
					import appContext from "../../../components/app_context.js";
 | 
				
			||||||
 | 
					import utils from "../../../services/utils.js";
 | 
				
			||||||
 | 
					import linkContextMenuService from "../../../menus/link_context_menu.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// we're intentionally displaying the launcher title and icon instead of the target
 | 
				
			||||||
 | 
					// e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok),
 | 
				
			||||||
 | 
					// but on the launchpad you want them distinguishable.
 | 
				
			||||||
 | 
					// for titles, the note titles may follow a different scheme than maybe desirable on the launchpad
 | 
				
			||||||
 | 
					// another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons).
 | 
				
			||||||
 | 
					// The only downside is more work in setting up the typical case
 | 
				
			||||||
 | 
					// where you actually want to have both title and icon in sync, but for those cases there are bookmarks
 | 
				
			||||||
export default class NoteLauncher extends AbstractLauncher {
 | 
					export default class NoteLauncher extends AbstractLauncher {
 | 
				
			||||||
    constructor(launcherNote) {
 | 
					    constructor(launcherNote) {
 | 
				
			||||||
        super(launcherNote);
 | 
					        super(launcherNote);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.title(this.launcherNote.title)
 | 
					        this.title(this.launcherNote.title)
 | 
				
			||||||
            .icon(this.launcherNote.getIcon())
 | 
					            .icon(this.launcherNote.getIcon())
 | 
				
			||||||
            .onClick(() => this.launch());
 | 
					            .onClick((widget, evt) => this.launch(evt))
 | 
				
			||||||
 | 
					            .onAuxClick((widget, evt) => this.launch(evt))
 | 
				
			||||||
 | 
					            .onContextMenu(evt => {
 | 
				
			||||||
 | 
					                const targetNoteId = this.getTargetNoteId();
 | 
				
			||||||
 | 
					                if (!targetNoteId) {
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    launch() {
 | 
					                linkContextMenuService.openContextMenu(targetNoteId, evt);
 | 
				
			||||||
        // we're intentionally displaying the launcher title and icon instead of the target
 | 
					            });
 | 
				
			||||||
        // e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok),
 | 
					    }
 | 
				
			||||||
        // but on the launchpad you want them distinguishable.
 | 
					
 | 
				
			||||||
        // for titles, the note titles may follow a different scheme than maybe desirable on the launchpad
 | 
					    launch(evt) {
 | 
				
			||||||
        // another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons).
 | 
					        const targetNoteId = this.getTargetNoteId();
 | 
				
			||||||
        // The only (but major) downside is more work in setting up the typical case where you actually want to have both title and icon in sync.
 | 
					        if (!targetNoteId) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!evt) {
 | 
				
			||||||
 | 
					            // keyboard shortcut
 | 
				
			||||||
 | 
					            appContext.tabManager.getActiveContext().setNote(targetNoteId)
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const ctrlKey = utils.isCtrlKey(evt);
 | 
				
			||||||
 | 
					        if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
 | 
				
			||||||
 | 
					            appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            appContext.tabManager.getActiveContext().setNote(targetNoteId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getTargetNoteId() {
 | 
				
			||||||
        const targetNoteId = this.launcherNote.getRelationValue('targetNote');
 | 
					        const targetNoteId = this.launcherNote.getRelationValue('targetNote');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!targetNoteId) {
 | 
					        if (!targetNoteId) {
 | 
				
			||||||
@@ -25,7 +57,7 @@ export default class NoteLauncher extends AbstractLauncher {
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId, true);
 | 
					        return targetNoteId;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getTitle() {
 | 
					    getTitle() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,10 +13,23 @@ export default class OnClickButtonWidget extends AbstractButtonWidget {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            console.warn(`Button widget '${this.componentId}' has no defined click handler`, this.settings);
 | 
					            console.warn(`Button widget '${this.componentId}' has no defined click handler`, this.settings);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (this.settings.onAuxClick) {
 | 
				
			||||||
 | 
					            this.$widget.on("auxclick", e => {
 | 
				
			||||||
 | 
					                this.$widget.tooltip("hide");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                this.settings.onAuxClick(this, e);
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    onClick(handler) {
 | 
					    onClick(handler) {
 | 
				
			||||||
        this.settings.onClick = handler;
 | 
					        this.settings.onClick = handler;
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onAuxClick(handler) {
 | 
				
			||||||
 | 
					        this.settings.onAuxClick = handler;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -452,7 +452,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
                    if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
 | 
					                    if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
 | 
				
			||||||
                        const files = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
 | 
					                        const files = [...dataTransfer.files]; // chrome has issue that dataTransfer.files empties after async operation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        const importService = await import('../services/import.js');
 | 
					                        const importService = await import('../services/import');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        importService.uploadFiles(node.data.noteId, files, {
 | 
					                        importService.uploadFiles(node.data.noteId, files, {
 | 
				
			||||||
                            safeImport: true,
 | 
					                            safeImport: true,
 | 
				
			||||||
@@ -568,7 +568,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
                    $span.append($refreshSearchButton);
 | 
					                    $span.append($refreshSearchButton);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (!['search', 'launcher'].includes(note.type) && !note.isOptions()) {
 | 
					                if (!['search', 'launcher'].includes(note.type) && !note.isOptions() && !note.isLaunchBarConfig()) {
 | 
				
			||||||
                    const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>');
 | 
					                    const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    $span.append($createChildNoteButton);
 | 
					                    $span.append($createChildNoteButton);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,7 +132,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
 | 
				
			|||||||
        this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
 | 
					        this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (glob.isDev && ENABLE_INSPECTOR) {
 | 
					        if (glob.isDev && ENABLE_INSPECTOR) {
 | 
				
			||||||
            await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js');
 | 
					            await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector');
 | 
				
			||||||
            CKEditorInspector.attach(this.textEditor);
 | 
					            CKEditorInspector.attach(this.textEditor);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,8 @@ const log = require('../../services/log');
 | 
				
			|||||||
const attributeService = require('../../services/attributes');
 | 
					const attributeService = require('../../services/attributes');
 | 
				
			||||||
const Attribute = require('../../becca/entities/attribute');
 | 
					const Attribute = require('../../becca/entities/attribute');
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getEffectiveNoteAttributes(req) {
 | 
					function getEffectiveNoteAttributes(req) {
 | 
				
			||||||
    const note = becca.getNote(req.params.noteId);
 | 
					    const note = becca.getNote(req.params.noteId);
 | 
				
			||||||
@@ -21,11 +23,11 @@ function updateNoteAttribute(req) {
 | 
				
			|||||||
        attribute = becca.getAttribute(body.attributeId);
 | 
					        attribute = becca.getAttribute(body.attributeId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!attribute) {
 | 
					        if (!attribute) {
 | 
				
			||||||
            return [404, `Attribute '${body.attributeId}' does not exist.`];
 | 
					            throw new NotFoundError(`Attribute '${body.attributeId}' does not exist.`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (attribute.noteId !== noteId) {
 | 
					        if (attribute.noteId !== noteId) {
 | 
				
			||||||
            return [400, `Attribute '${body.attributeId}' is not owned by ${noteId}`];
 | 
					            throw new ValidationError(`Attribute '${body.attributeId}' is not owned by ${noteId}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (body.type !== attribute.type
 | 
					        if (body.type !== attribute.type
 | 
				
			||||||
@@ -106,7 +108,7 @@ function deleteNoteAttribute(req) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (attribute) {
 | 
					    if (attribute) {
 | 
				
			||||||
        if (attribute.noteId !== noteId) {
 | 
					        if (attribute.noteId !== noteId) {
 | 
				
			||||||
            return [400, `Attribute ${attributeId} is not owned by ${noteId}`];
 | 
					            throw new ValidationError(`Attribute ${attributeId} is not owned by ${noteId}`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        attribute.markAsDeleted();
 | 
					        attribute.markAsDeleted();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,9 @@ const noteService = require('../../services/notes');
 | 
				
			|||||||
const becca = require('../../becca/becca');
 | 
					const becca = require('../../becca/becca');
 | 
				
			||||||
const TaskContext = require('../../services/task_context');
 | 
					const TaskContext = require('../../services/task_context');
 | 
				
			||||||
const branchService = require("../../services/branches");
 | 
					const branchService = require("../../services/branches");
 | 
				
			||||||
const log = require("../../services/log.js");
 | 
					const log = require("../../services/log");
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Code in this file deals with moving and cloning branches. Relationship between note and parent note is unique
 | 
					 * Code in this file deals with moving and cloning branches. Relationship between note and parent note is unique
 | 
				
			||||||
@@ -22,7 +24,7 @@ function moveBranchToParent(req) {
 | 
				
			|||||||
    const branchToMove = becca.getBranch(branchId);
 | 
					    const branchToMove = becca.getBranch(branchId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!parentBranch || !branchToMove) {
 | 
					    if (!parentBranch || !branchToMove) {
 | 
				
			||||||
        return [400, `One or both branches ${branchId}, ${parentBranchId} have not been found`];
 | 
					        throw new ValidationError(`One or both branches ${branchId}, ${parentBranchId} have not been found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return branchService.moveBranchToBranch(branchToMove, parentBranch, branchId);
 | 
					    return branchService.moveBranchToBranch(branchToMove, parentBranch, branchId);
 | 
				
			||||||
@@ -35,11 +37,11 @@ function moveBranchBeforeNote(req) {
 | 
				
			|||||||
    const beforeBranch = becca.getBranch(beforeBranchId);
 | 
					    const beforeBranch = becca.getBranch(beforeBranchId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!branchToMove) {
 | 
					    if (!branchToMove) {
 | 
				
			||||||
        return [404, `Can't find branch ${branchId}`];
 | 
					        throw new NotFoundError(`Can't find branch '${branchId}'`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!beforeBranch) {
 | 
					    if (!beforeBranch) {
 | 
				
			||||||
        return [404, `Can't find branch ${beforeBranchId}`];
 | 
					        throw new NotFoundError(`Can't find branch '${beforeBranchId}'`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const validationResult = treeService.validateParentChild(beforeBranch.parentNoteId, branchToMove.noteId, branchId);
 | 
					    const validationResult = treeService.validateParentChild(beforeBranch.parentNoteId, branchToMove.noteId, branchId);
 | 
				
			||||||
@@ -193,7 +195,7 @@ function deleteBranch(req) {
 | 
				
			|||||||
    const branch = becca.getBranch(req.params.branchId);
 | 
					    const branch = becca.getBranch(req.params.branchId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!branch) {
 | 
					    if (!branch) {
 | 
				
			||||||
        return [404, `Branch ${req.params.branchId} not found`];
 | 
					        throw new NotFoundError(`Branch '${req.params.branchId}' not found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');
 | 
					    const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ const opmlExportService = require('../../services/export/opml');
 | 
				
			|||||||
const becca = require('../../becca/becca');
 | 
					const becca = require('../../becca/becca');
 | 
				
			||||||
const TaskContext = require("../../services/task_context");
 | 
					const TaskContext = require("../../services/task_context");
 | 
				
			||||||
const log = require("../../services/log");
 | 
					const log = require("../../services/log");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function exportBranch(req, res) {
 | 
					function exportBranch(req, res) {
 | 
				
			||||||
    const {branchId, type, format, version, taskId} = req.params;
 | 
					    const {branchId, type, format, version, taskId} = req.params;
 | 
				
			||||||
@@ -34,11 +35,11 @@ function exportBranch(req, res) {
 | 
				
			|||||||
            opmlExportService.exportToOpml(taskContext, branch, version, res);
 | 
					            opmlExportService.exportToOpml(taskContext, branch, version, res);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            return [404, "Unrecognized export format " + format];
 | 
					            throw new NotFoundError(`Unrecognized export format '${format}'`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    catch (e) {
 | 
					    catch (e) {
 | 
				
			||||||
        const message = "Export failed with following error: '" + e.message + "'. More details might be in the logs.";
 | 
					        const message = `Export failed with following error: '${e.message}'. More details might be in the logs.`;
 | 
				
			||||||
        taskContext.reportError(message);
 | 
					        taskContext.reportError(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.error(message + e.stack);
 | 
					        log.error(message + e.stack);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ const { Readable } = require('stream');
 | 
				
			|||||||
const chokidar = require('chokidar');
 | 
					const chokidar = require('chokidar');
 | 
				
			||||||
const ws = require('../../services/ws');
 | 
					const ws = require('../../services/ws');
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function updateFile(req) {
 | 
					function updateFile(req) {
 | 
				
			||||||
    const {noteId} = req.params;
 | 
					    const {noteId} = req.params;
 | 
				
			||||||
@@ -18,7 +19,7 @@ function updateFile(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} doesn't exist.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note.saveNoteRevision();
 | 
					    note.saveNoteRevision();
 | 
				
			||||||
@@ -116,7 +117,7 @@ function saveToTmpDir(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404,`Note ${noteId} doesn't exist.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tmpObj = tmp.fileSync({postfix: getFilename(note)});
 | 
					    const tmpObj = tmp.fileSync({postfix: getFilename(note)});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,8 @@ const imageService = require('../../services/image');
 | 
				
			|||||||
const becca = require('../../becca/becca');
 | 
					const becca = require('../../becca/becca');
 | 
				
			||||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
					const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function returnImage(req, res) {
 | 
					function returnImage(req, res) {
 | 
				
			||||||
    const image = becca.getNote(req.params.noteId);
 | 
					    const image = becca.getNote(req.params.noteId);
 | 
				
			||||||
@@ -51,11 +53,11 @@ function uploadImage(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} doesn't exist.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!["image/png", "image/jpg", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
 | 
					    if (!["image/png", "image/jpg", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
 | 
				
			||||||
        return [400, "Unknown image type: " + file.mimetype];
 | 
					        throw new ValidationError(`Unknown image type: ${file.mimetype}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true, true);
 | 
					    const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true, true);
 | 
				
			||||||
@@ -73,7 +75,7 @@ function updateImage(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} doesn't exist.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
 | 
					    if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,8 @@ const becca = require('../../becca/becca');
 | 
				
			|||||||
const beccaLoader = require('../../becca/becca_loader');
 | 
					const beccaLoader = require('../../becca/becca_loader');
 | 
				
			||||||
const log = require('../../services/log');
 | 
					const log = require('../../services/log');
 | 
				
			||||||
const TaskContext = require('../../services/task_context');
 | 
					const TaskContext = require('../../services/task_context');
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function importToBranch(req) {
 | 
					async function importToBranch(req) {
 | 
				
			||||||
    const {parentNoteId} = req.params;
 | 
					    const {parentNoteId} = req.params;
 | 
				
			||||||
@@ -27,13 +29,13 @@ async function importToBranch(req) {
 | 
				
			|||||||
    const file = req.file;
 | 
					    const file = req.file;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!file) {
 | 
					    if (!file) {
 | 
				
			||||||
        return [400, "No file has been uploaded"];
 | 
					        throw new ValidationError("No file has been uploaded");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const parentNote = becca.getNote(parentNoteId);
 | 
					    const parentNote = becca.getNote(parentNoteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!parentNote) {
 | 
					    if (!parentNote) {
 | 
				
			||||||
        return [404, `Note ${parentNoteId} doesn't exist.`];
 | 
					        throw new NotFoundError(`Note '${parentNoteId}' doesn't exist.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const extension = path.extname(file.originalname).toLowerCase();
 | 
					    const extension = path.extname(file.originalname).toLowerCase();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
const { JSDOM } = require("jsdom");
 | 
					const { JSDOM } = require("jsdom");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function buildDescendantCountMap() {
 | 
					function buildDescendantCountMap() {
 | 
				
			||||||
    const noteIdToCountMap = {};
 | 
					    const noteIdToCountMap = {};
 | 
				
			||||||
@@ -326,7 +327,7 @@ function getBacklinkCount(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, "Not found"];
 | 
					        throw new NotFoundError(`Note '${noteId}' not found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
@@ -340,7 +341,7 @@ function getBacklinks(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} was not found`];
 | 
					        throw new NotFoundError(`Note '${noteId}' was not found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let backlinksWithExcerptCount = 0;
 | 
					    let backlinksWithExcerptCount = 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,16 +6,17 @@ const sql = require('../../services/sql');
 | 
				
			|||||||
const utils = require('../../services/utils');
 | 
					const utils = require('../../services/utils');
 | 
				
			||||||
const log = require('../../services/log');
 | 
					const log = require('../../services/log');
 | 
				
			||||||
const TaskContext = require('../../services/task_context');
 | 
					const TaskContext = require('../../services/task_context');
 | 
				
			||||||
const protectedSessionService = require('../../services/protected_session');
 | 
					 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNote(req) {
 | 
					function getNote(req) {
 | 
				
			||||||
    const noteId = req.params.noteId;
 | 
					    const noteId = req.params.noteId;
 | 
				
			||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, "Note " + noteId + " has not been found."];
 | 
					        throw new NotFoundError(`Note '${noteId}' has not been found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const pojo = note.getPojo();
 | 
					    const pojo = note.getPojo();
 | 
				
			||||||
@@ -197,11 +198,11 @@ function changeTitle(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note '${noteId}' has not been found`];
 | 
					        throw new NotFoundError(`Note '${noteId}' has not been found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note.isContentAvailable()) {
 | 
					    if (!note.isContentAvailable()) {
 | 
				
			||||||
        return [400, `Note '${noteId}' is not available for change`];
 | 
					        throw new ValidationError(`Note '${noteId}' is not available for change`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const noteTitleChanged = note.title !== title;
 | 
					    const noteTitleChanged = note.title !== title;
 | 
				
			||||||
@@ -290,7 +291,7 @@ function uploadModifiedFile(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note '${noteId}' has not been found`];
 | 
					        throw new NotFoundError(`Note '${noteId}' has not been found`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    log.info(`Updating note '${noteId}' with content from ${filePath}`);
 | 
					    log.info(`Updating note '${noteId}' with content from ${filePath}`);
 | 
				
			||||||
@@ -300,7 +301,7 @@ function uploadModifiedFile(req) {
 | 
				
			|||||||
    const fileContent = fs.readFileSync(filePath);
 | 
					    const fileContent = fs.readFileSync(filePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!fileContent) {
 | 
					    if (!fileContent) {
 | 
				
			||||||
        return [400, `File ${fileContent} is empty`];
 | 
					        throw new ValidationError(`File '${fileContent}' is empty`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note.setContent(fileContent);
 | 
					    note.setContent(fileContent);
 | 
				
			||||||
@@ -311,11 +312,11 @@ function forceSaveNoteRevision(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} not found.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' not found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note.isContentAvailable()) {
 | 
					    if (!note.isContentAvailable()) {
 | 
				
			||||||
        return [400, `Note revision of a protected note cannot be created outside of a protected session.`];
 | 
					        throw new ValidationError(`Note revision of a protected note cannot be created outside of a protected session.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note.saveNoteRevision();
 | 
					    note.saveNoteRevision();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
const optionService = require('../../services/options');
 | 
					const optionService = require('../../services/options');
 | 
				
			||||||
const log = require('../../services/log');
 | 
					const log = require('../../services/log');
 | 
				
			||||||
const searchService = require('../../services/search/services/search');
 | 
					const searchService = require('../../services/search/services/search');
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// options allowed to be updated directly in options dialog
 | 
					// options allowed to be updated directly in options dialog
 | 
				
			||||||
const ALLOWED_OPTIONS = new Set([
 | 
					const ALLOWED_OPTIONS = new Set([
 | 
				
			||||||
@@ -82,7 +83,7 @@ function updateOption(req) {
 | 
				
			|||||||
    const {name, value} = req.params;
 | 
					    const {name, value} = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!update(name, value)) {
 | 
					    if (!update(name, value)) {
 | 
				
			||||||
        return [400, "not allowed option to change"];
 | 
					        throw new ValidationError("not allowed option to change");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const passwordService = require('../../services/password');
 | 
					const passwordService = require('../../services/password');
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function changePassword(req) {
 | 
					function changePassword(req) {
 | 
				
			||||||
    if (passwordService.isPasswordSet()) {
 | 
					    if (passwordService.isPasswordSet()) {
 | 
				
			||||||
@@ -14,7 +15,7 @@ function changePassword(req) {
 | 
				
			|||||||
function resetPassword(req) {
 | 
					function resetPassword(req) {
 | 
				
			||||||
    // protection against accidental call (not a security measure)
 | 
					    // protection against accidental call (not a security measure)
 | 
				
			||||||
    if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") {
 | 
					    if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") {
 | 
				
			||||||
        return [400, "Incorrect password reset confirmation"];
 | 
					        throw new ValidationError("Incorrect password reset confirmation");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return passwordService.resetPassword();
 | 
					    return passwordService.resetPassword();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,12 +6,14 @@ const searchService = require('../../services/search/services/search');
 | 
				
			|||||||
const bulkActionService = require("../../services/bulk_actions");
 | 
					const bulkActionService = require("../../services/bulk_actions");
 | 
				
			||||||
const cls = require("../../services/cls");
 | 
					const cls = require("../../services/cls");
 | 
				
			||||||
const {formatAttrForSearch} = require("../../services/attribute_formatter");
 | 
					const {formatAttrForSearch} = require("../../services/attribute_formatter");
 | 
				
			||||||
 | 
					const ValidationError = require("../../errors/validation_error");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function searchFromNote(req) {
 | 
					function searchFromNote(req) {
 | 
				
			||||||
    const note = becca.getNote(req.params.noteId);
 | 
					    const note = becca.getNote(req.params.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${req.params.noteId} has not been found.`];
 | 
					        throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (note.isDeleted) {
 | 
					    if (note.isDeleted) {
 | 
				
			||||||
@@ -20,7 +22,7 @@ function searchFromNote(req) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (note.type !== 'search') {
 | 
					    if (note.type !== 'search') {
 | 
				
			||||||
        return [400, `Note ${req.params.noteId} is not a search note.`]
 | 
					        throw new ValidationError(`Note '${req.params.noteId}' is not a search note.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return searchService.searchFromNote(note);
 | 
					    return searchService.searchFromNote(note);
 | 
				
			||||||
@@ -30,16 +32,16 @@ function searchAndExecute(req) {
 | 
				
			|||||||
    const note = becca.getNote(req.params.noteId);
 | 
					    const note = becca.getNote(req.params.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${req.params.noteId} has not been found.`];
 | 
					        throw new NotFoundError(`Note '${req.params.noteId}' has not been found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (note.isDeleted) {
 | 
					    if (note.isDeleted) {
 | 
				
			||||||
        // this can be triggered from recent changes and it's harmless to return empty list rather than fail
 | 
					        // this can be triggered from recent changes, and it's harmless to return empty list rather than fail
 | 
				
			||||||
        return [];
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (note.type !== 'search') {
 | 
					    if (note.type !== 'search') {
 | 
				
			||||||
        return [400, `Note ${req.params.noteId} is not a search note.`]
 | 
					        throw new ValidationError(`Note '${req.params.noteId}' is not a search note.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {searchResultNoteIds} = searchService.searchFromNote(note);
 | 
					    const {searchResultNoteIds} = searchService.searchFromNote(note);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const similarityService = require('../../becca/similarity');
 | 
					const similarityService = require('../../becca/similarity');
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function getSimilarNotes(req) {
 | 
					async function getSimilarNotes(req) {
 | 
				
			||||||
    const noteId = req.params.noteId;
 | 
					    const noteId = req.params.noteId;
 | 
				
			||||||
@@ -9,7 +10,7 @@ async function getSimilarNotes(req) {
 | 
				
			|||||||
    const note = becca.getNote(noteId);
 | 
					    const note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} not found.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' not found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return await similarityService.findSimilarNotes(noteId);
 | 
					    return await similarityService.findSimilarNotes(noteId);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const sql = require('../../services/sql');
 | 
					const sql = require('../../services/sql');
 | 
				
			||||||
const becca = require("../../becca/becca");
 | 
					const becca = require("../../becca/becca");
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSchema() {
 | 
					function getSchema() {
 | 
				
			||||||
    const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
 | 
					    const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
 | 
				
			||||||
@@ -21,7 +22,7 @@ function execute(req) {
 | 
				
			|||||||
    const note = becca.getNote(req.params.noteId);
 | 
					    const note = becca.getNote(req.params.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${req.params.noteId} was not found.`];
 | 
					        throw new NotFoundError(`Note '${req.params.noteId}' was not found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const queries = note.getContent().split("\n---");
 | 
					    const queries = note.getContent().split("\n---");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
const sql = require('../../services/sql');
 | 
					const sql = require('../../services/sql');
 | 
				
			||||||
const becca = require('../../becca/becca');
 | 
					const becca = require('../../becca/becca');
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNoteSize(req) {
 | 
					function getNoteSize(req) {
 | 
				
			||||||
    const {noteId} = req.params;
 | 
					    const {noteId} = req.params;
 | 
				
			||||||
@@ -26,7 +27,7 @@ function getSubtreeSize(req) {
 | 
				
			|||||||
    const note = becca.notes[noteId];
 | 
					    const note = becca.notes[noteId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
        return [404, `Note ${noteId} was not found.`];
 | 
					        throw new NotFoundError(`Note '${noteId}' was not found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const subTreeNoteIds = note.getSubtreeNoteIds();
 | 
					    const subTreeNoteIds = note.getSubtreeNoteIds();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const becca = require('../../becca/becca');
 | 
					const becca = require('../../becca/becca');
 | 
				
			||||||
const log = require('../../services/log');
 | 
					const log = require('../../services/log');
 | 
				
			||||||
 | 
					const NotFoundError = require("../../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNotesAndBranchesAndAttributes(noteIds) {
 | 
					function getNotesAndBranchesAndAttributes(noteIds) {
 | 
				
			||||||
    noteIds = new Set(noteIds);
 | 
					    noteIds = new Set(noteIds);
 | 
				
			||||||
@@ -141,7 +142,7 @@ function getTree(req) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!(subTreeNoteId in becca.notes)) {
 | 
					    if (!(subTreeNoteId in becca.notes)) {
 | 
				
			||||||
        return [404, `Note ${subTreeNoteId} not found in the cache`];
 | 
					        throw new NotFoundError(`Note '${subTreeNoteId}' not found in the cache`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    collect(becca.notes[subTreeNoteId]);
 | 
					    collect(becca.notes[subTreeNoteId]);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ const myScryptService = require('../services/my_scrypt');
 | 
				
			|||||||
const log = require('../services/log');
 | 
					const log = require('../services/log');
 | 
				
			||||||
const passwordService = require("../services/password");
 | 
					const passwordService = require("../services/password");
 | 
				
			||||||
const assetPath = require("../services/asset_path");
 | 
					const assetPath = require("../services/asset_path");
 | 
				
			||||||
 | 
					const ValidationError = require("../errors/validation_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function loginPage(req, res) {
 | 
					function loginPage(req, res) {
 | 
				
			||||||
    res.render('login', {
 | 
					    res.render('login', {
 | 
				
			||||||
@@ -23,7 +24,7 @@ function setPasswordPage(req, res) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function setPassword(req, res) {
 | 
					function setPassword(req, res) {
 | 
				
			||||||
    if (passwordService.isPasswordSet()) {
 | 
					    if (passwordService.isPasswordSet()) {
 | 
				
			||||||
        return [400, "Password has been already set"];
 | 
					        throw new ValidationError("Password has been already set");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let {password1, password2} = req.body;
 | 
					    let {password1, password2} = req.body;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ const loginRoute = require('./login');
 | 
				
			|||||||
const indexRoute = require('./index');
 | 
					const indexRoute = require('./index');
 | 
				
			||||||
const utils = require('../services/utils');
 | 
					const utils = require('../services/utils');
 | 
				
			||||||
const multer = require('multer');
 | 
					const multer = require('multer');
 | 
				
			||||||
 | 
					const ValidationError = require("../errors/validation_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// API routes
 | 
					// API routes
 | 
				
			||||||
const treeApiRoute = require('./api/tree');
 | 
					const treeApiRoute = require('./api/tree');
 | 
				
			||||||
@@ -61,6 +62,7 @@ const csurf = require('csurf');
 | 
				
			|||||||
const {createPartialContentHandler} = require("express-partial-content");
 | 
					const {createPartialContentHandler} = require("express-partial-content");
 | 
				
			||||||
const rateLimit = require("express-rate-limit");
 | 
					const rateLimit = require("express-rate-limit");
 | 
				
			||||||
const AbstractEntity = require("../becca/entities/abstract_entity");
 | 
					const AbstractEntity = require("../becca/entities/abstract_entity");
 | 
				
			||||||
 | 
					const NotFoundError = require("../errors/not_found_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const csrfMiddleware = csurf({
 | 
					const csrfMiddleware = csurf({
 | 
				
			||||||
    cookie: true,
 | 
					    cookie: true,
 | 
				
			||||||
@@ -169,13 +171,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                            log.request(req, res, Date.now() - start, responseLength);
 | 
					                            log.request(req, res, Date.now() - start, responseLength);
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                        .catch(e => {
 | 
					                        .catch(e => handleException(method, path, e, res));
 | 
				
			||||||
                            log.error(`${method} ${path} threw exception: ` + e.stack);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            res.setHeader("Content-Type", "text/plain")
 | 
					 | 
				
			||||||
                                .status(500)
 | 
					 | 
				
			||||||
                                .send(e.message);
 | 
					 | 
				
			||||||
                        });
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else {
 | 
					                else {
 | 
				
			||||||
                    const responseLength = resultHandler(req, res, result);
 | 
					                    const responseLength = resultHandler(req, res, result);
 | 
				
			||||||
@@ -185,13 +181,31 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (e) {
 | 
					        catch (e) {
 | 
				
			||||||
 | 
					            handleException(method, path, e, res);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function handleException(method, path, e, res) {
 | 
				
			||||||
    log.error(`${method} ${path} threw exception: ` + e.stack);
 | 
					    log.error(`${method} ${path} threw exception: ` + e.stack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (e instanceof ValidationError) {
 | 
				
			||||||
 | 
					        res.setHeader("Content-Type", "application/json")
 | 
				
			||||||
 | 
					            .status(400)
 | 
				
			||||||
 | 
					            .send({
 | 
				
			||||||
 | 
					                message: e.message
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    } if (e instanceof NotFoundError) {
 | 
				
			||||||
 | 
					        res.setHeader("Content-Type", "application/json")
 | 
				
			||||||
 | 
					            .status(404)
 | 
				
			||||||
 | 
					            .send({
 | 
				
			||||||
 | 
					                message: e.message
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
        res.setHeader("Content-Type", "text/plain")
 | 
					        res.setHeader("Content-Type", "text/plain")
 | 
				
			||||||
            .status(500)
 | 
					            .status(500)
 | 
				
			||||||
            .send(e.message);
 | 
					            .send(e.message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MAX_ALLOWED_FILE_SIZE_MB = 250;
 | 
					const MAX_ALLOWED_FILE_SIZE_MB = 250;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,7 @@ module.exports = [
 | 
				
			|||||||
    { type: 'label', name: 'template' },
 | 
					    { type: 'label', name: 'template' },
 | 
				
			||||||
    { type: 'label', name: 'toc' },
 | 
					    { type: 'label', name: 'toc' },
 | 
				
			||||||
    { type: 'label', name: 'color' },
 | 
					    { type: 'label', name: 'color' },
 | 
				
			||||||
 | 
					    { type: 'label', name: 'keepCurrentHoisting'},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // relation names
 | 
					    // relation names
 | 
				
			||||||
    { type: 'relation', name: 'internalLink' },
 | 
					    { type: 'relation', name: 'internalLink' },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
const becca = require("../becca/becca");
 | 
					const becca = require("../becca/becca");
 | 
				
			||||||
const noteService = require("./notes");
 | 
					const noteService = require("./notes");
 | 
				
			||||||
const log = require("./log.js");
 | 
					const log = require("./log");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const LBTPL_ROOT = "lbTplRoot";
 | 
					const LBTPL_ROOT = "lbTplRoot";
 | 
				
			||||||
const LBTPL_BASE = "lbTplBase";
 | 
					const LBTPL_BASE = "lbTplBase";
 | 
				
			||||||
@@ -36,7 +36,8 @@ const HIDDEN_SUBTREE_DEFINITION = {
 | 
				
			|||||||
            title: 'Note Map',
 | 
					            title: 'Note Map',
 | 
				
			||||||
            type: 'noteMap',
 | 
					            type: 'noteMap',
 | 
				
			||||||
            attributes: [
 | 
					            attributes: [
 | 
				
			||||||
                { type: 'label', name: 'mapRootId', value: 'hoisted' }
 | 
					                { type: 'label', name: 'mapRootNoteId', value: 'hoisted' },
 | 
				
			||||||
 | 
					                { type: 'label', name: 'keepCurrentHoisting' }
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -56,6 +57,12 @@ const HIDDEN_SUBTREE_DEFINITION = {
 | 
				
			|||||||
            title: 'Bulk action',
 | 
					            title: 'Bulk action',
 | 
				
			||||||
            type: 'doc',
 | 
					            type: 'doc',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // place for user scripts hidden stuff (scripts should not create notes directly under hidden root)
 | 
				
			||||||
 | 
					            id: 'userHidden',
 | 
				
			||||||
 | 
					            title: 'User Hidden',
 | 
				
			||||||
 | 
					            type: 'text',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            id: LBTPL_ROOT,
 | 
					            id: LBTPL_ROOT,
 | 
				
			||||||
            title: 'Launch Bar Templates',
 | 
					            title: 'Launch Bar Templates',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,8 @@ const Branch = require('../becca/entities/branch');
 | 
				
			|||||||
const Note = require('../becca/entities/note');
 | 
					const Note = require('../becca/entities/note');
 | 
				
			||||||
const Attribute = require('../becca/entities/attribute');
 | 
					const Attribute = require('../becca/entities/attribute');
 | 
				
			||||||
const dayjs = require("dayjs");
 | 
					const dayjs = require("dayjs");
 | 
				
			||||||
const htmlSanitizer = require("./html_sanitizer.js");
 | 
					const htmlSanitizer = require("./html_sanitizer");
 | 
				
			||||||
 | 
					const ValidationError = require("../errors/validation_error");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNewNotePosition(parentNoteId) {
 | 
					function getNewNotePosition(parentNoteId) {
 | 
				
			||||||
    const note = becca.notes[parentNoteId];
 | 
					    const note = becca.notes[parentNoteId];
 | 
				
			||||||
@@ -107,15 +108,15 @@ function getAndValidateParent(params) {
 | 
				
			|||||||
    const parentNote = becca.notes[params.parentNoteId];
 | 
					    const parentNote = becca.notes[params.parentNoteId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!parentNote) {
 | 
					    if (!parentNote) {
 | 
				
			||||||
        throw new Error(`Parent note "${params.parentNoteId}" not found.`);
 | 
					        throw new ValidationError(`Parent note "${params.parentNoteId}" not found.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (parentNote.type === 'launcher') {
 | 
					    if (parentNote.type === 'launcher' && parentNote.noteId !== 'lbBookmarks') {
 | 
				
			||||||
        throw new Error(`Launchers should not have child notes.`);
 | 
					        throw new ValidationError(`Creating child notes into launcher notes is not allowed.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!params.ignoreForbiddenParents && (parentNote.isLaunchBarConfig() || parentNote.isOptions())) {
 | 
					    if (!params.ignoreForbiddenParents && (['lbRoot', 'hidden'].includes(parentNote.noteId) || parentNote.isOptions())) {
 | 
				
			||||||
        throw new Error(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
 | 
					        throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return parentNote;
 | 
					    return parentNote;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ const PropertyComparisonExp = require('../expressions/property_comparison');
 | 
				
			|||||||
const AttributeExistsExp = require('../expressions/attribute_exists');
 | 
					const AttributeExistsExp = require('../expressions/attribute_exists');
 | 
				
			||||||
const LabelComparisonExp = require('../expressions/label_comparison');
 | 
					const LabelComparisonExp = require('../expressions/label_comparison');
 | 
				
			||||||
const NoteFlatTextExp = require('../expressions/note_flat_text');
 | 
					const NoteFlatTextExp = require('../expressions/note_flat_text');
 | 
				
			||||||
const NoteContentFulltextExp = require('../expressions/note_content_fulltext.js');
 | 
					const NoteContentFulltextExp = require('../expressions/note_content_fulltext');
 | 
				
			||||||
const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
 | 
					const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
 | 
				
			||||||
const AncestorExp = require("../expressions/ancestor");
 | 
					const AncestorExp = require("../expressions/ancestor");
 | 
				
			||||||
const buildComparator = require('./build_comparator');
 | 
					const buildComparator = require('./build_comparator');
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ const becca = require('../../../becca/becca');
 | 
				
			|||||||
const beccaService = require('../../../becca/becca_service');
 | 
					const beccaService = require('../../../becca/becca_service');
 | 
				
			||||||
const utils = require('../../utils');
 | 
					const utils = require('../../utils');
 | 
				
			||||||
const log = require('../../log');
 | 
					const log = require('../../log');
 | 
				
			||||||
const scriptService = require("../../script.js");
 | 
					const scriptService = require("../../script");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function searchFromNote(note) {
 | 
					function searchFromNote(note) {
 | 
				
			||||||
    let searchResultNoteIds, highlightedTokens;
 | 
					    let searchResultNoteIds, highlightedTokens;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ const becca = require("../becca/becca");
 | 
				
			|||||||
const noteService = require("./notes");
 | 
					const noteService = require("./notes");
 | 
				
			||||||
const cls = require("./cls");
 | 
					const cls = require("./cls");
 | 
				
			||||||
const dateUtils = require("./date_utils");
 | 
					const dateUtils = require("./date_utils");
 | 
				
			||||||
const log = require("./log.js");
 | 
					const log = require("./log");
 | 
				
			||||||
const hiddenSubtreeService = require("./hidden_subtree");
 | 
					const hiddenSubtreeService = require("./hidden_subtree");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getInboxNote(date) {
 | 
					function getInboxNote(date) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,7 @@ const ws = require('./ws');
 | 
				
			|||||||
const taskContexts = {};
 | 
					const taskContexts = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TaskContext {
 | 
					class TaskContext {
 | 
				
			||||||
    constructor(taskId, taskType, data) {
 | 
					    constructor(taskId, taskType, data = null) {
 | 
				
			||||||
        this.taskId = taskId;
 | 
					        this.taskId = taskId;
 | 
				
			||||||
        this.taskType = taskType;
 | 
					        this.taskType = taskType;
 | 
				
			||||||
        this.data = data;
 | 
					        this.data = data;
 | 
				
			||||||
@@ -24,7 +24,7 @@ class TaskContext {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @returns {TaskContext} */
 | 
					    /** @returns {TaskContext} */
 | 
				
			||||||
    static getInstance(taskId, taskType, data) {
 | 
					    static getInstance(taskId, taskType, data = null) {
 | 
				
			||||||
        if (!taskContexts[taskId]) {
 | 
					        if (!taskContexts[taskId]) {
 | 
				
			||||||
            taskContexts[taskId] = new TaskContext(taskId, taskType, data);
 | 
					            taskContexts[taskId] = new TaskContext(taskId, taskType, data);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,11 @@ function init(httpServer, sessionParser) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    webSocketServer.on('error', error => {
 | 
				
			||||||
 | 
					        // https://github.com/zadam/trilium/issues/3374#issuecomment-1341053765
 | 
				
			||||||
 | 
					        console.log(error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function sendMessage(client, message) {
 | 
					function sendMessage(client, message) {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user