diff --git a/config-sample.ini b/config-sample.ini index 0e8da0360..d419ed473 100644 --- a/config-sample.ini +++ b/config-sample.ini @@ -16,7 +16,7 @@ noBackup=false # host=0.0.0.0 # port setting is relevant only for web deployments, desktop builds run on a fixed port (changeable with TRILIUM_PORT environment variable) port=8080 -# true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure). +# true for TLS/SSL/HTTPS (secure), false for HTTP (insecure). https=false # path to certificate (run "bash bin/generate-cert.sh" to generate self-signed certificate). Relevant only if https=true certPath= diff --git a/docker_healthcheck.js b/docker_healthcheck.js index 9213c401f..586b3a700 100755 --- a/docker_healthcheck.js +++ b/docker_healthcheck.js @@ -4,7 +4,7 @@ const fs = require("fs"); const dataDir = require("./src/services/data_dir"); const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8')); -if (config.https) { +if (config.Network.https) { // built-in TLS (terminated by trilium) is not supported yet, PRs are welcome // for reverse proxy terminated TLS this will works since config.https will be false process.exit(0); diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index c9d437e6c..ec7314b30 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -667,7 +667,7 @@ class BNote extends AbstractBeccaEntity { return this.ownedAttributes.filter(attr => attr.name === name); } else { - return this.ownedAttributes.slice(); + return this.ownedAttributes; } } diff --git a/src/becca/similarity.js b/src/becca/similarity.js index 61bd02f45..b0ed39e13 100644 --- a/src/becca/similarity.js +++ b/src/becca/similarity.js @@ -367,7 +367,7 @@ async function findSimilarNotes(noteId) { * We want to improve the standing of notes which have been created in similar time to each other since * there's a good chance they are related. * - * But there's an exception - if they were created really close to each other (withing few seconds) then + * But there's an exception - if they were created really close to each other (within few seconds) then * they are probably part of the import and not created by hand - these OTOH should not benefit. */ const {utcDateCreated} = candidateNote; diff --git a/src/etapi/etapi.openapi.yaml b/src/etapi/etapi.openapi.yaml index 2c6ca0d0d..b7de68b32 100644 --- a/src/etapi/etapi.openapi.yaml +++ b/src/etapi/etapi.openapi.yaml @@ -231,7 +231,7 @@ paths: schema: $ref: '#/components/schemas/EntityId' get: - description: Returns note content idenfied by its ID + description: Returns note content identified by its ID operationId: getNoteContent responses: '200': @@ -241,7 +241,7 @@ paths: schema: type: string put: - description: Updates note content idenfied by its ID + description: Updates note content identified by its ID operationId: putNoteContentById requestBody: description: html content of note diff --git a/src/public/app/services/attribute_autocomplete.js b/src/public/app/services/attribute_autocomplete.js index bd0bc3af2..761d5dbeb 100644 --- a/src/public/app/services/attribute_autocomplete.js +++ b/src/public/app/services/attribute_autocomplete.js @@ -41,8 +41,8 @@ function initAttributeNameAutocomplete({ $el, attributeType, open }) { async function initLabelValueAutocomplete({ $el, open, nameCallback }) { if ($el.hasClass("aa-input")) { - // we reinit everytime because autocomplete seems to have a bug where it retains state from last - // open even though the value was resetted + // we reinit every time because autocomplete seems to have a bug where it retains state from last + // open even though the value was reset $el.autocomplete('destroy'); } diff --git a/src/public/app/services/note_autocomplete.js b/src/public/app/services/note_autocomplete.js index 891f0ebb3..c27ae85d2 100644 --- a/src/public/app/services/note_autocomplete.js +++ b/src/public/app/services/note_autocomplete.js @@ -133,7 +133,7 @@ function initNoteAutocomplete($el, options) { showRecentNotes($el); // this will cause the click not give focus to the "show recent notes" button - // this is important because otherwise input will lose focus immediatelly and not show the results + // this is important because otherwise input will lose focus immediately and not show the results return false; }); diff --git a/src/public/app/services/note_create.js b/src/public/app/services/note_create.js index 28013eb46..8b8fb1a36 100644 --- a/src/public/app/services/note_create.js +++ b/src/public/app/services/note_create.js @@ -99,7 +99,7 @@ function parseSelectedHtml(selectedHtml) { if (dom.length > 0 && dom[0].tagName && dom[0].tagName.match(/h[1-6]/i)) { const title = $(dom[0]).text(); - // remove the title from content (only first occurence) + // remove the title from content (only first occurrence) const content = selectedHtml.replace(dom[0].outerHTML, ""); return [title, content]; diff --git a/src/public/app/services/note_list_renderer.js b/src/public/app/services/note_list_renderer.js index c619cada2..a60f8c731 100644 --- a/src/public/app/services/note_list_renderer.js +++ b/src/public/app/services/note_list_renderer.js @@ -161,7 +161,7 @@ class NoteListRenderer { constructor($parent, parentNote, noteIds, showNotePath = false) { this.$noteList = $(TPL); - // note list must be added to the DOM immediatelly, otherwise some functionality scripting (canvas) won't work + // note list must be added to the DOM immediately, otherwise some functionality scripting (canvas) won't work $parent.empty(); this.parentNote = parentNote; diff --git a/src/public/app/services/server.js b/src/public/app/services/server.js index 33b167c0e..48dece334 100644 --- a/src/public/app/services/server.js +++ b/src/public/app/services/server.js @@ -21,7 +21,7 @@ async function getHeaders(headers) { } if (utils.isElectron()) { - // passing it explicitely here because of the electron HTTP bypass + // passing it explicitly here because of the electron HTTP bypass allHeaders.cookie = document.cookie; } diff --git a/src/public/app/share.js b/src/public/app/share.js index f92ae09ed..03ad92515 100644 --- a/src/public/app/share.js +++ b/src/public/app/share.js @@ -1,7 +1,7 @@ /** * Fetch note with given ID from backend * - * @param noteId of the given note to be fetched. If falsy, fetches current note. + * @param noteId of the given note to be fetched. If false, fetches current note. */ async function fetchNote(noteId = null) { if (!noteId) { diff --git a/src/public/app/widgets/bulk_actions/abstract_bulk_action.js b/src/public/app/widgets/bulk_actions/abstract_bulk_action.js index 21c2860c8..6bb01d256 100644 --- a/src/public/app/widgets/bulk_actions/abstract_bulk_action.js +++ b/src/public/app/widgets/bulk_actions/abstract_bulk_action.js @@ -26,7 +26,7 @@ export default class AbstractBulkAction { } } - // to be overriden + // to be overridden doRender() {} async saveAction(data) { diff --git a/src/public/app/widgets/buttons/right_dropdown_button.js b/src/public/app/widgets/buttons/right_dropdown_button.js index 14c95acaa..d3b20a40e 100644 --- a/src/public/app/widgets/buttons/right_dropdown_button.js +++ b/src/public/app/widgets/buttons/right_dropdown_button.js @@ -50,7 +50,7 @@ export default class RightDropdownButtonWidget extends BasicWidget { this.$widget.find(".dropdown-menu").append(this.$dropdownContent); } - // to be overriden + // to be overridden async dropdownShow() {} hideDropdown() { diff --git a/src/public/app/widgets/dialogs/delete_notes.js b/src/public/app/widgets/dialogs/delete_notes.js index c9953fe82..8f091971e 100644 --- a/src/public/app/widgets/dialogs/delete_notes.js +++ b/src/public/app/widgets/dialogs/delete_notes.js @@ -25,7 +25,7 @@ const TPL = `
-
`; - -export default class HighlightedTextOptions extends OptionsWidget { - doRender() { - this.$widget = $(TPL); - this.$hlt = this.$widget.find("input.highlighted-text-check"); - this.$hlt.on('change', () => { - const hltVals = this.$widget.find('input.highlighted-text-check[type="checkbox"]:checked').map(function () { - return this.value; - }).get(); - this.updateOption('highlightedText', JSON.stringify(hltVals)); - }); - } - - async optionsLoaded(options) { - const hltVals = JSON.parse(options.highlightedText); - this.$widget.find('input.highlighted-text-check[type="checkbox"]').each(function () { - if ($.inArray($(this).val(), hltVals) !== -1) { - $(this).prop("checked", true); - } else { - $(this).prop("checked", false); - } - }); - } -} diff --git a/src/public/app/widgets/type_widgets/options/text_notes/highlights_list.js b/src/public/app/widgets/type_widgets/options/text_notes/highlights_list.js new file mode 100644 index 000000000..5e2a2595d --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/text_notes/highlights_list.js @@ -0,0 +1,40 @@ +import OptionsWidget from "../options_widget.js"; + +const TPL = ` +
+

Highlights List

+ +

You can customize the highlights list displayed in the right panel:

+ +
+ + + + + + +`; + +export default class HighlightsListOptions extends OptionsWidget { + doRender() { + this.$widget = $(TPL); + this.$hlt = this.$widget.find("input.highlights-list-check"); + this.$hlt.on('change', () => { + const hltVals = this.$widget.find('input.highlights-list-check[type="checkbox"]:checked').map(function () { + return this.value; + }).get(); + this.updateOption('highlightsList', JSON.stringify(hltVals)); + }); + } + + async optionsLoaded(options) { + const hltVals = JSON.parse(options.highlightsList); + this.$widget.find('input.highlights-list-check[type="checkbox"]').each(function () { + if ($.inArray($(this).val(), hltVals) !== -1) { + $(this).prop("checked", true); + } else { + $(this).prop("checked", false); + } + }); + } +} diff --git a/src/public/app/widgets/type_widgets/relation_map.js b/src/public/app/widgets/type_widgets/relation_map.js index abce1c4c3..beb16ff07 100644 --- a/src/public/app/widgets/type_widgets/relation_map.js +++ b/src/public/app/widgets/type_widgets/relation_map.js @@ -412,7 +412,7 @@ export default class RelationMapTypeWidget extends TypeWidget { } }); - // if there's no event, then this has been triggered programatically + // if there's no event, then this has been triggered programmatically if (!originalEvent) { return; } diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index d792ff38a..9452c08c5 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -1,6 +1,7 @@ "use strict"; const attributeService = require("../../services/attributes"); +const cloneService = require("../../services/cloning"); const noteService = require('../../services/notes'); const dateNoteService = require('../../services/date_notes'); const dateUtils = require('../../services/date_utils'); @@ -13,46 +14,25 @@ const path = require('path'); const BAttribute = require('../../becca/entities/battribute'); const htmlSanitizer = require('../../services/html_sanitizer'); const {formatAttrForSearch} = require("../../services/attribute_formatter"); - -function findClippingNote(clipperInboxNote, pageUrl) { - const notes = clipperInboxNote.searchNotesInSubtree( - formatAttrForSearch({ - type: 'label', - name: "pageUrl", - value: pageUrl - }, true) - ); - - for (const note of notes) { - if (note.getOwnedLabelValue('clipType') === 'clippings') { - return note; - } - } - - return null; -} - -function getClipperInboxNote() { - let clipperInbox = attributeService.getNoteWithLabel('clipperInbox'); - - if (!clipperInbox) { - clipperInbox = dateNoteService.getDayNote(dateUtils.localNowDate()); - } - - return clipperInbox; -} +const jsdom = require("jsdom"); +const { JSDOM } = jsdom; function addClipping(req) { + // if a note under the clipperInbox as the same 'pageUrl' attribute, + // add the content to that note and clone it under today's inbox + // otherwise just create a new note under today's inbox let {title, content, pageUrl, images} = req.body; + const clipType = 'clippings'; const clipperInbox = getClipperInboxNote(); + const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate()); pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); - let clippingNote = findClippingNote(clipperInbox, pageUrl); + let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType); if (!clippingNote) { clippingNote = noteService.createNewNote({ - parentNoteId: clipperInbox.noteId, + parentNoteId: dailyNote.noteId, title: title, content: '', type: 'text' @@ -67,13 +47,45 @@ function addClipping(req) { const existingContent = clippingNote.getContent(); - clippingNote.setContent(`${existingContent}${existingContent.trim() ? "
" : ""}${rewrittenContent}`); + clippingNote.setContent(`${existingContent}${existingContent.trim() ? "
" : ""}${rewrittenContent}`); + + if (clippingNote.parentNoteId !== dailyNote.noteId) { + cloneService.cloneNoteToParentNote(clippingNote.noteId, dailyNote.noteId); + } return { noteId: clippingNote.noteId }; } +function findClippingNote(clipperInboxNote, pageUrl, clipType) { + if (!pageUrl) { + return null; + } + + const notes = clipperInboxNote.searchNotesInSubtree( + formatAttrForSearch({ + type: 'label', + name: "pageUrl", + value: pageUrl + }, true) + ); + + return clipType + ? notes.find(note => note.getOwnedLabelValue('clipType') === clipType) + : notes[0]; +} + +function getClipperInboxNote() { + let clipperInbox = attributeService.getNoteWithLabel('clipperInbox'); + + if (!clipperInbox) { + clipperInbox = dateNoteService.getRootCalendarNote(); + } + + return clipperInbox; +} + function createNote(req) { let {title, content, pageUrl, images, clipType, labels} = req.body; @@ -81,26 +93,31 @@ function createNote(req) { title = `Clipped note from ${pageUrl}`; } - const clipperInbox = getClipperInboxNote(); - - const {note} = noteService.createNewNote({ - parentNoteId: clipperInbox.noteId, - title, - content, - type: 'text' - }); - clipType = htmlSanitizer.sanitize(clipType); - note.setLabel('clipType', clipType); + const clipperInbox = getClipperInboxNote(); + const dailyNote = dateNoteService.getDayNote(dateUtils.localNowDate()); + pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); + let note = findClippingNote(clipperInbox, pageUrl, clipType); - if (pageUrl) { - pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); + if (!note) { + note = noteService.createNewNote({ + parentNoteId: dailyNote.noteId, + title, + content: '', + type: 'text' + }).note; - note.setLabel('pageUrl', pageUrl); - note.setLabel('iconClass', 'bx bx-globe'); + note.setLabel('clipType', clipType); + + if (pageUrl) { + pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); + + note.setLabel('pageUrl', pageUrl); + note.setLabel('iconClass', 'bx bx-globe'); + } } - + if (labels) { for (const labelName in labels) { const labelValue = htmlSanitizer.sanitize(labels[labelName]); @@ -108,9 +125,9 @@ function createNote(req) { } } + const existingContent = note.getContent(); const rewrittenContent = processContent(images, note, content); - - note.setContent(rewrittenContent); + note.setContent(`${existingContent}${existingContent.trim() ? "
" : ""}${rewrittenContent}`); return { noteId: note.noteId @@ -158,6 +175,15 @@ function processContent(images, note, content) { // fallback if parsing/downloading images fails for some reason on the extension side ( rewrittenContent = noteService.downloadImages(note.noteId, rewrittenContent); + // Check if rewrittenContent contains at least one HTML tag + if (!/<.+?>/.test(rewrittenContent)) { + rewrittenContent = `

${rewrittenContent}

`; + } + // Create a JSDOM object from the existing HTML content + const dom = new JSDOM(rewrittenContent); + + // Get the content inside the body tag and serialize it + rewrittenContent = dom.window.document.body.innerHTML; return rewrittenContent; } @@ -187,9 +213,19 @@ function handshake() { } } +function findNotesByUrl(req){ + let pageUrl = req.params.noteUrl; + const clipperInbox = getClipperInboxNote(); + let foundPage = findClippingNote(clipperInbox, pageUrl, null); + return { + noteId: foundPage ? foundPage.noteId : null + } +} + module.exports = { createNote, addClipping, openNote, - handshake + handshake, + findNotesByUrl }; diff --git a/src/routes/api/options.js b/src/routes/api/options.js index 999bcb993..708b89ac1 100644 --- a/src/routes/api/options.js +++ b/src/routes/api/options.js @@ -49,7 +49,7 @@ const ALLOWED_OPTIONS = new Set([ 'compressImages', 'downloadImagesAutomatically', 'minTocHeadings', - 'highlightedText', + 'highlightsList', 'checkForUpdates', 'disableTray', 'eraseUnusedAttachmentsAfterSeconds', diff --git a/src/routes/api/recent_notes.js b/src/routes/api/recent_notes.js index dbfabdbf1..40139477a 100644 --- a/src/routes/api/recent_notes.js +++ b/src/routes/api/recent_notes.js @@ -11,7 +11,7 @@ function addRecentNote(req) { }).save(); if (Math.random() < 0.05) { - // it's not necessary to run this everytime ... + // it's not necessary to run this every time ... const cutOffDate = dateUtils.utcDateTimeStr(new Date(Date.now() - 24 * 3600 * 1000)); sql.execute(`DELETE FROM recent_notes WHERE utcDateCreated < ?`, [cutOffDate]); diff --git a/src/routes/api/sql.js b/src/routes/api/sql.js index b8a9c7ea6..00bbf852e 100644 --- a/src/routes/api/sql.js +++ b/src/routes/api/sql.js @@ -28,6 +28,12 @@ function execute(req) { for (let query of queries) { query = query.trim(); + while (query.startsWith('-- ')) { + // Query starts with one or more SQL comments, discard these before we execute. + const pivot = query.indexOf('\n'); + query = pivot > 0 ? query.substr(pivot + 1).trim() : ""; + } + if (!query) { continue; } diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js index 3dd66e27b..139f668b0 100644 --- a/src/routes/api/sync.js +++ b/src/routes/api/sync.js @@ -62,7 +62,7 @@ function checkSync() { function syncNow() { log.info("Received request to trigger sync now."); - // when explicitly asked for set in progress status immediatelly for faster user feedback + // when explicitly asked for set in progress status immediately for faster user feedback ws.syncPullInProgress(); return syncService.sync(); diff --git a/src/routes/routes.js b/src/routes/routes.js index 120c23dbd..9d70ff637 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -269,6 +269,7 @@ function register(app) { route(PST, '/api/clipper/clippings', clipperMiddleware, clipperRoute.addClipping, apiResultHandler); route(PST, '/api/clipper/notes', clipperMiddleware, clipperRoute.createNote, apiResultHandler); route(PST, '/api/clipper/open/:noteId', clipperMiddleware, clipperRoute.openNote, apiResultHandler); + route(GET, '/api/clipper/notes-by-url/:noteUrl', clipperMiddleware, clipperRoute.findNotesByUrl, apiResultHandler); apiRoute(GET, '/api/special-notes/inbox/:date', specialNotesRoute.getInboxNote); apiRoute(GET, '/api/special-notes/days/:date', specialNotesRoute.getDayNote); diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index a085bb915..49cec4052 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -395,7 +395,7 @@ class ConsistencyChecks { ({noteId, isProtected, type, mime}) => { if (this.autoFix) { // it might be possible that the blob is not available only because of the interrupted - // sync, and it will come later. It's therefore important to guarantee that this artifical + // sync, and it will come later. It's therefore important to guarantee that this artificial // record won't overwrite the real one coming from the sync. const fakeDate = "2000-01-01 00:00:00Z"; diff --git a/src/services/html_sanitizer.js b/src/services/html_sanitizer.js index eee6e5299..d6fb91c09 100644 --- a/src/services/html_sanitizer.js +++ b/src/services/html_sanitizer.js @@ -57,5 +57,7 @@ function sanitize(dirtyHtml) { module.exports = { sanitize, - sanitizeUrl + sanitizeUrl: url => { + return sanitizeUrl(url).trim(); + } }; diff --git a/src/services/options_init.js b/src/services/options_init.js index 547b4f956..714f912d9 100644 --- a/src/services/options_init.js +++ b/src/services/options_init.js @@ -83,7 +83,7 @@ const defaultOptions = [ { name: 'compressImages', value: 'true', isSynced: true }, { name: 'downloadImagesAutomatically', value: 'true', isSynced: true }, { name: 'minTocHeadings', value: '5', isSynced: true }, - { name: 'highlightedText', value: '["bold","italic","underline","color","bgColor"]', isSynced: true }, + { name: 'highlightsList', value: '["bold","italic","underline","color","bgColor"]', isSynced: true }, { name: 'checkForUpdates', value: 'true', isSynced: true }, { name: 'disableTray', value: 'false', isSynced: false }, { name: 'eraseUnusedAttachmentsAfterSeconds', value: '2592000', isSynced: true }, diff --git a/src/services/script.js b/src/services/script.js index 34eb26502..55538ff44 100644 --- a/src/services/script.js +++ b/src/services/script.js @@ -55,7 +55,7 @@ ${bundle.script}\r } /** - * THIS METHOD CANT BE ASYNC, OTHERWISE TRANSACTION WRAPPER WON'T BE EFFECTIVE AND WE WILL BE LOSING THE + * THIS METHOD CAN'T BE ASYNC, OTHERWISE TRANSACTION WRAPPER WON'T BE EFFECTIVE AND WE WILL BE LOSING THE * ENTITY CHANGES IN CLS. * * This method preserves frontend startNode - that's why we start execution from currentNote and override diff --git a/src/services/search/expressions/note_flat_text.js b/src/services/search/expressions/note_flat_text.js index 62985f590..3703b5a2f 100644 --- a/src/services/search/expressions/note_flat_text.js +++ b/src/services/search/expressions/note_flat_text.js @@ -19,20 +19,22 @@ class NoteFlatTextExp extends Expression { /** * @param {BNote} note - * @param {string[]} tokens - * @param {string[]} path + * @param {string[]} remainingTokens - tokens still needed to be found in the path towards root + * @param {string[]} takenPath - path so far taken towards from candidate note towards the root. + * It contains the suffix fragment of the full note path. */ - const searchDownThePath = (note, tokens, path) => { - if (tokens.length === 0) { - const retPath = this.getNotePath(note, path); + const searchPathTowardsRoot = (note, remainingTokens, takenPath) => { + if (remainingTokens.length === 0) { + // we're done, just build the result + const resultPath = this.getNotePath(note, takenPath); - if (retPath) { - const noteId = retPath[retPath.length - 1]; + if (resultPath) { + const noteId = resultPath[resultPath.length - 1]; if (!resultNoteSet.hasNoteId(noteId)) { // we could get here from multiple paths, the first one wins because the paths // are sorted by importance - executionContext.noteIdToNotePath[noteId] = retPath; + executionContext.noteIdToNotePath[noteId] = resultPath; resultNoteSet.add(becca.notes[noteId]); } @@ -42,22 +44,23 @@ class NoteFlatTextExp extends Expression { } if (note.parents.length === 0 || note.noteId === 'root') { + // we've reached root, but there are still remaining tokens -> this candidate note produced no result return; } const foundAttrTokens = []; - for (const token of tokens) { + for (const token of remainingTokens) { if (note.type.includes(token) || note.mime.includes(token)) { foundAttrTokens.push(token); } } - for (const attribute of note.ownedAttributes) { + for (const attribute of note.getOwnedAttributes()) { const normalizedName = utils.normalize(attribute.name); const normalizedValue = utils.normalize(attribute.value); - for (const token of tokens) { + for (const token of remainingTokens) { if (normalizedName.includes(token) || normalizedValue.includes(token)) { foundAttrTokens.push(token); } @@ -68,19 +71,19 @@ class NoteFlatTextExp extends Expression { const title = utils.normalize(beccaService.getNoteTitle(note.noteId, parentNote.noteId)); const foundTokens = foundAttrTokens.slice(); - for (const token of tokens) { + for (const token of remainingTokens) { if (title.includes(token)) { foundTokens.push(token); } } if (foundTokens.length > 0) { - const remainingTokens = tokens.filter(token => !foundTokens.includes(token)); + const newRemainingTokens = remainingTokens.filter(token => !foundTokens.includes(token)); - searchDownThePath(parentNote, remainingTokens, [...path, note.noteId]); + searchPathTowardsRoot(parentNote, newRemainingTokens, [note.noteId, ...takenPath]); } else { - searchDownThePath(parentNote, tokens, [...path, note.noteId]); + searchPathTowardsRoot(parentNote, remainingTokens, [note.noteId, ...takenPath]); } } } @@ -90,7 +93,7 @@ class NoteFlatTextExp extends Expression { for (const note of candidateNotes) { // autocomplete should be able to find notes by their noteIds as well (only leafs) if (this.tokens.length === 1 && note.noteId.toLowerCase() === this.tokens[0]) { - searchDownThePath(note, [], []); + searchPathTowardsRoot(note, [], [note.noteId]); continue; } @@ -123,7 +126,7 @@ class NoteFlatTextExp extends Expression { if (foundTokens.length > 0) { const remainingTokens = this.tokens.filter(token => !foundTokens.includes(token)); - searchDownThePath(parentNote, remainingTokens, [note.noteId]); + searchPathTowardsRoot(parentNote, remainingTokens, [note.noteId]); } } } @@ -131,14 +134,22 @@ class NoteFlatTextExp extends Expression { return resultNoteSet; } - getNotePath(note, path) { - if (path.length === 0) { + /** + * @param {BNote} note + * @param {string[]} takenPath + * @returns {string[]} + */ + getNotePath(note, takenPath) { + if (takenPath.length === 0) { + throw new Error("Path is not expected to be empty."); + } else if (takenPath.length === 1 && takenPath[0] === note.noteId) { return note.getBestNotePath(); } else { - const closestNoteId = path[0]; - const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath(); + // this note is the closest to root containing the last matching token(s), thus completing the requirements + // what's in this note's predecessors does not matter, thus we'll choose the best note path + const topMostMatchingTokenNotePath = becca.getNote(takenPath[0]).getBestNotePath(); - return [...closestNoteBestNotePath, ...path.slice(1)]; + return [...topMostMatchingTokenNotePath, ...takenPath.slice(1)]; } } diff --git a/src/services/search/services/search.js b/src/services/search/services/search.js index a2317d7c4..cda20b94d 100644 --- a/src/services/search/services/search.js +++ b/src/services/search/services/search.js @@ -10,7 +10,6 @@ const becca = require('../../../becca/becca'); const beccaService = require('../../../becca/becca_service'); const utils = require('../../utils'); const log = require('../../log'); -const scriptService = require("../../script"); const hoistedNoteService = require("../../hoisted_note"); function searchFromNote(note) { @@ -73,6 +72,7 @@ function searchFromRelation(note, relationName) { return []; } + const scriptService = require("../../script"); // to avoid circular dependency const result = scriptService.executeNote(scriptNote, {originEntity: note}); if (!Array.isArray(result)) { diff --git a/src/services/task_context.js b/src/services/task_context.js index 13fc22b04..be031ddac 100644 --- a/src/services/task_context.js +++ b/src/services/task_context.js @@ -13,7 +13,7 @@ class TaskContext { this.noteDeletionHandlerTriggered = false; // progressCount is meant to represent just some progress - to indicate the task is not stuck - this.progressCount = -1; // we're incrementing immediatelly + this.progressCount = -1; // we're incrementing immediately this.lastSentCountTs = 0; // 0 will guarantee the first message will be sent // just the fact this has been initialized is a progress which should be sent to clients diff --git a/src/views/setup.ejs b/src/views/setup.ejs index 80902e5ec..2b427d4d4 100644 --- a/src/views/setup.ejs +++ b/src/views/setup.ejs @@ -96,7 +96,7 @@
  • From the Trilium Menu, click Options.
  • Click on Sync tab.
  • Change server instance address to: and click save.
  • -
  • Click "Test sync" button to verify connection is successfull.
  • +
  • Click "Test sync" button to verify connection is successful.
  • Once you've completed these steps, click here.