start of search overhaul

This commit is contained in:
zadam 2020-05-03 09:18:57 +02:00
parent 9be1d1f697
commit 7ac2206e9b

View file

@ -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));