mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 18:05:55 +01:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
			v0.48.6-do
			...
			v0.48.8
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a81ea3771f | ||
|  | d9550dd59b | ||
|  | a810c08c02 | ||
|  | 263b7a84bb | ||
|  | 9d18bebb13 | ||
|  | 26bcfe5160 | ||
|  | 89c04e6b6b | ||
|  | 40fb4ff56b | ||
|  | d64c14482b | ||
|  | 564366861e | ||
|  | 8c11d022fb | ||
|  | 24210ef80c | ||
|  | 2135aa058e | ||
|  | 67542f448d | ||
|  | 08e9b59696 | ||
|  | fe605c012a | ||
|  | 4c7c53d8c8 | ||
|  | d345b7ed56 | ||
|  | 298af217e9 | ||
|  | 7d64f6a7dd | ||
|  | bc8b6284a6 | 
							
								
								
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,10 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="RunConfigurationProducerService"> | ||||
|     <option name="ignoredProducers"> | ||||
|       <set> | ||||
|         <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> | ||||
|       </set> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										14
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.48.4", | ||||
|   "version": "0.48.7", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
| @@ -2850,9 +2850,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "electron": { | ||||
|       "version": "13.6.1", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-13.6.1.tgz", | ||||
|       "integrity": "sha512-rZ6Y7RberigruefQpWOiI4bA9ppyT88GQF8htY6N1MrAgal5RrBc+Mh92CcGU7zT9QO+XO3DarSgZafNTepffQ==", | ||||
|       "version": "13.6.2", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-13.6.2.tgz", | ||||
|       "integrity": "sha512-ZXx9t68yXftvNZVnQ7v2XHcnH+MPUF6LNStoz4MMXuWpkF9gq3qwjcYSqnbM4wiVkvWVHIyYvt1yemmStza9dQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@electron/get": "^1.0.1", | ||||
| @@ -2861,9 +2861,9 @@ | ||||
|       }, | ||||
|       "dependencies": { | ||||
|         "@types/node": { | ||||
|           "version": "14.17.32", | ||||
|           "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.32.tgz", | ||||
|           "integrity": "sha512-JcII3D5/OapPGx+eJ+Ik1SQGyt6WvuqdRfh9jUwL6/iHGjmyOriBDciBUu7lEIBTL2ijxwrR70WUnw5AEDmFvQ==", | ||||
|           "version": "14.17.33", | ||||
|           "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.33.tgz", | ||||
|           "integrity": "sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==", | ||||
|           "dev": true | ||||
|         } | ||||
|       } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.48.6", | ||||
|   "version": "0.48.8", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -81,7 +81,7 @@ | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "cross-env": "7.0.3", | ||||
|     "electron": "13.6.1", | ||||
|     "electron": "13.6.3", | ||||
|     "electron-builder": "22.13.1", | ||||
|     "electron-packager": "15.4.0", | ||||
|     "electron-rebuild": "3.2.3", | ||||
|   | ||||
| @@ -58,6 +58,9 @@ class Branch extends AbstractEntity { | ||||
|     } | ||||
|  | ||||
|     init() { | ||||
|         this.becca.branches[this.branchId] = this; | ||||
|         this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; | ||||
|  | ||||
|         if (this.branchId === 'root') { | ||||
|             return; | ||||
|         } | ||||
| @@ -76,9 +79,6 @@ class Branch extends AbstractEntity { | ||||
|         if (!parentNote.children.includes(childNote)) { | ||||
|             parentNote.children.push(childNote); | ||||
|         } | ||||
|  | ||||
|         this.becca.branches[this.branchId] = this; | ||||
|         this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this; | ||||
|     } | ||||
|  | ||||
|     /** @returns {Note} */ | ||||
|   | ||||
| @@ -30,7 +30,7 @@ const TPL = ` | ||||
|  | ||||
|     <div class="form-group"> | ||||
|         <label for="image-max-width-height">Max width / height of an image in pixels (image will be resized if it exceeds this setting).</label> | ||||
|         <input class="form-control" id="image-max-width-height" type="number"> | ||||
|         <input class="form-control" id="image-max-width-height" type="number" min="1"> | ||||
|     </div> | ||||
|  | ||||
|     <div class="form-group"> | ||||
| @@ -67,7 +67,7 @@ const TPL = ` | ||||
|  | ||||
|     <div class="form-group"> | ||||
|         <label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label> | ||||
|         <input class="form-control" id="protected-session-timeout-in-seconds" type="number"> | ||||
|         <input class="form-control" id="protected-session-timeout-in-seconds" type="number" min="60"> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @@ -78,7 +78,7 @@ const TPL = ` | ||||
|  | ||||
|     <div class="form-group"> | ||||
|         <label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label> | ||||
|         <input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number"> | ||||
|         <input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number" min="10"> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| @@ -89,12 +89,12 @@ const TPL = ` | ||||
|  | ||||
|     <div class="form-group"> | ||||
|         <label for="auto-readonly-size-text">Automatic readonly size (text notes)</label> | ||||
|         <input class="form-control" id="auto-readonly-size-text" type="number"> | ||||
|         <input class="form-control" id="auto-readonly-size-text" type="number" min="0"> | ||||
|     </div> | ||||
|  | ||||
|     <div class="form-group"> | ||||
|         <label for="auto-readonly-size-code">Automatic readonly size (code notes)</label> | ||||
|         <input class="form-control" id="auto-readonly-size-code" type="number"> | ||||
|         <input class="form-control" id="auto-readonly-size-code" type="number" min="0"> | ||||
|     </div> | ||||
| </div>`; | ||||
|  | ||||
|   | ||||
| @@ -88,7 +88,7 @@ export default class MobileLayout { | ||||
|         return new FlexContainer('row').cssBlock(MOBILE_CSS) | ||||
|             .setParent(appContext) | ||||
|             .id('root-widget') | ||||
|             .css('height', '100vh') | ||||
|             .css('height', '100%') | ||||
|             .child(new ScreenContainer("tree", 'column') | ||||
|                 .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4") | ||||
|                 .css("max-height", "100%") | ||||
|   | ||||
| @@ -181,9 +181,9 @@ async function consumeFrontendUpdateData() { | ||||
|  | ||||
|         for (const entityChange of nonProcessedEntityChanges) { | ||||
|             processedEntityChangeIds.add(entityChange.id); | ||||
|         } | ||||
|  | ||||
|         lastProcessedEntityChangeId = Math.max(lastProcessedEntityChangeId, allEntityChanges[allEntityChanges.length - 1].id); | ||||
|             lastProcessedEntityChangeId = Math.max(lastProcessedEntityChangeId, entityChange.id); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     checkEntityChangeIdListeners(); | ||||
|   | ||||
| @@ -5,6 +5,18 @@ import froca from "../../services/froca.js"; | ||||
| export default class OpenNoteButtonWidget extends ButtonWidget { | ||||
|     targetNote(noteId) { | ||||
|         froca.getNote(noteId).then(note => { | ||||
|             if (!note) { | ||||
|                 console.log(`Note ${noteId} has not been found. This might happen on the first run before the target note is created.`); | ||||
|  | ||||
|                 if (!this.retried) { | ||||
|                     this.retried = true; | ||||
|  | ||||
|                     setTimeout(() => this.targetNote(noteId), 15000); // should be higher than timeout for createMissingSpecialNotes | ||||
|                 } | ||||
|  | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             this.icon(note.getIcon()); | ||||
|             this.title(note.title); | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ export default class RootContainer extends FlexContainer { | ||||
|         super('row'); | ||||
|  | ||||
|         this.id('root-widget'); | ||||
|         this.css('height', '100vh'); | ||||
|         this.css('height', '100%'); | ||||
|     } | ||||
|  | ||||
|     refresh() { | ||||
|   | ||||
| @@ -150,6 +150,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | ||||
|     doRender() { | ||||
|         this.$widget = $(TPL); | ||||
|         this.$tree = this.$widget.find('.tree'); | ||||
|         this.$treeActions = this.$widget.find(".tree-actions"); | ||||
|  | ||||
|         this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist()); | ||||
|         this.$tree.on("mousedown", ".refresh-search-button", e => this.refreshSearch(e)); | ||||
| @@ -200,20 +201,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | ||||
|             this.$hideIncludedImages.prop("checked", this.hideIncludedImages); | ||||
|             this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree); | ||||
|  | ||||
|             let top = this.$treeSettingsButton[0].offsetTop; | ||||
|             let left = this.$treeSettingsButton[0].offsetLeft; | ||||
|             top -= this.$treeSettingsPopup.outerHeight() + 10; | ||||
|             left += this.$treeSettingsButton.outerWidth() - this.$treeSettingsPopup.outerWidth(); | ||||
|  | ||||
|             if (left < 0) { | ||||
|                 left = 0; | ||||
|             } | ||||
|             const top = this.$treeActions[0].offsetTop - (this.$treeSettingsPopup.outerHeight()); | ||||
|             const left = Math.max( | ||||
|                 0, | ||||
|                 this.$treeActions[0].offsetLeft - this.$treeSettingsPopup.outerWidth() + this.$treeActions.outerWidth() | ||||
|             ); | ||||
|  | ||||
|             this.$treeSettingsPopup.css({ | ||||
|                 display: "block", | ||||
|                 top: top, | ||||
|                 left: left | ||||
|             }).addClass("show"); | ||||
|                 top, | ||||
|                 left | ||||
|             }).show(); | ||||
|  | ||||
|             return false; | ||||
|         }); | ||||
|   | ||||
| @@ -134,6 +134,8 @@ export default class QuickSearchWidget extends BasicWidget { | ||||
|     } | ||||
|  | ||||
|     async showInFullSearch() { | ||||
|         this.$dropdownToggle.dropdown("hide"); | ||||
|  | ||||
|         const searchNote = await dateNotesService.createSearchNote({searchString: this.$searchString.val()}); | ||||
|  | ||||
|         await froca.loadSearchNote(searchNote.noteId); | ||||
|   | ||||
| @@ -69,7 +69,10 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | ||||
|         this.$notePathList.empty(); | ||||
|  | ||||
|         if (this.noteId === 'root') { | ||||
|             await this.getRenderedPath('root'); | ||||
|             this.$notePathList.empty().append( | ||||
|                 await this.getRenderedPath('root') | ||||
|             ); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
| @@ -94,7 +97,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | ||||
|         this.$notePathList.empty().append(...renderedPaths); | ||||
|     } | ||||
|  | ||||
|     async getRenderedPath(notePath, notePathRecord) { | ||||
|     async getRenderedPath(notePath, notePathRecord = null) { | ||||
|         const title = await treeService.getNotePathTitle(notePath); | ||||
|  | ||||
|         const $noteLink = await linkService.createNoteLink(notePath, {title}); | ||||
| @@ -109,20 +112,20 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | ||||
|             $noteLink.addClass("path-current"); | ||||
|         } | ||||
|  | ||||
|         if (notePathRecord.isInHoistedSubTree) { | ||||
|         if (!notePathRecord || notePathRecord.isInHoistedSubTree) { | ||||
|             $noteLink.addClass("path-in-hoisted-subtree"); | ||||
|         } | ||||
|         else { | ||||
|             icons.push(`<span class="bx bx-trending-up" title="This path is outside of hoisted note and you would have to unhoist."></span>`); | ||||
|         } | ||||
|  | ||||
|         if (notePathRecord.isArchived) { | ||||
|         if (notePathRecord?.isArchived) { | ||||
|             $noteLink.addClass("path-archived"); | ||||
|  | ||||
|             icons.push(`<span class="bx bx-archive" title="Archived"></span>`); | ||||
|         } | ||||
|  | ||||
|         if (notePathRecord.isSearch) { | ||||
|         if (notePathRecord?.isSearch) { | ||||
|             $noteLink.addClass("path-search"); | ||||
|  | ||||
|             icons.push(`<span class="bx bx-search" title="Search"></span>`); | ||||
|   | ||||
| @@ -115,9 +115,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|  | ||||
|         const $input = $("<input>") | ||||
|             .prop("tabindex", 200 + definitionAttr.position) | ||||
|             .prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one | ||||
|             .prop("attribute-type", valueAttr.type) | ||||
|             .prop("attribute-name", valueAttr.name) | ||||
|             .attr("data-attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one | ||||
|             .attr("data-attribute-type", valueAttr.type) | ||||
|             .attr("data-attribute-name", valueAttr.name) | ||||
|             .prop("value", valueAttr.value) | ||||
|             .addClass("form-control") | ||||
|             .addClass("promoted-attribute-input") | ||||
| @@ -230,7 +230,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|         } | ||||
|  | ||||
|         if (definition.multiplicity === "multi") { | ||||
|             const addButton = $("<span>") | ||||
|             const $addButton = $("<span>") | ||||
|                 .addClass("bx bx-plus pointer") | ||||
|                 .prop("title", "Add new attribute") | ||||
|                 .on('click', async () => { | ||||
| @@ -246,12 +246,28 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|                     $new.find('input').trigger('focus'); | ||||
|                 }); | ||||
|  | ||||
|             const removeButton = $("<span>") | ||||
|             const $removeButton = $("<span>") | ||||
|                 .addClass("bx bx-trash pointer") | ||||
|                 .prop("title", "Remove this attribute") | ||||
|                 .on('click', async () => { | ||||
|                     if (valueAttr.attributeId) { | ||||
|                         await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId); | ||||
|                     const attributeId = $input.attr("data-attribute-id"); | ||||
|  | ||||
|                     if (attributeId) { | ||||
|                         await server.remove("notes/" + this.noteId + "/attributes/" + attributeId, this.componentId); | ||||
|                     } | ||||
|  | ||||
|                     // if it's the last one the create new empty form immediately | ||||
|                     const sameAttrSelector = `input[data-attribute-type='${valueAttr.type}'][data-attribute-name='${valueName}']`; | ||||
|  | ||||
|                     if (this.$widget.find(sameAttrSelector).length <= 1) { | ||||
|                         const $new = await this.createPromotedAttributeCell(definitionAttr, { | ||||
|                             attributeId: "", | ||||
|                             type: valueAttr.type, | ||||
|                             name: valueName, | ||||
|                             value: "" | ||||
|                         }, valueName); | ||||
|  | ||||
|                         $wrapper.after($new); | ||||
|                     } | ||||
|  | ||||
|                     $wrapper.remove(); | ||||
| @@ -259,9 +275,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|  | ||||
|             $multiplicityCell | ||||
|                 .append("  ") | ||||
|                 .append(addButton) | ||||
|                 .append($addButton) | ||||
|                 .append("  ") | ||||
|                 .append(removeButton); | ||||
|                 .append($removeButton); | ||||
|         } | ||||
|  | ||||
|         return $wrapper; | ||||
| @@ -275,7 +291,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|         if ($attr.prop("type") === "checkbox") { | ||||
|             value = $attr.is(':checked') ? "true" : "false"; | ||||
|         } | ||||
|         else if ($attr.prop("attribute-type") === "relation") { | ||||
|         else if ($attr.attr("data-attribute-type") === "relation") { | ||||
|             const selectedPath = $attr.getSelectedNotePath(); | ||||
|  | ||||
|             value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : ""; | ||||
| @@ -285,13 +301,13 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | ||||
|         } | ||||
|  | ||||
|         const result = await server.put(`notes/${this.noteId}/attribute`, { | ||||
|             attributeId: $attr.prop("attribute-id"), | ||||
|             type: $attr.prop("attribute-type"), | ||||
|             name: $attr.prop("attribute-name"), | ||||
|             attributeId: $attr.attr("data-attribute-id"), | ||||
|             type: $attr.attr("data-attribute-type"), | ||||
|             name: $attr.attr("data-attribute-name"), | ||||
|             value: value | ||||
|         }, this.componentId); | ||||
|  | ||||
|         $attr.prop("attribute-id", result.attributeId); | ||||
|         $attr.attr("data-attribute-id", result.attributeId); | ||||
|     } | ||||
|  | ||||
|     entitiesReloadedEvent({loadResults}) { | ||||
|   | ||||
| @@ -57,9 +57,7 @@ class ImageTypeWidget extends TypeWidget { | ||||
|     } | ||||
|  | ||||
|     async doRefresh(note) { | ||||
|         const imageHash = utils.randomString(10); | ||||
|  | ||||
|         this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}?${imageHash}`); | ||||
|         this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}`); | ||||
|     } | ||||
|  | ||||
|     copyImageToClipboardEvent({ntxId}) { | ||||
|   | ||||
| @@ -18,9 +18,11 @@ body { | ||||
|        on the last line of the editor. */ | ||||
|     position: fixed; | ||||
|     width: 100%; | ||||
|     height: 100%; | ||||
|     background-color: var(--main-background-color); | ||||
|     color: var(--main-text-color); | ||||
|     font-family: var(--main-font-family); | ||||
|     font-size: var(--main-font-size); | ||||
| } | ||||
|  | ||||
| a, a:visited, a:hover { | ||||
| @@ -58,7 +60,7 @@ table td, table th { | ||||
| } | ||||
|  | ||||
| code, kbd, pre, samp { | ||||
|     font-family: var(--monospace-font-family); | ||||
|     font-family: var(--monospace-font-family) !important; | ||||
| } | ||||
|  | ||||
| .input-group-text { | ||||
| @@ -714,10 +716,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href | ||||
|     border-color: var(--main-border-color) !important; | ||||
| } | ||||
|  | ||||
| body { | ||||
|     font-size: var(--main-font-size); | ||||
| } | ||||
|  | ||||
| .gutter { | ||||
|     background: linear-gradient(to bottom, transparent, var(--accented-background-color), transparent); | ||||
| } | ||||
|   | ||||
| @@ -60,6 +60,7 @@ function downloadNoteFile(noteId, res, contentDisposition = true) { | ||||
|         res.setHeader('Content-Disposition', utils.getContentDisposition(filename)); | ||||
|     } | ||||
|  | ||||
|     res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|     res.setHeader('Content-Type', note.mime); | ||||
|  | ||||
|     res.send(note.getContent()); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ function returnImage(req, res) { | ||||
|     } | ||||
|  | ||||
|     res.set('Content-Type', image.mime); | ||||
|     res.set("Cache-Control", "no-cache, no-store, must-revalidate"); | ||||
|  | ||||
|     res.send(image.getContent()); | ||||
| } | ||||
|   | ||||
| @@ -42,6 +42,11 @@ function getNeighbors(note, depth) { | ||||
|         } | ||||
|  | ||||
|         const targetNote = relation.getTargetNote(); | ||||
|  | ||||
|         if (targetNote.hasLabel('excludeFromNoteMap')) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         retNoteIds.push(targetNote.noteId); | ||||
|  | ||||
|         for (const noteId of getNeighbors(targetNote, depth - 1)) { | ||||
| @@ -56,6 +61,11 @@ function getNeighbors(note, depth) { | ||||
|         } | ||||
|  | ||||
|         const sourceNote = relation.getNote(); | ||||
|  | ||||
|         if (sourceNote.hasLabel('excludeFromNoteMap')) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         retNoteIds.push(sourceNote.noteId); | ||||
|  | ||||
|         for (const noteId of getNeighbors(sourceNote, depth - 1)) { | ||||
|   | ||||
| @@ -203,6 +203,10 @@ function changeTitle(req) { | ||||
|  | ||||
|     const noteTitleChanged = note.title !== title; | ||||
|  | ||||
|     if (noteTitleChanged) { | ||||
|         noteService.saveNoteRevision(note); | ||||
|     } | ||||
|  | ||||
|     note.title = title; | ||||
|  | ||||
|     note.save(); | ||||
|   | ||||
| @@ -204,6 +204,11 @@ function queueSector(req) { | ||||
|     entityChangesService.addEntityChangesForSector(entityName, sector); | ||||
| } | ||||
|  | ||||
| function checkEntityChanges() { | ||||
|     const consistencyChecks = require("../../services/consistency_checks"); | ||||
|     consistencyChecks.runEntityChangesChecks(); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     testSync, | ||||
|     checkSync, | ||||
| @@ -215,5 +220,6 @@ module.exports = { | ||||
|     update, | ||||
|     getStats, | ||||
|     syncFinished, | ||||
|     queueSector | ||||
|     queueSector, | ||||
|     checkEntityChanges | ||||
| }; | ||||
|   | ||||
| @@ -294,6 +294,7 @@ function register(app) { | ||||
|     route(GET, '/api/sync/changed', [auth.checkApiAuth], syncApiRoute.getChanged, apiResultHandler); | ||||
|     route(PUT, '/api/sync/update', [auth.checkApiAuth], syncApiRoute.update, apiResultHandler); | ||||
|     route(POST, '/api/sync/finished', [auth.checkApiAuth], syncApiRoute.syncFinished, apiResultHandler); | ||||
|     route(POST, '/api/sync/check-entity-changes', [auth.checkApiAuth], syncApiRoute.checkEntityChanges, apiResultHandler); | ||||
|     route(POST, '/api/sync/queue-sector/:entityName/:sector', [auth.checkApiAuth], syncApiRoute.queueSector, apiResultHandler); | ||||
|     route(GET, '/api/sync/stats', [], syncApiRoute.getStats, apiResultHandler); | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 185; | ||||
| const SYNC_VERSION = 21; | ||||
| const SYNC_VERSION = 22; | ||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -62,6 +62,7 @@ const BUILTIN_ATTRIBUTES = [ | ||||
|     { type: 'relation', name: 'renderNote', isDangerous: true } | ||||
| ]; | ||||
|  | ||||
| /** @returns {Note[]} */ | ||||
| function getNotesWithLabel(name, value) { | ||||
|     const query = formatAttrForSearch({type: 'label', name, value}, true); | ||||
|     return searchService.searchNotes(query, { | ||||
| @@ -71,6 +72,7 @@ function getNotesWithLabel(name, value) { | ||||
| } | ||||
|  | ||||
| // TODO: should be in search service | ||||
| /** @returns {Note|null} */ | ||||
| function getNoteWithLabel(name, value) { | ||||
|     // optimized version (~20 times faster) without using normal search, useful for e.g. finding date notes | ||||
|     const attrs = becca.findAttributes('label', name); | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2021-11-13T22:49:58+01:00", buildRevision: "c94603010630cfafe64575ab378c482bb39fb083" }; | ||||
| module.exports = { buildDate:"2021-12-13T11:12:31+01:00", buildRevision: "d9550dd59b9b0dff0b229c400cdf6585abcb226a" }; | ||||
|   | ||||
| @@ -701,6 +701,11 @@ function runOnDemandChecks(autoFix) { | ||||
|     consistencyChecks.runChecks(); | ||||
| } | ||||
|  | ||||
| function runEntityChangesChecks() { | ||||
|     const consistencyChecks = new ConsistencyChecks(true); | ||||
|     consistencyChecks.findEntityChangeIssues(); | ||||
| } | ||||
|  | ||||
| sqlInit.dbReady.then(() => { | ||||
|     setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000); | ||||
|  | ||||
| @@ -709,5 +714,6 @@ sqlInit.dbReady.then(() => { | ||||
| }); | ||||
|  | ||||
| module.exports = { | ||||
|     runOnDemandChecks | ||||
|     runOnDemandChecks, | ||||
|     runEntityChangesChecks | ||||
| }; | ||||
|   | ||||
| @@ -25,15 +25,6 @@ function createNote(parentNote, noteTitle) { | ||||
|     }).note; | ||||
| } | ||||
|  | ||||
| function getNoteStartingWith(parentNoteId, startsWith) { | ||||
|     const noteId = sql.getValue(`SELECT notes.noteId FROM notes JOIN branches USING(noteId)  | ||||
|                                     WHERE parentNoteId = ? AND title LIKE '${startsWith}%' | ||||
|                                     AND notes.isDeleted = 0 AND isProtected = 0  | ||||
|                                     AND branches.isDeleted = 0`, [parentNoteId]); | ||||
|  | ||||
|     return becca.getNote(noteId); | ||||
| } | ||||
|  | ||||
| /** @returns {Note} */ | ||||
| function getRootCalendarNote() { | ||||
|     let rootNote = attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL); | ||||
| @@ -65,8 +56,7 @@ function getYearNote(dateStr, rootNote) { | ||||
|  | ||||
|     const yearStr = dateStr.substr(0, 4); | ||||
|  | ||||
|     let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr) | ||||
|         || getNoteStartingWith(rootNote.noteId, yearStr); | ||||
|     let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr); | ||||
|  | ||||
|     if (yearNote) { | ||||
|         return yearNote; | ||||
| @@ -112,18 +102,12 @@ function getMonthNote(dateStr, rootNote) { | ||||
|         return monthNote; | ||||
|     } | ||||
|  | ||||
|     const yearNote = getYearNote(dateStr, rootNote); | ||||
|  | ||||
|     monthNote = getNoteStartingWith(yearNote.noteId, monthNumber); | ||||
|  | ||||
|     if (monthNote) { | ||||
|         return monthNote; | ||||
|     } | ||||
|  | ||||
|     const dateObj = dateUtils.parseLocalDate(dateStr); | ||||
|  | ||||
|     const noteTitle = getMonthNoteTitle(rootNote, monthNumber, dateObj); | ||||
|  | ||||
|     const yearNote = getYearNote(dateStr, rootNote); | ||||
|  | ||||
|     sql.transactional(() => { | ||||
|         monthNote = createNote(yearNote, noteTitle); | ||||
|  | ||||
| @@ -164,12 +148,6 @@ function getDateNote(dateStr) { | ||||
|     const monthNote = getMonthNote(dateStr, rootNote); | ||||
|     const dayNumber = dateStr.substr(8, 2); | ||||
|  | ||||
|     dateNote = getNoteStartingWith(monthNote.noteId, dayNumber); | ||||
|  | ||||
|     if (dateNote) { | ||||
|         return dateNote; | ||||
|     } | ||||
|  | ||||
|     const dateObj = dateUtils.parseLocalDate(dateStr); | ||||
|  | ||||
|     const noteTitle = getDateNoteTitle(rootNote, dayNumber, dateObj); | ||||
|   | ||||
| @@ -7,12 +7,10 @@ const becca = require("../becca/becca"); | ||||
|  | ||||
| let maxEntityChangeId = 0; | ||||
|  | ||||
| function addEntityChange(origEntityChange, keepOriginalId = false) { | ||||
| function addEntityChange(origEntityChange) { | ||||
|     const ec = {...origEntityChange}; | ||||
|  | ||||
|     if (!keepOriginalId) { | ||||
|         delete ec.id; | ||||
|     } | ||||
|     delete ec.id; | ||||
|  | ||||
|     ec.sourceId = ec.sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId(); | ||||
|     ec.isSynced = ec.isSynced ? 1 : 0; | ||||
|   | ||||
| @@ -918,5 +918,6 @@ module.exports = { | ||||
|     getUndeletedParentBranchIds, | ||||
|     triggerNoteTitleChanged, | ||||
|     eraseDeletedNotesNow, | ||||
|     eraseNotesWithDeleteId | ||||
|     eraseNotesWithDeleteId, | ||||
|     saveNoteRevision | ||||
| }; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ const protectedSessionService = require('../../protected_session'); | ||||
| const striptags = require('striptags'); | ||||
| const utils = require("../../utils"); | ||||
|  | ||||
| // FIXME: create common subclass with NoteContentUnprotectedFulltextExp to avoid duplication | ||||
| class NoteContentProtectedFulltextExp extends Expression { | ||||
|     constructor(operator, tokens, raw) { | ||||
|         super(); | ||||
| @@ -46,15 +47,7 @@ class NoteContentProtectedFulltextExp extends Expression { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             content = utils.normalize(content); | ||||
|  | ||||
|             if (type === 'text' && mime === 'text/html') { | ||||
|                 if (!this.raw && content.length < 20000) { // striptags is slow for very large notes | ||||
|                     content = striptags(content); | ||||
|                 } | ||||
|  | ||||
|                 content = content.replace(/ /g, ' '); | ||||
|             } | ||||
|             content = this.preprocessContent(content, type, mime); | ||||
|  | ||||
|             if (!this.tokens.find(token => !content.includes(token))) { | ||||
|                 resultNoteSet.add(becca.notes[noteId]); | ||||
| @@ -63,6 +56,23 @@ class NoteContentProtectedFulltextExp extends Expression { | ||||
|  | ||||
|         return resultNoteSet; | ||||
|     } | ||||
|  | ||||
|     preprocessContent(content, type, mime) { | ||||
|         content = utils.normalize(content.toString()); | ||||
|  | ||||
|         if (type === 'text' && mime === 'text/html') { | ||||
|             if (!this.raw && content.length < 20000) { // striptags is slow for very large notes | ||||
|                 // allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412 | ||||
|                 content = striptags(content, ['a']); | ||||
|  | ||||
|                 // at least the closing tag can be easily stripped | ||||
|                 content = content.replace(/<\/a>/ig, ""); | ||||
|             } | ||||
|  | ||||
|             content = content.replace(/ /g, ' '); | ||||
|         } | ||||
|         return content; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = NoteContentProtectedFulltextExp; | ||||
|   | ||||
| @@ -6,6 +6,7 @@ const becca = require('../../../becca/becca'); | ||||
| const striptags = require('striptags'); | ||||
| const utils = require("../../utils"); | ||||
|  | ||||
| // FIXME: create common subclass with NoteContentProtectedFulltextExp to avoid duplication | ||||
| class NoteContentUnprotectedFulltextExp extends Expression { | ||||
|     constructor(operator, tokens, raw) { | ||||
|         super(); | ||||
| @@ -32,15 +33,7 @@ class NoteContentUnprotectedFulltextExp extends Expression { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             content = utils.normalize(content.toString()); | ||||
|  | ||||
|             if (type === 'text' && mime === 'text/html') { | ||||
|                 if (!this.raw && content.length < 20000) { // striptags is slow for very large notes | ||||
|                     content = striptags(content); | ||||
|                 } | ||||
|  | ||||
|                 content = content.replace(/ /g, ' '); | ||||
|             } | ||||
|             content = this.preprocessContent(content, type, mime); | ||||
|  | ||||
|             if (!this.tokens.find(token => !content.includes(token))) { | ||||
|                 resultNoteSet.add(becca.notes[noteId]); | ||||
| @@ -49,6 +42,23 @@ class NoteContentUnprotectedFulltextExp extends Expression { | ||||
|  | ||||
|         return resultNoteSet; | ||||
|     } | ||||
|  | ||||
|     preprocessContent(content, type, mime) { | ||||
|         content = utils.normalize(content.toString()); | ||||
|  | ||||
|         if (type === 'text' && mime === 'text/html') { | ||||
|             if (!this.raw && content.length < 20000) { // striptags is slow for very large notes | ||||
|                 // allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412 | ||||
|                 content = striptags(content, ['a']); | ||||
|  | ||||
|                 // at least the closing tag can be easily stripped | ||||
|                 content = content.replace(/<\/a>/ig, ""); | ||||
|             } | ||||
|  | ||||
|             content = content.replace(/ /g, ' '); | ||||
|         } | ||||
|         return content; | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = NoteContentUnprotectedFulltextExp; | ||||
|   | ||||
| @@ -83,8 +83,12 @@ function findResultsWithExpression(expression, searchContext) { | ||||
|                 throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`); | ||||
|             } | ||||
|  | ||||
|             if (notePathArray.includes("hidden")) { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             return new SearchResult(notePathArray); | ||||
|         }); | ||||
|         }).filter(Boolean); | ||||
|  | ||||
|     for (const res of searchResults) { | ||||
|         res.computeScore(searchContext.highlightedTokens); | ||||
|   | ||||
| @@ -44,6 +44,7 @@ function getHiddenRoot() { | ||||
|         // isInheritable: false means that this notePath is automatically not preffered but at the same time | ||||
|         // the flag is not inherited to the children | ||||
|         hidden.addLabel('archived', "", false); | ||||
|         hidden.addLabel('excludeFromNoteMap', "", true); | ||||
|     } | ||||
|  | ||||
|     return hidden; | ||||
| @@ -206,6 +207,12 @@ function createMissingSpecialNotes() { | ||||
|     getSinglesNoteRoot(); | ||||
|     getSinglesNoteRoot(); | ||||
|     getGlobalNoteMap(); | ||||
|  | ||||
|     const hidden = getHiddenRoot(); | ||||
|  | ||||
|     if (!hidden.hasOwnedLabel('excludeFromNoteMap')) { | ||||
|         hidden.addLabel('excludeFromNoteMap', "", true); | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -149,7 +149,10 @@ async function pullChanges(syncContext) { | ||||
|  | ||||
|         sql.transactional(() => { | ||||
|             for (const {entityChange, entity} of entityChanges) { | ||||
|                 if (!sourceIdService.isLocalSourceId(entityChange.sourceId)) { | ||||
|                 // FIXME: temporary fix | ||||
|                 const existsAlready = !!sql.getValue("SELECT id FROM entity_changes WHERE entityName = ? AND entityId = ? AND utcDateChanged = ? AND hash = ?", [entityChange.entityName, entityChange.entityId, entityChange.utcDateChanged, entityChange.hash]); | ||||
|  | ||||
|                 if (!existsAlready && !sourceIdService.isLocalSourceId(entityChange.sourceId)) { | ||||
|                     if (!atLeastOnePullApplied) { // send only for first | ||||
|                         ws.syncPullInProgress(); | ||||
|  | ||||
| @@ -249,6 +252,14 @@ async function checkContentHash(syncContext) { | ||||
|  | ||||
|     const failedChecks = contentHashService.checkContentHashes(resp.entityHashes); | ||||
|  | ||||
|     if (failedChecks.length > 0) { | ||||
|         // before requeuing sectors make sure the entity changes are correct | ||||
|         const consistencyChecks = require("./consistency_checks"); | ||||
|         consistencyChecks.runEntityChangesChecks(); | ||||
|  | ||||
|         await syncRequest(syncContext, 'POST', `/api/sync/check-entity-changes`); | ||||
|     } | ||||
|  | ||||
|     for (const {entityName, sector} of failedChecks) { | ||||
|         entityChangesService.addEntityChangesForSector(entityName, sector); | ||||
|  | ||||
|   | ||||
| @@ -54,7 +54,7 @@ function updateNormalEntity(remoteEntityChange, entity) { | ||||
|  | ||||
|             sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId); | ||||
|  | ||||
|             entityChangesService.addEntityChange(remoteEntityChange, true); | ||||
|             entityChangesService.addEntityChange(remoteEntityChange); | ||||
|         }); | ||||
|  | ||||
|         return true; | ||||
| @@ -71,7 +71,7 @@ function updateNormalEntity(remoteEntityChange, entity) { | ||||
|         sql.transactional(() => { | ||||
|             sql.replace(remoteEntityChange.entityName, entity); | ||||
|  | ||||
|             entityChangesService.addEntityChange(remoteEntityChange, true); | ||||
|             entityChangesService.addEntityChange(remoteEntityChange); | ||||
|         }); | ||||
|  | ||||
|         return true; | ||||
| @@ -86,7 +86,7 @@ function updateNoteReordering(entityChange, entity) { | ||||
|             sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]); | ||||
|         } | ||||
|  | ||||
|         entityChangesService.addEntityChange(entityChange, true); | ||||
|         entityChangesService.addEntityChange(entityChange); | ||||
|     }); | ||||
|  | ||||
|     return true; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user