mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			32 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | fb3d5f25ac | ||
|  | 9d7d79ef94 | ||
|  | ba33a0d330 | ||
|  | aea81c9872 | ||
|  | 806ab22fa8 | ||
|  | 9c2b98915e | ||
|  | f2ca9276d6 | ||
|  | 48b697f408 | ||
|  | e1e185e5db | ||
|  | e06c5703ee | ||
|  | fe3bb2c5f6 | ||
|  | 6afc299efb | ||
|  | 369274ead7 | ||
|  | 04e6431c09 | ||
|  | e89057a771 | ||
|  | 4f27254e64 | ||
|  | 577dc95ab8 | ||
|  | a266d6a3d5 | ||
|  | 749b6cb57e | ||
|  | b0b2951ff6 | ||
|  | 1f3d73b9fd | ||
|  | bdfd760b9d | ||
|  | 7133e60267 | ||
|  | fc4edf4aa7 | ||
|  | eaf93a70cd | ||
|  | b093569ec5 | ||
|  | 4633c68a0c | ||
|  | 33571e0ef3 | ||
|  | 31876d2cf9 | ||
|  | 81c6043cb6 | ||
|  | 1982d054ef | ||
|  | e56979c482 | 
| @@ -1,10 +1,13 @@ | |||||||
| [General] | [General] | ||||||
| # Instance name can be used to distinguish between different instances | # Instance name can be used to distinguish between different instances using backend api.getInstanceName() | ||||||
| instanceName= | instanceName= | ||||||
|  |  | ||||||
| # set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password) | # set to true to allow using Trilium without authentication (makes sense for server build only, desktop build doesn't need password) | ||||||
| noAuthentication=false | noAuthentication=false | ||||||
|  |  | ||||||
|  | # set to true to disable backups (e.g. because of limited space on server) | ||||||
|  | noBackup=false | ||||||
|  |  | ||||||
| # Disable automatically generating desktop icon | # Disable automatically generating desktop icon | ||||||
| # noDesktopIcon=true | # noDesktopIcon=true | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.45.5", |   "version": "0.45.6", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.45.6", |   "version": "0.45.9", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
|   | |||||||
| @@ -51,6 +51,12 @@ const TPL = ` | |||||||
|         <label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label> |         <label for="erase-notes-after-time-in-seconds">Erase notes after X seconds</label> | ||||||
|         <input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0"> |         <input class="form-control" id="erase-notes-after-time-in-seconds" type="number" min="0"> | ||||||
|     </div> |     </div> | ||||||
|  |      | ||||||
|  |     <p>You can also trigger erasing manually:</p> | ||||||
|  |      | ||||||
|  |     <button id="erase-deleted-notes-now-button" class="btn">Erase deleted notes now</button> | ||||||
|  |      | ||||||
|  |     <br/><br/> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
| <div> | <div> | ||||||
| @@ -117,6 +123,13 @@ export default class ProtectedSessionOptions { | |||||||
|             return false; |             return false; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         this.$eraseDeletedNotesButton = $("#erase-deleted-notes-now-button"); | ||||||
|  |         this.$eraseDeletedNotesButton.on('click', () => { | ||||||
|  |             server.post('notes/erase-deleted-notes-now').then(() => { | ||||||
|  |                 toastService.showMessage("Deleted notes have been erased."); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds"); |         this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds"); | ||||||
|  |  | ||||||
|         this.$protectedSessionTimeout.on('change', () => { |         this.$protectedSessionTimeout.on('change', () => { | ||||||
|   | |||||||
| @@ -75,15 +75,17 @@ class NoteShort { | |||||||
|         this.parentToBranch[parentNoteId] = branchId; |         this.parentToBranch[parentNoteId] = branchId; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     addChild(childNoteId, branchId) { |     addChild(childNoteId, branchId, sort = true) { | ||||||
|         if (!this.children.includes(childNoteId)) { |         if (!this.children.includes(childNoteId)) { | ||||||
|             this.children.push(childNoteId); |             this.children.push(childNoteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.childToBranch[childNoteId] = branchId; |         this.childToBranch[childNoteId] = branchId; | ||||||
|  |  | ||||||
|  |         if (sort) { | ||||||
|             this.sortChildren(); |             this.sortChildren(); | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     sortChildren() { |     sortChildren() { | ||||||
|         const branchIdPos = {}; |         const branchIdPos = {}; | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ async function syncNow() { | |||||||
|         toastService.showMessage("Sync finished successfully."); |         toastService.showMessage("Sync finished successfully."); | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|         if (result.message.length > 100) { |         if (result.message.length > 200) { | ||||||
|             result.message = result.message.substr(0, 100); |             result.message = result.message.substr(0, 200) + "..."; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         toastService.showError("Sync failed: " + result.message); |         toastService.showError("Sync failed: " + result.message); | ||||||
|   | |||||||
| @@ -87,6 +87,8 @@ class TreeCache { | |||||||
|         const branchRows = resp.branches; |         const branchRows = resp.branches; | ||||||
|         const attributeRows = resp.attributes; |         const attributeRows = resp.attributes; | ||||||
|  |  | ||||||
|  |         const noteIdsToSort = new Set(); | ||||||
|  |  | ||||||
|         for (const noteRow of noteRows) { |         for (const noteRow of noteRows) { | ||||||
|             const {noteId} = noteRow; |             const {noteId} = noteRow; | ||||||
|  |  | ||||||
| @@ -153,7 +155,9 @@ class TreeCache { | |||||||
|             const parentNote = this.notes[branch.parentNoteId]; |             const parentNote = this.notes[branch.parentNoteId]; | ||||||
|  |  | ||||||
|             if (parentNote) { |             if (parentNote) { | ||||||
|                 parentNote.addChild(branch.noteId, branch.branchId); |                 parentNote.addChild(branch.noteId, branch.branchId, false); | ||||||
|  |  | ||||||
|  |                 noteIdsToSort.add(parentNote.noteId); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -178,6 +182,11 @@ class TreeCache { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // sort all of them at once, this avoids repeated sorts (#1480) | ||||||
|  |         for (const noteId of noteIdsToSort) { | ||||||
|  |             this.notes[noteId].sortChildren(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async reloadNotes(noteIds) { |     async reloadNotes(noteIds) { | ||||||
|   | |||||||
| @@ -234,6 +234,8 @@ export default class AttributeDetailWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|         this.$inputName = this.$widget.find('.attr-input-name'); |         this.$inputName = this.$widget.find('.attr-input-name'); | ||||||
|         this.$inputName.on('keyup', () => this.userEditedAttribute()); |         this.$inputName.on('keyup', () => this.userEditedAttribute()); | ||||||
|  |         this.$inputName.on('change', () => this.userEditedAttribute()); | ||||||
|  |         this.$inputName.on('autocomplete:closed', () => this.userEditedAttribute()); | ||||||
|  |  | ||||||
|         this.$inputName.on('focus', () => { |         this.$inputName.on('focus', () => { | ||||||
|             attributeAutocompleteService.initAttributeNameAutocomplete({ |             attributeAutocompleteService.initAttributeNameAutocomplete({ | ||||||
|   | |||||||
| @@ -248,13 +248,22 @@ export default class NoteDetailWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|         this.$widget.find('.note-detail-printable:visible').printThis({ |         this.$widget.find('.note-detail-printable:visible').printThis({ | ||||||
|             header: $("<h2>").text(this.note && this.note.title).prop('outerHTML'), |             header: $("<h2>").text(this.note && this.note.title).prop('outerHTML'), | ||||||
|             footer: "<script>document.body.className += ' ck-content printed-content';</script>", |             footer: ` | ||||||
|  | <script src="libraries/katex/katex.min.js"></script> | ||||||
|  | <script src="libraries/katex/auto-render.min.js"></script> | ||||||
|  | <script> | ||||||
|  |     document.body.className += ' ck-content printed-content'; | ||||||
|  |      | ||||||
|  |     renderMathInElement(document.body, {}); | ||||||
|  | </script> | ||||||
|  | `, | ||||||
|             importCSS: false, |             importCSS: false, | ||||||
|             loadCSS: [ |             loadCSS: [ | ||||||
|                 "libraries/codemirror/codemirror.css", |                 "libraries/codemirror/codemirror.css", | ||||||
|                 "libraries/ckeditor/ckeditor-content.css", |                 "libraries/ckeditor/ckeditor-content.css", | ||||||
|                 "libraries/ckeditor/ckeditor-content.css", |                 "libraries/ckeditor/ckeditor-content.css", | ||||||
|                 "libraries/bootstrap/css/bootstrap.min.css", |                 "libraries/bootstrap/css/bootstrap.min.css", | ||||||
|  |                 "libraries/katex/katex.min.css", | ||||||
|                 "stylesheets/print.css", |                 "stylesheets/print.css", | ||||||
|                 "stylesheets/relation_map.css", |                 "stylesheets/relation_map.css", | ||||||
|                 "stylesheets/themes.css" |                 "stylesheets/themes.css" | ||||||
|   | |||||||
| @@ -59,6 +59,7 @@ export default class NotePathsWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|         this.$currentPath = this.$widget.find('.current-path'); |         this.$currentPath = this.$widget.find('.current-path'); | ||||||
|         this.$dropdown = this.$widget.find(".dropdown"); |         this.$dropdown = this.$widget.find(".dropdown"); | ||||||
|  |         this.$dropdownToggle = this.$widget.find('.dropdown-toggle'); | ||||||
|  |  | ||||||
|         this.$notePathList = this.$dropdown.find(".note-path-list"); |         this.$notePathList = this.$dropdown.find(".note-path-list"); | ||||||
|  |  | ||||||
| @@ -100,6 +101,8 @@ export default class NotePathsWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|             parentNoteId = noteId; |             parentNoteId = noteId; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         this.$dropdownToggle.dropdown('hide'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async renderDropdown() { |     async renderDropdown() { | ||||||
| @@ -141,20 +144,20 @@ export default class NotePathsWidget extends TabAwareWidget { | |||||||
|     async addPath(notePath, isCurrent) { |     async addPath(notePath, isCurrent) { | ||||||
|         const title = await treeService.getNotePathTitle(notePath); |         const title = await treeService.getNotePathTitle(notePath); | ||||||
|  |  | ||||||
|         const noteLink = await linkService.createNoteLink(notePath, {title}); |         const $noteLink = await linkService.createNoteLink(notePath, {title}); | ||||||
|  |  | ||||||
|         noteLink |         $noteLink | ||||||
|             .addClass("dropdown-item"); |             .addClass("dropdown-item"); | ||||||
|  |  | ||||||
|         noteLink |         $noteLink | ||||||
|             .find('a') |             .find('a') | ||||||
|             .addClass("no-tooltip-preview"); |             .addClass("no-tooltip-preview"); | ||||||
|  |  | ||||||
|         if (isCurrent) { |         if (isCurrent) { | ||||||
|             noteLink.addClass("current"); |             $noteLink.addClass("current"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.$notePathList.append(noteLink); |         this.$notePathList.append($noteLink); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     entitiesReloadedEvent({loadResults}) { |     entitiesReloadedEvent({loadResults}) { | ||||||
|   | |||||||
| @@ -1198,7 +1198,25 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         this.clearSelectedNodes(); |         this.clearSelectedNodes(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     canBeMovedUpOrDown(node) { | ||||||
|  |         if (node.data.noteId === 'root') { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         const parentNote = treeCache.getNoteFromCache(node.getParent().data.noteId); | ||||||
|  |  | ||||||
|  |         if (parentNote && parentNote.hasLabel('sorted')) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     moveNoteUpCommand({node}) { |     moveNoteUpCommand({node}) { | ||||||
|  |         if (!this.canBeMovedUpOrDown(node)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const beforeNode = node.getPrevSibling(); |         const beforeNode = node.getPrevSibling(); | ||||||
|  |  | ||||||
|         if (beforeNode !== null) { |         if (beforeNode !== null) { | ||||||
| @@ -1207,7 +1225,12 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     moveNoteDownCommand({node}) { |     moveNoteDownCommand({node}) { | ||||||
|  |         if (!this.canBeMovedUpOrDown(node)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const afterNode = node.getNextSibling(); |         const afterNode = node.getNextSibling(); | ||||||
|  |  | ||||||
|         if (afterNode !== null) { |         if (afterNode !== null) { | ||||||
|             branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId); |             branchService.moveAfterBranch([node.data.branchId], afterNode.data.branchId); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -41,6 +41,9 @@ export default class NoteTypeWidget extends TabAwareWidget { | |||||||
|         this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); |         this.$noteTypeDropdown = this.$widget.find(".note-type-dropdown"); | ||||||
|         this.$noteTypeButton = this.$widget.find(".note-type-button"); |         this.$noteTypeButton = this.$widget.find(".note-type-button"); | ||||||
|         this.$noteTypeDesc = this.$widget.find(".note-type-desc"); |         this.$noteTypeDesc = this.$widget.find(".note-type-desc"); | ||||||
|  |  | ||||||
|  |         this.$widget.on('click', '.dropdown-item', | ||||||
|  |             () => this.$widget.find('.dropdown-toggle').dropdown('toggle')); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshWithNote(note) { |     async refreshWithNote(note) { | ||||||
| @@ -64,8 +67,6 @@ export default class NoteTypeWidget extends TabAwareWidget { | |||||||
|                     const noteType = NOTE_TYPES.find(nt => nt.type === type); |                     const noteType = NOTE_TYPES.find(nt => nt.type === type); | ||||||
|  |  | ||||||
|                     this.save(noteType.type, noteType.mime); |                     this.save(noteType.type, noteType.mime); | ||||||
|  |  | ||||||
|                     this.$widget.find('.dropdown-toggle').dropdown('toggle'); |  | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|             if (this.note.type === noteType.type) { |             if (this.note.type === noteType.type) { | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ const TPL = ` | |||||||
|         cursor: text !important; |         cursor: text !important; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .note-detail-editable-text *:first-child { |     .note-detail-editable-text *:not(figure):first-child { | ||||||
|         margin-top: 0 !important; |         margin-top: 0 !important; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
| @@ -67,6 +67,11 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async doRefresh(note) { |     async doRefresh(note) { | ||||||
|  |         // we load CKEditor also for read only notes because they contain content styles required for correct rendering of even read only notes | ||||||
|  |         // we could load just ckeditor-content.css but that causes CSS conflicts when both build CSS and this content CSS is loaded at the same time | ||||||
|  |         // (see https://github.com/zadam/trilium/issues/1590 for example of such conflict) | ||||||
|  |         await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); | ||||||
|  |  | ||||||
|         const noteComplement = await treeCache.getNoteComplement(note.noteId); |         const noteComplement = await treeCache.getNoteComplement(note.noteId); | ||||||
|  |  | ||||||
|         this.$content.html(noteComplement.content); |         this.$content.html(noteComplement.content); | ||||||
|   | |||||||
| @@ -703,6 +703,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | |||||||
|     padding: 10px; |     padding: 10px; | ||||||
|     border-radius: 10px; |     border-radius: 10px; | ||||||
|     background-color: var(--accented-background-color); |     background-color: var(--accented-background-color); | ||||||
|  |     clear: both; | ||||||
| } | } | ||||||
|  |  | ||||||
| .include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */ | .include-note.ck-placeholder::before { /* remove placeholder in otherwise empty note */ | ||||||
|   | |||||||
| @@ -23,11 +23,7 @@ function exportBranch(req, res) { | |||||||
|  |  | ||||||
|     try { |     try { | ||||||
|         if (type === 'subtree' && (format === 'html' || format === 'markdown')) { |         if (type === 'subtree' && (format === 'html' || format === 'markdown')) { | ||||||
|             const start = Date.now(); |  | ||||||
|  |  | ||||||
|             zipExportService.exportToZip(taskContext, branch, format, res); |             zipExportService.exportToZip(taskContext, branch, format, res); | ||||||
|  |  | ||||||
|             console.log("Export took", Date.now() - start, "ms"); |  | ||||||
|         } |         } | ||||||
|         else if (type === 'single') { |         else if (type === 'single') { | ||||||
|             singleExportService.exportSingleNote(taskContext, branch, format, res); |             singleExportService.exportSingleNote(taskContext, branch, format, res); | ||||||
|   | |||||||
| @@ -193,6 +193,10 @@ function duplicateSubtree(req) { | |||||||
|     return noteService.duplicateSubtree(noteId, parentNoteId); |     return noteService.duplicateSubtree(noteId, parentNoteId); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function eraseDeletedNotesNow() { | ||||||
|  |     noteService.eraseDeletedNotesNow(); | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNote, |     getNote, | ||||||
|     updateNote, |     updateNote, | ||||||
| @@ -204,5 +208,6 @@ module.exports = { | |||||||
|     setNoteTypeMime, |     setNoteTypeMime, | ||||||
|     getRelationMap, |     getRelationMap, | ||||||
|     changeTitle, |     changeTitle, | ||||||
|     duplicateSubtree |     duplicateSubtree, | ||||||
|  |     eraseDeletedNotesNow | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -54,12 +54,22 @@ function getBundlesWithLabel(label, value) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function getStartupBundles() { | function getStartupBundles() { | ||||||
|  |     if (!process.env.TRILIUM_SAFE_MODE) { | ||||||
|         return getBundlesWithLabel("run", "frontendStartup"); |         return getBundlesWithLabel("run", "frontendStartup"); | ||||||
|     } |     } | ||||||
|  |     else { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function getWidgetBundles() { | function getWidgetBundles() { | ||||||
|  |     if (!process.env.TRILIUM_SAFE_MODE) { | ||||||
|         return getBundlesWithLabel("widget"); |         return getBundlesWithLabel("widget"); | ||||||
|     } |     } | ||||||
|  |     else { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function getRelationBundles(req) { | function getRelationBundles(req) { | ||||||
|     const noteId = req.params.noteId; |     const noteId = req.params.noteId; | ||||||
|   | |||||||
| @@ -38,6 +38,8 @@ function saveSyncSeed(req) { | |||||||
|         }] |         }] | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     log.info("Saved sync seed."); | ||||||
|  |  | ||||||
|     sqlInit.createDatabaseForSync(options); |     sqlInit.createDatabaseForSync(options); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ function getTree(req) { | |||||||
|     const noteIds = sql.getColumn(` |     const noteIds = sql.getColumn(` | ||||||
|         WITH RECURSIVE |         WITH RECURSIVE | ||||||
|             treeWithDescendants(noteId, isExpanded) AS ( |             treeWithDescendants(noteId, isExpanded) AS ( | ||||||
|                 SELECT noteId, 1 FROM branches WHERE parentNoteId = ? AND isDeleted = 0 |                 SELECT noteId, isExpanded FROM branches WHERE parentNoteId = ? AND isDeleted = 0 | ||||||
|                 UNION |                 UNION | ||||||
|                 SELECT branches.noteId, branches.isExpanded FROM branches |                 SELECT branches.noteId, branches.isExpanded FROM branches | ||||||
|                   JOIN treeWithDescendants ON branches.parentNoteId = treeWithDescendants.noteId |                   JOIN treeWithDescendants ON branches.parentNoteId = treeWithDescendants.noteId | ||||||
|   | |||||||
| @@ -153,6 +153,7 @@ function register(app) { | |||||||
|     route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); |     route(GET, '/api/notes/:noteId/revisions/:noteRevisionId/download', [auth.checkApiAuthOrElectron], noteRevisionsApiRoute.downloadNoteRevision); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision); |     apiRoute(PUT, '/api/notes/:noteId/restore-revision/:noteRevisionId', noteRevisionsApiRoute.restoreNoteRevision); | ||||||
|     apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); |     apiRoute(POST, '/api/notes/relation-map', notesApiRoute.getRelationMap); | ||||||
|  |     apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); |     apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle); | ||||||
|     apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree); |     apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2020-12-04T22:08:24+01:00", buildRevision: "b7b1324dd0b2af6abc8cd5b98f620ca227582d4d" }; | module.exports = { buildDate:"2021-02-05T21:38:32+01:00", buildRevision: "9d7d79ef94bc7734393ae7f89993e76bbc7d97e3" }; | ||||||
|   | |||||||
| @@ -650,7 +650,7 @@ class ConsistencyChecks { | |||||||
|         // root branch should always be expanded |         // root branch should always be expanded | ||||||
|         sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'"); |         sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'"); | ||||||
|  |  | ||||||
|         if (this.unrecoveredConsistencyErrors) { |         if (!this.unrecoveredConsistencyErrors) { | ||||||
|             // we run this only if basic checks passed since this assumes basic data consistency |             // we run this only if basic checks passed since this assumes basic data consistency | ||||||
|  |  | ||||||
|             this.checkTreeCycles(); |             this.checkTreeCycles(); | ||||||
|   | |||||||
| @@ -52,6 +52,10 @@ function encrypt(key, plainText, ivLength = 13) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function decrypt(key, cipherText, ivLength = 13) { | function decrypt(key, cipherText, ivLength = 13) { | ||||||
|  |     if (cipherText === null) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (!key) { |     if (!key) { | ||||||
|         return "[protected]"; |         return "[protected]"; | ||||||
|     } |     } | ||||||
| @@ -93,6 +97,10 @@ function decrypt(key, cipherText, ivLength = 13) { | |||||||
| function decryptString(dataKey, cipherText) { | function decryptString(dataKey, cipherText) { | ||||||
|     const buffer = decrypt(dataKey, cipherText); |     const buffer = decrypt(dataKey, cipherText); | ||||||
|  |  | ||||||
|  |     if (buffer === null) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const str = buffer.toString('utf-8'); |     const str = buffer.toString('utf-8'); | ||||||
|  |  | ||||||
|     if (str === 'false') { |     if (str === 'false') { | ||||||
|   | |||||||
| @@ -143,7 +143,7 @@ function exportToZip(taskContext, branch, format, res) { | |||||||
|         const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable(); |         const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable(); | ||||||
|  |  | ||||||
|         // if it's a leaf then we'll export it even if it's empty |         // if it's a leaf then we'll export it even if it's empty | ||||||
|         if (available && ((note.getContent()).length > 0 || childBranches.length === 0)) { |         if (available && (note.getContent().length > 0 || childBranches.length === 0)) { | ||||||
|             meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames); |             meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -234,7 +234,7 @@ function exportToZip(taskContext, branch, format, res) { | |||||||
|     <link rel="stylesheet" href="${cssUrl}"> |     <link rel="stylesheet" href="${cssUrl}"> | ||||||
|     <base target="_parent"> |     <base target="_parent"> | ||||||
| </head> | </head> | ||||||
| <body> | <body class="ck-content"> | ||||||
|   <h1>${utils.escapeHtml(title)}</h1> |   <h1>${utils.escapeHtml(title)}</h1> | ||||||
| ${content} | ${content} | ||||||
| </body> | </body> | ||||||
| @@ -433,14 +433,13 @@ ${content} | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     const note = branch.getNote(); |     const note = branch.getNote(); | ||||||
|     const zipFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".zip"; |     const zipFileName = (branch.prefix ? `${branch.prefix} - ` : "") + note.title + ".zip"; | ||||||
|  |  | ||||||
|     res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName)); |     res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName)); | ||||||
|     res.setHeader('Content-Type', 'application/zip'); |     res.setHeader('Content-Type', 'application/zip'); | ||||||
|  |  | ||||||
|     zipFile.end(); |  | ||||||
|  |  | ||||||
|     zipFile.outputStream.pipe(res); |     zipFile.outputStream.pipe(res); | ||||||
|  |     zipFile.end(); | ||||||
|  |  | ||||||
|     taskContext.taskSucceeded(); |     taskContext.taskSucceeded(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ const scriptService = require('./script'); | |||||||
| const treeService = require('./tree'); | const treeService = require('./tree'); | ||||||
| const noteService = require('./notes'); | const noteService = require('./notes'); | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
|  | const noteCache = require('./note_cache/note_cache'); | ||||||
| const Attribute = require('../entities/attribute'); | const Attribute = require('../entities/attribute'); | ||||||
|  |  | ||||||
| function runAttachedRelations(note, relationName, originEntity) { | function runAttachedRelations(note, relationName, originEntity) { | ||||||
| @@ -22,11 +23,15 @@ eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => { | |||||||
|     runAttachedRelations(note, 'runOnNoteTitleChange', note); |     runAttachedRelations(note, 'runOnNoteTitleChange', note); | ||||||
|  |  | ||||||
|     if (!note.isRoot()) { |     if (!note.isRoot()) { | ||||||
|         const parents = note.getParentNotes(); |         const noteFromCache = noteCache.notes[note.noteId]; | ||||||
|  |  | ||||||
|         for (const parent of parents) { |         if (!noteFromCache) { | ||||||
|             if (parent.hasOwnedLabel("sorted")) { |             return; | ||||||
|                 treeService.sortNotesAlphabetically(parent.noteId); |         } | ||||||
|  |  | ||||||
|  |         for (const parentNote of noteFromCache.parents) { | ||||||
|  |             if (parentNote.hasLabel("sorted")) { | ||||||
|  |                 treeService.sortNotesAlphabetically(parentNote.noteId); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -70,12 +75,26 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => | |||||||
|                 if (templateNoteContent) { |                 if (templateNoteContent) { | ||||||
|                     note.setContent(templateNoteContent); |                     note.setContent(templateNoteContent); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 note.type = templateNote.type; | ||||||
|  |                 note.mime = templateNote.mime; | ||||||
|  |                 note.save(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId); |             noteService.duplicateSubtreeWithoutRoot(templateNote.noteId, note.noteId); | ||||||
|         } |         } | ||||||
|         else if (entity.type === 'label' && entity.name === 'sorted') { |         else if (entity.type === 'label' && entity.name === 'sorted') { | ||||||
|             treeService.sortNotesAlphabetically(entity.noteId); |             treeService.sortNotesAlphabetically(entity.noteId); | ||||||
|  |  | ||||||
|  |             if (entity.isInheritable) { | ||||||
|  |                 const note = noteCache.notes[entity.noteId]; | ||||||
|  |  | ||||||
|  |                 if (note) { | ||||||
|  |                     for (const noteId of note.subtreeNoteIds) { | ||||||
|  |                         treeService.sortNotesAlphabetically(noteId); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     else if (entityName === 'notes') { |     else if (entityName === 'notes') { | ||||||
| @@ -90,10 +109,10 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote | |||||||
| function processInverseRelations(entityName, entity, handler) { | function processInverseRelations(entityName, entity, handler) { | ||||||
|     if (entityName === 'attributes' && entity.type === 'relation') { |     if (entityName === 'attributes' && entity.type === 'relation') { | ||||||
|         const note = entity.getNote(); |         const note = entity.getNote(); | ||||||
|         const attributes = (note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition'); |         const relDefinitions = note.getLabels('relation:' + entity.name); | ||||||
|  |  | ||||||
|         for (const attribute of attributes) { |         for (const relDefinition of relDefinitions) { | ||||||
|             const definition = attribute.value; |             const definition = relDefinition.getDefinition(); | ||||||
|  |  | ||||||
|             if (definition.inverseRelation && definition.inverseRelation.trim()) { |             if (definition.inverseRelation && definition.inverseRelation.trim()) { | ||||||
|                 const targetNote = entity.getTargetNote(); |                 const targetNote = entity.getTargetNote(); | ||||||
|   | |||||||
| @@ -133,6 +133,14 @@ class Note { | |||||||
|         return !!this.attributes.find(attr => attr.type === type && attr.name === name); |         return !!this.attributes.find(attr => attr.type === type && attr.name === name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     hasLabel(name) { | ||||||
|  |         return this.hasAttribute('label', name); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     hasRelation(name) { | ||||||
|  |         return this.hasAttribute('relation', name); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     getLabelValue(name) { |     getLabelValue(name) { | ||||||
|         const label = this.attributes.find(attr => attr.type === 'label' && attr.name === name); |         const label = this.attributes.find(attr => attr.type === 'label' && attr.name === name); | ||||||
|  |  | ||||||
| @@ -275,6 +283,11 @@ class Note { | |||||||
|         return arr.flat(); |         return arr.flat(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** @return {String[]} */ | ||||||
|  |     get subtreeNoteIds() { | ||||||
|  |         return this.subtreeNotes.map(note => note.noteId); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     get parentCount() { |     get parentCount() { | ||||||
|         return this.parents.length; |         return this.parents.length; | ||||||
|     } |     } | ||||||
| @@ -338,7 +351,7 @@ class Note { | |||||||
|  |  | ||||||
|     decrypt() { |     decrypt() { | ||||||
|         if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { |         if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { | ||||||
|             this.title = protectedSessionService.decryptString(note.title); |             this.title = protectedSessionService.decryptString(this.title); | ||||||
|  |  | ||||||
|             this.isDecrypted = true; |             this.isDecrypted = true; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1,9 +1,5 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const Note = require('./entities/note'); |  | ||||||
| const Branch = require('./entities/branch'); |  | ||||||
| const Attribute = require('./entities/attribute'); |  | ||||||
|  |  | ||||||
| class NoteCache { | class NoteCache { | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.reset(); |         this.reset(); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| const NoteRevision = require('../entities/note_revision'); | const NoteRevision = require('../entities/note_revision'); | ||||||
| const dateUtils = require('../services/date_utils'); | const dateUtils = require('../services/date_utils'); | ||||||
|  | const log = require('../services/log'); | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @param {Note} note |  * @param {Note} note | ||||||
| @@ -9,6 +10,7 @@ const dateUtils = require('../services/date_utils'); | |||||||
| function protectNoteRevisions(note) { | function protectNoteRevisions(note) { | ||||||
|     for (const revision of note.getRevisions()) { |     for (const revision of note.getRevisions()) { | ||||||
|         if (note.isProtected !== revision.isProtected) { |         if (note.isProtected !== revision.isProtected) { | ||||||
|  |             try { | ||||||
|                 const content = revision.getContent(); |                 const content = revision.getContent(); | ||||||
|  |  | ||||||
|                 revision.isProtected = note.isProtected; |                 revision.isProtected = note.isProtected; | ||||||
| @@ -18,6 +20,12 @@ function protectNoteRevisions(note) { | |||||||
|  |  | ||||||
|                 revision.save(); |                 revision.save(); | ||||||
|             } |             } | ||||||
|  |             catch (e) { | ||||||
|  |                 log.error("Could not un/protect note revision ID = " + revision.noteRevisionId); | ||||||
|  |  | ||||||
|  |                 throw e; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -185,6 +185,7 @@ function protectNoteRecursively(note, protect, includingSubTree, taskContext) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function protectNote(note, protect) { | function protectNote(note, protect) { | ||||||
|  |     try { | ||||||
|         if (protect !== note.isProtected) { |         if (protect !== note.isProtected) { | ||||||
|             const content = note.getContent(); |             const content = note.getContent(); | ||||||
|  |  | ||||||
| @@ -198,6 +199,12 @@ function protectNote(note, protect) { | |||||||
|  |  | ||||||
|         noteRevisionService.protectNoteRevisions(note); |         noteRevisionService.protectNoteRevisions(note); | ||||||
|     } |     } | ||||||
|  |     catch (e) { | ||||||
|  |         log.error("Could not un/protect note ID = " + note.noteId); | ||||||
|  |  | ||||||
|  |         throw e; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| function findImageLinks(content, foundLinks) { | function findImageLinks(content, foundLinks) { | ||||||
|     const re = /src="[^"]*api\/images\/([a-zA-Z0-9]+)\//g; |     const re = /src="[^"]*api\/images\/([a-zA-Z0-9]+)\//g; | ||||||
| @@ -668,8 +675,10 @@ function scanForLinks(note) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function eraseDeletedNotes() { | function eraseDeletedNotes(eraseNotesAfterTimeInSeconds = null) { | ||||||
|     const eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds'); |     if (eraseNotesAfterTimeInSeconds === null) { | ||||||
|  |         eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000); |     const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000); | ||||||
|  |  | ||||||
| @@ -719,6 +728,10 @@ function eraseDeletedNotes() { | |||||||
|     log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); |     log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function eraseDeletedNotesNow() { | ||||||
|  |     eraseDeletedNotes(0); | ||||||
|  | } | ||||||
|  |  | ||||||
| // do a replace in str - all keys should be replaced by the corresponding values | // do a replace in str - all keys should be replaced by the corresponding values | ||||||
| function replaceByMap(str, mapObj) { | function replaceByMap(str, mapObj) { | ||||||
|     const re = new RegExp(Object.keys(mapObj).join("|"),"g"); |     const re = new RegExp(Object.keys(mapObj).join("|"),"g"); | ||||||
| @@ -825,9 +838,9 @@ function getNoteIdMapping(origNote) { | |||||||
|  |  | ||||||
| sqlInit.dbReady.then(() => { | sqlInit.dbReady.then(() => { | ||||||
|     // first cleanup kickoff 5 minutes after startup |     // first cleanup kickoff 5 minutes after startup | ||||||
|     setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000); |     setTimeout(cls.wrap(() => eraseDeletedNotes()), 5 * 60 * 1000); | ||||||
|  |  | ||||||
|     setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000); |     setInterval(cls.wrap(() => eraseDeletedNotes()), 4 * 3600 * 1000); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
| @@ -841,5 +854,6 @@ module.exports = { | |||||||
|     duplicateSubtree, |     duplicateSubtree, | ||||||
|     duplicateSubtreeWithoutRoot, |     duplicateSubtreeWithoutRoot, | ||||||
|     getUndeletedParentBranches, |     getUndeletedParentBranches, | ||||||
|     triggerNoteTitleChanged |     triggerNoteTitleChanged, | ||||||
|  |     eraseDeletedNotesNow | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -31,10 +31,7 @@ function initNotSyncedOptions(initialized, startNotePath = 'root', opts = {}) { | |||||||
|     optionService.createOption('openTabs', JSON.stringify([ |     optionService.createOption('openTabs', JSON.stringify([ | ||||||
|         { |         { | ||||||
|             notePath: startNotePath, |             notePath: startNotePath, | ||||||
|             active: true, |             active: true | ||||||
|             sidebar: { |  | ||||||
|                 widgets: [] |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     ]), false); |     ]), false); | ||||||
|  |  | ||||||
| @@ -103,6 +100,15 @@ function initStartupOptions() { | |||||||
|             log.info(`Created missing option "${name}" with default value "${value}"`); |             log.info(`Created missing option "${name}" with default value "${value}"`); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (process.env.TRILIUM_START_NOTE_ID || process.env.TRILIUM_SAFE_MODE) { | ||||||
|  |         optionService.setOption('openTabs', JSON.stringify([ | ||||||
|  |             { | ||||||
|  |                 notePath: process.env.TRILIUM_START_NOTE_ID || 'root', | ||||||
|  |                 active: true | ||||||
|  |             } | ||||||
|  |         ])); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function getKeyboardDefaultOptions() { | function getKeyboardDefaultOptions() { | ||||||
|   | |||||||
| @@ -43,10 +43,18 @@ function decryptNotes(notes) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function encrypt(plainText) { | function encrypt(plainText) { | ||||||
|  |     if (plainText === null) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return dataEncryptionService.encrypt(getDataKey(), plainText); |     return dataEncryptionService.encrypt(getDataKey(), plainText); | ||||||
| } | } | ||||||
|  |  | ||||||
| function decrypt(cipherText) { | function decrypt(cipherText) { | ||||||
|  |     if (cipherText === null) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return dataEncryptionService.decrypt(getDataKey(), cipherText); |     return dataEncryptionService.decrypt(getDataKey(), cipherText); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,9 +22,11 @@ function runNotesWithLabel(runAttrValue) { | |||||||
| } | } | ||||||
|  |  | ||||||
| sqlInit.dbReady.then(() => { | sqlInit.dbReady.then(() => { | ||||||
|  |     if (!process.env.TRILIUM_SAFE_MODE) { | ||||||
|         setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000); |         setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000); | ||||||
|  |  | ||||||
|         setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000); |         setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000); | ||||||
|  |  | ||||||
|         setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000); |         setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000); | ||||||
|  |     } | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| const sql = require('./sql'); |  | ||||||
| const ScriptContext = require('./script_context'); | const ScriptContext = require('./script_context'); | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
|   | |||||||
| @@ -32,26 +32,29 @@ class NoteContentProtectedFulltextExp extends Expression { | |||||||
|                 FROM notes JOIN note_contents USING (noteId)  |                 FROM notes JOIN note_contents USING (noteId)  | ||||||
|                 WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) { |                 WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 1`)) { | ||||||
|  |  | ||||||
|  |             if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 content = protectedSessionService.decryptString(content); |                 content = protectedSessionService.decryptString(content); | ||||||
|             } |             } | ||||||
|             catch (e) { |             catch (e) { | ||||||
|                 log.info('Cannot decrypt content of note', noteId); |                 log.info(`Cannot decrypt content of note ${noteId}`); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             content = content.toLowerCase(); |             content = content.toLowerCase(); | ||||||
|  |  | ||||||
|             if (type === 'text' && mime === 'text/html') { |             if (type === 'text' && mime === 'text/html') { | ||||||
|  |                 if (content.length < 20000) { // striptags is slow for very large notes | ||||||
|                     content = striptags(content); |                     content = striptags(content); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|                 content = content.replace(/ /g, ' '); |                 content = content.replace(/ /g, ' '); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (this.tokens.find(token => !content.includes(token))) { |             if (!this.tokens.find(token => !content.includes(token))) { | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) { |  | ||||||
|                 resultNoteSet.add(noteCache.notes[noteId]); |                 resultNoteSet.add(noteCache.notes[noteId]); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -26,18 +26,21 @@ class NoteContentUnprotectedFulltextExp extends Expression { | |||||||
|                 FROM notes JOIN note_contents USING (noteId)  |                 FROM notes JOIN note_contents USING (noteId)  | ||||||
|                 WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) { |                 WHERE type IN ('text', 'code') AND isDeleted = 0 AND isProtected = 0`)) { | ||||||
|  |  | ||||||
|             content = content.toString().toLowerCase(); |             if (!inputNoteSet.hasNoteId(noteId) || !(noteId in noteCache.notes)) { | ||||||
|  |  | ||||||
|             if (type === 'text' && mime === 'text/html') { |  | ||||||
|                 content = striptags(content); |  | ||||||
|                 content = content.replace(/ /g, ' '); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (this.tokens.find(token => !content.includes(token))) { |  | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (inputNoteSet.hasNoteId(noteId) && noteId in noteCache.notes) { |             content = content.toString().toLowerCase(); | ||||||
|  |  | ||||||
|  |             if (type === 'text' && mime === 'text/html') { | ||||||
|  |                 if (content.length < 20000) { // striptags is slow for very large notes | ||||||
|  |                     content = striptags(content); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 content = content.replace(/ /g, ' '); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (!this.tokens.find(token => !content.includes(token))) { | ||||||
|                 resultNoteSet.add(noteCache.notes[noteId]); |                 resultNoteSet.add(noteCache.notes[noteId]); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ const Option = require('../entities/option'); | |||||||
| const TaskContext = require('./task_context.js'); | const TaskContext = require('./task_context.js'); | ||||||
| const migrationService = require('./migration'); | const migrationService = require('./migration'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
|  | const config = require('./config'); | ||||||
|  |  | ||||||
| const dbReady = utils.deferred(); | const dbReady = utils.deferred(); | ||||||
|  |  | ||||||
| @@ -131,6 +132,12 @@ function setDbAsInitialized() { | |||||||
| } | } | ||||||
|  |  | ||||||
| dbReady.then(() => { | dbReady.then(() => { | ||||||
|  |     if (config.General && config.General.noBackup === true) { | ||||||
|  |         log.info("Disabling scheduled backups."); | ||||||
|  |  | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     setInterval(() => require('./backup').regularBackup(), 4 * 60 * 60 * 1000); |     setInterval(() => require('./backup').regularBackup(), 4 * 60 * 60 * 1000); | ||||||
|  |  | ||||||
|     // kickoff first backup soon after start up |     // kickoff first backup soon after start up | ||||||
|   | |||||||
| @@ -63,8 +63,6 @@ | |||||||
| <link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet"> | <link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet"> | ||||||
| <script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script> | <script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script> | ||||||
|  |  | ||||||
| <link href="libraries/ckeditor/ckeditor-content.css" rel="stylesheet"> |  | ||||||
|  |  | ||||||
| <!-- Include Fancytree skin and library --> | <!-- Include Fancytree skin and library --> | ||||||
| <link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet"> | <link href="libraries/fancytree/skin-win8/ui.fancytree.css" rel="stylesheet"> | ||||||
| <script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script> | <script src="libraries/fancytree/jquery.fancytree-all-deps.min.js"></script> | ||||||
|   | |||||||
| @@ -127,8 +127,6 @@ | |||||||
| <link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet"> | <link href="libraries/bootstrap/css/bootstrap.min.css" rel="stylesheet"> | ||||||
| <script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script> | <script src="libraries/bootstrap/js/bootstrap.bundle.min.js"></script> | ||||||
|  |  | ||||||
| <link href="libraries/ckeditor/ckeditor-content.css" rel="stylesheet"> |  | ||||||
|  |  | ||||||
| <script src="app/mobile.js" crossorigin type="module"></script> | <script src="app/mobile.js" crossorigin type="module"></script> | ||||||
|  |  | ||||||
| <link href="stylesheets/themes.css" rel="stylesheet"> | <link href="stylesheets/themes.css" rel="stylesheet"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user