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

View File

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

View File

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