mirror of
https://github.com/zadam/trilium.git
synced 2024-09-21 16:16:04 +08:00
refactoring ...
This commit is contained in:
parent
f07025f741
commit
78ea0b4ba9
|
@ -136,22 +136,26 @@ class Note {
|
||||||
return this.flatTextCache;
|
return this.flatTextCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.flatTextCache = this.title.toLowerCase();
|
this.flatTextCache = '';
|
||||||
|
|
||||||
for (const branch of this.parentBranches) {
|
for (const branch of this.parentBranches) {
|
||||||
if (branch.prefix) {
|
if (branch.prefix) {
|
||||||
this.flatTextCache += ' ' + branch.prefix;
|
this.flatTextCache += branch.prefix + ' - ';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.flatTextCache += this.title;
|
||||||
|
|
||||||
for (const attr of this.attributes) {
|
for (const attr of this.attributes) {
|
||||||
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
|
// it's best to use space as separator since spaces are filtered from the search string by the tokenization into words
|
||||||
this.flatTextCache += ' ' + attr.name.toLowerCase();
|
this.flatTextCache += (attr.type === 'label' ? '#' : '@') + attr.name;
|
||||||
|
|
||||||
if (attr.value) {
|
if (attr.value) {
|
||||||
this.flatTextCache += ' ' + attr.value.toLowerCase();
|
this.flatTextCache += '=' + attr.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.flatTextCache = this.flatTextCache.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.flatTextCache;
|
return this.flatTextCache;
|
||||||
|
@ -205,11 +209,11 @@ class Note {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return {Note[]} */
|
/** @return {Note[]} */
|
||||||
get subtreeNotes() {
|
get subtreeNotesIncludingTemplated() {
|
||||||
const arr = [[this]];
|
const arr = [[this]];
|
||||||
|
|
||||||
for (const childNote of this.children) {
|
for (const childNote of this.children) {
|
||||||
arr.push(childNote.subtreeNotes);
|
arr.push(childNote.subtreeNotesIncludingTemplated);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const targetRelation of this.targetRelations) {
|
for (const targetRelation of this.targetRelations) {
|
||||||
|
@ -217,7 +221,7 @@ class Note {
|
||||||
const note = targetRelation.note;
|
const note = targetRelation.note;
|
||||||
|
|
||||||
if (note) {
|
if (note) {
|
||||||
arr.push(note.subtreeNotes);
|
arr.push(note.subtreeNotesIncludingTemplated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +229,17 @@ class Note {
|
||||||
return arr.flat();
|
return arr.flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return {Note[]} */
|
||||||
|
get subtreeNotes() {
|
||||||
|
const arr = [[this]];
|
||||||
|
|
||||||
|
for (const childNote of this.children) {
|
||||||
|
arr.push(childNote.subtreeNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr.flat();
|
||||||
|
}
|
||||||
|
|
||||||
/** @return {Note[]} - returns only notes which are templated, does not include their subtrees
|
/** @return {Note[]} - returns only notes which are templated, does not include their subtrees
|
||||||
* in effect returns notes which are influenced by note's non-inheritable attributes */
|
* in effect returns notes which are influenced by note's non-inheritable attributes */
|
||||||
get templatedNotes() {
|
get templatedNotes() {
|
||||||
|
@ -378,9 +393,9 @@ class AndOp {
|
||||||
this.subExpressions = subExpressions;
|
this.subExpressions = subExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(noteSet) {
|
execute(noteSet, searchContext) {
|
||||||
for (const subExpression of this.subExpressions) {
|
for (const subExpression of this.subExpressions) {
|
||||||
noteSet = subExpression.execute(noteSet);
|
noteSet = subExpression.execute(noteSet, searchContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
return noteSet;
|
return noteSet;
|
||||||
|
@ -392,11 +407,11 @@ class OrOp {
|
||||||
this.subExpressions = subExpressions;
|
this.subExpressions = subExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
execute(noteSet) {
|
execute(noteSet, searchContext) {
|
||||||
const resultNoteSet = new NoteSet();
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
for (const subExpression of this.subExpressions) {
|
for (const subExpression of this.subExpressions) {
|
||||||
resultNoteSet.mergeIn(subExpression.execute(noteSet));
|
resultNoteSet.mergeIn(subExpression.execute(noteSet, searchContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultNoteSet;
|
return resultNoteSet;
|
||||||
|
@ -404,25 +419,25 @@ class OrOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoteSet {
|
class NoteSet {
|
||||||
constructor(arr = []) {
|
constructor(notes = []) {
|
||||||
this.arr = arr;
|
this.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(note) {
|
add(note) {
|
||||||
this.arr.push(note);
|
this.notes.push(note);
|
||||||
}
|
}
|
||||||
|
|
||||||
addAll(notes) {
|
addAll(notes) {
|
||||||
this.arr.push(...notes);
|
this.notes.push(...notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasNoteId(noteId) {
|
hasNoteId(noteId) {
|
||||||
// TODO: optimize
|
// TODO: optimize
|
||||||
return !!this.arr.find(note => note.noteId === noteId);
|
return !!this.notes.find(note => note.noteId === noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeIn(anotherNoteSet) {
|
mergeIn(anotherNoteSet) {
|
||||||
this.arr = this.arr.concat(anotherNoteSet.arr);
|
this.notes = this.notes.concat(anotherNoteSet.arr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,7 +456,7 @@ class ExistsOp {
|
||||||
|
|
||||||
if (noteSet.hasNoteId(note.noteId)) {
|
if (noteSet.hasNoteId(note.noteId)) {
|
||||||
if (attr.isInheritable) {
|
if (attr.isInheritable) {
|
||||||
resultNoteSet.addAll(note.subtreeNotes);
|
resultNoteSet.addAll(note.subtreeNotesIncludingTemplated);
|
||||||
}
|
}
|
||||||
else if (note.isTemplate) {
|
else if (note.isTemplate) {
|
||||||
resultNoteSet.addAll(note.templatedNotes);
|
resultNoteSet.addAll(note.templatedNotes);
|
||||||
|
@ -470,7 +485,7 @@ class EqualsOp {
|
||||||
|
|
||||||
if (noteSet.hasNoteId(note.noteId) && attr.value === this.attributeValue) {
|
if (noteSet.hasNoteId(note.noteId) && attr.value === this.attributeValue) {
|
||||||
if (attr.isInheritable) {
|
if (attr.isInheritable) {
|
||||||
resultNoteSet.addAll(note.subtreeNotes);
|
resultNoteSet.addAll(note.subtreeNotesIncludingTemplated);
|
||||||
}
|
}
|
||||||
else if (note.isTemplate) {
|
else if (note.isTemplate) {
|
||||||
resultNoteSet.addAll(note.templatedNotes);
|
resultNoteSet.addAll(note.templatedNotes);
|
||||||
|
@ -483,10 +498,173 @@ class EqualsOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findNotesWithExpression(expression) {
|
class NoteContentFulltextOp {
|
||||||
const allNoteSet = new NoteSet(Object.values(notes));
|
constructor(tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
}
|
||||||
|
|
||||||
expression.execute(allNoteSet);
|
async execute(noteSet) {
|
||||||
|
const resultNoteSet = new NoteSet();
|
||||||
|
const wheres = this.tokens.map(token => "note_contents.content LIKE " + utils.prepareSqlForLike('%', token, '%'));
|
||||||
|
|
||||||
|
const noteIds = await sql.getColumn(`
|
||||||
|
SELECT notes.noteId
|
||||||
|
FROM notes
|
||||||
|
JOIN note_contents ON notes.noteId = note_contents.noteId
|
||||||
|
WHERE isDeleted = 0 AND isProtected = 0 AND ${wheres.join(' AND ')}`);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const noteId of noteIds) {
|
||||||
|
if (noteSet.hasNoteId(noteId) && noteId in notes) {
|
||||||
|
resultNoteSet.add(notes[noteId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteCacheFulltextOp {
|
||||||
|
constructor(tokens) {
|
||||||
|
this.tokens = tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
execute(noteSet, searchContext) {
|
||||||
|
const resultNoteSet = new NoteSet();
|
||||||
|
|
||||||
|
const candidateNotes = this.getCandidateNotes(noteSet);
|
||||||
|
|
||||||
|
for (const note of candidateNotes) {
|
||||||
|
// autocomplete should be able to find notes by their noteIds as well (only leafs)
|
||||||
|
if (this.tokens.length === 1 && note.noteId === this.tokens[0]) {
|
||||||
|
this.searchDownThePath(note, [], [], resultNoteSet, searchContext);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for leaf note it doesn't matter if "archived" label is inheritable or not
|
||||||
|
if (note.isArchived) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundAttrTokens = [];
|
||||||
|
|
||||||
|
for (const attribute of note.ownedAttributes) {
|
||||||
|
for (const token of this.tokens) {
|
||||||
|
if (attribute.name.toLowerCase().includes(token)
|
||||||
|
|| attribute.value.toLowerCase().includes(token)) {
|
||||||
|
foundAttrTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const parentNote of note.parents) {
|
||||||
|
const title = getNoteTitle(note.noteId, parentNote.noteId).toLowerCase();
|
||||||
|
const foundTokens = foundAttrTokens.slice();
|
||||||
|
|
||||||
|
for (const token of this.tokens) {
|
||||||
|
if (title.includes(token)) {
|
||||||
|
foundTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTokens.length > 0) {
|
||||||
|
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
||||||
|
|
||||||
|
this.searchDownThePath(parentNote, remainingTokens, [note.noteId], resultNoteSet, searchContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultNoteSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns noteIds which have at least one matching tokens
|
||||||
|
*
|
||||||
|
* @param {NoteSet} noteSet
|
||||||
|
* @return {String[]}
|
||||||
|
*/
|
||||||
|
getCandidateNotes(noteSet) {
|
||||||
|
const candidateNotes = [];
|
||||||
|
|
||||||
|
for (const note of noteSet.notes) {
|
||||||
|
for (const token of this.tokens) {
|
||||||
|
if (note.flatText.includes(token)) {
|
||||||
|
candidateNotes.push(note);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidateNotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDownThePath(note, tokens, path, resultNoteSet, searchContext) {
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
const retPath = getSomePath(note, path);
|
||||||
|
|
||||||
|
if (retPath) {
|
||||||
|
const noteId = retPath[retPath.length - 1];
|
||||||
|
searchContext.noteIdToNotePath[noteId] = retPath;
|
||||||
|
|
||||||
|
resultNoteSet.add(notes[noteId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!note.parents.length === 0 || note.noteId === 'root') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundAttrTokens = [];
|
||||||
|
|
||||||
|
for (const attribute of note.ownedAttributes) {
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (attribute.name.toLowerCase().includes(token)
|
||||||
|
|| attribute.value.toLowerCase().includes(token)) {
|
||||||
|
foundAttrTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const parentNote of note.parents) {
|
||||||
|
const title = getNoteTitle(note.noteId, parentNote.noteId).toLowerCase();
|
||||||
|
const foundTokens = foundAttrTokens.slice();
|
||||||
|
|
||||||
|
for (const token of tokens) {
|
||||||
|
if (title.includes(token)) {
|
||||||
|
foundTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTokens.length > 0) {
|
||||||
|
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
||||||
|
|
||||||
|
this.searchDownThePath(parentNote, remainingTokens, path.concat([note.noteId]), resultNoteSet, searchContext);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.searchDownThePath(parentNote, tokens, path.concat([note.noteId]), resultNoteSet, searchContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findNotesWithExpression(expression) {
|
||||||
|
|
||||||
|
const hoistedNote = notes[hoistedNoteService.getHoistedNoteId()];
|
||||||
|
const allNotes = (hoistedNote && hoistedNote.noteId !== 'root')
|
||||||
|
? hoistedNote.subtreeNotes
|
||||||
|
: Object.values(notes);
|
||||||
|
|
||||||
|
const allNoteSet = new NoteSet(allNotes);
|
||||||
|
|
||||||
|
const searchContext = {
|
||||||
|
noteIdToNotePath: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
expression.execute(allNoteSet, searchContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function findNotesWithFulltext(query, searchInContent) {
|
async function findNotesWithFulltext(query, searchInContent) {
|
||||||
|
@ -537,163 +715,6 @@ async function findNotesWithFulltext(query, searchInContent) {
|
||||||
return apiResults;
|
return apiResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns noteIds which have at least one matching tokens
|
|
||||||
*
|
|
||||||
* @param tokens
|
|
||||||
* @return {String[]}
|
|
||||||
*/
|
|
||||||
function getCandidateNotes(tokens) {
|
|
||||||
const candidateNotes = [];
|
|
||||||
|
|
||||||
for (const note of Object.values(notes)) {
|
|
||||||
for (const token of tokens) {
|
|
||||||
if (note.flatText.includes(token)) {
|
|
||||||
candidateNotes.push(note);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidateNotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findInNoteCache(tokens) {
|
|
||||||
let results = [];
|
|
||||||
|
|
||||||
const candidateNotes = getCandidateNotes(tokens);
|
|
||||||
|
|
||||||
for (const note of candidateNotes) {
|
|
||||||
// autocomplete should be able to find notes by their noteIds as well (only leafs)
|
|
||||||
if (tokens.length === 1 && note.noteId === tokens[0]) {
|
|
||||||
search(note, [], [], results);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// for leaf note it doesn't matter if "archived" label is inheritable or not
|
|
||||||
if (note.isArchived) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundAttrTokens = [];
|
|
||||||
|
|
||||||
for (const attribute of note.ownedAttributes) {
|
|
||||||
for (const token of tokens) {
|
|
||||||
if (attribute.name.toLowerCase().includes(token)
|
|
||||||
|| attribute.value.toLowerCase().includes(token)) {
|
|
||||||
foundAttrTokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const parentNote of note.parents) {
|
|
||||||
const title = getNoteTitle(note.noteId, parentNote.noteId).toLowerCase();
|
|
||||||
const foundTokens = foundAttrTokens.slice();
|
|
||||||
|
|
||||||
for (const token of tokens) {
|
|
||||||
if (title.includes(token)) {
|
|
||||||
foundTokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundTokens.length > 0) {
|
|
||||||
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
|
||||||
|
|
||||||
search(parentNote, remainingTokens, [note.noteId], results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findInNoteContent(tokens) {
|
|
||||||
const wheres = tokens.map(token => "note_contents.content LIKE " + utils.prepareSqlForLike('%', token, '%'));
|
|
||||||
|
|
||||||
const noteIds = await sql.getColumn(`
|
|
||||||
SELECT notes.noteId
|
|
||||||
FROM notes
|
|
||||||
JOIN note_contents ON notes.noteId = note_contents.noteId
|
|
||||||
WHERE isDeleted = 0 AND isProtected = 0 AND ${wheres.join(' AND ')}`);
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
for (const noteId of noteIds) {
|
|
||||||
const note = notes[noteId];
|
|
||||||
|
|
||||||
if (!note) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notePath = getSomePath(note);
|
|
||||||
const parentNoteId = notePath.length > 1 ? notePath[notePath.length - 2] : null;
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
noteId: noteId,
|
|
||||||
branchId: getBranch(noteId, parentNoteId),
|
|
||||||
pathArray: notePath,
|
|
||||||
titleArray: getNoteTitleArrayForPath(notePath)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.length > 1 ? retPath[retPath.length - 2] : null;
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
noteId: thisNoteId,
|
|
||||||
branchId: getBranch(thisNoteId, thisParentNoteId),
|
|
||||||
pathArray: retPath,
|
|
||||||
titleArray: getNoteTitleArrayForPath(retPath)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!note.parents.length === 0 || note.noteId === 'root') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundAttrTokens = [];
|
|
||||||
|
|
||||||
for (const attribute of note.ownedAttributes) {
|
|
||||||
for (const token of tokens) {
|
|
||||||
if (attribute.name.toLowerCase().includes(token)
|
|
||||||
|| attribute.value.toLowerCase().includes(token)) {
|
|
||||||
foundAttrTokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const parentNote of note.parents) {
|
|
||||||
const title = getNoteTitle(note.noteId, parentNote.noteId).toLowerCase();
|
|
||||||
const foundTokens = foundAttrTokens.slice();
|
|
||||||
|
|
||||||
for (const token of tokens) {
|
|
||||||
if (title.includes(token)) {
|
|
||||||
foundTokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (foundTokens.length > 0) {
|
|
||||||
const remainingTokens = tokens.filter(token => !foundTokens.includes(token));
|
|
||||||
|
|
||||||
search(parentNote, remainingTokens, path.concat([note.noteId]), results);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
search(parentNote, tokens, path.concat([note.noteId]), results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function highlightResults(results, allTokens) {
|
function highlightResults(results, allTokens) {
|
||||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
||||||
// which would make the resulting HTML string invalid.
|
// which would make the resulting HTML string invalid.
|
||||||
|
|
Loading…
Reference in a new issue