mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 18:05:55 +01:00 
			
		
		
		
	Merge branch 'master' into dev
# Conflicts: # package-lock.json # package.json # src/public/app/widgets/dialogs/note_revisions.js # src/services/handlers.js # src/services/hidden_subtree.js # src/services/search/services/parse.js
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -12,5 +12,10 @@ module.exports = () => { | |||||||
|  |  | ||||||
|             attr.markAsDeleted("0204__migrate_bookmarks_to_clones"); |             attr.markAsDeleted("0204__migrate_bookmarks_to_clones"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // bookmarkFolder used to work in 0.57 without the bookmarked label | ||||||
|  |         for (const attr of becca.findAttributes('label','bookmarkFolder')) { | ||||||
|  |             cloningService.toggleNoteInParent(true, attr.noteId, '_lbBookmarks'); | ||||||
|  |         } | ||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -82,10 +82,8 @@ function getNoteTitleArrayForPath(notePathArray) { | |||||||
|         throw new Error(`${notePathArray} is not an array.`); |         throw new Error(`${notePathArray} is not an array.`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const hoistedNoteId = cls.getHoistedNoteId(); |     if (notePathArray.length === 1) { | ||||||
|  |         return [getNoteTitle(notePathArray[0])]; | ||||||
|     if (notePathArray.length === 1 && notePathArray[0] === hoistedNoteId) { |  | ||||||
|         return [getNoteTitle(hoistedNoteId)]; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const titles = []; |     const titles = []; | ||||||
| @@ -94,6 +92,7 @@ function getNoteTitleArrayForPath(notePathArray) { | |||||||
|     let hoistedNotePassed = false; |     let hoistedNotePassed = false; | ||||||
|  |  | ||||||
|     // this is a notePath from outside of hoisted subtree so full title path needs to be returned |     // this is a notePath from outside of hoisted subtree so full title path needs to be returned | ||||||
|  |     const hoistedNoteId = cls.getHoistedNoteId(); | ||||||
|     const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId); |     const outsideOfHoistedSubtree = !notePathArray.includes(hoistedNoteId); | ||||||
|  |  | ||||||
|     for (const noteId of notePathArray) { |     for (const noteId of notePathArray) { | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ const BNoteRevision = require("./bnote_revision"); | |||||||
| const TaskContext = require("../../services/task_context"); | const TaskContext = require("../../services/task_context"); | ||||||
| const dayjs = require("dayjs"); | const dayjs = require("dayjs"); | ||||||
| const utc = require('dayjs/plugin/utc'); | const utc = require('dayjs/plugin/utc'); | ||||||
|  | const eventService = require("../../services/events"); | ||||||
| dayjs.extend(utc) | dayjs.extend(utc) | ||||||
|  |  | ||||||
| const LABEL = 'label'; | const LABEL = 'label'; | ||||||
| @@ -314,6 +315,11 @@ class BNote extends AbstractBeccaEntity { | |||||||
|             utcDateChanged: pojo.utcDateModified, |             utcDateChanged: pojo.utcDateModified, | ||||||
|             isSynced: true |             isSynced: true | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         eventService.emit(eventService.ENTITY_CHANGED, { | ||||||
|  |             entityName: 'note_contents', | ||||||
|  |             entity: this | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     setJsonContent(content) { |     setJsonContent(content) { | ||||||
| @@ -1124,6 +1130,13 @@ class BNote extends AbstractBeccaEntity { | |||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|  |      */ | ||||||
|  |     isHiddenCompletely() { | ||||||
|  |         return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param ancestorNoteId |      * @param ancestorNoteId | ||||||
|      * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths |      * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths | ||||||
| @@ -1368,7 +1381,7 @@ class BNote extends AbstractBeccaEntity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     isOptions() { |     isOptions() { | ||||||
|         return this.noteId.startsWith("options"); |         return this.noteId.startsWith("_options"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get isDeleted() { |     get isDeleted() { | ||||||
|   | |||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | <p>Back and Forward buttons allow you to move in the navigation history.</p> | ||||||
|  |  | ||||||
|  | <p>These launchers are active only in the desktop build and will be ignored in the server edition where you can use the native browser navigation buttons instead.</p> | ||||||
| @@ -360,6 +360,13 @@ class FNote { | |||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|  |      */ | ||||||
|  |     isHiddenCompletely() { | ||||||
|  |         return !this.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     __filterAttrs(attributes, type, name) { |     __filterAttrs(attributes, type, name) { | ||||||
|         this.__validateTypeName(type, name); |         this.__validateTypeName(type, name); | ||||||
|  |  | ||||||
| @@ -852,7 +859,7 @@ class FNote { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     isOptions() { |     isOptions() { | ||||||
|         return this.noteId.startsWith("options"); |         return this.noteId.startsWith("_options"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -230,6 +230,10 @@ function init() { | |||||||
|  |  | ||||||
|     $.fn.getSelectedNoteId = function () { |     $.fn.getSelectedNoteId = function () { | ||||||
|         const notePath = $(this).getSelectedNotePath(); |         const notePath = $(this).getSelectedNotePath(); | ||||||
|  |         if (!notePath) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const chunks = notePath.split('/'); |         const chunks = notePath.split('/'); | ||||||
|  |  | ||||||
|         return chunks.length >= 1 ? chunks[chunks.length - 1] : null; |         return chunks.length >= 1 ? chunks[chunks.length - 1] : null; | ||||||
|   | |||||||
| @@ -39,7 +39,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @param {function|string} command |      * @param {function|string} command | ||||||
|      * @returns {CommandButtonWidget} |      * @returns {this} | ||||||
|      */ |      */ | ||||||
|     command(command) { |     command(command) { | ||||||
|         this.settings.command = command; |         this.settings.command = command; | ||||||
|   | |||||||
| @@ -303,7 +303,7 @@ export default class GlobalMenuWidget extends BasicWidget { | |||||||
|         const resp = await fetch(RELEASES_API_URL); |         const resp = await fetch(RELEASES_API_URL); | ||||||
|         const data = await resp.json(); |         const data = await resp.json(); | ||||||
|  |  | ||||||
|         return data.tag_name.substring(1); |         return data?.tag_name?.substring(1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     downloadLatestVersionCommand() { |     downloadLatestVersionCommand() { | ||||||
|   | |||||||
| @@ -23,6 +23,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget { | |||||||
|     doRender() { |     doRender() { | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|  |  | ||||||
|  |         if (!utils.isElectron()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         this.webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents(); |         this.webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents(); | ||||||
|  |  | ||||||
|         // without this the history is preserved across frontend reloads |         // without this the history is preserved across frontend reloads | ||||||
|   | |||||||
| @@ -234,14 +234,14 @@ export default class NoteRevisionsDialog extends BasicWidget { | |||||||
|                 renderMathInElement($content[0], {trust: true}); |                 renderMathInElement($content[0], {trust: true}); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else if (revisionItem.type === 'code') { |         else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { | ||||||
|             this.$content.html($("<pre>").text(fullNoteRevision.content)); |             this.$content.html($("<pre>").text(fullNoteRevision.content)); | ||||||
|         } |         } | ||||||
|         else if (revisionItem.type === 'image') { |         else if (revisionItem.type === 'image') { | ||||||
|             this.$content.html($("<img>") |             this.$content.html($("<img>") | ||||||
|                 // reason why we put this inline as base64 is that we do not want to let user copy this |                 // reason why we put this inline as base64 is that we do not want to let user copy this | ||||||
|                 // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note |                 // as a URL to be used in a note. Instead, if they copy and paste it into a note, it will be an uploaded as a new note | ||||||
|                 .attr("src", `data:${note.mime};base64,${fullNoteRevision.content}`) |                 .attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`) | ||||||
|                 .css("max-width", "100%") |                 .css("max-width", "100%") | ||||||
|                 .css("max-height", "100%")); |                 .css("max-height", "100%")); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         this.$saveToNoteButton.toggle( |         this.$saveToNoteButton.toggle( | ||||||
|             note.mime === 'text/x-sqlite;schema=trilium' |             note.mime === 'text/x-sqlite;schema=trilium' | ||||||
|             && !note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden')) |             && note.isHiddenCompletely() | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env=')); |         this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env=')); | ||||||
|   | |||||||
| @@ -1309,6 +1309,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             await this.tree.reload([rootNode]); |             await this.tree.reload([rootNode]); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         await this.filterHoistedBranch(); | ||||||
|  |  | ||||||
|         if (activeNotePath) { |         if (activeNotePath) { | ||||||
|             const node = await this.getNodeFromPath(activeNotePath, true); |             const node = await this.getNodeFromPath(activeNotePath, true); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -270,7 +270,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | |||||||
|     async refreshWithNote(note) { |     async refreshWithNote(note) { | ||||||
|         this.$component.show(); |         this.$component.show(); | ||||||
|  |  | ||||||
|         this.$saveToNoteButton.toggle(!note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'))); |         this.$saveToNoteButton.toggle(note.isHiddenCompletely()); | ||||||
|  |  | ||||||
|         this.$searchOptions.empty(); |         this.$searchOptions.empty(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -112,7 +112,7 @@ function update(name, value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getUserThemes() { | function getUserThemes() { | ||||||
|     const notes = searchService.searchNotes("#appTheme"); |     const notes = searchService.searchNotes("#appTheme", {ignoreHoistedNote: true}); | ||||||
|     const ret = []; |     const ret = []; | ||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2023-01-11T23:44:33+01:00", buildRevision: "bdfdc0402ddb23e9af002580f368bc52e4268b3a" }; | module.exports = { buildDate:"2023-01-16T22:39:28+01:00", buildRevision: "9fd0b85ff2be264be35ec2052c956b654f0dac9e" }; | ||||||
|   | |||||||
| @@ -71,6 +71,7 @@ module.exports = [ | |||||||
|     { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, |     { type: 'relation', name: 'runOnNoteCreation', isDangerous: true }, | ||||||
|     { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true }, |     { type: 'relation', name: 'runOnNoteTitleChange', isDangerous: true }, | ||||||
|     { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, |     { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, | ||||||
|  |     { type: 'relation', name: 'runOnNoteContentChange', isDangerous: true }, | ||||||
|     { type: 'relation', name: 'runOnNoteDeletion', isDangerous: true }, |     { type: 'relation', name: 'runOnNoteDeletion', isDangerous: true }, | ||||||
|     { type: 'relation', name: 'runOnBranchCreation', isDangerous: true }, |     { type: 'relation', name: 'runOnBranchCreation', isDangerous: true }, | ||||||
|     { type: 'relation', name: 'runOnBranchDeletion', isDangerous: true }, |     { type: 'relation', name: 'runOnBranchDeletion', isDangerous: true }, | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ const treeService = require('./tree'); | |||||||
| const noteService = require('./notes'); | const noteService = require('./notes'); | ||||||
| const becca = require('../becca/becca'); | const becca = require('../becca/becca'); | ||||||
| const BAttribute = require('../becca/entities/battribute'); | const BAttribute = require('../becca/entities/battribute'); | ||||||
|  | const hiddenSubtreeService = require("./hidden_subtree"); | ||||||
|  | const oneTimeTimer = require("./one_time_timer"); | ||||||
|  |  | ||||||
| function runAttachedRelations(note, relationName, originEntity) { | function runAttachedRelations(note, relationName, originEntity) { | ||||||
|     if (!note) { |     if (!note) { | ||||||
| @@ -206,6 +208,16 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => | |||||||
|     if (entityName === 'branches') { |     if (entityName === 'branches') { | ||||||
|         runAttachedRelations(entity.getNote(), 'runOnBranchDeletion', entity); |         runAttachedRelations(entity.getNote(), 'runOnBranchDeletion', entity); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (entityName === 'notes' && entity.noteId.startsWith("_")) { | ||||||
|  |         // "named" note has been deleted, we will probably need to rebuild the hidden subtree | ||||||
|  |         // scheduling so that bulk deletes won't trigger so many checks | ||||||
|  |         oneTimeTimer.scheduleExecution('hidden-subtree-check', 1000, () => { | ||||||
|  |             console.log("Checking hidden subtree"); | ||||||
|  |  | ||||||
|  |             hiddenSubtreeService.checkHiddenSubtree(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| const becca = require("../becca/becca"); | const becca = require("../becca/becca"); | ||||||
| const noteService = require("./notes"); | const noteService = require("./notes"); | ||||||
| const BAttribute = require("../becca/entities/battribute"); | const BAttribute = require("../becca/entities/battribute"); | ||||||
|  | const log = require("./log"); | ||||||
|  | const migrationService = require("./migration"); | ||||||
|  |  | ||||||
| const LBTPL_ROOT = "_lbTplRoot"; | const LBTPL_ROOT = "_lbTplRoot"; | ||||||
| const LBTPL_BASE = "_lbTplBase"; | const LBTPL_BASE = "_lbTplBase"; | ||||||
| @@ -179,8 +181,10 @@ const HIDDEN_SUBTREE_DEFINITION = { | |||||||
|                     isExpanded: true, |                     isExpanded: true, | ||||||
|                     attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ], |                     attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ], | ||||||
|                     children: [ |                     children: [ | ||||||
|                         { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square' }, |                         { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-arrow-square', | ||||||
|                         { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square' }, |                             attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]}, | ||||||
|  |                         { id: '_lbForwardInHistory', title: 'Go to Next Note', type: 'launcher', builtinWidget: 'forwardInHistoryButton', icon: 'bx bxs-right-arrow-square', | ||||||
|  |                             attributes: [ { type: 'label', name: 'docName', value: 'launchbar_history_navigation' } ]}, | ||||||
|                         { id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' }, |                         { id: '_lbBackendLog', title: 'Backend Log', type: 'launcher', targetNoteId: '_backendLog', icon: 'bx bx-terminal' }, | ||||||
|                     ] |                     ] | ||||||
|                 }, |                 }, | ||||||
| @@ -237,6 +241,12 @@ const HIDDEN_SUBTREE_DEFINITION = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| function checkHiddenSubtree() { | function checkHiddenSubtree() { | ||||||
|  |     if (!migrationService.isDbUpToDate()) { | ||||||
|  |         // on-delete hook might get triggered during some future migration and cause havoc | ||||||
|  |         log.info("Will not check hidden subtree until migration is finished."); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     checkHiddenSubtreeRecursively('root', HIDDEN_SUBTREE_DEFINITION); |     checkHiddenSubtreeRecursively('root', HIDDEN_SUBTREE_DEFINITION); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -76,8 +76,8 @@ function importEnex(taskContext, file, parentNote) { | |||||||
|         content = content.replace(/<\/ol>\s*<li>/g, "</ol></li><li>"); |         content = content.replace(/<\/ol>\s*<li>/g, "</ol></li><li>"); | ||||||
|  |  | ||||||
|         // Replace en-todo with unicode ballot box |         // Replace en-todo with unicode ballot box | ||||||
|         content = content.replace(/<en-todo\s+checked="true"\/>/g, "\u2611 "); |         content = content.replace(/<en-todo\s+checked="true"\s*\/>/g, "\u2611 "); | ||||||
|         content = content.replace(/<en-todo(\s+checked="false")?\/>/g, "\u2610 "); |         content = content.replace(/<en-todo(\s+checked="false")?\s*\/>/g, "\u2610 "); | ||||||
|  |  | ||||||
|         // Replace OneNote converted checkboxes with unicode ballot box based |         // Replace OneNote converted checkboxes with unicode ballot box based | ||||||
|         // on known hash of checkboxes for regular, p1, and p2 checkboxes |         // on known hash of checkboxes for regular, p1, and p2 checkboxes | ||||||
|   | |||||||
| @@ -119,5 +119,6 @@ async function migrateIfNecessary() { | |||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     migrateIfNecessary |     migrateIfNecessary, | ||||||
|  |     isDbUpToDate | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -108,9 +108,14 @@ function getAndValidateParent(params) { | |||||||
|         throw new ValidationError(`Only 'launcher' notes can be created in parent '${params.parentNoteId}'`); |         throw new ValidationError(`Only 'launcher' notes can be created in parent '${params.parentNoteId}'`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!params.ignoreForbiddenParents && (['_lbRoot', '_hidden'].includes(parentNote.noteId) || parentNote.isOptions())) { |     if (!params.ignoreForbiddenParents) { | ||||||
|  |         if (['_lbRoot', '_hidden'].includes(parentNote.noteId) | ||||||
|  |             || parentNote.noteId.startsWith("_lbTpl") | ||||||
|  |             || parentNote.isOptions()) { | ||||||
|  |  | ||||||
|             throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`); |             throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return parentNote; |     return parentNote; | ||||||
| } | } | ||||||
| @@ -283,8 +288,12 @@ function protectNote(note, protect) { | |||||||
|  |  | ||||||
|             note.isProtected = protect; |             note.isProtected = protect; | ||||||
|  |  | ||||||
|  |             // see https://github.com/zadam/trilium/issues/3523 | ||||||
|  |             // IIRC a zero-sized buffer can be returned as null from the database | ||||||
|  |             if (content !== null) { | ||||||
|                 // this will force de/encryption |                 // this will force de/encryption | ||||||
|                 note.setContent(content); |                 note.setContent(content); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             note.save(); |             note.save(); | ||||||
|         } |         } | ||||||
| @@ -592,11 +601,6 @@ function updateNoteContent(noteId, content) { | |||||||
|     content = saveLinks(note, content); |     content = saveLinks(note, content); | ||||||
|  |  | ||||||
|     note.setContent(content); |     note.setContent(content); | ||||||
|  |  | ||||||
|     eventService.emit(eventService.ENTITY_CHANGED, { |  | ||||||
|         entityName: 'note_contents', |  | ||||||
|         entity: note |  | ||||||
|     }); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								src/services/one_time_timer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/services/one_time_timer.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | const scheduledExecutions = {}; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Subsequent calls will not move the timer to future. The first caller determines the time of execution. | ||||||
|  |  * | ||||||
|  |  * The good thing about synchronous better-sqlite3 is that this cannot interrupt transaction. The execution will be called | ||||||
|  |  * only outside of a transaction. | ||||||
|  |  */ | ||||||
|  | function scheduleExecution(name, milliseconds, cb) { | ||||||
|  |     if (name in scheduledExecutions) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     scheduledExecutions[name] = true; | ||||||
|  |  | ||||||
|  |     setTimeout(() => { | ||||||
|  |         delete scheduledExecutions[name]; | ||||||
|  |  | ||||||
|  |         cb(); | ||||||
|  |     }, milliseconds); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  |     scheduleExecution | ||||||
|  | }; | ||||||
							
								
								
									
										23
									
								
								src/services/search/expressions/is_hidden.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/services/search/expressions/is_hidden.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | const Expression = require('./expression'); | ||||||
|  | const NoteSet = require('../note_set'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Note is hidden when all its note paths start in hidden subtree (i.e. the note is not cloned into visible tree) | ||||||
|  |  */ | ||||||
|  | class IsHiddenExp extends Expression { | ||||||
|  |     execute(inputNoteSet, executionContext, searchContext) { | ||||||
|  |         const resultNoteSet = new NoteSet(); | ||||||
|  |  | ||||||
|  |         for (const note of inputNoteSet.notes) { | ||||||
|  |             if (note.isHiddenCompletely()) { | ||||||
|  |                 resultNoteSet.add(note); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return resultNoteSet; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = IsHiddenExp; | ||||||
| @@ -6,10 +6,11 @@ class SearchContext { | |||||||
|     constructor(params = {}) { |     constructor(params = {}) { | ||||||
|         this.fastSearch = !!params.fastSearch; |         this.fastSearch = !!params.fastSearch; | ||||||
|         this.includeArchivedNotes = !!params.includeArchivedNotes; |         this.includeArchivedNotes = !!params.includeArchivedNotes; | ||||||
|  |         this.includeHiddenNotes = !!params.includeHiddenNotes; | ||||||
|         this.ignoreHoistedNote = !!params.ignoreHoistedNote; |         this.ignoreHoistedNote = !!params.ignoreHoistedNote; | ||||||
|         this.ancestorNoteId = params.ancestorNoteId; |         this.ancestorNoteId = params.ancestorNoteId; | ||||||
|  |  | ||||||
|         if (!this.ancestorNoteId && !this.ignoreHoistedNote && !hoistedNoteService.isHoistedInHiddenSubtree()) { |         if (!this.ancestorNoteId && !this.ignoreHoistedNote) { | ||||||
|             // hoisting in hidden subtree should not limit autocomplete |             // hoisting in hidden subtree should not limit autocomplete | ||||||
|             // since we want to link (create relations) to the normal non-hidden notes |             // since we want to link (create relations) to the normal non-hidden notes | ||||||
|             this.ancestorNoteId = hoistedNoteService.getHoistedNoteId(); |             this.ancestorNoteId = hoistedNoteService.getHoistedNoteId(); | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ const buildComparator = require('./build_comparator'); | |||||||
| const ValueExtractor = require('../value_extractor'); | const ValueExtractor = require('../value_extractor'); | ||||||
| const utils = require("../../utils"); | const utils = require("../../utils"); | ||||||
| const TrueExp = require("../expressions/true"); | const TrueExp = require("../expressions/true"); | ||||||
|  | const IsHiddenExp = require("../expressions/is_hidden"); | ||||||
|  |  | ||||||
| function getFulltext(tokens, searchContext) { | function getFulltext(tokens, searchContext) { | ||||||
|     tokens = tokens.map(t => utils.removeDiacritic(t.token)); |     tokens = tokens.map(t => utils.removeDiacritic(t.token)); | ||||||
| @@ -429,7 +430,7 @@ function parse({fulltextTokens, expressionTokens, searchContext}) { | |||||||
|  |  | ||||||
|     let exp = AndExp.of([ |     let exp = AndExp.of([ | ||||||
|         searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", "=", "false"), |         searchContext.includeArchivedNotes ? null : new PropertyComparisonExp(searchContext, "isarchived", "=", "false"), | ||||||
|         (searchContext.ancestorNoteId && searchContext.ancestorNoteId !== 'root') ? new AncestorExp(searchContext.ancestorNoteId, searchContext.ancestorDepth) : null, |         getAncestorExp(searchContext), | ||||||
|         getFulltext(fulltextTokens, searchContext), |         getFulltext(fulltextTokens, searchContext), | ||||||
|         expression |         expression | ||||||
|     ]); |     ]); | ||||||
| @@ -448,4 +449,14 @@ function parse({fulltextTokens, expressionTokens, searchContext}) { | |||||||
|     return exp; |     return exp; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getAncestorExp({ancestorNoteId, ancestorDepth, includeHiddenNotes}) { | ||||||
|  |     if (ancestorNoteId && ancestorNoteId !== 'root') { | ||||||
|  |         return new AncestorExp(ancestorNoteId, ancestorDepth); | ||||||
|  |     } else if (!includeHiddenNotes) { | ||||||
|  |         return new NotExp(new IsHiddenExp()); | ||||||
|  |     } else { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = parse; | module.exports = parse; | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ const beccaService = require('../../../becca/becca_service'); | |||||||
| const utils = require('../../utils'); | const utils = require('../../utils'); | ||||||
| const log = require('../../log'); | const log = require('../../log'); | ||||||
| const scriptService = require("../../script"); | const scriptService = require("../../script"); | ||||||
|  | const hoistedNoteService = require("../../hoisted_note"); | ||||||
|  |  | ||||||
| function searchFromNote(note) { | function searchFromNote(note) { | ||||||
|     let searchResultNoteIds, highlightedTokens; |     let searchResultNoteIds, highlightedTokens; | ||||||
| @@ -272,7 +273,11 @@ function searchNotesForAutocomplete(query) { | |||||||
|     const searchContext = new SearchContext({ |     const searchContext = new SearchContext({ | ||||||
|         fastSearch: true, |         fastSearch: true, | ||||||
|         includeArchivedNotes: false, |         includeArchivedNotes: false, | ||||||
|         fuzzyAttributeSearch: true |         includeHiddenNotes: true, | ||||||
|  |         fuzzyAttributeSearch: true, | ||||||
|  |         ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree() | ||||||
|  |             ? 'root' | ||||||
|  |             : hoistedNoteService.getHoistedNoteId() | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     const allSearchResults = findResultsWithQuery(query, searchContext); |     const allSearchResults = findResultsWithQuery(query, searchContext); | ||||||
|   | |||||||
| @@ -218,7 +218,7 @@ function resetLauncher(noteId) { | |||||||
|         log.info(`Note ${noteId} is not a resettable launcher note.`); |         log.info(`Note ${noteId} is not a resettable launcher note.`); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     hiddenSubtreeService.checkHiddenSubtree(); |     // the re-building deleted launchers will be done in handlers | ||||||
| } | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ const {JSDOM} = require("jsdom"); | |||||||
| const shaca = require("./shaca/shaca"); | const shaca = require("./shaca/shaca"); | ||||||
| const assetPath = require("../services/asset_path"); | const assetPath = require("../services/asset_path"); | ||||||
| const shareRoot = require('./share_root'); | const shareRoot = require('./share_root'); | ||||||
|  | const escapeHtml = require('escape-html'); | ||||||
|  |  | ||||||
| function getContent(note) { | function getContent(note) { | ||||||
|     if (note.isProtected) { |     if (note.isProtected) { | ||||||
| @@ -112,17 +113,17 @@ function renderCode(result) { | |||||||
|  |  | ||||||
| function renderMermaid(result) { | function renderMermaid(result) { | ||||||
|     result.content = ` |     result.content = ` | ||||||
| <div class="mermaid">${result.content}</div> | <div class="mermaid">${escapeHtml(result.content)}</div> | ||||||
| <hr> | <hr> | ||||||
| <details> | <details> | ||||||
|     <summary>Chart source</summary> |     <summary>Chart source</summary> | ||||||
|     <pre>${result.content}</pre> |     <pre>${escapeHtml(result.content)}</pre> | ||||||
| </details>` | </details>` | ||||||
|     result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`; |     result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`; | ||||||
| } | } | ||||||
|  |  | ||||||
| function renderImage(result, note) { | function renderImage(result, note) { | ||||||
|     result.content = `<img src="api/images/${note.noteId}/${note.title}?${note.utcDateModified}">`; |     result.content = `<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">`; | ||||||
| } | } | ||||||
|  |  | ||||||
| function renderFile(note, result) { | function renderFile(note, result) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user