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
61 changed files with 1895 additions and 1737 deletions

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