Merge remote-tracking branch 'origin/next58' into next58

# Conflicts:
#	src/public/app/components/note_context.js
This commit is contained in:
zadam 2022-12-10 14:37:02 +01:00
commit 1f468f81cc
61 changed files with 1895 additions and 1737 deletions

View File

@ -30,6 +30,7 @@ rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db $BUILD_DIR/
rm -rf $BUILD_DIR/dump-db/node_modules
cp bin/tpl/trilium-portable.sh $BUILD_DIR/
chmod 755 $BUILD_DIR/trilium-portable.sh

View File

@ -26,6 +26,7 @@ mv "./dist/Trilium Notes-darwin-x64" $BUILD_DIR
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
cp -r dump-db $BUILD_DIR/
rm -rf $BUILD_DIR/dump-db/node_modules
echo "Zipping mac x64 electron distribution..."

View File

@ -31,6 +31,7 @@ chmod 755 $PKG_DIR/trilium.sh
cp bin/tpl/anonymize-database.sql $PKG_DIR/
cp -r dump-db $PKG_DIR/
rm -rf $PKG_DIR/dump-db/node_modules
VERSION=`jq -r ".version" package.json`

View File

@ -28,6 +28,7 @@ rm -r $BUILD_DIR/swiftshader
cp bin/tpl/anonymize-database.sql $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/

View File

@ -14,9 +14,6 @@ mkdir $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 libraries $DIR/
cp -r src $DIR/

View File

@ -1,5 +1,5 @@
module.exports = () => {
const hiddenSubtreeService = require('../../src/services/hidden_subtree.js');
const hiddenSubtreeService = require('../../src/services/hidden_subtree');
const cls = require("../../src/services/cls");
const beccaLoader = require("../../src/becca/becca_loader");

View File

@ -2,7 +2,7 @@
const yargs = require('yargs/yargs')
const { hideBin } = require('yargs/helpers')
const dumpService = require("./inc/dump.js");
const dumpService = require("./inc/dump");
yargs(hideBin(process.argv))
.command('$0 <path_to_document> <target_directory>', 'dump the contents of document.db into the target directory', (yargs) => {

View File

@ -1,6 +1,6 @@
const crypto = require("crypto");
const sql = require("./sql.js");
const decryptService = require("./decrypt.js");
const sql = require("./sql");
const decryptService = require("./decrypt");
function getDataKey(password) {
if (!password) {

View File

@ -1,9 +1,9 @@
const fs = require("fs");
const sanitize = require("sanitize-filename");
const sql = require("./sql.js");
const decryptService = require("./decrypt.js");
const dataKeyService = require("./data_key.js");
const extensionService = require("./extension.js");
const sql = require("./sql");
const decryptService = require("./decrypt");
const dataKeyService = require("./data_key");
const extensionService = require("./extension");
function dumpDocument(documentPath, targetPath, options) {
const stats = {

View 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.

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

1
package-lock.json generated
View File

@ -5,7 +5,6 @@
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.57.3",
"hasInstallScript": true,
"license": "AGPL-3.0-only",

View File

@ -4,7 +4,7 @@ const Branch = require('../../src/becca/entities/branch');
const SearchContext = require('../../src/services/search/search_context');
const dateUtils = require('../../src/services/date_utils');
const becca = require('../../src/becca/becca');
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking');
describe("Search", () => {
let rootNote;

View File

@ -1,4 +1,4 @@
const {note} = require('./becca_mocking.js');
const {note} = require('./becca_mocking');
const ValueExtractor = require('../../src/services/search/value_extractor');
const becca = require('../../src/becca/becca');
const SearchContext = require("../../src/services/search/search_context");

View File

@ -1,6 +1,6 @@
"use strict";
const becca = require('./becca.js');
const becca = require('./becca');
const cls = require('../services/cls');
const protectedSessionService = require('../services/protected_session');
const log = require('../services/log');

View File

@ -4,7 +4,7 @@ const Note = require('./note');
const AbstractEntity = require("./abstract_entity");
const sql = require("../../services/sql");
const dateUtils = require("../../services/date_utils");
const utils = require("../../services/utils.js");
const utils = require("../../services/utils");
const TaskContext = require("../../services/task_context");
const cls = require("../../services/cls");
const log = require("../../services/log");

View File

@ -1329,7 +1329,7 @@ class Note extends AbstractEntity {
}
isLaunchBarConfig() {
return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'];
return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
}
isOptions() {

View File

@ -1,6 +1,6 @@
const becca = require('./becca');
const log = require('../services/log');
const beccaService = require('./becca_service.js');
const beccaService = require('./becca_service');
const dateUtils = require('../services/date_utils');
const { JSDOM } = require("jsdom");

View File

@ -0,0 +1,7 @@
class NotFoundError {
constructor(message) {
this.message = message;
}
}
module.exports = NotFoundError;

View File

@ -0,0 +1,7 @@
class ValidationError {
constructor(message) {
this.message = message;
}
}
module.exports = ValidationError;

View File

@ -1,5 +1,5 @@
const appInfo = require('../services/app_info');
const eu = require("./etapi_utils.js");
const eu = require("./etapi_utils");
function register(router) {
eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {

View File

@ -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
let hoistedNoteId = 'hidden';

View File

@ -828,7 +828,7 @@ class NoteShort {
}
isLaunchBarConfig() {
return this.type === 'launcher' || ['lbRoot', 'lbAvailableShortcuts', 'lbVisibleShortcuts'].includes(this.noteId);
return this.type === 'launcher' || ['lbRoot', 'lbAvailableLaunchers', 'lbVisibleLaunchers'].includes(this.noteId);
}
isOptions() {

View File

@ -90,27 +90,27 @@ function getNotePathFromLink($link) {
return url ? getNotePathFromUrl(url) : null;
}
function goToLink(e) {
const $link = $(e.target).closest("a,.block-link");
function goToLink(evt) {
const $link = $(evt.target).closest("a,.block-link");
const address = $link.attr('href');
if (address?.startsWith("data:")) {
return true;
}
e.preventDefault();
e.stopPropagation();
evt.preventDefault();
evt.stopPropagation();
const notePath = getNotePathFromLink($link);
const ctrlKey = (!utils.isMac() && e.ctrlKey) || (utils.isMac() && e.metaKey);
const ctrlKey = utils.isCtrlKey(evt);
if (notePath) {
if ((e.which === 1 && ctrlKey) || e.which === 2) {
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath);
}
else if (e.which === 1) {
const ntxId = $(e.target).closest("[data-ntx-id]").attr("data-ntx-id");
else if (evt.which === 1) {
const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");
const noteContext = ntxId
? appContext.tabManager.getNoteContextById(ntxId)
@ -124,7 +124,7 @@ function goToLink(e) {
}
}
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.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
) {

View File

@ -1,4 +1,5 @@
import utils from './utils.js';
import ValidationError from "./validation_error.js";
const REQUEST_LOGGING_ENABLED = false;
@ -102,10 +103,15 @@ async function call(method, url, data, headers = {}) {
return resp.body;
}
async function reportError(method, url, status, error) {
const message = "Error when calling " + method + " " + url + ": " + status + " - " + error;
async function reportError(method, url, status, response) {
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.throwError(message);
}

View File

@ -60,6 +60,11 @@ function isMac() {
return navigator.platform.indexOf('Mac') > -1;
}
function isCtrlKey(evt) {
return (!isMac() && evt.ctrlKey)
|| (isMac() && evt.metaKey);
}
function assertArguments() {
for (const i in arguments) {
if (!arguments[i]) {
@ -362,6 +367,7 @@ export default {
now,
isElectron,
isMac,
isCtrlKey,
assertArguments,
escapeHtml,
stopWatch,

View File

@ -0,0 +1,7 @@
export default class ValidationError {
constructor(resp) {
for (const key in resp) {
this[key] = resp[key];
}
}
}

View File

@ -357,7 +357,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
// disable spellcheck for attribute editor
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);
}

View File

@ -1,23 +1,55 @@
import AbstractLauncher from "./abstract_launcher.js";
import dialogService from "../../../services/dialog.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 {
constructor(launcherNote) {
super(launcherNote);
this.title(this.launcherNote.title)
.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;
}
linkContextMenuService.openContextMenu(targetNoteId, evt);
});
}
launch() {
// 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 (but major) downside is more work in setting up the typical case where you actually want to have both title and icon in sync.
launch(evt) {
const targetNoteId = this.getTargetNoteId();
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');
if (!targetNoteId) {
@ -25,7 +57,7 @@ export default class NoteLauncher extends AbstractLauncher {
return;
}
appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId, true);
return targetNoteId;
}
getTitle() {

View File

@ -13,10 +13,23 @@ export default class OnClickButtonWidget extends AbstractButtonWidget {
} else {
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) {
this.settings.onClick = handler;
return this;
}
onAuxClick(handler) {
this.settings.onAuxClick = handler;
return this;
}
}

View File

@ -452,7 +452,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
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, {
safeImport: true,
@ -568,7 +568,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
$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>');
$span.append($createChildNoteButton);

View File

@ -132,7 +132,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget {
this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
if (glob.isDev && ENABLE_INSPECTOR) {
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector.js');
await import(/* webpackIgnore: true */'../../../libraries/ckeditor/inspector');
CKEditorInspector.attach(this.textEditor);
}
}

View File

@ -5,6 +5,8 @@ const log = require('../../services/log');
const attributeService = require('../../services/attributes');
const Attribute = require('../../becca/entities/attribute');
const becca = require("../../becca/becca");
const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function getEffectiveNoteAttributes(req) {
const note = becca.getNote(req.params.noteId);
@ -21,11 +23,11 @@ function updateNoteAttribute(req) {
attribute = becca.getAttribute(body.attributeId);
if (!attribute) {
return [404, `Attribute '${body.attributeId}' does not exist.`];
throw new NotFoundError(`Attribute '${body.attributeId}' does not exist.`);
}
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
@ -106,7 +108,7 @@ function deleteNoteAttribute(req) {
if (attribute) {
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();

View File

@ -8,7 +8,9 @@ const noteService = require('../../services/notes');
const becca = require('../../becca/becca');
const TaskContext = require('../../services/task_context');
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
@ -22,7 +24,7 @@ function moveBranchToParent(req) {
const branchToMove = becca.getBranch(branchId);
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);
@ -35,11 +37,11 @@ function moveBranchBeforeNote(req) {
const beforeBranch = becca.getBranch(beforeBranchId);
if (!branchToMove) {
return [404, `Can't find branch ${branchId}`];
throw new NotFoundError(`Can't find branch '${branchId}'`);
}
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);
@ -193,7 +195,7 @@ function deleteBranch(req) {
const branch = becca.getBranch(req.params.branchId);
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');

View File

@ -6,6 +6,7 @@ const opmlExportService = require('../../services/export/opml');
const becca = require('../../becca/becca');
const TaskContext = require("../../services/task_context");
const log = require("../../services/log");
const NotFoundError = require("../../errors/not_found_error");
function exportBranch(req, res) {
const {branchId, type, format, version, taskId} = req.params;
@ -34,11 +35,11 @@ function exportBranch(req, res) {
opmlExportService.exportToOpml(taskContext, branch, version, res);
}
else {
return [404, "Unrecognized export format " + format];
throw new NotFoundError(`Unrecognized export format '${format}'`);
}
}
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);
log.error(message + e.stack);

View File

@ -10,6 +10,7 @@ const { Readable } = require('stream');
const chokidar = require('chokidar');
const ws = require('../../services/ws');
const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error");
function updateFile(req) {
const {noteId} = req.params;
@ -18,7 +19,7 @@ function updateFile(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} doesn't exist.`];
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
note.saveNoteRevision();
@ -116,7 +117,7 @@ function saveToTmpDir(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404,`Note ${noteId} doesn't exist.`];
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
}
const tmpObj = tmp.fileSync({postfix: getFilename(note)});

View File

@ -4,6 +4,8 @@ const imageService = require('../../services/image');
const becca = require('../../becca/becca');
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
const fs = require('fs');
const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function returnImage(req, res) {
const image = becca.getNote(req.params.noteId);
@ -51,11 +53,11 @@ function uploadImage(req) {
const note = becca.getNote(noteId);
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)) {
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);
@ -73,7 +75,7 @@ function updateImage(req) {
const note = becca.getNote(noteId);
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)) {

View File

@ -10,6 +10,8 @@ const becca = require('../../becca/becca');
const beccaLoader = require('../../becca/becca_loader');
const log = require('../../services/log');
const TaskContext = require('../../services/task_context');
const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
async function importToBranch(req) {
const {parentNoteId} = req.params;
@ -27,13 +29,13 @@ async function importToBranch(req) {
const file = req.file;
if (!file) {
return [400, "No file has been uploaded"];
throw new ValidationError("No file has been uploaded");
}
const parentNote = becca.getNote(parentNoteId);
if (!parentNote) {
return [404, `Note ${parentNoteId} doesn't exist.`];
throw new NotFoundError(`Note '${parentNoteId}' doesn't exist.`);
}
const extension = path.extname(file.originalname).toLowerCase();

View File

@ -2,6 +2,7 @@
const becca = require("../../becca/becca");
const { JSDOM } = require("jsdom");
const NotFoundError = require("../../errors/not_found_error");
function buildDescendantCountMap() {
const noteIdToCountMap = {};
@ -326,7 +327,7 @@ function getBacklinkCount(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, "Not found"];
throw new NotFoundError(`Note '${noteId}' not found`);
}
else {
return {
@ -340,7 +341,7 @@ function getBacklinks(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} was not found`];
throw new NotFoundError(`Note '${noteId}' was not found`);
}
let backlinksWithExcerptCount = 0;

View File

@ -6,16 +6,17 @@ const sql = require('../../services/sql');
const utils = require('../../services/utils');
const log = require('../../services/log');
const TaskContext = require('../../services/task_context');
const protectedSessionService = require('../../services/protected_session');
const fs = require('fs');
const becca = require("../../becca/becca");
const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function getNote(req) {
const noteId = req.params.noteId;
const note = becca.getNote(noteId);
if (!note) {
return [404, "Note " + noteId + " has not been found."];
throw new NotFoundError(`Note '${noteId}' has not been found.`);
}
const pojo = note.getPojo();
@ -197,11 +198,11 @@ function changeTitle(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note '${noteId}' has not been found`];
throw new NotFoundError(`Note '${noteId}' has not been found`);
}
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;
@ -290,7 +291,7 @@ function uploadModifiedFile(req) {
const note = becca.getNote(noteId);
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}`);
@ -300,7 +301,7 @@ function uploadModifiedFile(req) {
const fileContent = fs.readFileSync(filePath);
if (!fileContent) {
return [400, `File ${fileContent} is empty`];
throw new ValidationError(`File '${fileContent}' is empty`);
}
note.setContent(fileContent);
@ -311,11 +312,11 @@ function forceSaveNoteRevision(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} not found.`];
throw new NotFoundError(`Note '${noteId}' not found.`);
}
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();

View File

@ -3,6 +3,7 @@
const optionService = require('../../services/options');
const log = require('../../services/log');
const searchService = require('../../services/search/services/search');
const ValidationError = require("../../errors/validation_error");
// options allowed to be updated directly in options dialog
const ALLOWED_OPTIONS = new Set([
@ -82,7 +83,7 @@ function updateOption(req) {
const {name, value} = req.params;
if (!update(name, value)) {
return [400, "not allowed option to change"];
throw new ValidationError("not allowed option to change");
}
}

View File

@ -1,6 +1,7 @@
"use strict";
const passwordService = require('../../services/password');
const ValidationError = require("../../errors/validation_error");
function changePassword(req) {
if (passwordService.isPasswordSet()) {
@ -14,7 +15,7 @@ function changePassword(req) {
function resetPassword(req) {
// protection against accidental call (not a security measure)
if (req.query.really !== "yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes") {
return [400, "Incorrect password reset confirmation"];
throw new ValidationError("Incorrect password reset confirmation");
}
return passwordService.resetPassword();

View File

@ -6,12 +6,14 @@ const searchService = require('../../services/search/services/search');
const bulkActionService = require("../../services/bulk_actions");
const cls = require("../../services/cls");
const {formatAttrForSearch} = require("../../services/attribute_formatter");
const ValidationError = require("../../errors/validation_error");
const NotFoundError = require("../../errors/not_found_error");
function searchFromNote(req) {
const note = becca.getNote(req.params.noteId);
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) {
@ -20,7 +22,7 @@ function searchFromNote(req) {
}
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);
@ -30,16 +32,16 @@ function searchAndExecute(req) {
const note = becca.getNote(req.params.noteId);
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) {
// 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 [];
}
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);

View File

@ -2,6 +2,7 @@
const similarityService = require('../../becca/similarity');
const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error");
async function getSimilarNotes(req) {
const noteId = req.params.noteId;
@ -9,7 +10,7 @@ async function getSimilarNotes(req) {
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} not found.`];
throw new NotFoundError(`Note '${noteId}' not found.`);
}
return await similarityService.findSimilarNotes(noteId);

View File

@ -2,6 +2,7 @@
const sql = require('../../services/sql');
const becca = require("../../becca/becca");
const NotFoundError = require("../../errors/not_found_error");
function getSchema() {
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);
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---");

View File

@ -1,5 +1,6 @@
const sql = require('../../services/sql');
const becca = require('../../becca/becca');
const NotFoundError = require("../../errors/not_found_error");
function getNoteSize(req) {
const {noteId} = req.params;
@ -26,7 +27,7 @@ function getSubtreeSize(req) {
const note = becca.notes[noteId];
if (!note) {
return [404, `Note ${noteId} was not found.`];
throw new NotFoundError(`Note '${noteId}' was not found.`);
}
const subTreeNoteIds = note.getSubtreeNoteIds();

View File

@ -2,6 +2,7 @@
const becca = require('../../becca/becca');
const log = require('../../services/log');
const NotFoundError = require("../../errors/not_found_error");
function getNotesAndBranchesAndAttributes(noteIds) {
noteIds = new Set(noteIds);
@ -141,7 +142,7 @@ function getTree(req) {
}
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]);

View File

@ -6,6 +6,7 @@ const myScryptService = require('../services/my_scrypt');
const log = require('../services/log');
const passwordService = require("../services/password");
const assetPath = require("../services/asset_path");
const ValidationError = require("../errors/validation_error");
function loginPage(req, res) {
res.render('login', {
@ -23,7 +24,7 @@ function setPasswordPage(req, res) {
function setPassword(req, res) {
if (passwordService.isPasswordSet()) {
return [400, "Password has been already set"];
throw new ValidationError("Password has been already set");
}
let {password1, password2} = req.body;

View File

@ -5,6 +5,7 @@ const loginRoute = require('./login');
const indexRoute = require('./index');
const utils = require('../services/utils');
const multer = require('multer');
const ValidationError = require("../errors/validation_error");
// API routes
const treeApiRoute = require('./api/tree');
@ -61,6 +62,7 @@ const csurf = require('csurf');
const {createPartialContentHandler} = require("express-partial-content");
const rateLimit = require("express-rate-limit");
const AbstractEntity = require("../becca/entities/abstract_entity");
const NotFoundError = require("../errors/not_found_error");
const csrfMiddleware = csurf({
cookie: true,
@ -169,13 +171,7 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
log.request(req, res, Date.now() - start, responseLength);
})
.catch(e => {
log.error(`${method} ${path} threw exception: ` + e.stack);
res.setHeader("Content-Type", "text/plain")
.status(500)
.send(e.message);
});
.catch(e => handleException(method, path, e, res));
}
else {
const responseLength = resultHandler(req, res, result);
@ -185,15 +181,33 @@ function route(method, path, middleware, routeHandler, resultHandler, transactio
}
}
catch (e) {
log.error(`${method} ${path} threw exception: ` + e.stack);
res.setHeader("Content-Type", "text/plain")
.status(500)
.send(e.message);
handleException(method, path, e, res);
}
});
}
function handleException(method, path, e, res) {
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")
.status(500)
.send(e.message);
}
}
const MAX_ALLOWED_FILE_SIZE_MB = 250;
const GET = 'get', POST = 'post', PUT = 'put', PATCH = 'patch', DELETE = 'delete';

View File

@ -60,6 +60,7 @@ module.exports = [
{ type: 'label', name: 'template' },
{ type: 'label', name: 'toc' },
{ type: 'label', name: 'color' },
{ type: 'label', name: 'keepCurrentHoisting'},
// relation names
{ type: 'relation', name: 'internalLink' },

View File

@ -1,6 +1,6 @@
const becca = require("../becca/becca");
const noteService = require("./notes");
const log = require("./log.js");
const log = require("./log");
const LBTPL_ROOT = "lbTplRoot";
const LBTPL_BASE = "lbTplBase";
@ -36,7 +36,8 @@ const HIDDEN_SUBTREE_DEFINITION = {
title: 'Note Map',
type: 'noteMap',
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',
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,
title: 'Launch Bar Templates',

View File

@ -18,7 +18,8 @@ const Branch = require('../becca/entities/branch');
const Note = require('../becca/entities/note');
const Attribute = require('../becca/entities/attribute');
const dayjs = require("dayjs");
const htmlSanitizer = require("./html_sanitizer.js");
const htmlSanitizer = require("./html_sanitizer");
const ValidationError = require("../errors/validation_error");
function getNewNotePosition(parentNoteId) {
const note = becca.notes[parentNoteId];
@ -107,15 +108,15 @@ function getAndValidateParent(params) {
const parentNote = becca.notes[params.parentNoteId];
if (!parentNote) {
throw new Error(`Parent note "${params.parentNoteId}" not found.`);
throw new ValidationError(`Parent note "${params.parentNoteId}" not found.`);
}
if (parentNote.type === 'launcher') {
throw new Error(`Launchers should not have child notes.`);
if (parentNote.type === 'launcher' && parentNote.noteId !== 'lbBookmarks') {
throw new ValidationError(`Creating child notes into launcher notes is not allowed.`);
}
if (!params.ignoreForbiddenParents && (parentNote.isLaunchBarConfig() || parentNote.isOptions())) {
throw new Error(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
if (!params.ignoreForbiddenParents && (['lbRoot', 'hidden'].includes(parentNote.noteId) || parentNote.isOptions())) {
throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`);
}
return parentNote;

View File

@ -12,7 +12,7 @@ const PropertyComparisonExp = require('../expressions/property_comparison');
const AttributeExistsExp = require('../expressions/attribute_exists');
const LabelComparisonExp = require('../expressions/label_comparison');
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 AncestorExp = require("../expressions/ancestor");
const buildComparator = require('./build_comparator');

View File

@ -10,7 +10,7 @@ const becca = require('../../../becca/becca');
const beccaService = require('../../../becca/becca_service');
const utils = require('../../utils');
const log = require('../../log');
const scriptService = require("../../script.js");
const scriptService = require("../../script");
function searchFromNote(note) {
let searchResultNoteIds, highlightedTokens;

View File

@ -4,7 +4,7 @@ const becca = require("../becca/becca");
const noteService = require("./notes");
const cls = require("./cls");
const dateUtils = require("./date_utils");
const log = require("./log.js");
const log = require("./log");
const hiddenSubtreeService = require("./hidden_subtree");
function getInboxNote(date) {

View File

@ -6,7 +6,7 @@ const ws = require('./ws');
const taskContexts = {};
class TaskContext {
constructor(taskId, taskType, data) {
constructor(taskId, taskType, data = null) {
this.taskId = taskId;
this.taskType = taskType;
this.data = data;
@ -24,7 +24,7 @@ class TaskContext {
}
/** @returns {TaskContext} */
static getInstance(taskId, taskType, data) {
static getInstance(taskId, taskType, data = null) {
if (!taskContexts[taskId]) {
taskContexts[taskId] = new TaskContext(taskId, taskType, data);
}

View File

@ -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) {