mirror of
https://github.com/zadam/trilium.git
synced 2025-11-07 15:09:01 +01:00
feat(quick_search): within the quick search, allow for "infinite" scrolling of results
This commit is contained in:
parent
057040af06
commit
58535df676
@ -67,7 +67,8 @@ const TPL = /*html*/`
|
|||||||
<input type="text" class="form-control form-control-sm search-string" placeholder="${t("quick-search.placeholder")}">
|
<input type="text" class="form-control form-control-sm search-string" placeholder="${t("quick-search.placeholder")}">
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
const MAX_DISPLAYED_NOTES = 15;
|
const INITIAL_DISPLAYED_NOTES = 15;
|
||||||
|
const LOAD_MORE_BATCH_SIZE = 10;
|
||||||
|
|
||||||
// TODO: Deduplicate with server.
|
// TODO: Deduplicate with server.
|
||||||
interface QuickSearchResponse {
|
interface QuickSearchResponse {
|
||||||
@ -90,6 +91,12 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
private $searchString!: JQuery<HTMLElement>;
|
private $searchString!: JQuery<HTMLElement>;
|
||||||
private $dropdownMenu!: JQuery<HTMLElement>;
|
private $dropdownMenu!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
|
// State for infinite scrolling
|
||||||
|
private allSearchResults: Array<any> = [];
|
||||||
|
private allSearchResultNoteIds: string[] = [];
|
||||||
|
private currentDisplayedCount: number = 0;
|
||||||
|
private isLoadingMore: boolean = false;
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$searchString = this.$widget.find(".search-string");
|
this.$searchString = this.$widget.find(".search-string");
|
||||||
@ -105,6 +112,11 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
|
|
||||||
this.$widget.find(".input-group-prepend").on("shown.bs.dropdown", () => this.search());
|
this.$widget.find(".input-group-prepend").on("shown.bs.dropdown", () => this.search());
|
||||||
|
|
||||||
|
// Add scroll event listener for infinite scrolling
|
||||||
|
this.$dropdownMenu.on("scroll", () => {
|
||||||
|
this.handleScroll();
|
||||||
|
});
|
||||||
|
|
||||||
if (utils.isMobile()) {
|
if (utils.isMobile()) {
|
||||||
this.$searchString.keydown((e) => {
|
this.$searchString.keydown((e) => {
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
@ -148,6 +160,12 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset state for new search
|
||||||
|
this.allSearchResults = [];
|
||||||
|
this.allSearchResultNoteIds = [];
|
||||||
|
this.currentDisplayedCount = 0;
|
||||||
|
this.isLoadingMore = false;
|
||||||
|
|
||||||
this.$dropdownMenu.empty();
|
this.$dropdownMenu.empty();
|
||||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
|
this.$dropdownMenu.append(`<span class="dropdown-item disabled"><span class="bx bx-loader bx-spin"></span>${t("quick-search.searching")}</span>`);
|
||||||
|
|
||||||
@ -165,17 +183,39 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
setTimeout(() => tooltip.dispose(), 4000);
|
setTimeout(() => tooltip.dispose(), 4000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store all results for infinite scrolling
|
||||||
|
this.allSearchResults = searchResults || [];
|
||||||
|
this.allSearchResultNoteIds = searchResultNoteIds || [];
|
||||||
|
|
||||||
this.$dropdownMenu.empty();
|
this.$dropdownMenu.empty();
|
||||||
|
|
||||||
// Use highlighted search results if available, otherwise fall back to basic display
|
if (this.allSearchResults.length === 0 && this.allSearchResultNoteIds.length === 0) {
|
||||||
if (searchResults && searchResults.length > 0) {
|
|
||||||
const displayedResults = searchResults.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResults.length));
|
|
||||||
|
|
||||||
if (displayedResults.length === 0) {
|
|
||||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.no-results")}</span>`);
|
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.no-results")}</span>`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const result of displayedResults) {
|
// Display initial batch
|
||||||
|
await this.displayMoreResults(INITIAL_DISPLAYED_NOTES);
|
||||||
|
this.addShowInFullSearchButton();
|
||||||
|
|
||||||
|
this.dropdown.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async displayMoreResults(batchSize: number) {
|
||||||
|
if (this.isLoadingMore) return;
|
||||||
|
this.isLoadingMore = true;
|
||||||
|
|
||||||
|
// Remove the "Show in full search" button temporarily
|
||||||
|
this.$dropdownMenu.find('.show-in-full-search').remove();
|
||||||
|
this.$dropdownMenu.find('.dropdown-divider').remove();
|
||||||
|
|
||||||
|
// Use highlighted search results if available, otherwise fall back to basic display
|
||||||
|
if (this.allSearchResults.length > 0) {
|
||||||
|
const startIndex = this.currentDisplayedCount;
|
||||||
|
const endIndex = Math.min(startIndex + batchSize, this.allSearchResults.length);
|
||||||
|
const resultsToDisplay = this.allSearchResults.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
for (const result of resultsToDisplay) {
|
||||||
const noteId = result.notePath.split("/").pop();
|
const noteId = result.notePath.split("/").pop();
|
||||||
if (!noteId) continue;
|
if (!noteId) continue;
|
||||||
|
|
||||||
@ -216,19 +256,14 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
this.$dropdownMenu.append($item);
|
this.$dropdownMenu.append($item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchResults.length > MAX_DISPLAYED_NOTES) {
|
this.currentDisplayedCount = endIndex;
|
||||||
const numRemainingResults = searchResults.length - MAX_DISPLAYED_NOTES;
|
|
||||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.more-results", { number: numRemainingResults })}</span>`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback to original behavior if no highlighted results
|
// Fallback to original behavior if no highlighted results
|
||||||
const displayedNoteIds = searchResultNoteIds.slice(0, Math.min(MAX_DISPLAYED_NOTES, searchResultNoteIds.length));
|
const startIndex = this.currentDisplayedCount;
|
||||||
|
const endIndex = Math.min(startIndex + batchSize, this.allSearchResultNoteIds.length);
|
||||||
|
const noteIdsToDisplay = this.allSearchResultNoteIds.slice(startIndex, endIndex);
|
||||||
|
|
||||||
if (displayedNoteIds.length === 0) {
|
for (const note of await froca.getNotes(noteIdsToDisplay)) {
|
||||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.no-results")}</span>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const note of await froca.getNotes(displayedNoteIds)) {
|
|
||||||
const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
|
const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
|
||||||
$link.addClass("dropdown-item");
|
$link.addClass("dropdown-item");
|
||||||
$link.attr("tabIndex", "0");
|
$link.attr("tabIndex", "0");
|
||||||
@ -255,13 +290,38 @@ export default class QuickSearchWidget extends BasicWidget {
|
|||||||
this.$dropdownMenu.append($link);
|
this.$dropdownMenu.append($link);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchResultNoteIds.length > MAX_DISPLAYED_NOTES) {
|
this.currentDisplayedCount = endIndex;
|
||||||
const numRemainingResults = searchResultNoteIds.length - MAX_DISPLAYED_NOTES;
|
}
|
||||||
this.$dropdownMenu.append(`<span class="dropdown-item disabled">${t("quick-search.more-results", { number: numRemainingResults })}</span>`);
|
|
||||||
|
this.isLoadingMore = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleScroll() {
|
||||||
|
if (this.isLoadingMore) return;
|
||||||
|
|
||||||
|
const dropdown = this.$dropdownMenu[0];
|
||||||
|
const scrollTop = dropdown.scrollTop;
|
||||||
|
const scrollHeight = dropdown.scrollHeight;
|
||||||
|
const clientHeight = dropdown.clientHeight;
|
||||||
|
|
||||||
|
// Trigger loading more when user scrolls near the bottom (within 50px)
|
||||||
|
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||||||
|
const totalResults = this.allSearchResults.length > 0 ? this.allSearchResults.length : this.allSearchResultNoteIds.length;
|
||||||
|
|
||||||
|
if (this.currentDisplayedCount < totalResults) {
|
||||||
|
this.displayMoreResults(LOAD_MORE_BATCH_SIZE).then(() => {
|
||||||
|
this.addShowInFullSearchButton();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const $showInFullButton = $('<a class="dropdown-item" tabindex="0">').text(t("quick-search.show-in-full-search"));
|
private addShowInFullSearchButton() {
|
||||||
|
// Remove existing button if it exists
|
||||||
|
this.$dropdownMenu.find('.show-in-full-search').remove();
|
||||||
|
this.$dropdownMenu.find('.dropdown-divider').remove();
|
||||||
|
|
||||||
|
const $showInFullButton = $('<a class="dropdown-item show-in-full-search" tabindex="0">').text(t("quick-search.show-in-full-search"));
|
||||||
|
|
||||||
this.$dropdownMenu.append($(`<div class="dropdown-divider">`));
|
this.$dropdownMenu.append($(`<div class="dropdown-divider">`));
|
||||||
this.$dropdownMenu.append($showInFullButton);
|
this.$dropdownMenu.append($showInFullButton);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user