initial integration of note list renderer into search results

This commit is contained in:
zadam 2020-10-25 23:02:12 +01:00
parent 37e111d8a9
commit 4281111344
7 changed files with 118 additions and 29 deletions

View File

@ -34,10 +34,18 @@ async function createSqlConsole() {
return await treeCache.getNote(note.noteId); 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 { export default {
getTodayNote, getTodayNote,
getDateNote, getDateNote,
getMonthNote, getMonthNote,
getYearNote, getYearNote,
createSqlConsole createSqlConsole,
} createSearchNote
}

View File

@ -67,6 +67,15 @@ export default class DialogCommandExecutor extends Component {
appContext.triggerCommand('focusOnDetail', {tabId: tabContext.tabId}); 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() { showBackendLogCommand() {
import("../dialogs/backend_log.js").then(d => d.showDialog()); import("../dialogs/backend_log.js").then(d => d.showDialog());
} }

View File

@ -135,12 +135,20 @@ const TPL = `
</div>`; </div>`;
class NoteListRenderer { 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.$noteList = $(TPL);
this.parentNote = parentNote; this.parentNote = parentNote;
this.notes = notes; this.noteIds = noteIds;
this.page = 1; 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'); this.viewType = parentNote.getLabelValue('viewType');
if (!['list', 'grid'].includes(this.viewType)) { if (!['list', 'grid'].includes(this.viewType)) {
@ -205,7 +213,8 @@ class NoteListRenderer {
const startIdx = (this.page - 1) * this.pageSize; const startIdx = (this.page - 1) * this.pageSize;
const endIdx = startIdx + 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) { for (const note of pageNotes) {
// image is already visible in the parent note so no need to display it separately in the book // 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; continue;
} }
const $card = await this.renderNote(note); const $card = await this.renderNote(note, this.parentNote.hasLabel('expanded'));
$container.append($card); $container.append($card);
} }
@ -225,7 +234,7 @@ class NoteListRenderer {
renderPager() { renderPager() {
const $pager = this.$noteList.find('.note-list-pager').empty(); 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); $pager.toggle(pageCount > 1);
@ -255,7 +264,8 @@ class NoteListRenderer {
} }
// TODO: we should also render (promoted) attributes // 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 notePath = /*this.notePath + '/' + */ note.noteId;
const $expander = $('<span class="note-expander bx bx-chevron-right"></span>'); const $expander = $('<span class="note-expander bx bx-chevron-right"></span>');
@ -270,7 +280,7 @@ class NoteListRenderer {
$expander.on('click', () => this.toggleContent($card, note, !$card.hasClass("expanded"))); $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; return $card;
} }

View File

@ -164,9 +164,9 @@ export default class SearchBoxWidget extends BasicWidget {
} }
} }
searchNotesEvent() { // searchNotesEvent() {
this.toggleSearchEvent(); // this.toggleSearchEvent();
} // }
resetSearchEvent() { resetSearchEvent() {
this.$searchInput.val(""); this.$searchInput.val("");

View File

@ -1,11 +1,18 @@
import TypeWidget from "./type_widget.js"; import TypeWidget from "./type_widget.js";
import noteAutocompleteService from "../../services/note_autocomplete.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 = ` const TPL = `
<div class="note-detail-search note-detail-printable"> <div class="note-detail-search note-detail-printable">
<style> <style>
.note-detail-search { .note-detail-search {
padding: 7px; padding: 7px;
height: 100%;
display: flex;
flex-direction: column;
} }
.search-setting-table { .search-setting-table {
@ -48,6 +55,11 @@ const TPL = `
.search-setting-expander:hover .search-setting-expander-text { .search-setting-expander:hover .search-setting-expander-text {
color: var(--main-text-color); color: var(--main-text-color);
} }
.note-result-wrapper {
height: 100%;
overflow: auto;
}
</style> </style>
<div class="area-expander"> <div class="area-expander">
@ -63,7 +75,7 @@ const TPL = `
<tr> <tr>
<td>Search string:</td> <td>Search string:</td>
<td colspan="3"> <td colspan="3">
<input type="text" class="form-control"> <input type="text" class="form-control search-string">
</td> </td>
</tr> </tr>
<tr> <tr>
@ -85,6 +97,8 @@ const TPL = `
<hr class="w-100 search-setting-empty-expander" style="margin-bottom: 10px;"> <hr class="w-100 search-setting-empty-expander" style="margin-bottom: 10px;">
</div> </div>
<div class="note-result-wrapper"></div>
</div>`; </div>`;
export default class SearchTypeWidget extends TypeWidget { export default class SearchTypeWidget extends TypeWidget {
@ -93,13 +107,26 @@ export default class SearchTypeWidget extends TypeWidget {
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$searchString = this.$widget.find(".search-string"); this.$searchString = this.$widget.find(".search-string");
this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate());
this.$component = this.$widget.find('.note-detail-search'); this.$component = this.$widget.find('.note-detail-search');
this.$settingsArea = this.$widget.find('.search-settings'); this.$settingsArea = this.$widget.find('.search-settings');
this.spacedUpdate = new SpacedUpdate(() => this.updateSearch(), 2000);
this.$limitSearchToSubtree = this.$widget.find('.limit-search-to-subtree'); this.$limitSearchToSubtree = this.$widget.find('.limit-search-to-subtree');
noteAutocompleteService.initNoteAutocomplete(this.$limitSearchToSubtree); 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 = this.$widget.find('.area-expander');
this.$settingExpander.on('click', async () => { this.$settingExpander.on('click', async () => {
const collapse = this.$settingsArea.is(":visible"); const collapse = this.$settingsArea.is(":visible");
@ -110,25 +137,42 @@ export default class SearchTypeWidget extends TypeWidget {
this.$settingsArea.slideDown(200); 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) { async doRefresh(note) {
this.$help.html(window.glob.SEARCH_HELP_TEXT);
this.$component.show(); this.$component.show();
try { // try {
const noteComplement = await this.tabContext.getNoteComplement(); // const noteComplement = await this.tabContext.getNoteComplement();
const json = JSON.parse(noteComplement.content); // const json = JSON.parse(noteComplement.content);
//
this.$searchString.val(json.searchString); // this.$searchString.val(json.searchString);
} // }
catch (e) { // catch (e) {
console.log(e); // console.log(e);
this.$searchString.val(''); // this.$searchString.val('');
} // }
this.$searchString.on('input', () => this.spacedUpdate.scheduleUpdate());
} }
getContent() { getContent() {

View File

@ -51,10 +51,27 @@ function createSqlConsole() {
return note; 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 = { module.exports = {
getDateNote, getDateNote,
getMonthNote, getMonthNote,
getYearNote, getYearNote,
getDateNotesForMonth, getDateNotesForMonth,
createSqlConsole createSqlConsole,
createSearchNote
}; };

View File

@ -190,6 +190,7 @@ function register(app) {
apiRoute(GET, '/api/date-notes/year/:year', dateNotesRoute.getYearNote); apiRoute(GET, '/api/date-notes/year/:year', dateNotesRoute.getYearNote);
apiRoute(GET, '/api/date-notes/notes-for-month/:month', dateNotesRoute.getDateNotesForMonth); apiRoute(GET, '/api/date-notes/notes-for-month/:month', dateNotesRoute.getDateNotesForMonth);
apiRoute(POST, '/api/sql-console', dateNotesRoute.createSqlConsole); 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(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler); route(POST, '/api/images', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware], imageRoute.uploadImage, apiResultHandler);