trilium/src/services/note_cache.js

317 lines
9 KiB
JavaScript
Raw Normal View History

2018-04-18 12:26:42 +08:00
const sql = require('./sql');
const sqlInit = require('./sql_init');
const eventService = require('./events');
const repository = require('./repository');
const protectedSessionService = require('./protected_session');
const utils = require('./utils');
2018-04-18 12:26:42 +08:00
let loaded = false;
let noteTitles = {};
let protectedNoteTitles = {};
2018-04-18 12:26:42 +08:00
let noteIds;
2018-06-05 11:21:45 +08:00
let childParentToBranchId = {};
2018-04-18 12:26:42 +08:00
const childToParent = {};
let archived = {};
2018-04-18 12:26:42 +08:00
2018-04-20 08:59:44 +08:00
// key is 'childNoteId-parentNoteId' as a replacement for branchId which we don't use here
let prefixes = {};
2018-04-18 12:26:42 +08:00
async function load() {
noteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 0`);
2018-04-18 12:26:42 +08:00
noteIds = Object.keys(noteTitles);
prefixes = await sql.getMap(`SELECT noteId || '-' || parentNoteId, prefix FROM branches WHERE prefix IS NOT NULL AND prefix != ''`);
2018-04-20 08:59:44 +08:00
2018-06-05 11:21:45 +08:00
const relations = await sql.getRows(`SELECT branchId, noteId, parentNoteId FROM branches WHERE isDeleted = 0`);
2018-04-18 12:26:42 +08:00
for (const rel of relations) {
childToParent[rel.noteId] = childToParent[rel.noteId] || [];
childToParent[rel.noteId].push(rel.parentNoteId);
2018-06-05 11:21:45 +08:00
childParentToBranchId[`${rel.noteId}-${rel.parentNoteId}`] = rel.branchId;
2018-04-18 12:26:42 +08:00
}
archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`);
loaded = true;
2018-04-18 12:26:42 +08:00
}
function findNotes(query) {
if (!noteTitles || !query.length) {
2018-04-18 12:26:42 +08:00
return [];
}
const tokens = query.toLowerCase().split(" ");
const results = [];
let noteIds = Object.keys(noteTitles);
if (protectedSessionService.isProtectedSessionAvailable()) {
noteIds = 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);
continue;
}
// for leaf note it doesn't matter if "archived" label inheritable or not
if (noteId in archived) {
continue;
}
2018-04-20 08:59:44 +08:00
const parents = childToParent[noteId];
if (!parents) {
continue;
}
2018-04-18 12:26:42 +08:00
2018-04-20 08:59:44 +08:00
for (const parentNoteId of parents) {
// for parent note archived needs to be inheritable
if (archived[parentNoteId] === 1) {
2018-05-26 22:50:13 +08:00
continue;
}
2018-06-05 08:22:41 +08:00
const title = getNoteTitle(noteId, parentNoteId).toLowerCase();
2018-04-20 08:59:44 +08:00
const foundTokens = [];
for (const token of tokens) {
if (title.includes(token)) {
foundTokens.push(token);
}
2018-04-18 12:26:42 +08:00
}
2018-04-20 08:59:44 +08:00
if (foundTokens.length > 0) {
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
2018-04-18 12:26:42 +08:00
2018-04-20 08:59:44 +08:00
search(parentNoteId, remainingTokens, [noteId], results);
}
2018-04-18 12:26:42 +08:00
}
}
results.sort((a, b) => a.title < b.title ? -1 : 1);
2018-04-18 12:26:42 +08:00
return results;
}
2018-04-20 08:59:44 +08:00
function search(noteId, tokens, path, results) {
if (tokens.length === 0) {
2018-04-20 08:59:44 +08:00
const retPath = getSomePath(noteId, path);
2018-04-18 12:26:42 +08:00
if (retPath) {
const noteTitle = getNoteTitleForPath(retPath);
2018-06-05 11:21:45 +08:00
const thisNoteId = retPath[retPath.length - 1];
const thisParentNoteId = retPath[retPath.length - 2];
2018-04-18 12:26:42 +08:00
results.push({
2018-06-05 11:21:45 +08:00
noteId: thisNoteId,
branchId: childParentToBranchId[`${thisNoteId}-${thisParentNoteId}`],
title: noteTitle,
2018-06-06 10:47:47 +08:00
path: retPath.join('/')
});
}
return;
}
2018-04-20 08:59:44 +08:00
const parents = childToParent[noteId];
if (!parents || noteId === 'root') {
2018-04-20 08:59:44 +08:00
return;
}
for (const parentNoteId of parents) {
if (results.length >= 200) {
return;
}
// archived must be inheritable
if (archived[parentNoteId] === 1) {
2018-04-18 12:26:42 +08:00
continue;
}
2018-05-26 22:50:13 +08:00
const title = getNoteTitle(noteId, parentNoteId).toLowerCase();
2018-04-18 12:26:42 +08:00
const foundTokens = [];
for (const token of tokens) {
if (title.includes(token)) {
foundTokens.push(token);
}
}
if (foundTokens.length > 0) {
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
2018-04-20 08:59:44 +08:00
search(parentNoteId, remainingTokens, path.concat([noteId]), results);
2018-04-18 12:26:42 +08:00
}
else {
2018-04-20 08:59:44 +08:00
search(parentNoteId, tokens, path.concat([noteId]), results);
2018-04-18 12:26:42 +08:00
}
}
}
2018-06-05 08:22:41 +08:00
function getNoteTitle(noteId, parentNoteId) {
const prefix = prefixes[noteId + '-' + parentNoteId];
let title = noteTitles[noteId];
if (!title) {
if (protectedSessionService.isProtectedSessionAvailable()) {
title = protectedNoteTitles[noteId];
}
else {
title = '[protected]';
}
}
return (prefix ? (prefix + ' - ') : '') + title;
}
function getNoteTitleForPath(path) {
2018-04-20 08:59:44 +08:00
const titles = [];
2018-04-18 12:26:42 +08:00
2018-06-07 10:38:36 +08:00
if (path[0] === 'root') {
if (path.length === 1) {
return getNoteTitle('root');
}
else {
path = path.slice(1);
}
}
2018-04-20 08:59:44 +08:00
let parentNoteId = 'root';
2018-04-18 12:26:42 +08:00
2018-04-20 08:59:44 +08:00
for (const noteId of path) {
2018-06-05 08:22:41 +08:00
const title = getNoteTitle(noteId, parentNoteId);
2018-04-20 08:59:44 +08:00
titles.push(title);
parentNoteId = noteId;
}
2018-04-20 08:59:44 +08:00
return titles.join(' / ');
}
2018-04-20 08:59:44 +08:00
function getSomePath(noteId, path) {
if (noteId === 'root') {
path.reverse();
2018-04-20 08:59:44 +08:00
return path;
}
2018-04-20 08:59:44 +08:00
const parents = childToParent[noteId];
if (!parents || parents.length === 0) {
return false;
}
for (const parentNoteId of parents) {
// archived applies here only if inheritable
if (archived[parentNoteId] === 1) {
continue;
}
2018-04-20 08:59:44 +08:00
const retPath = getSomePath(parentNoteId, path.concat([noteId]));
if (retPath) {
return retPath;
}
}
return false;
}
function getNotePath(noteId) {
const retPath = getSomePath(noteId, []);
if (retPath) {
const noteTitle = getNoteTitleForPath(retPath);
2018-06-06 10:47:47 +08:00
const parentNoteId = childToParent[noteId][0];
return {
noteId: noteId,
2018-06-06 10:47:47 +08:00
branchId: childParentToBranchId[`${noteId}-${parentNoteId}`],
title: noteTitle,
path: retPath.join('/')
};
}
}
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => {
if (!loaded) {
return;
}
if (entityName === 'notes') {
const note = entity;
if (note.isDeleted) {
delete noteTitles[note.noteId];
delete childToParent[note.noteId];
}
else {
if (note.isProtected) {
if (protectedSessionService.isProtectedSessionAvailable()) {
protectedNoteTitles[note.noteId] = protectedSessionService.decryptNoteTitle(note.noteId, note.title);
}
}
else {
noteTitles[note.noteId] = note.title;
}
}
}
2018-04-20 08:59:44 +08:00
else if (entityName === 'branches') {
const branch = entity;
2018-04-20 08:59:44 +08:00
if (childToParent[branch.noteId]) {
childToParent[branch.noteId] = childToParent[branch.noteId].filter(noteId => noteId !== branch.parentNoteId)
}
if (branch.isDeleted) {
delete prefixes[branch.noteId + '-' + branch.parentNoteId];
2018-06-05 11:21:45 +08:00
delete childParentToBranchId[branch.noteId + '-' + branch.parentNoteId];
2018-04-20 08:59:44 +08:00
}
else {
if (branch.prefix) {
prefixes[branch.noteId + '-' + branch.parentNoteId] = branch.prefix;
}
childToParent[branch.noteId] = childToParent[branch.noteId] || [];
childToParent[branch.noteId].push(branch.parentNoteId);
2018-06-05 11:21:45 +08:00
childParentToBranchId[branch.noteId + '-' + branch.parentNoteId] = branch.branchId;
2018-04-20 08:59:44 +08:00
}
}
else if (entityName === 'attributes') {
const attribute = entity;
if (attribute.type === 'label' && attribute.name === 'archived') {
// we're not using label object directly, since there might be other non-deleted archived label
const hideLabel = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label'
AND name = 'archived' AND noteId = ?`, [attribute.noteId]);
if (hideLabel) {
archived[attribute.noteId] = hideLabel.isInheritable ? 1 : 0;
}
else {
delete archived[attribute.noteId];
}
}
}
});
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
if (!loaded) {
return;
}
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
for (const noteId in protectedNoteTitles) {
protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
}
});
sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
2018-04-18 12:26:42 +08:00
module.exports = {
findNotes,
2018-06-07 10:38:36 +08:00
getNotePath,
getNoteTitleForPath
2018-04-18 12:26:42 +08:00
};