diff --git a/.vscode/launch.json b/.vscode/launch.json index 87b70a760..810f84749 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,8 @@ "/**" ], "env": { - "TRILIUM_ENV": "dev" + "TRILIUM_ENV": "dev", + "TRILIUM_DATA_DIR": "./data" }, "outputCapture": "std", "program": "${workspaceFolder}/src/www" diff --git a/dump-db/package-lock.json b/dump-db/package-lock.json index 9b98e2f3d..509017062 100644 --- a/dump-db/package-lock.json +++ b/dump-db/package-lock.json @@ -387,9 +387,12 @@ } }, "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp-classic": { "version": "0.5.3", @@ -1163,9 +1166,9 @@ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" }, "mkdirp-classic": { "version": "0.5.3", diff --git a/package.json b/package.json index 9b355c140..9ab0e7e2a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "trilium", "productName": "Trilium Notes", "description": "Trilium Notes", - "version": "0.58.2-beta", + "version": "0.58.3-beta", "license": "AGPL-3.0-only", "main": "electron.js", "bin": { @@ -17,10 +17,10 @@ "start-server-no-dir": "cross-env TRILIUM_ENV=dev node ./src/www", "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev electron --inspect=5858 .", "start-electron-no-dir": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .", - "switch-server": "rm -r ./node_modules/better-sqlite3 && npm install", - "switch-electron": "rm -r ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild", - "build-backend-docs": "rm -r ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", - "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/right_panel_widget.js", + "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", + "switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild", + "build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js", + "build-frontend-docs": "rm -rf ./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/right_panel_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": "jasmine", @@ -29,6 +29,7 @@ "postinstall": "rimraf ./node_modules/canvas" }, "dependencies": { + "@braintree/sanitize-url": "^6.0.2", "@electron/remote": "2.0.9", "@excalidraw/excalidraw": "0.13.0", "archiver": "5.3.1", diff --git a/src/public/app/doc_notes/launchbar_script_launcher.html b/src/public/app/doc_notes/launchbar_script_launcher.html index 82c9d496c..0a17bf523 100644 --- a/src/public/app/doc_notes/launchbar_script_launcher.html +++ b/src/public/app/doc_notes/launchbar_script_launcher.html @@ -8,5 +8,5 @@

Example script

-alert("Current note is " + api.getActiveContextNote().title);
+api.showMessage("Current note is " + api.getActiveContextNote().title);
 
diff --git a/src/public/app/menus/tree_context_menu.js b/src/public/app/menus/tree_context_menu.js index 98ebebadc..52300b962 100644 --- a/src/public/app/menus/tree_context_menu.js +++ b/src/public/app/menus/tree_context_menu.js @@ -32,7 +32,7 @@ export default class TreeContextMenu { const isHoisted = note.noteId === appContext.tabManager.getActiveContext().hoistedNoteId; const parentNote = isNotRoot ? await froca.getNote(branch.parentNoteId) : null; - // some actions don't support multi-note so they are disabled when notes are selected + // some actions don't support multi-note, so they are disabled when notes are selected // the only exception is when the only selected note is the one that was right-clicked, then // it's clear what the user meant to do. const selNodes = this.treeWidget.getSelectedNodes(); diff --git a/src/public/app/services/froca.js b/src/public/app/services/froca.js index e26bba926..20d94a4b4 100644 --- a/src/public/app/services/froca.js +++ b/src/public/app/services/froca.js @@ -216,7 +216,7 @@ class Froca { getNotesFromCache(noteIds, silentNotFoundError = false) { return noteIds.map(noteId => { if (!this.notes[noteId] && !silentNotFoundError) { - console.trace(`Can't find note "${noteId}"`); + console.trace(`Can't find note '${noteId}'`); return null; } @@ -235,7 +235,7 @@ class Froca { return noteIds.map(noteId => { if (!this.notes[noteId] && !silentNotFoundError) { - console.trace(`Can't find note "${noteId}"`); + console.trace(`Can't find note '${noteId}'`); return null; } else { @@ -285,7 +285,7 @@ class Froca { getBranch(branchId, silentNotFoundError = false) { if (!(branchId in this.branches)) { if (!silentNotFoundError) { - logError(`Not existing branch ${branchId}`); + logError(`Not existing branch '${branchId}'`); } } else { @@ -295,13 +295,13 @@ class Froca { async getBranchId(parentNoteId, childNoteId) { if (childNoteId === 'root') { - return 'root'; + return 'none_root'; } const child = await this.getNote(childNoteId); if (!child) { - logError(`Could not find branchId for parent=${parentNoteId}, child=${childNoteId} since child does not exist`); + logError(`Could not find branchId for parent '${parentNoteId}', child '${childNoteId}' since child does not exist`); return null; } @@ -318,9 +318,9 @@ class Froca { .then(row => new FNoteComplement(row)) .catch(e => console.error(`Cannot get note complement for note '${noteId}'`)); - // we don't want to keep large payloads forever in memory so we clean that up quite quickly + // we don't want to keep large payloads forever in memory, so we clean that up quite quickly // this cache is more meant to share the data between different components within one business transaction (e.g. loading of the note into the tab context and all the components) - // this is also a work around for missing invalidation after change + // this is also a workaround for missing invalidation after change this.noteComplementPromises[noteId].then( () => setTimeout(() => this.noteComplementPromises[noteId] = null, 1000) ); diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index 76b25b648..ab8ea1434 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -320,8 +320,6 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { && attributeService.isAffecting(attr, this.note)); if (label || relation) { - console.log("OOOO"); - // probably incorrect event // calling this.refresh() is not enough since the event needs to be propagated to children as well this.triggerEvent('noteTypeMimeChanged', {noteId: this.noteId}); diff --git a/src/public/app/widgets/note_tree.js b/src/public/app/widgets/note_tree.js index af4ee93df..a2f21bb88 100644 --- a/src/public/app/widgets/note_tree.js +++ b/src/public/app/widgets/note_tree.js @@ -1606,7 +1606,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { const resp = await server.post(`special-notes/launchers/${node.data.noteId}/${launcherType}`); if (!resp.success) { - alert(resp.message); + toastService.showError(resp.message); } await ws.waitForMaxKnownEntityChangeId(); diff --git a/src/routes/api/clipper.js b/src/routes/api/clipper.js index 1f369cb7b..60914e979 100644 --- a/src/routes/api/clipper.js +++ b/src/routes/api/clipper.js @@ -14,8 +14,8 @@ const BAttribute = require('../../becca/entities/battribute'); const htmlSanitizer = require('../../services/html_sanitizer'); const {formatAttrForSearch} = require("../../services/attribute_formatter"); -function findClippingNote(todayNote, pageUrl) { - const notes = todayNote.searchNotesInSubtree( +function findClippingNote(clipperInboxNote, pageUrl) { + const notes = clipperInboxNote.searchNotesInSubtree( formatAttrForSearch({ type: 'label', name: "pageUrl", @@ -47,6 +47,7 @@ function addClipping(req) { const clipperInbox = getClipperInboxNote(); + pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); let clippingNote = findClippingNote(clipperInbox, pageUrl); if (!clippingNote) { @@ -57,8 +58,6 @@ function addClipping(req) { type: 'text' }).note; - pageUrl = htmlSanitizer.sanitize(pageUrl); - clippingNote.setLabel('clipType', 'clippings'); clippingNote.setLabel('pageUrl', pageUrl); clippingNote.setLabel('iconClass', 'bx bx-globe'); @@ -96,7 +95,7 @@ function createNote(req) { note.setLabel('clipType', clipType); if (pageUrl) { - pageUrl = htmlSanitizer.sanitize(pageUrl); + pageUrl = htmlSanitizer.sanitizeUrl(pageUrl); note.setLabel('pageUrl', pageUrl); note.setLabel('iconClass', 'bx bx-globe'); diff --git a/src/routes/api/export.js b/src/routes/api/export.js index 365c7e185..6294f2f25 100644 --- a/src/routes/api/export.js +++ b/src/routes/api/export.js @@ -13,7 +13,7 @@ function exportBranch(req, res) { const branch = becca.getBranch(branchId); if (!branch) { - const message = `Cannot export branch ${branchId} since it does not exist.`; + const message = `Cannot export branch '${branchId}' since it does not exist.`; log.error(message); res.setHeader("Content-Type", "text/plain") diff --git a/src/services/build.js b/src/services/build.js index 7461d1158..76698bb1a 100644 --- a/src/services/build.js +++ b/src/services/build.js @@ -1 +1 @@ -module.exports = { buildDate:"2022-12-29T00:12:54+01:00", buildRevision: "d36cf47974cd8bc6bd45c1da774a9a55d45f998e" }; +module.exports = { buildDate:"2023-01-04T22:36:31+01:00", buildRevision: "3a5fa2954dea0ff2ecdce9a28b3bb01f039d314d" }; diff --git a/src/services/html_sanitizer.js b/src/services/html_sanitizer.js index c675c428a..32b275d17 100644 --- a/src/services/html_sanitizer.js +++ b/src/services/html_sanitizer.js @@ -1,4 +1,5 @@ const sanitizeHtml = require('sanitize-html'); +const sanitizeUrl = require('@braintree/sanitize-url').sanitizeUrl; // intended mainly as protection against XSS via import // secondarily it (partly) protects against "CSS takeover" @@ -50,5 +51,6 @@ function sanitize(dirtyHtml) { } module.exports = { - sanitize + sanitize, + sanitizeUrl }; diff --git a/src/services/search/expressions/order_by_and_limit.js b/src/services/search/expressions/order_by_and_limit.js index 45f64e47b..c36e50388 100644 --- a/src/services/search/expressions/order_by_and_limit.js +++ b/src/services/search/expressions/order_by_and_limit.js @@ -48,14 +48,13 @@ class OrderByAndLimitExp extends Expression { } // if both are numbers then parse them for numerical comparison - // beware that isNaN will return false for empty string and null - if (valA.trim() !== "" && valB.trim() !== "" && !isNaN(valA) && !isNaN(valB)) { + if (this.isNumber(valA) && this.isNumber(valB)) { valA = parseFloat(valA); valB = parseFloat(valB); } if (!valA && !valB) { - // the attribute is not defined in either note so continue to next order definition + // the attribute value is empty/zero in both notes so continue to the next order definition continue; } else if (!valB || valA < valB) { return smaller; @@ -77,6 +76,17 @@ class OrderByAndLimitExp extends Expression { return noteSet; } + + isNumber(x) { + if (typeof x === 'number') { + return true; + } else if (typeof x === 'string') { + // isNaN will return false for blank string + return x.trim() !== "" && !isNaN(x); + } else { + return false; + } + } } module.exports = OrderByAndLimitExp; diff --git a/src/services/search/value_extractor.js b/src/services/search/value_extractor.js index 9534fc3fc..b8b0f96c7 100644 --- a/src/services/search/value_extractor.js +++ b/src/services/search/value_extractor.js @@ -1,8 +1,8 @@ "use strict"; /** - * Search string is lower cased for case insensitive comparison. But when retrieving properties - * we need case sensitive form so we have this translation object. + * Search string is lower cased for case-insensitive comparison. But when retrieving properties + * we need case-sensitive form, so we have this translation object. */ const PROP_MAPPING = { "noteid": "noteId",