Merge branch 'master' into next58

# Conflicts:
#	libraries/ckeditor/ckeditor.js
#	libraries/ckeditor/ckeditor.js.map
#	package-lock.json
#	package.json
#	src/public/app/layouts/desktop_layout.js
This commit is contained in:
zadam
2022-11-22 20:46:08 +01:00
141 changed files with 17696 additions and 14852 deletions

View File

@@ -191,6 +191,11 @@ function deleteBranch(req) {
const last = req.query.last === 'true';
const eraseNotes = req.query.eraseNotes === 'true';
const branch = becca.getBranch(req.params.branchId);
if (!branch) {
return [404, `Branch ${req.params.branchId} not found`];
}
const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');
const deleteId = utils.randomString(10);

View File

@@ -37,7 +37,7 @@ function getNeighbors(note, depth) {
const retNoteIds = [];
function isIgnoredRelation(relation) {
return ['relationMapLink', 'template', 'image'].includes(relation.name);
return ['relationMapLink', 'template', 'image', 'ancestor'].includes(relation.name);
}
// forward links
@@ -83,16 +83,28 @@ function getNeighbors(note, depth) {
function getLinkMap(req) {
const mapRootNote = becca.getNote(req.params.noteId);
// if the map root itself has ignore (journal typically) then there wouldn't be anything to display so
// if the map root itself has exclude attribute (journal typically) then there wouldn't be anything to display, so
// we'll just ignore it
const ignoreExcludeFromNoteMap = mapRootNote.hasLabel('excludeFromNoteMap');
let unfilteredNotes;
if (mapRootNote.type === 'search') {
// for search notes we want to consider the direct search results only without the descendants
unfilteredNotes = mapRootNote.getSearchResultNotes();
} else {
unfilteredNotes = mapRootNote.getSubtree({includeArchived: false, resolveSearch: true}).notes;
}
const noteIds = new Set(
mapRootNote.getSubtreeNotes(false)
unfilteredNotes
.filter(note => ignoreExcludeFromNoteMap || !note.hasLabel('excludeFromNoteMap'))
.map(note => note.noteId)
);
if (mapRootNote.type === 'search') {
noteIds.delete(mapRootNote.noteId);
}
for (const noteId of getNeighbors(mapRootNote, 3)) {
noteIds.add(noteId);
}
@@ -123,7 +135,7 @@ function getLinkMap(req) {
return true;
}
})
.map(rel => ({
.map(rel => ({
id: rel.noteId + "-" + rel.name + "-" + rel.value,
sourceNoteId: rel.noteId,
targetNoteId: rel.value,
@@ -142,8 +154,9 @@ function getTreeMap(req) {
// if the map root itself has ignore (journal typically) then there wouldn't be anything to display so
// we'll just ignore it
const ignoreExcludeFromNoteMap = mapRootNote.hasLabel('excludeFromNoteMap');
const subtree = mapRootNote.getSubtree({includeArchived: false, resolveSearch: true});
const notes = mapRootNote.getSubtreeNotes(false)
const notes = subtree.notes
.filter(note => ignoreExcludeFromNoteMap || !note.hasLabel('excludeFromNoteMap'))
.filter(note => {
if (note.type !== 'image' || note.getChildNotes().length > 0) {
@@ -158,7 +171,6 @@ function getTreeMap(req) {
return !note.getParentNotes().find(parentNote => parentNote.noteId === imageLinkRelation.noteId);
})
.concat(...mapRootNote.getParentNotes().filter(note => note.noteId !== 'none'))
.map(note => [
note.noteId,
note.getTitleOrProtected(),
@@ -170,25 +182,40 @@ function getTreeMap(req) {
const links = [];
for (const branch of Object.values(becca.branches)) {
if (!noteIds.has(branch.parentNoteId) || !noteIds.has(branch.noteId)) {
for (const {parentNoteId, childNoteId} of subtree.relationships) {
if (!noteIds.has(parentNoteId) || !noteIds.has(childNoteId)) {
continue;
}
links.push({
id: branch.branchId,
sourceNoteId: branch.parentNoteId,
targetNoteId: branch.noteId
sourceNoteId: parentNoteId,
targetNoteId: childNoteId
});
}
const noteIdToDescendantCountMap = buildDescendantCountMap();
updateDescendantCountMapForSearch(noteIdToDescendantCountMap, subtree.relationships);
return {
notes: notes,
noteIdToDescendantCountMap: buildDescendantCountMap(),
noteIdToDescendantCountMap: noteIdToDescendantCountMap,
links: links
};
}
function updateDescendantCountMapForSearch(noteIdToDescendantCountMap, relationships) {
for (const {parentNoteId, childNoteId} of relationships) {
const parentNote = becca.notes[parentNoteId];
if (!parentNote || parentNote.type !== 'search') {
continue;
}
noteIdToDescendantCountMap[parentNote.noteId] = noteIdToDescendantCountMap[parentNoteId] || 0;
noteIdToDescendantCountMap[parentNote.noteId] += noteIdToDescendantCountMap[childNoteId] || 1;
}
}
function removeImages(document) {
const images = document.getElementsByTagName('img');
while (images.length > 0) {
@@ -290,7 +317,7 @@ function findExcerpts(sourceNote, referencedNoteId) {
function getFilteredBacklinks(note) {
return note.getTargetRelations()
// search notes have "ancestor" relations which are not interesting
.filter(note => note.getNote().type !== 'search');
.filter(relation => !!relation.getNote() && relation.getNote().type !== 'search');
}
function getBacklinkCount(req) {

View File

@@ -5,6 +5,7 @@ const protectedSessionService = require('../../services/protected_session');
const noteRevisionService = require('../../services/note_revisions');
const utils = require('../../services/utils');
const sql = require('../../services/sql');
const cls = require('../../services/cls');
const path = require('path');
const becca = require("../../becca/becca");
@@ -124,8 +125,15 @@ function getEditedNotesOnDate(req) {
ORDER BY isDeleted
LIMIT 50`, {date: req.params.date + '%'});
const notes = becca.getNotes(noteIds, true)
.map(note => note.getPojo());
let notes = becca.getNotes(noteIds, true);
// Narrow down the results if a note is hoisted, similar to "Jump to note".
const hoistedNoteId = cls.getHoistedNoteId();
if (hoistedNoteId !== 'root') {
notes = notes.filter(note => note.hasAncestor(hoistedNoteId));
}
notes = notes.map(note => note.getPojo());
for (const note of notes) {
const notePath = note.isDeleted ? null : beccaService.getNotePath(note.noteId);

View File

@@ -6,6 +6,7 @@ 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");
@@ -305,6 +306,21 @@ function uploadModifiedFile(req) {
note.setContent(fileContent);
}
function forceSaveNoteRevision(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} not found.`];
}
if (!note.isContentAvailable()) {
return [400, `Note revision of a protected note cannot be created outside of a protected session.`];
}
note.saveNoteRevision();
}
module.exports = {
getNote,
updateNoteContent,
@@ -319,5 +335,6 @@ module.exports = {
duplicateSubtree,
eraseDeletedNotesNow,
getDeleteNotesPreview,
uploadModifiedFile
uploadModifiedFile,
forceSaveNoteRevision
};

View File

@@ -33,6 +33,7 @@ const ALLOWED_OPTIONS = new Set([
'editedNotesWidget',
'calendarWidget',
'vimKeymapEnabled',
'codeLineWrapEnabled',
'codeNotesMimeTypes',
'spellCheckEnabled',
'spellCheckLanguageCode',
@@ -58,7 +59,8 @@ const ALLOWED_OPTIONS = new Set([
'compressImages',
'downloadImagesAutomatically',
'minTocHeadings',
'checkForUpdates'
'checkForUpdates',
'disableTray'
]);
function getOptions() {

View File

@@ -2,49 +2,11 @@
const becca = require('../../becca/becca');
const SearchContext = require('../../services/search/search_context');
const log = require('../../services/log');
const scriptService = require('../../services/script');
const searchService = require('../../services/search/services/search');
const bulkActionService = require("../../services/bulk_actions");
const cls = require("../../services/cls");
const {formatAttrForSearch} = require("../../services/attribute_formatter");
function searchFromNoteInt(note) {
let searchResultNoteIds, highlightedTokens;
const searchScript = note.getRelationValue('searchScript');
const searchString = note.getLabelValue('searchString');
if (searchScript) {
searchResultNoteIds = searchFromRelation(note, 'searchScript');
highlightedTokens = [];
} else {
const searchContext = new SearchContext({
fastSearch: note.hasLabel('fastSearch'),
ancestorNoteId: note.getRelationValue('ancestor'),
ancestorDepth: note.getLabelValue('ancestorDepth'),
includeArchivedNotes: note.hasLabel('includeArchivedNotes'),
orderBy: note.getLabelValue('orderBy'),
orderDirection: note.getLabelValue('orderDirection'),
limit: note.getLabelValue('limit'),
debug: note.hasLabel('debug'),
fuzzyAttributeSearch: false
});
searchResultNoteIds = searchService.findResultsWithQuery(searchString, searchContext)
.map(sr => sr.noteId);
highlightedTokens = searchContext.highlightedTokens;
}
// we won't return search note's own noteId
// also don't allow root since that would force infinite cycle
return {
searchResultNoteIds: searchResultNoteIds.filter(resultNoteId => !['root', note.noteId].includes(resultNoteId)),
highlightedTokens
};
}
function searchFromNote(req) {
const note = becca.getNote(req.params.noteId);
@@ -61,7 +23,7 @@ function searchFromNote(req) {
return [400, `Note ${req.params.noteId} is not a search note.`]
}
return searchFromNoteInt(note);
return searchService.searchFromNote(note);
}
function searchAndExecute(req) {
@@ -80,48 +42,11 @@ function searchAndExecute(req) {
return [400, `Note ${req.params.noteId} is not a search note.`]
}
const {searchResultNoteIds} = searchFromNoteInt(note);
const {searchResultNoteIds} = searchService.searchFromNote(note);
bulkActionService.executeActions(note, searchResultNoteIds);
}
function searchFromRelation(note, relationName) {
const scriptNote = note.getRelationTarget(relationName);
if (!scriptNote) {
log.info(`Search note's relation ${relationName} has not been found.`);
return [];
}
if (!scriptNote.isJavaScript() || scriptNote.getScriptEnv() !== 'backend') {
log.info(`Note ${scriptNote.noteId} is not executable.`);
return [];
}
if (!note.isContentAvailable()) {
log.info(`Note ${scriptNote.noteId} is not available outside of protected session.`);
return [];
}
const result = scriptService.executeNote(scriptNote, { originEntity: note });
if (!Array.isArray(result)) {
log.info(`Result from ${scriptNote.noteId} is not an array.`);
return [];
}
if (result.length === 0) {
return [];
}
// we expect either array of noteIds (strings) or notes, in that case we extract noteIds ourselves
return typeof result[0] === 'string' ? result : result.map(item => item.noteId);
}
function quickSearch(req) {
const {searchString} = req.params;

View File

@@ -29,7 +29,7 @@ function getSubtreeSize(req) {
return [404, `Note ${noteId} was not found.`];
}
const subTreeNoteIds = note.getSubtreeNotes().map(note => note.noteId);
const subTreeNoteIds = note.getSubtreeNoteIds();
sql.fillParamList(subTreeNoteIds);