mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
start of search overhaul
This commit is contained in:
parent
9be1d1f697
commit
7ac2206e9b
@ -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));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user