mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	start of search overhaul
This commit is contained in:
		| @@ -7,6 +7,108 @@ const utils = require('./utils'); | |||||||
| const hoistedNoteService = require('./hoisted_note'); | const hoistedNoteService = require('./hoisted_note'); | ||||||
| const stringSimilarity = require('string-similarity'); | const stringSimilarity = require('string-similarity'); | ||||||
|  |  | ||||||
|  | /** @var {Object.<String, Note>} */ | ||||||
|  | let notes; | ||||||
|  | /** @var {Object.<String, Branch>} */ | ||||||
|  | let branches | ||||||
|  | /** @var {Object.<String, Attribute>} */ | ||||||
|  | let attributes; | ||||||
|  |  | ||||||
|  | /** @var {Object.<String, Attribute[]>} */ | ||||||
|  | let noteAttributeCache = {}; | ||||||
|  |  | ||||||
|  | let childParentToBranch = {}; | ||||||
|  |  | ||||||
|  | class Note { | ||||||
|  |     constructor(row) { | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.noteId = row.noteId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.title = row.title; | ||||||
|  |         /** @param {boolean} */ | ||||||
|  |         this.isProtected = !!row.isProtected; | ||||||
|  |         /** @param {Note[]} */ | ||||||
|  |         this.parents = []; | ||||||
|  |         /** @param {Attribute[]} */ | ||||||
|  |         this.ownedAttributes = []; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** @return {Attribute[]} */ | ||||||
|  |     get attributes() { | ||||||
|  |         if (this.noteId in noteAttributeCache) { | ||||||
|  |             const attrArrs = [ | ||||||
|  |                 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') { | ||||||
|  |                 for (const parentNote of this.parents) { | ||||||
|  |                     attrArrs.push(parentNote.inheritableAttributes); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             noteAttributeCache[this.noteId] = attrArrs.flat(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return noteAttributeCache[this.noteId]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** @return {Attribute[]} */ | ||||||
|  |     get inheritableAttributes() { | ||||||
|  |         return this.attributes.filter(attr => attr.isInheritable); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     hasAttribute(type, name) { | ||||||
|  |         return this.attributes.find(attr => attr.type === type && attr.name === name); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     get isArchived() { | ||||||
|  |         return this.hasAttribute('label', 'archived'); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class Branch { | ||||||
|  |     constructor(row) { | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.branchId = row.branchId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.noteId = row.noteId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.parentNoteId = row.parentNoteId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.prefix = row.prefix; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** @return {Note} */ | ||||||
|  |     get parentNote() { | ||||||
|  |         return notes[this.parentNoteId]; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class Attribute { | ||||||
|  |     constructor(row) { | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.attributeId = row.attributeId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.noteId = row.noteId; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.type = row.type; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.name = row.name; | ||||||
|  |         /** @param {string} */ | ||||||
|  |         this.value = row.value; | ||||||
|  |         /** @param {boolean} */ | ||||||
|  |         this.isInheritable = row.isInheritable; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| let loaded = false; | let loaded = false; | ||||||
| let loadedPromiseResolve; | let loadedPromiseResolve; | ||||||
| /** Is resolved after the initial load */ | /** Is resolved after the initial load */ | ||||||
| @@ -15,49 +117,60 @@ let loadedPromise = new Promise(res => loadedPromiseResolve = res); | |||||||
| let noteTitles = {}; | let noteTitles = {}; | ||||||
| let protectedNoteTitles = {}; | let protectedNoteTitles = {}; | ||||||
| let noteIds; | let noteIds; | ||||||
| let childParentToBranchId = {}; |  | ||||||
| const childToParent = {}; | const childToParent = {}; | ||||||
| let archived = {}; | let archived = {}; | ||||||
|  |  | ||||||
| // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here | // key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here | ||||||
| let prefixes = {}; | let prefixes = {}; | ||||||
|  |  | ||||||
| async function load() { | async function getMappedRows(query, cb) { | ||||||
|     noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`); |     const map = {}; | ||||||
|     noteIds = Object.keys(noteTitles); |     const results = await sql.getRows(query, []); | ||||||
|  |  | ||||||
|     prefixes = await sql.getMap(` |     for (const row of results) { | ||||||
|             SELECT noteId || '-' || parentNoteId, prefix  |         const keys = Object.keys(row); | ||||||
|             FROM branches  |  | ||||||
|             WHERE isDeleted = 0 AND prefix IS NOT NULL AND prefix != ''`); |  | ||||||
|  |  | ||||||
|     const branches = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`); |         map[row[keys[0]]] = cb(row); | ||||||
|  |  | ||||||
|     for (const rel of branches) { |  | ||||||
|         childToParent[rel.noteId] = childToParent[rel.noteId] || []; |  | ||||||
|         childToParent[rel.noteId].push(rel.parentNoteId); |  | ||||||
|         childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`); |     return map; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function load() { | ||||||
|  |     notes = await getMappedRows(`SELECT noteId, title, isProtected FROM notes WHERE isDeleted = 0`, | ||||||
|  |         row => new Note(row)); | ||||||
|  |  | ||||||
|  |     branches = await getMappedRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, | ||||||
|  |         row => new Branch(row)); | ||||||
|  |  | ||||||
|  |     attributes = await getMappedRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, | ||||||
|  |         row => new Attribute(row)); | ||||||
|  |  | ||||||
|  |     for (const branch of branches) { | ||||||
|  |         const childNote = notes[branch.noteId]; | ||||||
|  |  | ||||||
|  |         if (!childNote) { | ||||||
|  |             console.log(`Cannot find child note ${branch.noteId} of a branch ${branch.branchId}`); | ||||||
|  |             continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         childNote.parents.push(branch.parentNote); | ||||||
|  |         childParentToBranch[`${branch.noteId}-${branch.parentNoteId}`] = branch; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (protectedSessionService.isProtectedSessionAvailable()) { |     if (protectedSessionService.isProtectedSessionAvailable()) { | ||||||
|         await loadProtectedNotes(); |         await decryptProtectedNotes(); | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (const noteId in childToParent) { |  | ||||||
|         resortChildToParent(noteId); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     loaded = true; |     loaded = true; | ||||||
|     loadedPromiseResolve(); |     loadedPromiseResolve(); | ||||||
| } | } | ||||||
|  |  | ||||||
| async function loadProtectedNotes() { | async function decryptProtectedNotes() { | ||||||
|     protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`); |     for (const note of notes) { | ||||||
|  |         if (note.isProtected) { | ||||||
|     for (const noteId in protectedNoteTitles) { |             note.title = protectedSessionService.decryptString(note.title); | ||||||
|         protectedNoteTitles[noteId] = protectedSessionService.decryptString(protectedNoteTitles[noteId]); |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -103,13 +216,9 @@ async function findNotes(query) { | |||||||
|     const tokens = allTokens.slice(); |     const tokens = allTokens.slice(); | ||||||
|     let results = []; |     let results = []; | ||||||
|  |  | ||||||
|     let noteIds = Object.keys(noteTitles); |     for (const noteId in notes) { | ||||||
|  |         const note = notes[noteId]; | ||||||
|  |  | ||||||
|     if (protectedSessionService.isProtectedSessionAvailable()) { |  | ||||||
|         noteIds = [...new Set(noteIds.concat(Object.keys(protectedNoteTitles)))]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (const noteId of noteIds) { |  | ||||||
|         // 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 (noteId === query) { | ||||||
|             search(noteId, [], [], results); |             search(noteId, [], [], results); | ||||||
| @@ -117,22 +226,12 @@ async function findNotes(query) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // for leaf note it doesn't matter if "archived" label is inheritable or not |         // for leaf note it doesn't matter if "archived" label is inheritable or not | ||||||
|         if (noteId in archived) { |         if (note.isArchived) { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const parents = childToParent[noteId]; |         for (const parentNote of note.parents) { | ||||||
|         if (!parents) { |             const title = getNoteTitle(note, parentNote).toLowerCase(); | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         for (const parentNoteId of parents) { |  | ||||||
|             // for parent note archived needs to be inheritable |  | ||||||
|             if (archived[parentNoteId] === 1) { |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             const title = getNoteTitle(noteId, parentNoteId).toLowerCase(); |  | ||||||
|             const foundTokens = []; |             const foundTokens = []; | ||||||
|  |  | ||||||
|             for (const token of tokens) { |             for (const token of tokens) { | ||||||
| @@ -144,7 +243,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(parentNoteId, remainingTokens, [noteId], results); |                 search(parentNote, remainingTokens, [noteId], results); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -180,17 +279,21 @@ async function findNotes(query) { | |||||||
|     return apiResults; |     return apiResults; | ||||||
| } | } | ||||||
|  |  | ||||||
| function search(noteId, tokens, path, results) { | function getBranch(childNoteId, parentNoteId) { | ||||||
|     if (tokens.length === 0) { |     return childParentToBranch[`${childNoteId}-${parentNoteId}`]; | ||||||
|         const retPath = getSomePath(noteId, path); | } | ||||||
|  |  | ||||||
|         if (retPath && !isNotePathArchived(retPath)) { | function search(note, tokens, path, results) { | ||||||
|  |     if (tokens.length === 0) { | ||||||
|  |         const retPath = getSomePath(note, path); | ||||||
|  |  | ||||||
|  |         if (retPath) { | ||||||
|             const thisNoteId = retPath[retPath.length - 1]; |             const thisNoteId = retPath[retPath.length - 1]; | ||||||
|             const thisParentNoteId = retPath[retPath.length - 2]; |             const thisParentNoteId = retPath[retPath.length - 2]; | ||||||
|  |  | ||||||
|             results.push({ |             results.push({ | ||||||
|                 noteId: thisNoteId, |                 noteId: thisNoteId, | ||||||
|                 branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`], |                 branchId: getBranch(thisNoteId, thisParentNoteId), | ||||||
|                 pathArray: retPath, |                 pathArray: retPath, | ||||||
|                 titleArray: getNoteTitleArrayForPath(retPath) |                 titleArray: getNoteTitleArrayForPath(retPath) | ||||||
|             }); |             }); | ||||||
| @@ -199,18 +302,12 @@ function search(noteId, tokens, path, results) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const parents = childToParent[noteId]; |     if (!note.parents.length === 0 || noteId === 'root') { | ||||||
|     if (!parents || noteId === 'root') { |  | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const parentNoteId of parents) { |     for (const parentNote of note.parents) { | ||||||
|         // archived must be inheritable |         const title = getNoteTitle(note, parentNote).toLowerCase(); | ||||||
|         if (archived[parentNoteId] === 1) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         const title = getNoteTitle(noteId, parentNoteId).toLowerCase(); |  | ||||||
|         const foundTokens = []; |         const foundTokens = []; | ||||||
|  |  | ||||||
|         for (const token of tokens) { |         for (const token of tokens) { | ||||||
| @@ -222,17 +319,18 @@ function search(noteId, tokens, path, results) { | |||||||
|         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(parentNoteId, remainingTokens, path.concat([noteId]), results); |             search(parentNote, remainingTokens, path.concat([noteId]), results); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             search(parentNoteId, tokens, path.concat([noteId]), results); |             search(parentNote, tokens, path.concat([noteId]), results); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function isNotePathArchived(notePath) { | function isNotePathArchived(notePath) { | ||||||
|     // if the note is archived directly |     const noteId = notePath[notePath.length - 1]; | ||||||
|     if (archived[notePath[notePath.length - 1]] !== undefined) { |  | ||||||
|  |     if (archived[noteId] !== undefined) { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -268,8 +366,10 @@ function isInAncestor(noteId, ancestorNoteId) { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (const parentNoteId of childToParent[noteId] || []) { |     const note = notes[noteId]; | ||||||
|         if (isInAncestor(parentNoteId, ancestorNoteId)) { |  | ||||||
|  |     for (const parentNote of notes.parents) { | ||||||
|  |         if (isInAncestor(parentNote.noteId, ancestorNoteId)) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -288,21 +388,19 @@ function getNoteTitleFromPath(notePath) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNoteTitle(noteId, parentNoteId) { | function getNoteTitle(childNote, parentNote) { | ||||||
|     const prefix = prefixes[noteId + '-' + parentNoteId]; |     let title; | ||||||
|  |  | ||||||
|     let title = noteTitles[noteId]; |     if (childNote.isProtected) { | ||||||
|  |         title = protectedSessionService.isProtectedSessionAvailable() ? childNote.title : '[protected]'; | ||||||
|     if (!title) { |  | ||||||
|         if (protectedSessionService.isProtectedSessionAvailable()) { |  | ||||||
|             title = protectedNoteTitles[noteId]; |  | ||||||
|     } |     } | ||||||
|     else { |     else { | ||||||
|             title = '[protected]'; |         title = childNote.title; | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return (prefix ? (prefix + ' - ') : '') + title; |     const branch = getBranch(childNote.noteId, parentNote.noteId); | ||||||
|  |  | ||||||
|  |     return (branch.prefix ? (branch.prefix + ' - ') : '') + title; | ||||||
| } | } | ||||||
|  |  | ||||||
| function getNoteTitleArrayForPath(path) { | function getNoteTitleArrayForPath(path) { | ||||||
| @@ -540,7 +638,7 @@ function isAvailable(noteId) { | |||||||
| } | } | ||||||
|  |  | ||||||
| eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | ||||||
|     loadedPromise.then(() => loadProtectedNotes()); |     loadedPromise.then(() => decryptProtectedNotes()); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load)); | sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load)); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user