add highlighting to search results, closes #2977

This commit is contained in:
zadam 2022-07-10 15:52:02 +02:00
parent 89a4165c77
commit 570fabdc4a
3 changed files with 53 additions and 18 deletions

View File

@ -176,7 +176,7 @@ class Froca {
return;
}
const searchResultNoteIds = await server.get('search-note/' + note.noteId);
const {searchResultNoteIds, highlightedTokens} = await server.get('search-note/' + note.noteId);
if (!Array.isArray(searchResultNoteIds)) {
throw new Error(`Search note '${note.noteId}' failed: ${searchResultNoteIds}`);
@ -207,6 +207,7 @@ class Froca {
});
froca.notes[note.noteId].searchResultsLoaded = true;
froca.notes[note.noteId].highlightedTokens = highlightedTokens;
}
/** @returns {NoteShort[]} */

View File

@ -2,6 +2,7 @@ import linkService from "./link.js";
import noteContentRenderer from "./note_content_renderer.js";
import froca from "./froca.js";
import attributeRenderer from "./attribute_renderer.js";
import libraryLoader from "./library_loader.js";
const TPL = `
<div class="note-list">
@ -60,27 +61,27 @@ const TPL = `
padding-top: 10px;
}
.note-book-title {
.note-book-header {
margin-bottom: 0;
word-break: break-all;
}
/* not-expanded title is limited to one line only */
.note-book-card:not(.expanded) .note-book-title {
.note-book-card:not(.expanded) .note-book-header {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.note-book-title .rendered-note-attributes {
.note-book-header .rendered-note-attributes {
font-size: medium;
}
.note-book-title .rendered-note-attributes:before {
.note-book-header .rendered-note-attributes:before {
content: "\\00a0\\00a0";
}
.note-book-title .note-icon {
.note-book-header .note-icon {
font-size: 100%;
display: inline-block;
padding-right: 7px;
@ -112,7 +113,7 @@ const TPL = `
max-height: 100%;
}
.note-book-title {
.note-book-header {
flex-grow: 0;
}
@ -198,6 +199,15 @@ class NoteListRenderer {
return;
}
const highlightedTokens = this.parentNote.highlightedTokens || [];
if (highlightedTokens.length > 0) {
await libraryLoader.requireLibrary(libraryLoader.MARKJS);
this.highlightRegex = new RegExp(highlightedTokens.join("|"), 'gi');
} else {
this.highlightRegex = null;
}
this.$noteList.show();
const $container = this.$noteList.find('.note-list-container').empty();
@ -262,12 +272,13 @@ class NoteListRenderer {
const $card = $('<div class="note-book-card">')
.attr('data-note-id', note.noteId)
.append(
$('<h5 class="note-book-title">')
$('<h5 class="note-book-header">')
.append($expander)
.append($('<span class="note-icon">').addClass(note.getIcon()))
.append(this.viewType === 'grid'
? $("<span>").text(note.title)
: await linkService.createNoteLink(notePath, {showTooltip: false, showNotePath: this.showNotePath})
? $('<span class="note-book-title">').text(note.title)
: (await linkService.createNoteLink(notePath, {showTooltip: false, showNotePath: this.showNotePath}))
.addClass("note-book-title")
)
.append($renderedAttributes)
);
@ -281,6 +292,15 @@ class NoteListRenderer {
$expander.on('click', () => this.toggleContent($card, note, !$card.hasClass("expanded")));
if (this.highlightRegex) {
$card.find(".note-book-title").markRegExp(this.highlightRegex, {
element: "span",
className: "ck-find-result",
separateWordSearch: false,
caseSensitive: false
});
}
await this.toggleContent($card, note, expand);
return $card;
@ -291,7 +311,7 @@ class NoteListRenderer {
return;
}
const $expander = $card.find('> .note-book-title .note-expander');
const $expander = $card.find('> .note-book-header .note-expander');
if (expand || this.viewType === 'grid') {
$card.addClass("expanded");
@ -315,6 +335,15 @@ class NoteListRenderer {
trim: this.viewType === 'grid' // for grid only short content is needed
});
if (this.highlightRegex) {
$renderedContent.markRegExp(this.highlightRegex, {
element: "span",
className: "ck-find-result",
separateWordSearch: false,
caseSensitive: false
});
}
$content.append($renderedContent);
$content.addClass("type-" + type);
} catch (e) {

View File

@ -7,16 +7,16 @@ const scriptService = require('../../services/script');
const searchService = require('../../services/search/services/search');
const bulkActionService = require("../../services/bulk_actions");
const {formatAttrForSearch} = require("../../services/attribute_formatter");
const utils = require("../../services/utils.js");
function searchFromNoteInt(note) {
let searchResultNoteIds;
let searchResultNoteIds, highlightedTokens;
const searchScript = note.getRelationValue('searchScript');
const searchString = note.getLabelValue('searchString');
if (searchScript) {
searchResultNoteIds = searchFromRelation(note, 'searchScript');
highlightedTokens = [];
} else {
const searchContext = new SearchContext({
fastSearch: note.hasLabel('fastSearch'),
@ -32,14 +32,19 @@ function searchFromNoteInt(note) {
searchResultNoteIds = searchService.findResultsWithQuery(searchString, searchContext)
.map(sr => sr.noteId);
highlightedTokens = searchContext.highlightedTokens;
}
// we won't return search note's own noteId
// also don't allow root since that would force infinite cycle
return searchResultNoteIds.filter(resultNoteId => !['root', note.noteId].includes(resultNoteId));
return {
searchResultNoteIds: searchResultNoteIds.filter(resultNoteId => !['root', note.noteId].includes(resultNoteId)),
highlightedTokens
};
}
async function searchFromNote(req) {
function searchFromNote(req) {
const note = becca.getNote(req.params.noteId);
if (!note) {
@ -47,7 +52,7 @@ async function searchFromNote(req) {
}
if (note.isDeleted) {
// this can be triggered from recent changes and it's harmless to return empty list rather than fail
// this can be triggered from recent changes, and it's harmless to return empty list rather than fail
return [];
}
@ -55,7 +60,7 @@ async function searchFromNote(req) {
return [400, `Note ${req.params.noteId} is not a search note.`]
}
return await searchFromNoteInt(note);
return searchFromNoteInt(note);
}
function searchAndExecute(req) {
@ -74,7 +79,7 @@ function searchAndExecute(req) {
return [400, `Note ${req.params.noteId} is not a search note.`]
}
const searchResultNoteIds = searchFromNoteInt(note);
const {searchResultNoteIds} = searchFromNoteInt(note);
bulkActionService.executeActions(note, searchResultNoteIds);
}