diff --git a/css/style.css b/css/style.css index 3f61eb93..456ca6c6 100644 --- a/css/style.css +++ b/css/style.css @@ -1144,12 +1144,6 @@ button:not(.icon-button):focus { display: flex; } -.watch-history-status { - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - .status-spinner--inline { animation: spin 1s linear infinite; border: 0.125rem solid rgba(96, 165, 250, 0.35); @@ -1160,6 +1154,29 @@ button:not(.icon-button):focus { width: 1rem; } +.sidebar-loading-wrapper { + display: flex; + justify-content: center; + align-items: center; + padding: 3rem 0; + width: 100%; + grid-column: 1 / -1; +} + +.sidebar-loading-indicator { + display: inline-flex; + align-items: center; + gap: 0.75rem; + color: #6b7280; + font-size: 0.875rem; + font-weight: 500; + letter-spacing: 0.01em; +} + +.sidebar-loading-text { + white-space: nowrap; +} + .status-banner { align-items: center; background-color: rgba(31, 41, 55, 0.9); diff --git a/js/app.js b/js/app.js index 8fa09e81..e2226a4d 100644 --- a/js/app.js +++ b/js/app.js @@ -42,6 +42,7 @@ import { } from "./storage/r2-s3.js"; import { initQuickR2Upload } from "./r2-quick.js"; import { createWatchHistoryRenderer } from "./historyView.js"; +import { getSidebarLoadingMarkup } from "./sidebarLoading.js"; import { initViewCounter, subscribeToVideoViewCount, @@ -7530,15 +7531,17 @@ class bitvidApp { this.videoSubscription = null; } + if (this.videoList) { + if (this._lastRenderedVideoListElement !== this.videoList) { + this.lastRenderedVideoSignature = null; + } + this.videoList.innerHTML = getSidebarLoadingMarkup( + "Fetching recent videos…" + ); + } + // The rest of your existing logic: if (!this.videoSubscription) { - if (this.videoList) { - this.lastRenderedVideoSignature = null; - this.videoList.innerHTML = ` -

- Loading videos as they arrive... -

`; - } // Create a new subscription this.videoSubscription = nostrClient.subscribeVideos(() => { diff --git a/js/historyView.js b/js/historyView.js index 4c306b39..3c5a2b2a 100644 --- a/js/historyView.js +++ b/js/historyView.js @@ -4,6 +4,7 @@ import { nostrClient } from "./nostr.js"; import { WATCH_HISTORY_BATCH_RESOLVE } from "./config.js"; import { subscriptions } from "./subscriptions.js"; import { accessControl } from "./accessControl.js"; +import { getSidebarLoadingMarkup } from "./sidebarLoading.js"; const DEFAULT_BATCH_SIZE = 20; const BATCH_SIZE = WATCH_HISTORY_BATCH_RESOLVE ? DEFAULT_BATCH_SIZE : 1; @@ -672,14 +673,10 @@ export function createWatchHistoryRenderer(config = {}) { const nextMessage = typeof message === "string" ? message.trim() : ""; if (nextMessage) { - if (nextMessage === WATCH_HISTORY_LOADING_STATUS) { - statusEl.innerHTML = - '' + - WATCH_HISTORY_LOADING_STATUS + - ""; - } else { - statusEl.textContent = nextMessage; - } + const showSpinner = nextMessage === WATCH_HISTORY_LOADING_STATUS; + statusEl.innerHTML = getSidebarLoadingMarkup(nextMessage, { + showSpinner, + }); statusEl.classList.remove("hidden"); } else { statusEl.textContent = ""; diff --git a/js/sidebarLoading.js b/js/sidebarLoading.js new file mode 100644 index 00000000..4e88280d --- /dev/null +++ b/js/sidebarLoading.js @@ -0,0 +1,33 @@ +// js/sidebarLoading.js + +function escapeHtml(value) { + if (typeof value !== "string") { + return ""; + } + + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +export function getSidebarLoadingMarkup(message = "", options = {}) { + const trimmed = typeof message === "string" ? message.trim() : ""; + const copy = trimmed || "Fetching data…"; + const safeCopy = escapeHtml(copy); + const showSpinner = options?.showSpinner !== false; + const spinnerMarkup = showSpinner + ? '' + : ""; + + return ` + + `; +} diff --git a/js/subscriptions.js b/js/subscriptions.js index a11f5b6e..06d0519d 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -9,6 +9,7 @@ import { buildSubscriptionListEvent, SUBSCRIPTION_LIST_IDENTIFIER, } from "./nostrEventSchemas.js"; +import { getSidebarLoadingMarkup } from "./sidebarLoading.js"; function getAbsoluteShareUrl(nevent) { if (!nevent) { @@ -226,6 +227,8 @@ class SubscriptionsManager { return; } + container.innerHTML = getSidebarLoadingMarkup("Fetching subscriptions…"); + // Gather all videos const videos = await this.fetchSubscribedVideos(channelHexes); this.renderSameGridStyle(videos, containerId); diff --git a/views/history.html b/views/history.html index f5d08587..a6ce1557 100644 --- a/views/history.html +++ b/views/history.html @@ -1,21 +1,20 @@
-
-

Watch History

-
-
- - +

Watch History

+
+
+ +
diff --git a/views/subscriptions.html b/views/subscriptions.html index 309018a0..4f9a6bc9 100644 --- a/views/subscriptions.html +++ b/views/subscriptions.html @@ -9,5 +9,12 @@ gap: 2rem; padding: 1.5rem 0; " - > + > + +