mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-06 12:52:26 +00:00
[moderation] #25: per-video admin block list (hide a single video, not the author)
Admin moderation was author/community-level only. Admins (any editor — same gate as the author blacklist) can now hide a SINGLE offending video. - Record: a published kind-30000 NIP-51 list under d-tag bitvid:admin:event-blacklist, carrying blocked event ids as `e` tags (NOTE_TYPES.ADMIN_EVENT_BLACKLIST schema). Loaded/persisted through the existing admin-list machinery (adminListStore) and cached like whitelist/blacklist. - accessControl: eventBlacklist Set + getEventBlacklist/isEventBlacklisted/ addToEventBlacklist/removeFromEventBlacklist (editor-gated; normalizes hex or nevent/note) + onEventBlacklistChange. - Render filter already existed (app.blacklistedEventIds, honored by every feed/grid/ search/playback path) but was fed only by the static ADMIN_INITIAL_EVENT_BLACKLIST config. app._rebuildBlacklistedEventIds() now merges that static base with the dynamic admin list, and an onEventBlacklistChange listener rebuilds + refreshes grids on change. - UI: a "Block this video" item in the per-video ⋯ menu (editor-only), handled by handleBlacklistEventAction (js/ui/moreMenu/blacklistEventAction.js). Helpers extracted to js/adminEventBlacklistHelpers.js to keep adminListStore / accessControl / moreMenuController under their size caps. Tests: tests/admin-event-blacklist.test.mjs (9). Build + lint clean; admin/access/moderation suites 56/56. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
2e7e2016d3
commit
3e57ab2ea9
11 files changed
+529
-44
No files matched your search
+12
-12
@@ -5,6 +5,7 @@ import {
|
||||
createVideoMoreMenuPanel,
|
||||
} from "./components/videoMenuRenderers.js";
|
||||
import createPopover from "./overlay/popoverEngine.js";
|
||||
import { handleBlacklistEventAction } from "./moreMenu/blacklistEventAction.js";
|
||||
import {
|
||||
prepareStaticModal,
|
||||
openStaticModal,
|
||||
@@ -1019,9 +1020,7 @@ export default class MoreMenuController {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// The modal ⋯ trigger declares its context via `data-more-dropdown`
|
||||
// ("modal"), not `data-context` — reading only the latter made it
|
||||
// default to "card" and skip the modal video resolution below.
|
||||
// Modal ⋯ context comes from `data-more-dropdown`, not `data-context`.
|
||||
const context =
|
||||
button.getAttribute("data-context") ||
|
||||
button.getAttribute("data-more-dropdown") ||
|
||||
@@ -1034,8 +1033,7 @@ export default class MoreMenuController {
|
||||
|
||||
if (context === "modal") {
|
||||
video = this.callbacks.getCurrentVideo();
|
||||
// currentVideo can be null when the modal opened without playback
|
||||
// starting (e.g. a deep link); fall back to the modal's activeVideo.
|
||||
// currentVideo can be null on a deep link; fall back to activeVideo.
|
||||
if (!video && this.videoModal?.activeVideo) {
|
||||
video = this.videoModal.activeVideo;
|
||||
}
|
||||
@@ -1175,9 +1173,6 @@ export default class MoreMenuController {
|
||||
break;
|
||||
}
|
||||
case "view-stats": {
|
||||
// Prefer the card's own video; fall back to resolving it from the menu
|
||||
// item's eventId (card popovers don't always carry the object), and only
|
||||
// use the modal's current video for the modal context.
|
||||
let targetVideo = video || null;
|
||||
if (!targetVideo && dataset?.eventId) {
|
||||
targetVideo =
|
||||
@@ -1190,7 +1185,6 @@ export default class MoreMenuController {
|
||||
this.callbacks.showError("No video selected.");
|
||||
break;
|
||||
}
|
||||
// Dismiss the menu, then open the public popularity chart (lazy-loaded).
|
||||
try {
|
||||
this.activePopover?.close?.();
|
||||
} catch (error) {
|
||||
@@ -1469,6 +1463,15 @@ export default class MoreMenuController {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "blacklist-event": {
|
||||
await handleBlacklistEventAction({
|
||||
accessControl: this.accessControl,
|
||||
callbacks: this.callbacks,
|
||||
dataset,
|
||||
currentVideo,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "block-author": {
|
||||
const currentUserPubkey = this.callbacks.getCurrentUserPubkey();
|
||||
if (!currentUserPubkey) {
|
||||
@@ -1625,13 +1628,10 @@ export default class MoreMenuController {
|
||||
break;
|
||||
}
|
||||
case "event-details": {
|
||||
// Card context passes the card's video; modal context falls back to the
|
||||
// active video (the dispatch path doesn't carry the video object).
|
||||
let targetVideo = video;
|
||||
if (!targetVideo && context === "modal") {
|
||||
targetVideo = currentVideo;
|
||||
}
|
||||
// Dismiss the menu before opening the details modal.
|
||||
try {
|
||||
this.activePopover?.close?.();
|
||||
} catch (error) {
|
||||
|
||||
Reference in new issue
Block a user