From 42811113449006bdb1112c0fd4d0dd686d597030 Mon Sep 17 00:00:00 2001 From: zadam Date: Sun, 25 Oct 2020 23:02:12 +0100 Subject: [PATCH] initial integration of note list renderer into search results --- src/public/app/services/date_notes.js | 12 ++- .../app/services/dialog_command_executor.js | 9 +++ src/public/app/services/note_list_renderer.js | 26 +++++-- src/public/app/widgets/search_box.js | 6 +- src/public/app/widgets/type_widgets/search.js | 74 +++++++++++++++---- src/routes/api/date_notes.js | 19 ++++- src/routes/routes.js | 1 + 7 files changed, 118 insertions(+), 29 deletions(-) diff --git a/src/public/app/services/date_notes.js b/src/public/app/services/date_notes.js index 5a1a88ca0..f786487d6 100644 --- a/src/public/app/services/date_notes.js +++ b/src/public/app/services/date_notes.js @@ -34,10 +34,18 @@ async function createSqlConsole() { return await treeCache.getNote(note.noteId); } +/** @return {NoteShort} */ +async function createSearchNote() { + const note = await server.post('search-note'); + + return await treeCache.getNote(note.noteId); +} + export default { getTodayNote, getDateNote, getMonthNote, getYearNote, - createSqlConsole -} \ No newline at end of file + createSqlConsole, + createSearchNote +} diff --git a/src/public/app/services/dialog_command_executor.js b/src/public/app/services/dialog_command_executor.js index af57e56af..8f1145a8d 100644 --- a/src/public/app/services/dialog_command_executor.js +++ b/src/public/app/services/dialog_command_executor.js @@ -67,6 +67,15 @@ export default class DialogCommandExecutor extends Component { appContext.triggerCommand('focusOnDetail', {tabId: tabContext.tabId}); } + async searchNotesCommand() { + const searchNote = await dateNoteService.createSearchNote(); + + const tabContext = await appContext.tabManager.openTabWithNote(searchNote.noteId, true); + + appContext.triggerCommand('focusOnDetail', {tabId: tabContext.tabId}); + + } + showBackendLogCommand() { import("../dialogs/backend_log.js").then(d => d.showDialog()); } diff --git a/src/public/app/services/note_list_renderer.js b/src/public/app/services/note_list_renderer.js index 4136a7757..abcd11b55 100644 --- a/src/public/app/services/note_list_renderer.js +++ b/src/public/app/services/note_list_renderer.js @@ -135,12 +135,20 @@ const TPL = ` `; class NoteListRenderer { - constructor(parentNote, notes) { + /* + * We're using noteIds so that it's not necessary to load all notes at once when paging + */ + constructor(parentNote, noteIds) { this.$noteList = $(TPL); this.parentNote = parentNote; - this.notes = notes; + this.noteIds = noteIds; this.page = 1; - this.pageSize = 6; + this.pageSize = parseInt(parentNote.getLabelValue('pageSize')); + + if (!this.pageSize || this.pageSize < 1 || this.pageSize > 10000) { + this.pageSize = 10; + } + this.viewType = parentNote.getLabelValue('viewType'); if (!['list', 'grid'].includes(this.viewType)) { @@ -205,7 +213,8 @@ class NoteListRenderer { const startIdx = (this.page - 1) * this.pageSize; const endIdx = startIdx + this.pageSize; - const pageNotes = this.notes.slice(startIdx, Math.min(endIdx, this.notes.length)); + const pageNoteIds = this.noteIds.slice(startIdx, Math.min(endIdx, this.noteIds.length)); + const pageNotes = await treeCache.getNotes(pageNoteIds); for (const note of pageNotes) { // image is already visible in the parent note so no need to display it separately in the book @@ -213,7 +222,7 @@ class NoteListRenderer { continue; } - const $card = await this.renderNote(note); + const $card = await this.renderNote(note, this.parentNote.hasLabel('expanded')); $container.append($card); } @@ -225,7 +234,7 @@ class NoteListRenderer { renderPager() { const $pager = this.$noteList.find('.note-list-pager').empty(); - const pageCount = Math.ceil(this.notes.length / this.pageSize); + const pageCount = Math.ceil(this.noteIds.length / this.pageSize); $pager.toggle(pageCount > 1); @@ -255,7 +264,8 @@ class NoteListRenderer { } // TODO: we should also render (promoted) attributes - async renderNote(note) { + // FIXME: showing specific path might be necessary because of a match in the patch + async renderNote(note, expand = false) { const notePath = /*this.notePath + '/' + */ note.noteId; const $expander = $(''); @@ -270,7 +280,7 @@ class NoteListRenderer { $expander.on('click', () => this.toggleContent($card, note, !$card.hasClass("expanded"))); - await this.toggleContent($card, note, this.parentNote.hasLabel('expanded')); + await this.toggleContent($card, note, expand); return $card; } diff --git a/src/public/app/widgets/search_box.js b/src/public/app/widgets/search_box.js index d1645f8ff..784b58650 100644 --- a/src/public/app/widgets/search_box.js +++ b/src/public/app/widgets/search_box.js @@ -164,9 +164,9 @@ export default class SearchBoxWidget extends BasicWidget { } } - searchNotesEvent() { - this.toggleSearchEvent(); - } + // searchNotesEvent() { + // this.toggleSearchEvent(); + // } resetSearchEvent() { this.$searchInput.val(""); diff --git a/src/public/app/widgets/type_widgets/search.js b/src/public/app/widgets/type_widgets/search.js index 5ce4d1d94..babe20363 100644 --- a/src/public/app/widgets/type_widgets/search.js +++ b/src/public/app/widgets/type_widgets/search.js @@ -1,11 +1,18 @@ import TypeWidget from "./type_widget.js"; import noteAutocompleteService from "../../services/note_autocomplete.js"; +import SpacedUpdate from "../../services/spaced_update.js"; +import server from "../../services/server.js"; +import toastService from "../../services/toast.js"; +import NoteListRenderer from "../../services/note_list_renderer.js"; const TPL = ` `; export default class SearchTypeWidget extends TypeWidget { @@ -93,13 +107,26 @@ export default class SearchTypeWidget extends TypeWidget { doRender() { this.$widget = $(TPL); this.$searchString = this.$widget.find(".search-string"); + this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate()); + this.$component = this.$widget.find('.note-detail-search'); this.$settingsArea = this.$widget.find('.search-settings'); + this.spacedUpdate = new SpacedUpdate(() => this.updateSearch(), 2000); + this.$limitSearchToSubtree = this.$widget.find('.limit-search-to-subtree'); noteAutocompleteService.initNoteAutocomplete(this.$limitSearchToSubtree); + this.$limitSearchToSubtree.on('autocomplete:closed', e => { + this.spacedUpdate.scheduleUpdate(); + }); + + this.$searchWithinNoteContent = this.$widget.find('.search-within-note-content'); + this.$searchWithinNoteContent.on('change', () => { + this.spacedUpdate.scheduleUpdate(); + }); + this.$settingExpander = this.$widget.find('.area-expander'); this.$settingExpander.on('click', async () => { const collapse = this.$settingsArea.is(":visible"); @@ -110,25 +137,42 @@ export default class SearchTypeWidget extends TypeWidget { this.$settingsArea.slideDown(200); } }); + + this.$noteResultWrapper = this.$widget.find('.note-result-wrapper'); + } + + async updateSearch() { + const searchString = this.$searchString.val(); + const subNoteId = this.$limitSearchToSubtree.getSelectedNoteId(); + const includeNoteContent = this.$searchWithinNoteContent.is(":checked"); + + const response = await server.get(`search/${encodeURIComponent(searchString)}?includeNoteContent=${includeNoteContent}&excludeArchived=true&fuzzyAttributeSearch=false`); + + if (!response.success) { + toastService.showError("Search failed: " + response.message, 10000); + // even in this case we'll show the results + } + + const resultNoteIds = response.results.map(res => res.notePathArray[res.notePathArray.length - 1]); + + const noteListRenderer = new NoteListRenderer(this.note, resultNoteIds); + + this.$noteResultWrapper.empty().append(await noteListRenderer.renderList()); } async doRefresh(note) { - this.$help.html(window.glob.SEARCH_HELP_TEXT); - this.$component.show(); - try { - const noteComplement = await this.tabContext.getNoteComplement(); - const json = JSON.parse(noteComplement.content); - - this.$searchString.val(json.searchString); - } - catch (e) { - console.log(e); - this.$searchString.val(''); - } - - this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate()); + // try { + // const noteComplement = await this.tabContext.getNoteComplement(); + // const json = JSON.parse(noteComplement.content); + // + // this.$searchString.val(json.searchString); + // } + // catch (e) { + // console.log(e); + // this.$searchString.val(''); + // } } getContent() { diff --git a/src/routes/api/date_notes.js b/src/routes/api/date_notes.js index b4fcb20fe..d00e1f648 100644 --- a/src/routes/api/date_notes.js +++ b/src/routes/api/date_notes.js @@ -51,10 +51,27 @@ function createSqlConsole() { return note; } +function createSearchNote() { + const today = dateUtils.localNowDate(); + + const todayNote = dateNoteService.getDateNote(today); + + const {note} = noteService.createNewNote({ + parentNoteId: todayNote.noteId, + title: 'Search', + content: "", + type: 'search', + mime: 'application/json' + }); + + return note; +} + module.exports = { getDateNote, getMonthNote, getYearNote, getDateNotesForMonth, - createSqlConsole + createSqlConsole, + createSearchNote }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 101babdf8..73eb241d4 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -190,6 +190,7 @@ function register(app) { apiRoute(GET, '/api/date-notes/year/:year', dateNotesRoute.getYearNote); apiRoute(GET, '/api/date-notes/notes-for-month/:month', dateNotesRoute.getDateNotesForMonth); apiRoute(POST, '/api/sql-console', dateNotesRoute.createSqlConsole); + apiRoute(POST, '/api/search-note', dateNotesRoute.createSearchNote); route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage); route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);