mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Merge pull request #721 from PR0M3TH3AN/codex/refresh-video-grids-after-creator-update
Refresh video grids after moderation updates
This commit is contained in:
4 files changed
+211
-122
No files matched your search
@@ -44,6 +44,7 @@ import WatchHistoryController from "./ui/watchHistoryController.js";
|
||||
import WatchHistoryTelemetry from "./services/watchHistoryTelemetry.js";
|
||||
import { getSidebarLoadingMarkup } from "./sidebarLoading.js";
|
||||
import { subscriptions } from "./subscriptions.js";
|
||||
import { refreshActiveChannelVideoGrid } from "./channelProfile.js";
|
||||
import { isWatchHistoryDebugEnabled } from "./watchHistoryDebug.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import createPopover from "./ui/overlay/popoverEngine.js";
|
||||
@@ -827,6 +828,7 @@ class Application {
|
||||
handleEnsurePresenceAction: (payload) =>
|
||||
this.handleEnsurePresenceAction(payload),
|
||||
loadVideos: () => this.loadVideos(),
|
||||
refreshAllVideoGrids: (options) => this.refreshAllVideoGrids(options),
|
||||
onUserBlocksUpdated: () => {
|
||||
if (this.profileController) {
|
||||
try {
|
||||
@@ -2067,29 +2069,102 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
this.loadVideos(true).catch((error) => {
|
||||
devLogger.error("Failed to refresh videos after admin update:", error);
|
||||
this.refreshAllVideoGrids({
|
||||
reason: "access-control-update",
|
||||
forceMainReload: true,
|
||||
}).catch((error) => {
|
||||
devLogger.error("Failed to refresh video grids after admin update:", error);
|
||||
});
|
||||
window.dispatchEvent(new CustomEvent("bitvid:access-control-updated"));
|
||||
}
|
||||
|
||||
async onVideosShouldRefresh({ reason } = {}) {
|
||||
async refreshAllVideoGrids({ reason, forceMainReload = false } = {}) {
|
||||
const normalizedReason =
|
||||
typeof reason === "string" && reason.trim() ? reason.trim() : undefined;
|
||||
|
||||
if (typeof moderationService?.awaitUserBlockRefresh === "function") {
|
||||
try {
|
||||
await moderationService.awaitUserBlockRefresh();
|
||||
} catch (error) {
|
||||
const contextMessage = normalizedReason
|
||||
? ` before ${normalizedReason}`
|
||||
: "";
|
||||
devLogger.warn(
|
||||
`Failed to sync moderation summaries${contextMessage}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof moderationService?.awaitUserBlockRefresh === "function") {
|
||||
try {
|
||||
await moderationService.awaitUserBlockRefresh();
|
||||
} catch (error) {
|
||||
await this.loadVideos(forceMainReload);
|
||||
} catch (error) {
|
||||
const contextMessage = normalizedReason
|
||||
? ` after ${normalizedReason}`
|
||||
: "";
|
||||
devLogger.error(
|
||||
`Failed to refresh recent videos${contextMessage}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
const refreshTasks = [];
|
||||
|
||||
if (typeof subscriptions?.refreshActiveFeed === "function") {
|
||||
const subscriptionPromise = subscriptions
|
||||
.refreshActiveFeed({ reason: normalizedReason })
|
||||
.catch((error) => {
|
||||
const contextMessage = normalizedReason
|
||||
? ` after ${normalizedReason}`
|
||||
: "";
|
||||
devLogger.warn(
|
||||
"Failed to sync moderation summaries before refreshing videos:",
|
||||
`Failed to refresh subscriptions grid${contextMessage}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
refreshTasks.push(subscriptionPromise);
|
||||
}
|
||||
|
||||
await this.loadVideos(true);
|
||||
if (typeof refreshActiveChannelVideoGrid === "function") {
|
||||
try {
|
||||
const maybePromise = refreshActiveChannelVideoGrid({
|
||||
reason: normalizedReason,
|
||||
});
|
||||
if (maybePromise && typeof maybePromise.then === "function") {
|
||||
refreshTasks.push(
|
||||
maybePromise.catch((error) => {
|
||||
const contextMessage = normalizedReason
|
||||
? ` after ${normalizedReason}`
|
||||
: "";
|
||||
devLogger.warn(
|
||||
`Failed to refresh channel grid${contextMessage}:`,
|
||||
error,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
const contextMessage = normalizedReason
|
||||
? ` after ${normalizedReason}`
|
||||
: "";
|
||||
devLogger.warn(
|
||||
`Failed to trigger channel grid refresh${contextMessage}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshTasks.length) {
|
||||
await Promise.allSettled(refreshTasks);
|
||||
}
|
||||
}
|
||||
|
||||
async onVideosShouldRefresh({ reason } = {}) {
|
||||
try {
|
||||
await this.refreshAllVideoGrids({ reason, forceMainReload: true });
|
||||
} catch (error) {
|
||||
const context = reason ? ` after ${reason}` : "";
|
||||
devLogger.error(`Failed to refresh videos${context}:`, error);
|
||||
devLogger.error(`Failed to refresh video grids${context}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+76
-41
@@ -3690,6 +3690,39 @@ async function loadUserProfile(pubkey) {
|
||||
* Fetches and displays this user's videos (kind=30078).
|
||||
* Filters out older overshadowed notes, blacklisted, etc.
|
||||
*/
|
||||
export function refreshActiveChannelVideoGrid({ reason } = {}) {
|
||||
if (typeof window === "undefined") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (hashParams.get("view") !== "channel-profile") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!currentChannelHex) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const activeNpub = hashParams.get("npub");
|
||||
if (currentChannelNpub && activeNpub && activeNpub !== currentChannelNpub) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
try {
|
||||
const result = loadUserVideos(currentChannelHex);
|
||||
return result instanceof Promise ? result : Promise.resolve(result);
|
||||
} catch (error) {
|
||||
userLogger.warn(
|
||||
reason
|
||||
? `Failed to trigger channel refresh after ${reason}:`
|
||||
: "Failed to trigger channel refresh:",
|
||||
error,
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUserVideos(pubkey) {
|
||||
const app = getApp();
|
||||
const container = document.getElementById("channelVideoList");
|
||||
@@ -3835,34 +3868,35 @@ async function loadUserVideos(pubkey) {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("bitvid:access-control-updated", () => {
|
||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (hashParams.get("view") !== "channel-profile") {
|
||||
return;
|
||||
}
|
||||
if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
|
||||
window.addEventListener("bitvid:access-control-updated", () => {
|
||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (hashParams.get("view") !== "channel-profile") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentChannelHex) {
|
||||
return;
|
||||
}
|
||||
if (!currentChannelHex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeNpub = hashParams.get("npub");
|
||||
if (currentChannelNpub && activeNpub && activeNpub !== currentChannelNpub) {
|
||||
return;
|
||||
}
|
||||
const activeNpub = hashParams.get("npub");
|
||||
if (currentChannelNpub && activeNpub && activeNpub !== currentChannelNpub) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadUserVideos(currentChannelHex).catch((error) => {
|
||||
userLogger.error(
|
||||
"Failed to refresh channel videos after admin update:",
|
||||
error
|
||||
);
|
||||
loadUserVideos(currentChannelHex).catch((error) => {
|
||||
userLogger.error(
|
||||
"Failed to refresh channel videos after admin update:",
|
||||
error
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (hashParams.get("view") !== "channel-profile") {
|
||||
return;
|
||||
}
|
||||
window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
const hashParams = new URLSearchParams(window.location.hash.slice(1));
|
||||
if (hashParams.get("view") !== "channel-profile") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentChannelHex) {
|
||||
return;
|
||||
@@ -3896,27 +3930,28 @@ window.addEventListener("bitvid:auth-changed", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (detail.status === "login") {
|
||||
zapInFlight = false;
|
||||
resetZapRetryState();
|
||||
setZapStatus("", "neutral");
|
||||
if (channelZapPendingOpen) {
|
||||
channelZapPendingOpen = false;
|
||||
const zapButton = getChannelZapButton();
|
||||
const requiresLogin =
|
||||
zapButton?.dataset?.requiresLogin === "true" ||
|
||||
zapButton?.hasAttribute("hidden");
|
||||
if (zapButton && !requiresLogin) {
|
||||
const opened = openZapControls({ focus: true });
|
||||
if (!opened) {
|
||||
setupZapButton({ force: true });
|
||||
openZapControls({ focus: true });
|
||||
if (detail.status === "login") {
|
||||
zapInFlight = false;
|
||||
resetZapRetryState();
|
||||
setZapStatus("", "neutral");
|
||||
if (channelZapPendingOpen) {
|
||||
channelZapPendingOpen = false;
|
||||
const zapButton = getChannelZapButton();
|
||||
const requiresLogin =
|
||||
zapButton?.dataset?.requiresLogin === "true" ||
|
||||
zapButton?.hasAttribute("hidden");
|
||||
if (zapButton && !requiresLogin) {
|
||||
const opened = openZapControls({ focus: true });
|
||||
if (!opened) {
|
||||
setupZapButton({ force: true });
|
||||
openZapControls({ focus: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep only the newest version of each video root.
|
||||
|
||||
+20
-26
@@ -69,6 +69,14 @@ export default class MoreMenuController {
|
||||
onUserBlocksUpdated: callbacks.onUserBlocksUpdated || (() => {}),
|
||||
};
|
||||
|
||||
this.callbacks.refreshAllVideoGrids =
|
||||
typeof callbacks.refreshAllVideoGrids === "function"
|
||||
? (options) => callbacks.refreshAllVideoGrids(options)
|
||||
: (options = {}) =>
|
||||
Promise.resolve(
|
||||
this.callbacks.loadVideos(Boolean(options?.forceMainReload)),
|
||||
).catch(() => {});
|
||||
|
||||
this.moreMenuGlobalHandlerBound = false;
|
||||
|
||||
this.videoListView = null;
|
||||
@@ -1198,20 +1206,14 @@ export default class MoreMenuController {
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.callbacks.loadVideos();
|
||||
} catch (loadError) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn("[MoreMenu] Failed to reload videos after muting", loadError);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.subscriptions?.refreshActiveFeed?.({
|
||||
await this.callbacks.refreshAllVideoGrids({
|
||||
reason: "viewer-mute-update",
|
||||
forceMainReload: true,
|
||||
});
|
||||
} catch (refreshError) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn(
|
||||
"[Subscriptions] Failed to refresh after viewer mute update:",
|
||||
"[MoreMenu] Failed to refresh video grids after viewer mute update:",
|
||||
refreshError,
|
||||
);
|
||||
}
|
||||
@@ -1272,23 +1274,14 @@ export default class MoreMenuController {
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.callbacks.loadVideos();
|
||||
} catch (loadError) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn(
|
||||
"[MoreMenu] Failed to reload videos after unmuting",
|
||||
loadError,
|
||||
);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.subscriptions?.refreshActiveFeed?.({
|
||||
await this.callbacks.refreshAllVideoGrids({
|
||||
reason: "viewer-mute-update",
|
||||
forceMainReload: true,
|
||||
});
|
||||
} catch (refreshError) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn(
|
||||
"[Subscriptions] Failed to refresh after viewer mute removal:",
|
||||
"[MoreMenu] Failed to refresh video grids after viewer mute removal:",
|
||||
refreshError,
|
||||
);
|
||||
}
|
||||
@@ -1369,13 +1362,14 @@ export default class MoreMenuController {
|
||||
if (result?.ok) {
|
||||
this.callbacks.showSuccess("Creator added to the blacklist.");
|
||||
try {
|
||||
await this.subscriptions?.refreshActiveFeed?.({
|
||||
await this.callbacks.refreshAllVideoGrids({
|
||||
reason: "admin-blacklist-update",
|
||||
forceMainReload: true,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn(
|
||||
"[Subscriptions] Failed to refresh after blacklist update:",
|
||||
"[MoreMenu] Failed to refresh video grids after blacklist update:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
@@ -1478,15 +1472,15 @@ export default class MoreMenuController {
|
||||
}
|
||||
}
|
||||
|
||||
await this.callbacks.loadVideos();
|
||||
try {
|
||||
await this.subscriptions?.refreshActiveFeed?.({
|
||||
await this.callbacks.refreshAllVideoGrids({
|
||||
reason: "user-block-update",
|
||||
forceMainReload: true,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.isDevMode) {
|
||||
userLogger.warn(
|
||||
"[Subscriptions] Failed to refresh after user block update:",
|
||||
"[MoreMenu] Failed to refresh video grids after user block update:",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,15 +47,9 @@ test('blacklist-author requires moderator access and refreshes subscriptions', a
|
||||
};
|
||||
|
||||
let refreshedWith = null;
|
||||
const subscriptions = {
|
||||
refreshActiveFeed: async (args) => {
|
||||
refreshedWith = args;
|
||||
},
|
||||
};
|
||||
|
||||
const controller = new MoreMenuController({
|
||||
accessControl,
|
||||
subscriptions,
|
||||
callbacks: {
|
||||
getCurrentUserNpub: () => 'npub1actor',
|
||||
getCurrentVideo: () => ({ pubkey: 'authorhex' }),
|
||||
@@ -67,6 +61,9 @@ test('blacklist-author requires moderator access and refreshes subscriptions', a
|
||||
calls.push(['success', message]);
|
||||
},
|
||||
canCurrentUserManageBlacklist: () => true,
|
||||
refreshAllVideoGrids: async (args) => {
|
||||
refreshedWith = args;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -80,7 +77,10 @@ test('blacklist-author requires moderator access and refreshes subscriptions', a
|
||||
['add', 'npub1actor', 'npub:authorhex'],
|
||||
['success', 'Creator added to the blacklist.'],
|
||||
]);
|
||||
assert.deepEqual(refreshedWith, { reason: 'admin-blacklist-update' });
|
||||
assert.deepEqual(refreshedWith, {
|
||||
reason: 'admin-blacklist-update',
|
||||
forceMainReload: true,
|
||||
});
|
||||
});
|
||||
|
||||
test('blacklist-author shows error when no moderator session is available', async () => {
|
||||
@@ -117,12 +117,6 @@ test('block-author updates user blocks, reloads videos, and refreshes feeds', as
|
||||
},
|
||||
};
|
||||
|
||||
const subscriptions = {
|
||||
refreshActiveFeed: async (args) => {
|
||||
events.push(['refreshActiveFeed', args]);
|
||||
},
|
||||
};
|
||||
|
||||
const moderationStub = {
|
||||
awaitUserBlockRefresh: async () => {
|
||||
events.push(['awaitUserBlockRefresh']);
|
||||
@@ -131,7 +125,6 @@ test('block-author updates user blocks, reloads videos, and refreshes feeds', as
|
||||
|
||||
const controller = new MoreMenuController({
|
||||
userBlocks,
|
||||
subscriptions,
|
||||
moderationService: moderationStub,
|
||||
callbacks: {
|
||||
getCurrentUserPubkey: () => 'actorhex',
|
||||
@@ -143,12 +136,12 @@ test('block-author updates user blocks, reloads videos, and refreshes feeds', as
|
||||
showSuccess: (message) => {
|
||||
events.push(['success', message]);
|
||||
},
|
||||
loadVideos: async () => {
|
||||
events.push(['loadVideos']);
|
||||
},
|
||||
onUserBlocksUpdated: () => {
|
||||
events.push(['refreshBlockedList']);
|
||||
},
|
||||
refreshAllVideoGrids: async (args) => {
|
||||
events.push(['refreshAllVideoGrids', args]);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -163,8 +156,10 @@ test('block-author updates user blocks, reloads videos, and refreshes feeds', as
|
||||
['success', "Creator blocked. You won't see their videos anymore."],
|
||||
['refreshBlockedList'],
|
||||
['awaitUserBlockRefresh'],
|
||||
['loadVideos'],
|
||||
['refreshActiveFeed', { reason: 'user-block-update' }],
|
||||
[
|
||||
'refreshAllVideoGrids',
|
||||
{ reason: 'user-block-update', forceMainReload: true },
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -181,21 +176,11 @@ test('mute-author updates viewer mute list and refreshes feeds', async () => {
|
||||
isAuthorMutedByViewer: () => false,
|
||||
};
|
||||
|
||||
const subscriptions = {
|
||||
refreshActiveFeed: async (args) => {
|
||||
events.push(['refreshActiveFeed', args]);
|
||||
},
|
||||
};
|
||||
|
||||
const controller = new MoreMenuController({
|
||||
moderationService: moderationStub,
|
||||
subscriptions,
|
||||
callbacks: {
|
||||
getCurrentUserPubkey: () => 'viewerhex',
|
||||
getCurrentVideo: () => ({ pubkey: 'fallbackhex' }),
|
||||
loadVideos: async () => {
|
||||
events.push(['loadVideos']);
|
||||
},
|
||||
showSuccess: (message) => {
|
||||
events.push(['success', message]);
|
||||
},
|
||||
@@ -203,6 +188,9 @@ test('mute-author updates viewer mute list and refreshes feeds', async () => {
|
||||
events.push(['error', message]);
|
||||
},
|
||||
safeDecodeNpub: () => '',
|
||||
refreshAllVideoGrids: async (args) => {
|
||||
events.push(['refreshAllVideoGrids', args]);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -213,8 +201,10 @@ test('mute-author updates viewer mute list and refreshes feeds', async () => {
|
||||
assert.deepEqual(events, [
|
||||
['addMute', targetHex],
|
||||
['success', 'Creator muted. Their videos will be downranked in your feeds.'],
|
||||
['loadVideos'],
|
||||
['refreshActiveFeed', { reason: 'viewer-mute-update' }],
|
||||
[
|
||||
'refreshAllVideoGrids',
|
||||
{ reason: 'viewer-mute-update', forceMainReload: true },
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -231,21 +221,11 @@ test('unmute-author removes creators from viewer mute list', async () => {
|
||||
isAuthorMutedByViewer: () => true,
|
||||
};
|
||||
|
||||
const subscriptions = {
|
||||
refreshActiveFeed: async (args) => {
|
||||
events.push(['refreshActiveFeed', args]);
|
||||
},
|
||||
};
|
||||
|
||||
const controller = new MoreMenuController({
|
||||
moderationService: moderationStub,
|
||||
subscriptions,
|
||||
callbacks: {
|
||||
getCurrentUserPubkey: () => 'viewerhex',
|
||||
getCurrentVideo: () => ({ pubkey: 'fallbackhex' }),
|
||||
loadVideos: async () => {
|
||||
events.push(['loadVideos']);
|
||||
},
|
||||
showSuccess: (message) => {
|
||||
events.push(['success', message]);
|
||||
},
|
||||
@@ -253,6 +233,9 @@ test('unmute-author removes creators from viewer mute list', async () => {
|
||||
events.push(['error', message]);
|
||||
},
|
||||
safeDecodeNpub: () => '',
|
||||
refreshAllVideoGrids: async (args) => {
|
||||
events.push(['refreshAllVideoGrids', args]);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -263,7 +246,9 @@ test('unmute-author removes creators from viewer mute list', async () => {
|
||||
assert.deepEqual(events, [
|
||||
['removeMute', targetHex],
|
||||
['success', 'Creator removed from your mute list.'],
|
||||
['loadVideos'],
|
||||
['refreshActiveFeed', { reason: 'viewer-mute-update' }],
|
||||
[
|
||||
'refreshAllVideoGrids',
|
||||
{ reason: 'viewer-mute-update', forceMainReload: true },
|
||||
],
|
||||
]);
|
||||
});
|
||||
Reference in new issue
Block a user