mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +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"); | ||||
|         } | ||||
|  | ||||
|         // 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.`); | ||||
|     } | ||||
|  | ||||
|     const hoistedNoteId = cls.getHoistedNoteId(); | ||||
|  | ||||
|     if (notePathArray.length === 1 && notePathArray[0] === hoistedNoteId) { | ||||
|         return [getNoteTitle(hoistedNoteId)]; | ||||
|     if (notePathArray.length === 1) { | ||||
|         return [getNoteTitle(notePathArray[0])]; | ||||
|     } | ||||
|  | ||||
|     const titles = []; | ||||
| @@ -94,6 +92,7 @@ function getNoteTitleArrayForPath(notePathArray) { | ||||
|     let hoistedNotePassed = false; | ||||
|  | ||||
|     // 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); | ||||
|  | ||||
|     for (const noteId of notePathArray) { | ||||
|   | ||||
| @@ -11,6 +11,7 @@ const BNoteRevision = require("./bnote_revision"); | ||||
| const TaskContext = require("../../services/task_context"); | ||||
| const dayjs = require("dayjs"); | ||||
| const utc = require('dayjs/plugin/utc'); | ||||
| const eventService = require("../../services/events"); | ||||
| dayjs.extend(utc) | ||||
|  | ||||
| const LABEL = 'label'; | ||||
| @@ -314,6 +315,11 @@ class BNote extends AbstractBeccaEntity { | ||||
|             utcDateChanged: pojo.utcDateModified, | ||||
|             isSynced: true | ||||
|         }); | ||||
|  | ||||
|         eventService.emit(eventService.ENTITY_CHANGED, { | ||||
|             entityName: 'note_contents', | ||||
|             entity: this | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     setJsonContent(content) { | ||||
| @@ -1124,6 +1130,13 @@ class BNote extends AbstractBeccaEntity { | ||||
|         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 | ||||
|      * @returns {boolean} - true if ancestorNoteId occurs in at least one of the note's paths | ||||
| @@ -1368,7 +1381,7 @@ class BNote extends AbstractBeccaEntity { | ||||
|     } | ||||
|  | ||||
|     isOptions() { | ||||
|         return this.noteId.startsWith("options"); | ||||
|         return this.noteId.startsWith("_options"); | ||||
|     } | ||||
|  | ||||
|     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 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) { | ||||
|         this.__validateTypeName(type, name); | ||||
|  | ||||
| @@ -852,7 +859,7 @@ class FNote { | ||||
|     } | ||||
|  | ||||
|     isOptions() { | ||||
|         return this.noteId.startsWith("options"); | ||||
|         return this.noteId.startsWith("_options"); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -230,6 +230,10 @@ function init() { | ||||
|  | ||||
|     $.fn.getSelectedNoteId = function () { | ||||
|         const notePath = $(this).getSelectedNotePath(); | ||||
|         if (!notePath) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         const chunks = notePath.split('/'); | ||||
|  | ||||
|         return chunks.length >= 1 ? chunks[chunks.length - 1] : null; | ||||
|   | ||||
| @@ -39,7 +39,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget { | ||||
|  | ||||
|     /** | ||||
|      * @param {function|string} command | ||||
|      * @returns {CommandButtonWidget} | ||||
|      * @returns {this} | ||||
|      */ | ||||
|     command(command) { | ||||
|         this.settings.command = command; | ||||
|   | ||||
| @@ -303,7 +303,7 @@ export default class GlobalMenuWidget extends BasicWidget { | ||||
|         const resp = await fetch(RELEASES_API_URL); | ||||
|         const data = await resp.json(); | ||||
|  | ||||
|         return data.tag_name.substring(1); | ||||
|         return data?.tag_name?.substring(1); | ||||
|     } | ||||
|  | ||||
|     downloadLatestVersionCommand() { | ||||
|   | ||||
| @@ -23,6 +23,10 @@ export default class HistoryNavigationButton extends ButtonFromNoteWidget { | ||||
|     doRender() { | ||||
|         super.doRender(); | ||||
|  | ||||
|         if (!utils.isElectron()) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.webContents = utils.dynamicRequire('@electron/remote').getCurrentWebContents(); | ||||
|  | ||||
|         // without this the history is preserved across frontend reloads | ||||
|   | ||||
| @@ -234,14 +234,14 @@ export default class NoteRevisionsDialog extends BasicWidget { | ||||
|                 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)); | ||||
|         } | ||||
|         else if (revisionItem.type === 'image') { | ||||
|             this.$content.html($("<img>") | ||||
|                 // 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 | ||||
|                 .attr("src", `data:${note.mime};base64,${fullNoteRevision.content}`) | ||||
|                 .attr("src", `data:${fullNoteRevision.mime};base64,${fullNoteRevision.content}`) | ||||
|                 .css("max-width", "100%") | ||||
|                 .css("max-height", "100%")); | ||||
|         } | ||||
|   | ||||
| @@ -76,7 +76,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget { | ||||
|  | ||||
|         this.$saveToNoteButton.toggle( | ||||
|             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=')); | ||||
|   | ||||
| @@ -1309,6 +1309,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | ||||
|             await this.tree.reload([rootNode]); | ||||
|         }); | ||||
|  | ||||
|         await this.filterHoistedBranch(); | ||||
|  | ||||
|         if (activeNotePath) { | ||||
|             const node = await this.getNodeFromPath(activeNotePath, true); | ||||
|  | ||||
|   | ||||
| @@ -270,7 +270,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|     async refreshWithNote(note) { | ||||
|         this.$component.show(); | ||||
|  | ||||
|         this.$saveToNoteButton.toggle(!note.getAllNotePaths().find(notePathArr => !notePathArr.includes('_hidden'))); | ||||
|         this.$saveToNoteButton.toggle(note.isHiddenCompletely()); | ||||
|  | ||||
|         this.$searchOptions.empty(); | ||||
|  | ||||
|   | ||||
| @@ -112,7 +112,7 @@ function update(name, value) { | ||||
| } | ||||
|  | ||||
| function getUserThemes() { | ||||
|     const notes = searchService.searchNotes("#appTheme"); | ||||
|     const notes = searchService.searchNotes("#appTheme", {ignoreHoistedNote: true}); | ||||
|     const ret = []; | ||||
|  | ||||
|     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: 'runOnNoteTitleChange', isDangerous: true }, | ||||
|     { type: 'relation', name: 'runOnNoteChange', isDangerous: true }, | ||||
|     { type: 'relation', name: 'runOnNoteContentChange', isDangerous: true }, | ||||
|     { type: 'relation', name: 'runOnNoteDeletion', isDangerous: true }, | ||||
|     { type: 'relation', name: 'runOnBranchCreation', isDangerous: true }, | ||||
|     { type: 'relation', name: 'runOnBranchDeletion', isDangerous: true }, | ||||
|   | ||||
| @@ -4,6 +4,8 @@ const treeService = require('./tree'); | ||||
| const noteService = require('./notes'); | ||||
| const becca = require('../becca/becca'); | ||||
| const BAttribute = require('../becca/entities/battribute'); | ||||
| const hiddenSubtreeService = require("./hidden_subtree"); | ||||
| const oneTimeTimer = require("./one_time_timer"); | ||||
|  | ||||
| function runAttachedRelations(note, relationName, originEntity) { | ||||
|     if (!note) { | ||||
| @@ -206,6 +208,16 @@ eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => | ||||
|     if (entityName === 'branches') { | ||||
|         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 = { | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| const becca = require("../becca/becca"); | ||||
| const noteService = require("./notes"); | ||||
| const BAttribute = require("../becca/entities/battribute"); | ||||
| const log = require("./log"); | ||||
| const migrationService = require("./migration"); | ||||
|  | ||||
| const LBTPL_ROOT = "_lbTplRoot"; | ||||
| const LBTPL_BASE = "_lbTplBase"; | ||||
| @@ -179,8 +181,10 @@ const HIDDEN_SUBTREE_DEFINITION = { | ||||
|                     isExpanded: true, | ||||
|                     attributes: [ { type: 'label', name: 'docName', value: 'launchbar_intro' } ], | ||||
|                     children: [ | ||||
|                         { 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' }, | ||||
|                         { id: '_lbBackInHistory', title: 'Go to Previous Note', type: 'launcher', builtinWidget: 'backInHistoryButton', icon: 'bx bxs-left-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' }, | ||||
|                     ] | ||||
|                 }, | ||||
| @@ -237,6 +241,12 @@ const HIDDEN_SUBTREE_DEFINITION = { | ||||
| }; | ||||
|  | ||||
| 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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -76,8 +76,8 @@ function importEnex(taskContext, file, parentNote) { | ||||
|         content = content.replace(/<\/ol>\s*<li>/g, "</ol></li><li>"); | ||||
|  | ||||
|         // Replace en-todo with unicode ballot box | ||||
|         content = content.replace(/<en-todo\s+checked="true"\/>/g, "\u2611 "); | ||||
|         content = content.replace(/<en-todo(\s+checked="false")?\/>/g, "\u2610 "); | ||||
|         content = content.replace(/<en-todo\s+checked="true"\s*\/>/g, "\u2611 "); | ||||
|         content = content.replace(/<en-todo(\s+checked="false")?\s*\/>/g, "\u2610 "); | ||||
|  | ||||
|         // Replace OneNote converted checkboxes with unicode ballot box based | ||||
|         // on known hash of checkboxes for regular, p1, and p2 checkboxes | ||||
|   | ||||
| @@ -119,5 +119,6 @@ async function migrateIfNecessary() { | ||||
| } | ||||
|  | ||||
| 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}'`); | ||||
|     } | ||||
|  | ||||
|     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.`); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return parentNote; | ||||
| } | ||||
| @@ -283,8 +288,12 @@ function protectNote(note, 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 | ||||
|                 note.setContent(content); | ||||
|             } | ||||
|  | ||||
|             note.save(); | ||||
|         } | ||||
| @@ -592,11 +601,6 @@ function updateNoteContent(noteId, content) { | ||||
|     content = saveLinks(note, 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 = {}) { | ||||
|         this.fastSearch = !!params.fastSearch; | ||||
|         this.includeArchivedNotes = !!params.includeArchivedNotes; | ||||
|         this.includeHiddenNotes = !!params.includeHiddenNotes; | ||||
|         this.ignoreHoistedNote = !!params.ignoreHoistedNote; | ||||
|         this.ancestorNoteId = params.ancestorNoteId; | ||||
|  | ||||
|         if (!this.ancestorNoteId && !this.ignoreHoistedNote && !hoistedNoteService.isHoistedInHiddenSubtree()) { | ||||
|         if (!this.ancestorNoteId && !this.ignoreHoistedNote) { | ||||
|             // hoisting in hidden subtree should not limit autocomplete | ||||
|             // since we want to link (create relations) to the normal non-hidden notes | ||||
|             this.ancestorNoteId = hoistedNoteService.getHoistedNoteId(); | ||||
|   | ||||
| @@ -19,6 +19,7 @@ const buildComparator = require('./build_comparator'); | ||||
| const ValueExtractor = require('../value_extractor'); | ||||
| const utils = require("../../utils"); | ||||
| const TrueExp = require("../expressions/true"); | ||||
| const IsHiddenExp = require("../expressions/is_hidden"); | ||||
|  | ||||
| function getFulltext(tokens, searchContext) { | ||||
|     tokens = tokens.map(t => utils.removeDiacritic(t.token)); | ||||
| @@ -429,7 +430,7 @@ function parse({fulltextTokens, expressionTokens, searchContext}) { | ||||
|  | ||||
|     let exp = AndExp.of([ | ||||
|         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), | ||||
|         expression | ||||
|     ]); | ||||
| @@ -448,4 +449,14 @@ function parse({fulltextTokens, expressionTokens, searchContext}) { | ||||
|     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; | ||||
|   | ||||
| @@ -11,6 +11,7 @@ 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) { | ||||
|     let searchResultNoteIds, highlightedTokens; | ||||
| @@ -272,7 +273,11 @@ function searchNotesForAutocomplete(query) { | ||||
|     const searchContext = new SearchContext({ | ||||
|         fastSearch: true, | ||||
|         includeArchivedNotes: false, | ||||
|         fuzzyAttributeSearch: true | ||||
|         includeHiddenNotes: true, | ||||
|         fuzzyAttributeSearch: true, | ||||
|         ancestorNoteId: hoistedNoteService.isHoistedInHiddenSubtree() | ||||
|             ? 'root' | ||||
|             : hoistedNoteService.getHoistedNoteId() | ||||
|     }); | ||||
|  | ||||
|     const allSearchResults = findResultsWithQuery(query, searchContext); | ||||
|   | ||||
| @@ -218,7 +218,7 @@ function resetLauncher(noteId) { | ||||
|         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 assetPath = require("../services/asset_path"); | ||||
| const shareRoot = require('./share_root'); | ||||
| const escapeHtml = require('escape-html'); | ||||
|  | ||||
| function getContent(note) { | ||||
|     if (note.isProtected) { | ||||
| @@ -112,17 +113,17 @@ function renderCode(result) { | ||||
|  | ||||
| function renderMermaid(result) { | ||||
|     result.content = ` | ||||
| <div class="mermaid">${result.content}</div> | ||||
| <div class="mermaid">${escapeHtml(result.content)}</div> | ||||
| <hr> | ||||
| <details> | ||||
|     <summary>Chart source</summary> | ||||
|     <pre>${result.content}</pre> | ||||
|     <pre>${escapeHtml(result.content)}</pre> | ||||
| </details>` | ||||
|     result.header += `<script src="../../${assetPath}/libraries/mermaid.min.js"></script>`; | ||||
| } | ||||
|  | ||||
| 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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user