From 9447d3f9b59f88c06773338386909d47ff273206 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 31 Jan 2021 22:45:45 +0100 Subject: [PATCH] quick search widget WIP --- src/public/app/widgets/quick_search.js | 87 +++++++++++++++++++ src/public/app/widgets/standard_top_widget.js | 8 +- src/routes/api/search.js | 16 +++- src/routes/routes.js | 1 + src/services/search/search_context.js | 4 +- 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/public/app/widgets/quick_search.js diff --git a/src/public/app/widgets/quick_search.js b/src/public/app/widgets/quick_search.js new file mode 100644 index 000000000..d979e04e4 --- /dev/null +++ b/src/public/app/widgets/quick_search.js @@ -0,0 +1,87 @@ +import BasicWidget from "./basic_widget.js"; +import server from "../services/server.js"; +import linkService from "../services/link.js"; +import treeCache from "../services/tree_cache.js"; +import utils from "../services/utils.js"; + +const TPL = ` + +`; + +const MAX_DISPLAYED_NOTES = 20; + +export default class QuickSearchWidget extends BasicWidget { + doRender() { + this.$widget = $(TPL); + this.overflowing(); + + this.$searchString = this.$widget.find('.search-string'); + this.$dropdownMenu = this.$widget.find('.dropdown-menu'); + this.$dropdownToggle = this.$widget.find('.search-button'); + + this.$widget.find('.input-group-append').on('show.bs.dropdown', () => this.search()); + + utils.bindElShortcut(this.$searchString, 'return', () => { + this.$dropdownToggle.dropdown('show'); + + this.$searchString.focus(); + }); + + return this.$widget; + } + + async search() { + this.$dropdownMenu.empty(); + this.$dropdownMenu.append(' Searching ...'); + + const resultNoteIds = await server.get('quick-search/' + encodeURIComponent(this.$searchString.val())); + + const displayedNoteIds = resultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, resultNoteIds.length)); + + this.$dropdownMenu.empty(); + + this.$dropdownMenu.append(''); + + if (displayedNoteIds.length === 0) { + this.$dropdownMenu.append('No results found'); + } + + for (const note of await treeCache.getNotes(displayedNoteIds)) { + const $link = await linkService.createNoteLink(note.noteId, {showNotePath: true}); + $link.addClass('dropdown-item'); + + this.$dropdownMenu.append($link); + } + + if (resultNoteIds.length > MAX_DISPLAYED_NOTES) { + this.$dropdownMenu.append(`... and ${resultNoteIds.length - MAX_DISPLAYED_NOTES} more results.`); + } + + this.$dropdownToggle.dropdown('update'); + } +} diff --git a/src/public/app/widgets/standard_top_widget.js b/src/public/app/widgets/standard_top_widget.js index ee18e22f9..90055ea7c 100644 --- a/src/public/app/widgets/standard_top_widget.js +++ b/src/public/app/widgets/standard_top_widget.js @@ -1,6 +1,7 @@ import BasicWidget from "./basic_widget.js"; import HistoryNavigationWidget from "./history_navigation.js"; import protectedSessionService from "../services/protected_session.js"; +import QuickSearchWidget from "./quick_search.js"; const TPL = `
@@ -13,7 +14,7 @@ const TPL = ` height: 35px; } - .standard-top-widget button { + .standard-top-widget button:not(.search-button) { padding: 1px 5px 1px 5px; font-size: 90%; margin-bottom: 2px; @@ -84,6 +85,11 @@ export default class StandardTopWidget extends BasicWidget { this.$widget.prepend(historyNavigationWidget.render()); + const quickSearchWidget = new QuickSearchWidget(); + this.child(quickSearchWidget); + + this.$widget.append(quickSearchWidget.render()); + this.$enterProtectedSessionButton = this.$widget.find(".enter-protected-session-button"); this.$enterProtectedSessionButton.on('click', protectedSessionService.enterProtectedSession); diff --git a/src/routes/api/search.js b/src/routes/api/search.js index d15916c10..d5969afac 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -200,6 +200,19 @@ async function searchFromRelation(note, relationName) { return typeof result[0] === 'string' ? result : result.map(item => item.noteId); } +function quickSearch(req) { + const {searchString} = req.params; + + const searchContext = new SearchContext({ + fastSearch: false, + includeArchivedNotes: false, + fuzzyAttributeSearch: false + }); + + return searchService.findNotesWithQuery(searchString, searchContext) + .map(sr => sr.noteId); +} + function getRelatedNotes(req) { const attr = req.body; @@ -276,5 +289,6 @@ function formatValue(val) { module.exports = { searchFromNote, searchAndExecute, - getRelatedNotes + getRelatedNotes, + quickSearch }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 0bf7b25cb..d8b4c6b9e 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -262,6 +262,7 @@ function register(app) { route(POST, '/api/sender/image', [auth.checkToken, uploadMiddleware], senderRoute.uploadImage, apiResultHandler); route(POST, '/api/sender/note', [auth.checkToken], senderRoute.saveNote, apiResultHandler); + apiRoute(GET, '/api/quick-search/:searchString', searchRoute.quickSearch); apiRoute(GET, '/api/search-note/:noteId', searchRoute.searchFromNote); apiRoute(POST, '/api/search-and-execute-note/:noteId', searchRoute.searchAndExecute); apiRoute(POST, '/api/search-related', searchRoute.getRelatedNotes); diff --git a/src/services/search/search_context.js b/src/services/search/search_context.js index 01691e472..864878bae 100644 --- a/src/services/search/search_context.js +++ b/src/services/search/search_context.js @@ -1,9 +1,11 @@ "use strict"; +const cls = require('../cls'); + class SearchContext { constructor(params = {}) { this.fastSearch = !!params.fastSearch; - this.ancestorNoteId = params.ancestorNoteId; + this.ancestorNoteId = params.ancestorNoteId || cls.getHoistedNoteId(); this.ancestorDepth = params.ancestorDepth; this.includeArchivedNotes = !!params.includeArchivedNotes; this.orderBy = params.orderBy;