mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
add highlighting to search results, closes #2977
This commit is contained in:
parent
89a4165c77
commit
570fabdc4a
@ -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[]} */
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user