mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
initial integration of note list renderer into search results
This commit is contained in:
parent
37e111d8a9
commit
4281111344
@ -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
|
||||||
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -164,9 +164,9 @@ export default class SearchBoxWidget extends BasicWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNotesEvent() {
|
// searchNotesEvent() {
|
||||||
this.toggleSearchEvent();
|
// this.toggleSearchEvent();
|
||||||
}
|
// }
|
||||||
|
|
||||||
resetSearchEvent() {
|
resetSearchEvent() {
|
||||||
this.$searchInput.val("");
|
this.$searchInput.val("");
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user