mirror of
https://github.com/zadam/trilium.git
synced 2024-11-11 18:08:13 +08:00
start of search overhaul
This commit is contained in:
parent
9be1d1f697
commit
7ac2206e9b
1 changed files with 176 additions and 78 deletions
|
@ -7,6 +7,108 @@ const utils = require('./utils');
|
|||
const hoistedNoteService = require('./hoisted_note');
|
||||
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 loadedPromiseResolve;
|
||||
/** Is resolved after the initial load */
|
||||
|
@ -15,49 +117,60 @@ let loadedPromise = new Promise(res => loadedPromiseResolve = res);
|
|||
let noteTitles = {};
|
||||
let protectedNoteTitles = {};
|
||||
let noteIds;
|
||||
let childParentToBranchId = {};
|
||||
const childToParent = {};
|
||||
let archived = {};
|
||||
|
||||
// key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
|
||||
let prefixes = {};
|
||||
|
||||
async function load() {
|
||||
noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
|
||||
noteIds = Object.keys(noteTitles);
|
||||
async function getMappedRows(query, cb) {
|
||||
const map = {};
|
||||
const results = await sql.getRows(query, []);
|
||||
|
||||
prefixes = await sql.getMap(`
|
||||
SELECT noteId || '-' || parentNoteId, prefix
|
||||
FROM branches
|
||||
WHERE isDeleted = 0 AND prefix IS NOT NULL AND prefix != ''`);
|
||||
for (const row of results) {
|
||||
const keys = Object.keys(row);
|
||||
|
||||
const branches = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
|
||||
|
||||
for (const rel of branches) {
|
||||
childToParent[rel.noteId] = childToParent[rel.noteId] || [];
|
||||
childToParent[rel.noteId].push(rel.parentNoteId);
|
||||
childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId;
|
||||
map[row[keys[0]]] = cb(row);
|
||||
}
|
||||
|
||||
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()) {
|
||||
await loadProtectedNotes();
|
||||
}
|
||||
|
||||
for (const noteId in childToParent) {
|
||||
resortChildToParent(noteId);
|
||||
await decryptProtectedNotes();
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
loadedPromiseResolve();
|
||||
}
|
||||
|
||||
async function loadProtectedNotes() {
|
||||
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
|
||||
|
||||
for (const noteId in protectedNoteTitles) {
|
||||
protectedNoteTitles[noteId] = protectedSessionService.decryptString(protectedNoteTitles[noteId]);
|
||||
async function decryptProtectedNotes() {
|
||||
for (const note of notes) {
|
||||
if (note.isProtected) {
|
||||
note.title = protectedSessionService.decryptString(note.title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,13 +216,9 @@ async function findNotes(query) {
|
|||
const tokens = allTokens.slice();
|
||||
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)
|
||||
if (noteId === query) {
|
||||
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
|
||||
if (noteId in archived) {
|
||||
if (note.isArchived) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parents = childToParent[noteId];
|
||||
if (!parents) {
|
||||
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();
|
||||
for (const parentNote of note.parents) {
|
||||
const title = getNoteTitle(note, parentNote).toLowerCase();
|
||||
const foundTokens = [];
|
||||
|
||||
for (const token of tokens) {
|
||||
|
@ -144,7 +243,7 @@ async function findNotes(query) {
|
|||
if (foundTokens.length > 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
function search(noteId, tokens, path, results) {
|
||||
if (tokens.length === 0) {
|
||||
const retPath = getSomePath(noteId, path);
|
||||
function getBranch(childNoteId, parentNoteId) {
|
||||
return childParentToBranch[`${childNoteId}-${parentNoteId}`];
|
||||
}
|
||||
|
||||
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 thisParentNoteId = retPath[retPath.length - 2];
|
||||
|
||||
results.push({
|
||||
noteId: thisNoteId,
|
||||
branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`],
|
||||
branchId: getBranch(thisNoteId, thisParentNoteId),
|
||||
pathArray: retPath,
|
||||
titleArray: getNoteTitleArrayForPath(retPath)
|
||||
});
|
||||
|
@ -199,18 +302,12 @@ function search(noteId, tokens, path, results) {
|
|||
return;
|
||||
}
|
||||
|
||||
const parents = childToParent[noteId];
|
||||
if (!parents || noteId === 'root') {
|
||||
if (!note.parents.length === 0 || noteId === 'root') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const parentNoteId of parents) {
|
||||
// archived must be inheritable
|
||||
if (archived[parentNoteId] === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const title = getNoteTitle(noteId, parentNoteId).toLowerCase();
|
||||
for (const parentNote of note.parents) {
|
||||
const title = getNoteTitle(note, parentNote).toLowerCase();
|
||||
const foundTokens = [];
|
||||
|
||||
for (const token of tokens) {
|
||||
|
@ -222,17 +319,18 @@ function search(noteId, tokens, path, results) {
|
|||
if (foundTokens.length > 0) {
|
||||
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
||||
|
||||
search(parentNoteId, remainingTokens, path.concat([noteId]), results);
|
||||
search(parentNote, remainingTokens, path.concat([noteId]), results);
|
||||
}
|
||||
else {
|
||||
search(parentNoteId, tokens, path.concat([noteId]), results);
|
||||
search(parentNote, tokens, path.concat([noteId]), results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isNotePathArchived(notePath) {
|
||||
// if the note is archived directly
|
||||
if (archived[notePath[notePath.length - 1]] !== undefined) {
|
||||
const noteId = notePath[notePath.length - 1];
|
||||
|
||||
if (archived[noteId] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -268,8 +366,10 @@ function isInAncestor(noteId, ancestorNoteId) {
|
|||
return true;
|
||||
}
|
||||
|
||||
for (const parentNoteId of childToParent[noteId] || []) {
|
||||
if (isInAncestor(parentNoteId, ancestorNoteId)) {
|
||||
const note = notes[noteId];
|
||||
|
||||
for (const parentNote of notes.parents) {
|
||||
if (isInAncestor(parentNote.noteId, ancestorNoteId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -288,21 +388,19 @@ function getNoteTitleFromPath(notePath) {
|
|||
}
|
||||
}
|
||||
|
||||
function getNoteTitle(noteId, parentNoteId) {
|
||||
const prefix = prefixes[noteId + '-' + parentNoteId];
|
||||
function getNoteTitle(childNote, parentNote) {
|
||||
let title;
|
||||
|
||||
let title = noteTitles[noteId];
|
||||
|
||||
if (!title) {
|
||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||
title = protectedNoteTitles[noteId];
|
||||
}
|
||||
else {
|
||||
title = '[protected]';
|
||||
}
|
||||
if (childNote.isProtected) {
|
||||
title = protectedSessionService.isProtectedSessionAvailable() ? childNote.title : '[protected]';
|
||||
}
|
||||
else {
|
||||
title = childNote.title;
|
||||
}
|
||||
|
||||
return (prefix ? (prefix + ' - ') : '') + title;
|
||||
const branch = getBranch(childNote.noteId, parentNote.noteId);
|
||||
|
||||
return (branch.prefix ? (branch.prefix + ' - ') : '') + title;
|
||||
}
|
||||
|
||||
function getNoteTitleArrayForPath(path) {
|
||||
|
@ -540,7 +638,7 @@ function isAvailable(noteId) {
|
|||
}
|
||||
|
||||
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||
loadedPromise.then(() => loadProtectedNotes());
|
||||
loadedPromise.then(() => decryptProtectedNotes());
|
||||
});
|
||||
|
||||
sqlInit.dbReady.then(() => utils.stopWatch("Note cache load", load));
|
||||
|
|
Loading…
Reference in a new issue