mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 15:11:31 +08:00 
			
		
		
		
	note cache refactoring
This commit is contained in:
		
							parent
							
								
									b07accfd9d
								
							
						
					
					
						commit
						7992f32d34
					
				
							
								
								
									
										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
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return noteAttributeCache[this.noteId];
 | 
					            this.attributeCache = parentAttributes.concat(templateAttributes);
 | 
				
			||||||
 | 
					            this.inheritableAttributeCache = [];
 | 
				
			||||||
 | 
					            this.templateAttributeCache = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (const attr of this.attributeCache) {
 | 
				
			||||||
 | 
					                if (attr.isInheritable) {
 | 
				
			||||||
 | 
					                    this.inheritableAttributeCache.push(attr);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addSubTreeNoteIdsTo(noteIdSet) {
 | 
					                if (attr.type === 'relation' && attr.name === 'template') {
 | 
				
			||||||
        noteIdSet.add(this.noteId);
 | 
					                    this.templateAttributeCache.push(attr);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const child of this.children) {
 | 
					 | 
				
			||||||
            child.addSubTreeNoteIdsTo(noteIdSet);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.attributeCache;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get isAffectingSubtree() {
 | 
				
			||||||
 | 
					        return this.isInheritable
 | 
				
			||||||
 | 
					            || (this.type === 'relation' && this.name === 'template');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** @type {Object.<String, String>} */
 | 
					    get targetNote() {
 | 
				
			||||||
let fulltext = {};
 | 
					        if (this.type === 'relation') {
 | 
				
			||||||
 | 
					            return notes[this.value];
 | 
				
			||||||
/** @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) {
 | 
					 | 
				
			||||||
        this.attributeIds.add(attribute.attributeId);
 | 
					 | 
				
			||||||
        this.isInheritable = this.isInheritable || attribute.isInheritable;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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 note of Object.values(notes)) {
 | 
				
			||||||
        for (const token of tokens) {
 | 
					        for (const token of tokens) {
 | 
				
			||||||
        for (const noteId in fulltext) {
 | 
					            if (note.fulltext.includes(token)) {
 | 
				
			||||||
            if (!fulltext[noteId].includes(token)) {
 | 
					                candidateNotes.push(note);
 | 
				
			||||||
                continue;
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            candidateNoteIds.add(noteId);
 | 
					    return candidateNotes;
 | 
				
			||||||
            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;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 {
 | 
					            else {
 | 
				
			||||||
            attributes[attributeId] = new Attribute(entity);
 | 
					                note.fulltextCache = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const note = notes[noteId];
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            const attr = new Attribute(entity);
 | 
				
			||||||
 | 
					            attributes[attributeId] = attr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (note) {
 | 
					            if (note) {
 | 
				
			||||||
                note.ownedAttributes.push(attributes[attributeId]);
 | 
					                note.ownedAttributes.push(attr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (attr.isAffectingSubtree) {
 | 
				
			||||||
 | 
					                    note.invalidateSubtreeCaches();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    this.invalidateThisCache();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user