Merge branch 'master' into next57

# Conflicts:
#	package-lock.json
This commit is contained in:
zadam 2022-10-23 21:13:09 +02:00
commit 12943cc808
13 changed files with 123 additions and 61 deletions

View file

@ -1,6 +1,13 @@
#!/bin/sh
if wget --spider -S "127.0.0.1:8080/api/health-check" 2>&1 | awk 'NR==2' | grep -w "HTTP/1.1 200 OK" ; then
exit 0
else
exit 1
fi
# Try connecting to /api/health-check using both http and https.
# TODO: we should only be connecting with the actual protocol that is enabled
# TODO: this assumes we use the default port 8080
for proto in http https; do
if wget --spider -S "$proto://127.0.0.1:8080/api/health-check" 2>&1 | awk 'NR==2' | grep -w "HTTP/1.1 200 OK" ; then
exit 0
fi
done
exit 1

View file

@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.56.0-beta",
"version": "0.56.1",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@ -21,9 +21,9 @@
"build-frontend-docs": "rm -r ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/collapsible_widget.js",
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
"webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js",
"test": "jasmine",
"test-jasmine": "jasmine",
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
"test-all": "npm run test && npm run test-es6",
"test": "npm run test-jasmine && npm run test-es6",
"postinstall": "rimraf ./node_modules/canvas"
},
"dependencies": {

View file

@ -210,7 +210,7 @@ const ATTR_HELP = {
"workspaceTemplate": "This note will appear in the selection of available template when creating new note, but only when hoisted into a workspace containing this template",
"searchHome": "new search notes will be created as children of this note",
"hoistedSearchHome": "new search notes will be created as children of this note when hoisted to some ancestor of this note",
"inbox": "default inbox location for new notes",
"inbox": "default inbox location for new notes - when you create a note using \"new note\" button in the sidebar, notes will be created as child notes in the note marked as with <code>#inbox</code> label.",
"hoistedInbox": "default inbox location for new notes when hoisted to some ancestor of this note",
"sqlConsoleHome": "default location of SQL console notes",
"bookmarked": "note with this label will appear in bookmarks",
@ -240,15 +240,15 @@ const ATTR_HELP = {
"keyboardShortcut": "Defines a keyboard shortcut which will immediately jump to this note. Example: 'ctrl+alt+e'. Requires frontend reload for the change to take effect."
},
"relation": {
"runOnNoteCreation": "executes when note is created on backend",
"runOnNoteCreation": "executes when note is created on backend. Use this relation if you want to run the script for all notes created under a specific subtree. In that case, create it on the subtree root note and make it inheritable. A new note created within the subtree (any depth) will trigger the script.",
"runOnChildNoteCreation": "executes when new note is created under the note where this relation is defined",
"runOnNoteTitleChange": "executes when note title is changed (includes note creation as well)",
"runOnNoteChange": "executes when note is changed (includes note creation as well)",
"runOnNoteDeletion": "executes when note is being deleted",
"runOnBranchCreation": "executes when a branch is created. Branch is a link between parent note and child note and is created e.g. when cloning or moving note.",
"runOnBranchDeletion": "executes when a branch is deleted. Branch is a link between parent note and child note and is deleted e.g. when moving note (old branch/link is deleted).",
"runOnChildNoteCreation": "executes when new note is created under this note",
"runOnAttributeCreation": "executes when new attribute is created under this note",
"runOnAttributeChange": "executes when attribute is changed under this note",
"runOnAttributeCreation": "executes when new attribute is created for the note which defines this relation",
"runOnAttributeChange": " executes when the attribute is changed of a note which defines this relation. This is triggered also when the attribute is deleted",
"template": "attached note's attributes will be inherited even without parent-child relationship. See template for details.",
"renderNote": 'notes of type "render HTML note" will be rendered using a code note (HTML or script) and it is necessary to point using this relation to which note should be rendered',
"widget": "target of this relation will be executed and rendered as a widget in the sidebar",

View file

@ -86,7 +86,7 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
this.clearItems();
// can't use froca since that would count only relations from loaded notes
const resp = await server.get(`notes/${this.noteId}/backlink-count`);
const resp = await server.get(`note-map/${this.noteId}/backlink-count`);
if (!resp || !resp.count) {
this.toggle(false);

View file

@ -50,21 +50,25 @@ export default class AbstractTextTypeWidget extends TypeWidget {
const note = await froca.getNote(noteId);
if (note) {
const $wrapper = $('<div class="include-note-wrapper">');
const $link = await linkService.createNoteLink(note.noteId, {
showTooltip: false
});
$el.empty().append(
$wrapper.empty().append(
$('<h4 class="include-note-title">')
.append($link)
);
const {$renderedContent, type} = await noteContentRenderer.getRenderedContent(note);
$el.append(
$wrapper.append(
$(`<div class="include-note-content type-${type}">`)
.append($renderedContent)
);
$el.empty().append($wrapper);
}
}

View file

@ -69,7 +69,7 @@ export default class EmptyTypeWidget extends TypeWidget {
}
async doRefresh(note) {
const workspaceNotes = await searchService.searchForNotes('#workspace');
const workspaceNotes = await searchService.searchForNotes('#workspace #!template');
this.$workspaceNotes.empty();

View file

@ -287,6 +287,27 @@ function findExcerpts(sourceNote, referencedNoteId) {
return excerpts;
}
function getFilteredBacklinks(note) {
return note.getTargetRelations()
// search notes have "ancestor" relations which are not interesting
.filter(note => note.getNote().type !== 'search');
}
function getBacklinkCount(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
if (!note) {
return [404, "Not found"];
}
else {
return {
count: getFilteredBacklinks(note).length
};
}
}
function getBacklinks(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
@ -295,11 +316,9 @@ function getBacklinks(req) {
return [404, `Note ${noteId} was not found`];
}
let backlinks = note.getTargetRelations();
let backlinksWithExcerptCount = 0;
return backlinks.filter(note => !note.getNote().hasLabel('excludeFromNoteMap')).map(backlink => {
return getFilteredBacklinks(note).map(backlink => {
const sourceNote = backlink.note;
if (sourceNote.type !== 'text' || backlinksWithExcerptCount > 50) {
@ -323,5 +342,6 @@ function getBacklinks(req) {
module.exports = {
getLinkMap,
getTreeMap,
getBacklinkCount,
getBacklinks
};

View file

@ -305,21 +305,6 @@ function uploadModifiedFile(req) {
note.setContent(fileContent);
}
function getBacklinkCount(req) {
const {noteId} = req.params;
const note = becca.getNote(noteId);
if (!note) {
return [404, "Not found"];
}
else {
return {
count: note.getTargetRelations().filter(note => !note.getNote().hasLabel('excludeFromNoteMap')).length
};
}
}
module.exports = {
getNote,
updateNoteContent,
@ -334,6 +319,5 @@ module.exports = {
duplicateSubtree,
eraseDeletedNotesNow,
getDeleteNotesPreview,
uploadModifiedFile,
getBacklinkCount
uploadModifiedFile
};

View file

@ -264,7 +264,6 @@ function register(app) {
apiRoute(DELETE, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.eraseNoteRevision);
route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision);
apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision);
apiRoute(GET, '/api/notes/:noteId/backlink-count', notesApiRoute.getBacklinkCount);
apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap);
apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow);
apiRoute(PUT, '/api/notes/:noteId/title', notesApiRoute.changeTitle);
@ -306,6 +305,7 @@ function register(app) {
apiRoute(POST, '/api/note-map/:noteId/tree', noteMapRoute.getTreeMap);
apiRoute(POST, '/api/note-map/:noteId/link', noteMapRoute.getLinkMap);
apiRoute(GET, '/api/note-map/:noteId/backlink-count', noteMapRoute.getBacklinkCount);
apiRoute(GET, '/api/note-map/:noteId/backlinks', noteMapRoute.getBacklinks);
apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote);

View file

@ -1 +1 @@
module.exports = { buildDate:"2022-10-15T12:49:56+02:00", buildRevision: "70c929241391a465a1e818dcb41af30dd176bc34" };
module.exports = { buildDate:"2022-10-22T23:07:24+02:00", buildRevision: "c1127ec4293143e9ff6080cbe9c14a8366a132bd" };

View file

@ -40,6 +40,10 @@ function disableEntityEvents() {
namespace.set('disableEntityEvents', true);
}
function enableEntityEvents() {
namespace.set('disableEntityEvents', false);
}
function isEntityEventsDisabled() {
return !!namespace.get('disableEntityEvents');
}
@ -83,6 +87,7 @@ module.exports = {
getComponentId,
getLocalNowDateTime,
disableEntityEvents,
enableEntityEvents,
isEntityEventsDisabled,
reset,
getAndClearEntityChangeIds,

View file

@ -33,10 +33,6 @@ function getNewNotePosition(parentNoteId) {
return maxNotePos + 10;
}
function triggerChildNoteCreated(childNote, parentNote) {
eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote, parentNote });
}
function triggerNoteTitleChanged(note) {
eventService.emit(eventService.NOTE_TITLE_CHANGED, note);
}
@ -140,24 +136,43 @@ function createNewNote(params) {
}
return sql.transactional(() => {
const note = new Note({
noteId: params.noteId, // optionally can force specific noteId
title: params.title,
isProtected: !!params.isProtected,
type: params.type,
mime: deriveMime(params.type, params.mime)
}).save();
let note, branch, isEntityEventsDisabled;
note.setContent(params.content);
try {
isEntityEventsDisabled = cls.isEntityEventsDisabled();
const branch = new Branch({
branchId: params.branchId,
noteId: note.noteId,
parentNoteId: params.parentNoteId,
notePosition: params.notePosition !== undefined ? params.notePosition : getNewNotePosition(params.parentNoteId),
prefix: params.prefix,
isExpanded: !!params.isExpanded
}).save();
if (!isEntityEventsDisabled) {
// it doesn't make sense to run note creation events on a partially constructed note, so
// defer them until note creation is completed
cls.disableEntityEvents();
}
note = new Note({
noteId: params.noteId, // optionally can force specific noteId
title: params.title,
isProtected: !!params.isProtected,
type: params.type,
mime: deriveMime(params.type, params.mime)
}).save();
note.setContent(params.content);
branch = new Branch({
branchId: params.branchId,
noteId: note.noteId,
parentNoteId: params.parentNoteId,
notePosition: params.notePosition !== undefined ? params.notePosition : getNewNotePosition(params.parentNoteId),
prefix: params.prefix,
isExpanded: !!params.isExpanded
}).save();
}
finally {
if (!isEntityEventsDisabled) {
// re-enable entity events only if there were previously enabled
// (they can be disabled in case of import)
cls.enableEntityEvents();
}
}
scanForLinks(note);
@ -175,7 +190,21 @@ function createNewNote(params) {
}
triggerNoteTitleChanged(note);
triggerChildNoteCreated(note, parentNote);
eventService.emit(eventService.ENTITY_CREATED, {
entityName: 'notes',
entity: note
});
eventService.emit(eventService.ENTITY_CREATED, {
entityName: 'branches',
entity: branch
});
eventService.emit(eventService.CHILD_NOTE_CREATED, {
childNote: note,
parentNote: parentNote
});
log.info(`Created new note '${note.noteId}', branch '${branch.branchId}' of type '${note.type}', mime '${note.mime}'`);

View file

@ -39,12 +39,25 @@ function getFulltext(tokens, searchContext) {
}
}
const OPERATORS = [
"=",
"!=",
"*=*",
"*=",
"=*",
">",
">=",
"<",
"<=",
"%="
];
function isOperator(token) {
if (Array.isArray(token)) {
return false;
}
return token.token.match(/^[!=<>*%]+$/);
return OPERATORS.includes(token.token);
}
function getExpression(tokens, searchContext, level = 0) {