quick search widget WIP

This commit is contained in:
zadam 2021-01-31 22:45:45 +01:00
parent 2ac78c2e03
commit 9447d3f9b5
5 changed files with 113 additions and 3 deletions

View File

@ -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 = `
<div class="quick-search input-group input-group-sm" style="width: 250px;">
<style>
.quick-search .dropdown-menu {
max-height: 600px;
max-width: 400px;
overflow-y: auto;
overflow-x: hidden;
text-overflow: ellipsis;
box-shadow: 10px 10px 93px -25px black;
}
</style>
<input type="text" class="form-control form-control-sm search-string" placeholder="Quick search">
<div class="input-group-append">
<button class="btn btn-outline-secondary search-button" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="bx bx-search"></span>
</button>
<div class="dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
<div role="separator" class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Separated link</a>
</div>
</div>
</div>
</div>`;
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('<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span> Searching ...</span>');
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('<div class="dropdown-item"><button class="btn btn-sm">Show in full search</button></div>');
if (displayedNoteIds.length === 0) {
this.$dropdownMenu.append('<span class="dropdown-item disabled">No results found</span>');
}
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(`<span class="dropdown-item disabled">... and ${resultNoteIds.length - MAX_DISPLAYED_NOTES} more results.</span>`);
}
this.$dropdownToggle.dropdown('update');
}
}

View File

@ -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 = `
<div class="standard-top-widget">
@ -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);

View File

@ -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
};

View File

@ -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);

View File

@ -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;