diff --git a/src/public/app/services/froca.js b/src/public/app/services/froca.js index 91af4f1b2..e06386772 100644 --- a/src/public/app/services/froca.js +++ b/src/public/app/services/froca.js @@ -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[]} */ diff --git a/src/public/app/services/note_list_renderer.js b/src/public/app/services/note_list_renderer.js index 1006374a5..2ed5b5ea3 100644 --- a/src/public/app/services/note_list_renderer.js +++ b/src/public/app/services/note_list_renderer.js @@ -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 = `
@@ -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 = $('
') .attr('data-note-id', note.noteId) .append( - $('
') + $('
') .append($expander) .append($('').addClass(note.getIcon())) .append(this.viewType === 'grid' - ? $("").text(note.title) - : await linkService.createNoteLink(notePath, {showTooltip: false, showNotePath: this.showNotePath}) + ? $('').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) { diff --git a/src/routes/api/search.js b/src/routes/api/search.js index f164a290f..2b00fc607 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -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); }