|
44 | 44 | const searchForm = document.querySelector("[data-search-form]"); |
45 | 45 | const searchInput = document.querySelector("[data-search-input]"); |
46 | 46 | const searchSourceInput = document.querySelector("[data-search-source]"); |
| 47 | + const refreshButton = document.querySelector("[data-refresh-button]"); |
47 | 48 | const themeToggle = document.querySelector("[data-theme-toggle]"); |
48 | 49 | const toast = document.querySelector("[data-toast]"); |
49 | 50 | const pageSize = Number(cardsGrid?.dataset.pageSize || 12); |
|
68 | 69 | let searchTimer = null; |
69 | 70 | let ignoreNextEmptySearchInput = false; |
70 | 71 | let requestSequence = 0; |
| 72 | + let refreshInFlight = false; |
71 | 73 | let feedLoading = false; |
72 | 74 | let feedLoadingMode = ""; |
73 | 75 | let activeToastKind = ""; |
|
378 | 380 | const showButton = hasNext && loadedCount > 0; |
379 | 381 | const loadingMore = feedLoading && feedLoadingMode === "append"; |
380 | 382 | viewMoreButton.hidden = !showButton; |
381 | | - viewMoreButton.disabled = !showButton || feedLoading; |
| 383 | + viewMoreButton.disabled = !showButton || refreshInFlight || feedLoading; |
382 | 384 | viewMoreButton.textContent = loadingMore ? "Loading…" : "View more"; |
383 | 385 | }; |
384 | 386 |
|
|
533 | 535 | } |
534 | 536 | }; |
535 | 537 |
|
| 538 | + const setRefreshButtonLoading = (active) => { |
| 539 | + if (!refreshButton) return; |
| 540 | + refreshButton.disabled = active; |
| 541 | + refreshButton.classList.toggle("is-loading", active); |
| 542 | + refreshButton.setAttribute( |
| 543 | + "aria-label", |
| 544 | + active ? "Refreshing feed" : "Refresh feed", |
| 545 | + ); |
| 546 | + refreshButton.setAttribute( |
| 547 | + "title", |
| 548 | + active ? "Refreshing feed" : "Refresh feed", |
| 549 | + ); |
| 550 | + }; |
| 551 | + |
536 | 552 | const fetchItems = async ({ |
537 | 553 | source, |
538 | 554 | query, |
|
704 | 720 |
|
705 | 721 | async function refetchCurrentViewAfterReconnect() { |
706 | 722 | syncConnectivityState(); |
707 | | - if (!browserOnline || reconnectRefetchInFlight) { |
| 723 | + if (!browserOnline || refreshInFlight || reconnectRefetchInFlight) { |
708 | 724 | return false; |
709 | 725 | } |
710 | 726 | reconnectRefetchInFlight = true; |
|
718 | 734 | } |
719 | 735 | } |
720 | 736 |
|
| 737 | + async function refreshFeedList() { |
| 738 | + syncConnectivityState(); |
| 739 | + if (!browserOnline || refreshInFlight) { |
| 740 | + return false; |
| 741 | + } |
| 742 | + refreshInFlight = true; |
| 743 | + cancelPendingSearch(); |
| 744 | + setRefreshButtonLoading(true); |
| 745 | + renderFeedBody(); |
| 746 | + try { |
| 747 | + await refetchCurrentView({ loadingMessage: "Refreshing feed…" }); |
| 748 | + showToast("Feed refreshed", "success"); |
| 749 | + return true; |
| 750 | + } catch (error) { |
| 751 | + showToast("Refresh failed", "error"); |
| 752 | + return false; |
| 753 | + } finally { |
| 754 | + refreshInFlight = false; |
| 755 | + setRefreshButtonLoading(false); |
| 756 | + renderFeedBody(); |
| 757 | + } |
| 758 | + } |
| 759 | + |
721 | 760 | function openConfigDialog() { |
722 | 761 | syncConfigOptions(); |
723 | 762 | syncDensityOptions(); |
|
934 | 973 | }); |
935 | 974 | } |
936 | 975 |
|
| 976 | + if (refreshButton) { |
| 977 | + refreshButton.addEventListener("click", async () => { |
| 978 | + await refreshFeedList(); |
| 979 | + }); |
| 980 | + } |
| 981 | + |
937 | 982 | if (themeToggle) { |
938 | 983 | themeToggle.addEventListener("click", () => { |
939 | 984 | applyTheme(root.dataset.theme === "dark" ? "light" : "dark"); |
|
974 | 1019 | renderFilters(); |
975 | 1020 | renderSearch(); |
976 | 1021 | renderFeedBody(); |
| 1022 | + setRefreshButtonLoading(false); |
977 | 1023 | renderConnectionIndicator(); |
978 | 1024 |
|
979 | 1025 | if (shouldBootstrapRefetch) { |
|
0 commit comments