mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	note cache refactoring
This commit is contained in:
		
							
								
								
									
										34
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										34
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.42.1", |   "version": "0.42.2", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
| @@ -2218,9 +2218,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "cli-spinners": { |     "cli-spinners": { | ||||||
|       "version": "2.2.0", |       "version": "2.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", |       "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz", | ||||||
|       "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", |       "integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "cli-table3": { |     "cli-table3": { | ||||||
| @@ -3802,9 +3802,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron-rebuild": { |     "electron-rebuild": { | ||||||
|       "version": "1.10.1", |       "version": "1.11.0", | ||||||
|       "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.10.1.tgz", |       "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.11.0.tgz", | ||||||
|       "integrity": "sha512-KSqp0Xiu7CCvKL2aEdPp/vNe2Rr11vaO8eM/wq9gQJTY02UjtAJ3l7WLV7Mf8oR+UJReJO8SWOWs/FozqK8ggA==", |       "integrity": "sha512-cn6AqZBQBVtaEyj5jZW1/LOezZZ22PA1HvhEP7asvYPJ8PDF4i4UFt9be4i9T7xJKiSiomXvY5Fd+dSq3FXZxA==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "colors": "^1.3.3", |         "colors": "^1.3.3", | ||||||
| @@ -3877,9 +3877,9 @@ | |||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "yargs": { |         "yargs": { | ||||||
|           "version": "14.2.2", |           "version": "14.2.3", | ||||||
|           "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", |           "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", | ||||||
|           "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", |           "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", | ||||||
|           "dev": true, |           "dev": true, | ||||||
|           "requires": { |           "requires": { | ||||||
|             "cliui": "^5.0.0", |             "cliui": "^5.0.0", | ||||||
| @@ -3892,13 +3892,13 @@ | |||||||
|             "string-width": "^3.0.0", |             "string-width": "^3.0.0", | ||||||
|             "which-module": "^2.0.0", |             "which-module": "^2.0.0", | ||||||
|             "y18n": "^4.0.0", |             "y18n": "^4.0.0", | ||||||
|             "yargs-parser": "^15.0.0" |             "yargs-parser": "^15.0.1" | ||||||
|           } |           } | ||||||
|         }, |         }, | ||||||
|         "yargs-parser": { |         "yargs-parser": { | ||||||
|           "version": "15.0.0", |           "version": "15.0.1", | ||||||
|           "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", |           "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", | ||||||
|           "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", |           "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", | ||||||
|           "dev": true, |           "dev": true, | ||||||
|           "requires": { |           "requires": { | ||||||
|             "camelcase": "^5.0.0", |             "camelcase": "^5.0.0", | ||||||
| @@ -9929,9 +9929,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "rxjs": { |     "rxjs": { | ||||||
|       "version": "6.5.4", |       "version": "6.5.5", | ||||||
|       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", |       "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", | ||||||
|       "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", |       "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "tslib": "^1.9.0" |         "tslib": "^1.9.0" | ||||||
|   | |||||||
| @@ -81,7 +81,7 @@ | |||||||
|     "electron": "9.0.0-beta.24", |     "electron": "9.0.0-beta.24", | ||||||
|     "electron-builder": "22.6.0", |     "electron-builder": "22.6.0", | ||||||
|     "electron-packager": "14.2.1", |     "electron-packager": "14.2.1", | ||||||
|     "electron-rebuild": "1.10.1", |     "electron-rebuild": "1.11.0", | ||||||
|     "jsdoc": "3.6.4", |     "jsdoc": "3.6.4", | ||||||
|     "lorem-ipsum": "2.0.3", |     "lorem-ipsum": "2.0.3", | ||||||
|     "webpack": "5.0.0-beta.16", |     "webpack": "5.0.0-beta.16", | ||||||
|   | |||||||
| @@ -8,13 +8,13 @@ const sql = require('../services/sql'); | |||||||
| /** | /** | ||||||
|  * Attribute is key value pair owned by a note. |  * Attribute is key value pair owned by a note. | ||||||
|  * |  * | ||||||
|  * @property {string} attributeId |  * @property {string} attributeId - immutable | ||||||
|  * @property {string} noteId |  * @property {string} noteId - immutable | ||||||
|  * @property {string} type |  * @property {string} type - immutable | ||||||
|  * @property {string} name |  * @property {string} name - immutable | ||||||
|  * @property {string} value |  * @property {string} value | ||||||
|  * @property {int} position |  * @property {int} position | ||||||
|  * @property {boolean} isInheritable |  * @property {boolean} isInheritable - immutable | ||||||
|  * @property {boolean} isDeleted |  * @property {boolean} isDeleted | ||||||
|  * @property {string|null} deleteId - ID identifying delete transaction |  * @property {string|null} deleteId - ID identifying delete transaction | ||||||
|  * @property {string} utcDateCreated |  * @property {string} utcDateCreated | ||||||
| @@ -108,14 +108,14 @@ class Attribute extends Entity { | |||||||
|         delete pojo.__note; |         delete pojo.__note; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     createClone(type, name, value) { |     createClone(type, name, value, isInheritable) { | ||||||
|         return new Attribute({ |         return new Attribute({ | ||||||
|             noteId: this.noteId, |             noteId: this.noteId, | ||||||
|             type: type, |             type: type, | ||||||
|             name: name, |             name: name, | ||||||
|             value: value, |             value: value, | ||||||
|             position: this.position, |             position: this.position, | ||||||
|             isInheritable: this.isInheritable, |             isInheritable: isInheritable, | ||||||
|             isDeleted: false, |             isDeleted: false, | ||||||
|             utcDateCreated: this.utcDateCreated, |             utcDateCreated: this.utcDateCreated, | ||||||
|             utcDateModified: this.utcDateModified |             utcDateModified: this.utcDateModified | ||||||
|   | |||||||
| @@ -9,9 +9,9 @@ const sql = require('../services/sql'); | |||||||
|  * Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId. |  * Branch represents note's placement in the tree - it's essentially pair of noteId and parentNoteId. | ||||||
|  * Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree. |  * Each note can have multiple (at least one) branches, meaning it can be placed into multiple places in the tree. | ||||||
|  * |  * | ||||||
|  * @property {string} branchId - primary key |  * @property {string} branchId - primary key, immutable | ||||||
|  * @property {string} noteId |  * @property {string} noteId - immutable | ||||||
|  * @property {string} parentNoteId |  * @property {string} parentNoteId - immutable | ||||||
|  * @property {int} notePosition |  * @property {int} notePosition | ||||||
|  * @property {string} prefix |  * @property {string} prefix | ||||||
|  * @property {boolean} isExpanded |  * @property {boolean} isExpanded | ||||||
| @@ -77,4 +77,4 @@ class Branch extends Entity { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = Branch; | module.exports = Branch; | ||||||
|   | |||||||
| @@ -98,10 +98,11 @@ async function updateNoteAttributes(req) { | |||||||
|  |  | ||||||
|             if (attribute.type !== attributeEntity.type |             if (attribute.type !== attributeEntity.type | ||||||
|                 || attribute.name !== attributeEntity.name |                 || attribute.name !== attributeEntity.name | ||||||
|                 || (attribute.type === 'relation' && attribute.value !== attributeEntity.value)) { |                 || (attribute.type === 'relation' && attribute.value !== attributeEntity.value) | ||||||
|  |                 || attribute.isInheritable !== attributeEntity.isInheritable) { | ||||||
|  |  | ||||||
|                 if (attribute.type !== 'relation' || !!attribute.value.trim()) { |                 if (attribute.type !== 'relation' || !!attribute.value.trim()) { | ||||||
|                     const newAttribute = attributeEntity.createClone(attribute.type, attribute.name, attribute.value); |                     const newAttribute = attributeEntity.createClone(attribute.type, attribute.name, attribute.value, attribute.isInheritable); | ||||||
|                     await newAttribute.save(); |                     await newAttribute.save(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ async function getSimilarNotes(req) { | |||||||
|         return [404, `Note ${noteId} not found.`]; |         return [404, `Note ${noteId} not found.`]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const results = await noteCacheService.findSimilarNotes(note.title); |     const results = await noteCacheService.findSimilarNotes(noteId); | ||||||
|  |  | ||||||
|     return results |     return results | ||||||
|         .filter(note => note.noteId !== noteId); |         .filter(note => note.noteId !== noteId); | ||||||
| @@ -20,4 +20,4 @@ async function getSimilarNotes(req) { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getSimilarNotes |     getSimilarNotes | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -35,46 +35,75 @@ class Note { | |||||||
|         this.children = []; |         this.children = []; | ||||||
|         /** @param {Attribute[]} */ |         /** @param {Attribute[]} */ | ||||||
|         this.ownedAttributes = []; |         this.ownedAttributes = []; | ||||||
|  |  | ||||||
|  |         /** @param {Attribute[]|null} */ | ||||||
|  |         this.attributeCache = null; | ||||||
|  |         /** @param {Attribute[]|null} */ | ||||||
|  |         this.templateAttributeCache = null; | ||||||
|  |         /** @param {Attribute[]|null} */ | ||||||
|  |         this.inheritableAttributeCache = null; | ||||||
|  |  | ||||||
|  |         /** @param {string|null} */ | ||||||
|  |         this.fulltextCache = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return {Attribute[]} */ |     /** @return {Attribute[]} */ | ||||||
|     get attributes() { |     get attributes() { | ||||||
|         if (!(this.noteId in noteAttributeCache)) { |         if (!this.attributeCache) { | ||||||
|             const attrArrs = [ |             const parentAttributes = this.ownedAttributes.slice(); | ||||||
|                 this.ownedAttributes |  | ||||||
|             ]; |  | ||||||
|  |  | ||||||
|             for (const templateAttr of this.ownedAttributes.filter(oa => oa.type === 'relation' && oa.name === 'template')) { |  | ||||||
|                 const templateNote = notes[templateAttr.value]; |  | ||||||
|  |  | ||||||
|                 if (templateNote) { |  | ||||||
|                     attrArrs.push(templateNote.attributes); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (this.noteId !== 'root') { |             if (this.noteId !== 'root') { | ||||||
|                 for (const parentNote of this.parents) { |                 for (const parentNote of this.parents) { | ||||||
|                     attrArrs.push(parentNote.inheritableAttributes); |                     parentAttributes.push(...parentNote.inheritableAttributes); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             noteAttributeCache[this.noteId] = attrArrs.flat(); |             const templateAttributes = []; | ||||||
|  |  | ||||||
|  |             for (const ownedAttr of parentAttributes) { // parentAttributes so we process also inherited templates | ||||||
|  |                 if (ownedAttr.type === 'relation' && ownedAttr.name === 'template') { | ||||||
|  |                     const templateNote = notes[ownedAttr.value]; | ||||||
|  |  | ||||||
|  |                     if (templateNote) { | ||||||
|  |                         templateAttributes.push(...templateNote.attributes); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.attributeCache = parentAttributes.concat(templateAttributes); | ||||||
|  |             this.inheritableAttributeCache = []; | ||||||
|  |             this.templateAttributeCache = []; | ||||||
|  |  | ||||||
|  |             for (const attr of this.attributeCache) { | ||||||
|  |                 if (attr.isInheritable) { | ||||||
|  |                     this.inheritableAttributeCache.push(attr); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 if (attr.type === 'relation' && attr.name === 'template') { | ||||||
|  |                     this.templateAttributeCache.push(attr); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return noteAttributeCache[this.noteId]; |         return this.attributeCache; | ||||||
|     } |  | ||||||
|  |  | ||||||
|     addSubTreeNoteIdsTo(noteIdSet) { |  | ||||||
|         noteIdSet.add(this.noteId); |  | ||||||
|  |  | ||||||
|         for (const child of this.children) { |  | ||||||
|             child.addSubTreeNoteIdsTo(noteIdSet); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** @return {Attribute[]} */ |     /** @return {Attribute[]} */ | ||||||
|     get inheritableAttributes() { |     get inheritableAttributes() { | ||||||
|         return this.attributes.filter(attr => attr.isInheritable); |         if (!this.inheritableAttributeCache) { | ||||||
|  |             this.attributes; // will refresh also this.inheritableAttributeCache | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this.inheritableAttributeCache; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** @return {Attribute[]} */ | ||||||
|  |     get templateAttributes() { | ||||||
|  |         if (!this.templateAttributeCache) { | ||||||
|  |             this.attributes; // will refresh also this.templateAttributeCache | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this.templateAttributeCache; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     hasAttribute(type, name) { |     hasAttribute(type, name) { | ||||||
| @@ -94,6 +123,63 @@ class Note { | |||||||
|     resortParents() { |     resortParents() { | ||||||
|         this.parents.sort((a, b) => a.hasInheritableOwnedArchivedLabel ? 1 : -1); |         this.parents.sort((a, b) => a.hasInheritableOwnedArchivedLabel ? 1 : -1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get fulltext() { | ||||||
|  |         if (!this.fulltextCache) { | ||||||
|  |             this.fulltextCache = this.title.toLowerCase(); | ||||||
|  |  | ||||||
|  |             for (const attr of this.attributes) { | ||||||
|  |                 // it's best to use space as separator since spaces are filtered from the search string by the tokenization into words | ||||||
|  |                 this.fulltextCache += ' ' + attr.name.toLowerCase(); | ||||||
|  |  | ||||||
|  |                 if (attr.value) { | ||||||
|  |                     this.fulltextCache += ' ' + attr.value.toLowerCase(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return this.fulltextCache; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     invalidateThisCache() { | ||||||
|  |         this.fulltextCache = null; | ||||||
|  |  | ||||||
|  |         this.attributeCache = null; | ||||||
|  |         this.templateAttributeCache = null; | ||||||
|  |         this.inheritableAttributeCache = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     invalidateSubtreeCaches() { | ||||||
|  |         this.invalidateThisCache(); | ||||||
|  |  | ||||||
|  |         for (const childNote of this.children) { | ||||||
|  |             childNote.invalidateSubtreeCaches(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (const templateAttr of this.templateAttributes) { | ||||||
|  |             const targetNote = templateAttr.targetNote; | ||||||
|  |  | ||||||
|  |             if (targetNote) { | ||||||
|  |                 targetNote.invalidateSubtreeCaches(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     invalidateSubtreeFulltext() { | ||||||
|  |         this.fulltextCache = null; | ||||||
|  |  | ||||||
|  |         for (const childNote of this.children) { | ||||||
|  |             childNote.invalidateSubtreeFulltext(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for (const templateAttr of this.templateAttributes) { | ||||||
|  |             const targetNote = templateAttr.targetNote; | ||||||
|  |  | ||||||
|  |             if (targetNote) { | ||||||
|  |                 targetNote.invalidateSubtreeFulltext(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| class Branch { | class Branch { | ||||||
| @@ -137,47 +223,16 @@ class Attribute { | |||||||
|         /** @param {boolean} */ |         /** @param {boolean} */ | ||||||
|         this.isInheritable = !!row.isInheritable; |         this.isInheritable = !!row.isInheritable; | ||||||
|     } |     } | ||||||
| } |  | ||||||
|  |  | ||||||
| /** @type {Object.<String, String>} */ |     get isAffectingSubtree() { | ||||||
| let fulltext = {}; |         return this.isInheritable | ||||||
|  |             || (this.type === 'relation' && this.name === 'template'); | ||||||
| /** @type {Object.<String, AttributeMeta>} */ |  | ||||||
| let attributeMetas = {}; |  | ||||||
|  |  | ||||||
| class AttributeMeta { |  | ||||||
|     constructor(attribute) { |  | ||||||
|         this.type = attribute.type; |  | ||||||
|         this.name = attribute.name; |  | ||||||
|         this.isInheritable = attribute.isInheritable; |  | ||||||
|         this.attributeIds = new Set(attribute.attributeId); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     addAttribute(attribute) { |     get targetNote() { | ||||||
|         this.attributeIds.add(attribute.attributeId); |         if (this.type === 'relation') { | ||||||
|         this.isInheritable = this.isInheritable || attribute.isInheritable; |             return notes[this.value]; | ||||||
|     } |  | ||||||
|  |  | ||||||
|     updateAttribute(attribute) { |  | ||||||
|         if (attribute.isDeleted) { |  | ||||||
|             this.attributeIds.delete(attribute.attributeId); |  | ||||||
|         } |         } | ||||||
|         else { |  | ||||||
|             this.attributeIds.add(attribute.attributeId); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         this.isInheritable = !!this.attributeIds.find(attributeId => attributes[attributeId].isInheritable); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function addToAttributeMeta(attribute) { |  | ||||||
|     const key = `${attribute.type}-${attribute.name}`; |  | ||||||
|  |  | ||||||
|     if (!(key in attributeMetas)) { |  | ||||||
|         attributeMetas[key] = new AttributeMeta(attribute); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|         attributeMetas[key].addAttribute(attribute); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -186,9 +241,6 @@ let loadedPromiseResolve; | |||||||
| /** Is resolved after the initial load */ | /** Is resolved after the initial load */ | ||||||
| let loadedPromise = new Promise(res => loadedPromiseResolve = res); | let loadedPromise = new Promise(res => loadedPromiseResolve = res); | ||||||
|  |  | ||||||
| // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here |  | ||||||
| let prefixes = {}; |  | ||||||
|  |  | ||||||
| async function getMappedRows(query, cb) { | async function getMappedRows(query, cb) { | ||||||
|     const map = {}; |     const map = {}; | ||||||
|     const results = await sql.getRows(query, []); |     const results = await sql.getRows(query, []); | ||||||
| @@ -202,17 +254,6 @@ async function getMappedRows(query, cb) { | |||||||
|     return map; |     return map; | ||||||
| } | } | ||||||
|  |  | ||||||
| function updateFulltext(note) { |  | ||||||
|     let ft = note.title.toLowerCase(); |  | ||||||
|  |  | ||||||
|     for (const attr of note.attributes) { |  | ||||||
|         ft += '|' + attr.name.toLowerCase(); |  | ||||||
|         ft += '|' + attr.value.toLowerCase(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fulltext[note.noteId] = ft; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| async function load() { | async function load() { | ||||||
|     notes = await getMappedRows(`SELECT noteId, title, isProtected FROM notes WHERE isDeleted = 0`, |     notes = await getMappedRows(`SELECT noteId, title, isProtected FROM notes WHERE isDeleted = 0`, | ||||||
|         row => new Note(row)); |         row => new Note(row)); | ||||||
| @@ -225,8 +266,6 @@ async function load() { | |||||||
|  |  | ||||||
|     for (const attr of Object.values(attributes)) { |     for (const attr of Object.values(attributes)) { | ||||||
|         notes[attr.noteId].ownedAttributes.push(attr); |         notes[attr.noteId].ownedAttributes.push(attr); | ||||||
|  |  | ||||||
|         addToAttributeMeta(attributes); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const branch of Object.values(branches)) { |     for (const branch of Object.values(branches)) { | ||||||
| @@ -250,10 +289,6 @@ async function load() { | |||||||
|         await decryptProtectedNotes(); |         await decryptProtectedNotes(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const note of Object.values(notes)) { |  | ||||||
|         updateFulltext(note); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     loaded = true; |     loaded = true; | ||||||
|     loadedPromiseResolve(); |     loadedPromiseResolve(); | ||||||
| } | } | ||||||
| @@ -325,38 +360,21 @@ function highlightResults(results, allTokens) { | |||||||
|  * Returns noteIds which have at least one matching tokens |  * Returns noteIds which have at least one matching tokens | ||||||
|  * |  * | ||||||
|  * @param tokens |  * @param tokens | ||||||
|  * @return {Set<String>} |  * @return {String[]} | ||||||
|  */ |  */ | ||||||
| function getCandidateNotes(tokens) { | function getCandidateNotes(tokens) { | ||||||
|     const candidateNoteIds = new Set(); |     const candidateNotes = []; | ||||||
|  |  | ||||||
|     for (const token of tokens) { |     for (const note of Object.values(notes)) { | ||||||
|         for (const noteId in fulltext) { |         for (const token of tokens) { | ||||||
|             if (!fulltext[noteId].includes(token)) { |             if (note.fulltext.includes(token)) { | ||||||
|                 continue; |                 candidateNotes.push(note); | ||||||
|  |                 break; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             candidateNoteIds.add(noteId); |  | ||||||
|             const note = notes[noteId]; |  | ||||||
|             const inheritableAttrs = note.ownedAttributes.filter(attr => attr.isInheritable); |  | ||||||
|  |  | ||||||
|             searchingAttrs: |  | ||||||
|                 // for matching inheritable attributes, include the whole note subtree to the candidates |  | ||||||
|                 for (const attr of inheritableAttrs) { |  | ||||||
|                     const lcName = attr.name.toLowerCase(); |  | ||||||
|                     const lcValue = attr.value.toLowerCase(); |  | ||||||
|  |  | ||||||
|                     for (const token of tokens) { |  | ||||||
|                         if (lcName.includes(token) || lcValue.includes(token)) { |  | ||||||
|                             note.addSubTreeNoteIdsTo(candidateNoteIds); |  | ||||||
|  |  | ||||||
|                             break searchingAttrs; |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     return candidateNoteIds; |  | ||||||
|  |     return candidateNotes; | ||||||
| } | } | ||||||
|  |  | ||||||
| async function findNotes(query) { | async function findNotes(query) { | ||||||
| @@ -370,18 +388,16 @@ async function findNotes(query) { | |||||||
|         .split(/[ -]/) |         .split(/[ -]/) | ||||||
|         .filter(token => token !== '/'); // '/' is used as separator |         .filter(token => token !== '/'); // '/' is used as separator | ||||||
|  |  | ||||||
|     const candidateNoteIds = getCandidateNotes(allTokens); |     const candidateNotes = getCandidateNotes(allTokens); | ||||||
|  |  | ||||||
|     // now we have set of noteIds which match at least one token |     // now we have set of noteIds which match at least one token | ||||||
|  |  | ||||||
|     let results = []; |     let results = []; | ||||||
|     const tokens = allTokens.slice(); |     const tokens = allTokens.slice(); | ||||||
|  |  | ||||||
|     for (const noteId of candidateNoteIds) { |     for (const note of candidateNotes) { | ||||||
|         const note = notes[noteId]; |  | ||||||
|  |  | ||||||
|         // autocomplete should be able to find notes by their noteIds as well (only leafs) |         // autocomplete should be able to find notes by their noteIds as well (only leafs) | ||||||
|         if (noteId === query) { |         if (note.noteId === query) { | ||||||
|             search(note, [], [], results); |             search(note, [], [], results); | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| @@ -415,7 +431,7 @@ async function findNotes(query) { | |||||||
|             if (foundTokens.length > 0) { |             if (foundTokens.length > 0) { | ||||||
|                 const remainingTokens = tokens.filter(token => !foundTokens.includes(token)); |                 const remainingTokens = tokens.filter(token => !foundTokens.includes(token)); | ||||||
|  |  | ||||||
|                 search(parentNote, remainingTokens, [noteId], results); |                 search(parentNote, remainingTokens, [note.noteId], results); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -678,11 +694,11 @@ function getNotePath(noteId) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function evaluateSimilarity(text, note, results) { | function evaluateSimilarity(sourceNote, candidateNote, results) { | ||||||
|     let coeff = stringSimilarity.compareTwoStrings(text, note.title); |     let coeff = stringSimilarity.compareTwoStrings(sourceNote.fulltext, candidateNote.fulltext); | ||||||
|  |  | ||||||
|     if (coeff > 0.4) { |     if (coeff > 0.4) { | ||||||
|         const notePath = getSomePath(note); |         const notePath = getSomePath(candidateNote); | ||||||
|  |  | ||||||
|         // this takes care of note hoisting |         // this takes care of note hoisting | ||||||
|         if (!notePath) { |         if (!notePath) { | ||||||
| @@ -693,7 +709,7 @@ function evaluateSimilarity(text, note, results) { | |||||||
|             coeff -= 0.2; // archived penalization |             coeff -= 0.2; // archived penalization | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         results.push({coeff, notePath, noteId: note.noteId}); |         results.push({coeff, notePath, noteId: candidateNote.noteId}); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -707,16 +723,22 @@ function setImmediatePromise() { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function findSimilarNotes(title) { | async function findSimilarNotes(noteId) { | ||||||
|     const results = []; |     const results = []; | ||||||
|     let i = 0; |     let i = 0; | ||||||
|  |  | ||||||
|  |     const origNote = notes[noteId]; | ||||||
|  |  | ||||||
|  |     if (!origNote) { | ||||||
|  |         return []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     for (const note of Object.values(notes)) { |     for (const note of Object.values(notes)) { | ||||||
|         if (note.isProtected && !note.isDecrypted) { |         if (note.isProtected && !note.isDecrypted) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         evaluateSimilarity(title, note, results); |         evaluateSimilarity(origNote, note, results); | ||||||
|  |  | ||||||
|         i++; |         i++; | ||||||
|  |  | ||||||
| @@ -744,9 +766,12 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED | |||||||
|             delete notes[noteId]; |             delete notes[noteId]; | ||||||
|         } |         } | ||||||
|         else if (noteId in notes) { |         else if (noteId in notes) { | ||||||
|  |             const note = notes[noteId]; | ||||||
|  |  | ||||||
|             // we can assume we have protected session since we managed to update |             // we can assume we have protected session since we managed to update | ||||||
|             notes[noteId].title = entity.title; |             note.title = entity.title; | ||||||
|             notes[noteId].isDecrypted = true; |             note.isDecrypted = true; | ||||||
|  |             note.fulltextCache = null; | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             notes[noteId] = new Note(entity); |             notes[noteId] = new Note(entity); | ||||||
| @@ -760,6 +785,10 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED | |||||||
|  |  | ||||||
|             if (childNote) { |             if (childNote) { | ||||||
|                 childNote.parents = childNote.parents.filter(parent => parent.noteId !== parentNoteId); |                 childNote.parents = childNote.parents.filter(parent => parent.noteId !== parentNoteId); | ||||||
|  |  | ||||||
|  |                 if (childNote.parents.length > 0) { | ||||||
|  |                     childNote.invalidateSubtreeCaches(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const parentNote = notes[parentNoteId]; |             const parentNote = notes[parentNoteId]; | ||||||
| @@ -787,30 +816,46 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED | |||||||
|     } |     } | ||||||
|     else if (entityName === 'attributes') { |     else if (entityName === 'attributes') { | ||||||
|         const {attributeId, noteId} = entity; |         const {attributeId, noteId} = entity; | ||||||
|  |         const note = notes[noteId]; | ||||||
|  |         const attr = attributes[attributeId]; | ||||||
|  |  | ||||||
|         if (entity.isDeleted) { |         if (entity.isDeleted) { | ||||||
|             const note = notes[noteId]; |             if (note && attr) { | ||||||
|  |  | ||||||
|             if (note) { |  | ||||||
|                 note.ownedAttributes = note.ownedAttributes.filter(attr => attr.attributeId !== attributeId); |                 note.ownedAttributes = note.ownedAttributes.filter(attr => attr.attributeId !== attributeId); | ||||||
|  |  | ||||||
|  |                 if (attr.isAffectingSubtree) { | ||||||
|  |                     note.invalidateSubtreeCaches(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             delete attributes[entity.attributeId]; |             delete attributes[attributeId]; | ||||||
|         } |         } | ||||||
|         else if (attributeId in attributes) { |         else if (attributeId in attributes) { | ||||||
|             const attr = attributes[attributeId]; |             const attr = attributes[attributeId]; | ||||||
|  |  | ||||||
|             // attr name cannot change |             // attr name and isInheritable are immutable | ||||||
|             attr.value = entity.value; |             attr.value = entity.value; | ||||||
|             attr.isInheritable = entity.isInheritable; |  | ||||||
|  |             if (attr.isAffectingSubtree) { | ||||||
|  |                 note.invalidateSubtreeFulltext(); | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 note.fulltextCache = null; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             attributes[attributeId] = new Attribute(entity); |             const attr = new Attribute(entity); | ||||||
|  |             attributes[attributeId] = attr; | ||||||
|             const note = notes[noteId]; |  | ||||||
|  |  | ||||||
|             if (note) { |             if (note) { | ||||||
|                 note.ownedAttributes.push(attributes[attributeId]); |                 note.ownedAttributes.push(attr); | ||||||
|  |  | ||||||
|  |                 if (attr.isAffectingSubtree) { | ||||||
|  |                     note.invalidateSubtreeCaches(); | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                     this.invalidateThisCache(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user