add also content fulltext

This commit is contained in:
zadam 2020-05-14 23:11:59 +02:00
parent 1ec446137d
commit a3e2369599

View file

@ -42,7 +42,7 @@ class Note {
this.inheritableAttributeCache = null; this.inheritableAttributeCache = null;
/** @param {string|null} */ /** @param {string|null} */
this.fulltextCache = null; this.flatTextCache = null;
if (protectedSessionService.isProtectedSessionAvailable()) { if (protectedSessionService.isProtectedSessionAvailable()) {
decryptProtectedNote(this); decryptProtectedNote(this);
@ -132,36 +132,39 @@ class Note {
this.parents.sort((a, b) => a.hasInheritableOwnedArchivedLabel ? 1 : -1); this.parents.sort((a, b) => a.hasInheritableOwnedArchivedLabel ? 1 : -1);
} }
get fulltext() { /**
if (!this.fulltextCache) { * @return {string} - returns flattened textual representation of note, prefixes and attributes usable for searching
*/
get flatText() {
if (!this.flatTextCache) {
if (this.isHideInAutocompleteOrArchived) { if (this.isHideInAutocompleteOrArchived) {
this.fulltextCache = " "; // can't be empty this.flatTextCache = " "; // can't be empty
return this.fulltextCache; return this.flatTextCache;
} }
this.fulltextCache = this.title.toLowerCase(); this.flatTextCache = this.title.toLowerCase();
for (const branch of this.parentBranches) { for (const branch of this.parentBranches) {
if (branch.prefix) { if (branch.prefix) {
this.fulltextCache += ' ' + branch.prefix; this.flatTextCache += ' ' + branch.prefix;
} }
} }
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.fulltextCache += ' ' + attr.name.toLowerCase(); this.flatTextCache += ' ' + attr.name.toLowerCase();
if (attr.value) { if (attr.value) {
this.fulltextCache += ' ' + attr.value.toLowerCase(); this.flatTextCache += ' ' + attr.value.toLowerCase();
} }
} }
} }
return this.fulltextCache; return this.flatTextCache;
} }
invalidateThisCache() { invalidateThisCache() {
this.fulltextCache = null; this.flatTextCache = null;
this.attributeCache = null; this.attributeCache = null;
this.templateAttributeCache = null; this.templateAttributeCache = null;
@ -184,18 +187,18 @@ class Note {
} }
} }
invalidateSubtreeFulltext() { invalidateSubtreeFlatText() {
this.fulltextCache = null; this.flatTextCache = null;
for (const childNote of this.children) { for (const childNote of this.children) {
childNote.invalidateSubtreeFulltext(); childNote.invalidateSubtreeFlatText();
} }
for (const templateAttr of this.templateAttributes) { for (const templateAttr of this.templateAttributes) {
const targetNote = templateAttr.targetNote; const targetNote = templateAttr.targetNote;
if (targetNote) { if (targetNote) {
targetNote.invalidateSubtreeFulltext(); targetNote.invalidateSubtreeFlatText();
} }
} }
} }
@ -384,7 +387,7 @@ function getCandidateNotes(tokens) {
for (const note of Object.values(notes)) { for (const note of Object.values(notes)) {
for (const token of tokens) { for (const token of tokens) {
if (note.fulltext.includes(token)) { if (note.flatText.includes(token)) {
candidateNotes.push(note); candidateNotes.push(note);
break; break;
} }
@ -394,27 +397,14 @@ function getCandidateNotes(tokens) {
return candidateNotes; return candidateNotes;
} }
async function findNotes(query) { function findInNoteCache(tokens) {
if (!query.length) {
return [];
}
const allTokens = query
.trim() // necessary because even with .split() trailing spaces are tokens which causes havoc
.toLowerCase()
.split(/[ -]/)
.filter(token => token !== '/'); // '/' is used as separator
const candidateNotes = getCandidateNotes(allTokens);
// now we have set of noteIds which match at least one token
let results = []; let results = [];
const tokens = allTokens.slice();
const candidateNotes = getCandidateNotes(tokens);
for (const note of candidateNotes) { for (const note of candidateNotes) {
// 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 (note.noteId === query) { if (tokens.length === 1 && note.noteId === tokens[0]) {
search(note, [], [], results); search(note, [], [], results);
continue; continue;
} }
@ -453,6 +443,58 @@ async function findNotes(query) {
} }
} }
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;
}
async function findNotes(query, searchInContent = false) {
if (!query.trim().length) {
return [];
}
const tokens = query
.trim() // necessary because even with .split() trailing spaces are tokens which causes havoc
.toLowerCase()
.split(/[ -]/)
.filter(token => token !== '/'); // '/' is used as separator
const cacheResults = findInNoteCache(tokens);
const contentResults = searchInContent ? await findInNoteContent(tokens) : [];
let results = cacheResults.concat(contentResults);
if (hoistedNoteService.getHoistedNoteId() !== 'root') { if (hoistedNoteService.getHoistedNoteId() !== 'root') {
results = results.filter(res => res.pathArray.includes(hoistedNoteService.getHoistedNoteId())); results = results.filter(res => res.pathArray.includes(hoistedNoteService.getHoistedNoteId()));
} }
@ -479,7 +521,7 @@ async function findNotes(query) {
}; };
}); });
highlightResults(apiResults, allTokens); highlightResults(apiResults, tokens);
return apiResults; return apiResults;
} }
@ -494,7 +536,7 @@ function search(note, tokens, path, results) {
if (retPath) { if (retPath) {
const thisNoteId = retPath[retPath.length - 1]; const thisNoteId = retPath[retPath.length - 1];
const thisParentNoteId = retPath[retPath.length - 2]; const thisParentNoteId = retPath.length > 1 ? retPath[retPath.length - 2] : null;
results.push({ results.push({
noteId: thisNoteId, noteId: thisNoteId,
@ -712,7 +754,7 @@ function getNotePath(noteId) {
} }
function evaluateSimilarity(sourceNote, candidateNote, results) { function evaluateSimilarity(sourceNote, candidateNote, results) {
let coeff = stringSimilarity.compareTwoStrings(sourceNote.fulltext, candidateNote.fulltext); let coeff = stringSimilarity.compareTwoStrings(sourceNote.flatText, candidateNote.flatText);
if (coeff > 0.4) { if (coeff > 0.4) {
const notePath = getSomePath(candidateNote); const notePath = getSomePath(candidateNote);
@ -789,7 +831,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
note.title = entity.title; note.title = entity.title;
note.isProtected = entity.isProtected; note.isProtected = entity.isProtected;
note.isDecrypted = !entity.isProtected || !!entity.isContentAvailable; note.isDecrypted = !entity.isProtected || !!entity.isContentAvailable;
note.fulltextCache = null; note.flatTextCache = null;
decryptProtectedNote(note); decryptProtectedNote(note);
} }
@ -828,7 +870,7 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
branches[branchId].prefix = entity.prefix; branches[branchId].prefix = entity.prefix;
if (childNote) { if (childNote) {
childNote.fulltextCache = null; childNote.flatTextCache = null;
} }
} }
else { else {
@ -862,10 +904,10 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
attr.value = entity.value; attr.value = entity.value;
if (attr.isAffectingSubtree) { if (attr.isAffectingSubtree) {
note.invalidateSubtreeFulltext(); note.invalidateSubtreeFlatText();
} }
else { else {
note.fulltextCache = null; note.flatTextCache = null;
} }
} }
else { else {