mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-30 09:22:24 +00:00
Merge pull request #1199 from PR0M3TH3AN/video-card-moderation-badge-overlay-5905417972080224775
Overlay moderation badge on blurred thumbnails
This commit is contained in:
3 files changed
+153
-14
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { normalizeDesignSystemContext } from "../../designSystem.js";
|
||||
import { formatShortNpub } from "../../utils/formatters.js";
|
||||
import { sanitizeProfileMediaUrl } from "../../utils/profileMedia.js";
|
||||
import { normalizeVideoModerationContext } from "../moderationUiHelpers.js";
|
||||
import { buildModerationBadgeText } from "../moderationCopy.js";
|
||||
|
||||
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
||||
const DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
@@ -69,6 +71,10 @@ export class SimilarContentCard {
|
||||
this.viewCountEl = null;
|
||||
this.discussionCountEl = null;
|
||||
|
||||
this.moderationBadgeEl = null;
|
||||
this.moderationBadgeLabelEl = null;
|
||||
this.moderationBadgeTextEl = null;
|
||||
|
||||
this.build();
|
||||
}
|
||||
|
||||
@@ -342,6 +348,7 @@ export class SimilarContentCard {
|
||||
root.appendChild(content);
|
||||
}
|
||||
|
||||
this.refreshModerationUi();
|
||||
this.applyPointerDatasets();
|
||||
this.bindEvents();
|
||||
}
|
||||
@@ -361,6 +368,89 @@ export class SimilarContentCard {
|
||||
return anchor;
|
||||
}
|
||||
|
||||
getModerationContext() {
|
||||
return normalizeVideoModerationContext(this.video?.moderation);
|
||||
}
|
||||
|
||||
buildModerationBadge(context = this.getModerationContext()) {
|
||||
if (!context.shouldShow || !context.trustedMuted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const badge = this.document.createElement("div");
|
||||
badge.className = "moderation-badge opacity-95";
|
||||
badge.dataset.variant = "warning";
|
||||
badge.dataset.moderationBadge = "true";
|
||||
badge.dataset.moderationState = "trusted-mute";
|
||||
|
||||
// Position it centrally over the thumbnail like in VideoCard
|
||||
badge.style.pointerEvents = "auto";
|
||||
|
||||
const label = this.document.createElement("span");
|
||||
label.className = "moderation-badge__label inline-flex items-center gap-xs";
|
||||
|
||||
// Simplified icon for small card
|
||||
const iconWrapper = this.document.createElement("span");
|
||||
iconWrapper.className = "moderation-badge__icon";
|
||||
iconWrapper.setAttribute("aria-hidden", "true");
|
||||
|
||||
const svg = this.document.createElementNS(SVG_NAMESPACE, "svg");
|
||||
svg.setAttribute("viewBox", "0 0 20 20");
|
||||
svg.setAttribute("focusable", "false");
|
||||
svg.classList.add("moderation-badge__icon-mark");
|
||||
|
||||
const path = this.document.createElementNS(SVG_NAMESPACE, "path");
|
||||
path.setAttribute("fill", "currentColor");
|
||||
path.setAttribute("d", "M10 18a8 8 0 100-16 8 8 0 000 16zm-.75-11.75a.75.75 0 011.5 0v4.5a.75.75 0 01-1.5 0v-4.5zm.75 8.5a1 1 0 100-2 1 1 0 000 2z");
|
||||
path.setAttribute("fill-rule", "evenodd");
|
||||
path.setAttribute("clip-rule", "evenodd");
|
||||
|
||||
svg.appendChild(path);
|
||||
iconWrapper.appendChild(svg);
|
||||
label.appendChild(iconWrapper);
|
||||
|
||||
const text = this.document.createElement("span");
|
||||
text.className = "moderation-badge__text";
|
||||
// Shorten text for small cards if needed, but keeping standard for now
|
||||
text.textContent = "Blocked by trusted contact";
|
||||
|
||||
label.appendChild(text);
|
||||
badge.appendChild(label);
|
||||
|
||||
this.moderationBadgeEl = badge;
|
||||
return badge;
|
||||
}
|
||||
|
||||
refreshModerationUi() {
|
||||
const context = this.getModerationContext();
|
||||
|
||||
if (this.moderationBadgeEl) {
|
||||
this.moderationBadgeEl.remove();
|
||||
this.moderationBadgeEl = null;
|
||||
}
|
||||
|
||||
// Check specifically for muted/blurred state where we want the overlay
|
||||
if (context.shouldShow && context.trustedMuted && !context.overrideActive) {
|
||||
// Ensure media link exists
|
||||
if (!this.mediaLinkEl) return;
|
||||
|
||||
const badge = this.buildModerationBadge(context);
|
||||
if (badge) {
|
||||
// Create a wrapper for centering if needed, or use classes on badge if possible.
|
||||
// Similar to VideoCard, let's create a container for absolute positioning
|
||||
const container = this.document.createElement("div");
|
||||
container.className = "absolute inset-0 flex items-center justify-center p-2 z-10 pointer-events-none";
|
||||
container.appendChild(badge);
|
||||
|
||||
this.mediaLinkEl.appendChild(container);
|
||||
// We track the container removal via moderationBadgeEl reference if we wanted strict cleanup,
|
||||
// but for now just rebuilding is fine if we store the container.
|
||||
// Let's store badge el as the container actually to make removal easier
|
||||
this.moderationBadgeEl = container;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildThumbnail() {
|
||||
const img = this.document.createElement("img");
|
||||
img.decoding = "async";
|
||||
|
||||
@@ -493,14 +493,28 @@ export class VideoCard {
|
||||
}
|
||||
|
||||
const moderationBadgeSlot = this.createElement("div", {
|
||||
classNames: ["video-card__moderation-slot", "px-md", "pt-md"],
|
||||
classNames: [
|
||||
"video-card__moderation-overlay",
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"p-4",
|
||||
"z-10",
|
||||
"pointer-events-none",
|
||||
],
|
||||
});
|
||||
moderationBadgeSlot.hidden = true;
|
||||
moderationBadgeSlot.setAttribute("aria-hidden", "true");
|
||||
this.moderationBadgeSlot = moderationBadgeSlot;
|
||||
|
||||
// Append slot to anchor (media wrapper) instead of root to overlay thumbnail
|
||||
if (this.anchorEl) {
|
||||
this.anchorEl.appendChild(moderationBadgeSlot);
|
||||
}
|
||||
|
||||
root.appendChild(anchor);
|
||||
root.appendChild(moderationBadgeSlot);
|
||||
root.appendChild(content);
|
||||
|
||||
this.applyPlaybackDatasets();
|
||||
@@ -1632,18 +1646,24 @@ export class VideoCard {
|
||||
let slot = this.moderationBadgeSlot;
|
||||
const hiddenActive = context.activeHidden && !context.overrideActive;
|
||||
|
||||
if (!slot && this.root) {
|
||||
if (!slot && this.anchorEl) {
|
||||
slot = this.createElement("div", {
|
||||
classNames: ["video-card__moderation-slot", "px-md", "pt-md"],
|
||||
classNames: [
|
||||
"video-card__moderation-overlay",
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"p-4",
|
||||
"z-10",
|
||||
"pointer-events-none",
|
||||
],
|
||||
});
|
||||
slot.hidden = true;
|
||||
slot.setAttribute("aria-hidden", "true");
|
||||
this.moderationBadgeSlot = slot;
|
||||
if (this.anchorEl && this.anchorEl.parentElement === this.root) {
|
||||
this.root.insertBefore(slot, this.anchorEl.nextSibling);
|
||||
} else {
|
||||
this.root.appendChild(slot);
|
||||
}
|
||||
this.anchorEl.appendChild(slot);
|
||||
}
|
||||
|
||||
if (badge) {
|
||||
@@ -1724,6 +1744,8 @@ export class VideoCard {
|
||||
if (!badge) {
|
||||
const nextBadge = this.buildModerationBadge(context);
|
||||
if (nextBadge && slot) {
|
||||
nextBadge.style.pointerEvents = "auto"; // Enable interaction on badge itself
|
||||
nextBadge.classList.add("opacity-95"); // Slight transparency
|
||||
slot.appendChild(nextBadge);
|
||||
slot.hidden = false;
|
||||
slot.removeAttribute("aria-hidden");
|
||||
@@ -1732,6 +1754,8 @@ export class VideoCard {
|
||||
return;
|
||||
}
|
||||
|
||||
badge.style.pointerEvents = "auto";
|
||||
badge.classList.add("opacity-95");
|
||||
badge.dataset.variant = context.overrideActive ? "neutral" : "warning";
|
||||
const state = context.overrideActive
|
||||
? "override"
|
||||
@@ -1985,9 +2009,17 @@ export class VideoCard {
|
||||
this.contentEl.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
if (badgeSlot && this.moderationBadgeEl) {
|
||||
badgeSlot.hidden = false;
|
||||
badgeSlot.removeAttribute("aria-hidden");
|
||||
// In hidden state (blocked), the anchor/thumbnail is hidden, so overlay slot
|
||||
// won't be visible. We rely on the summary container below.
|
||||
// We hide the badge slot here because we don't want it floating if anchor is hidden
|
||||
// (though usually anchor hidden means children hidden too).
|
||||
// However, if we move badgeSlot to be a child of anchorEl, it will be hidden automatically.
|
||||
|
||||
if (badgeSlot) {
|
||||
// If it's a child of anchorEl, it's hidden by parent.
|
||||
// We can force it hidden to be safe.
|
||||
badgeSlot.hidden = true;
|
||||
badgeSlot.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
const summaryContainer = this.ensureHiddenSummaryContainer();
|
||||
@@ -2008,7 +2040,11 @@ export class VideoCard {
|
||||
this.contentEl.removeAttribute("hidden");
|
||||
this.contentEl.removeAttribute("aria-hidden");
|
||||
}
|
||||
if (badgeSlot && !this.moderationBadgeEl) {
|
||||
// Restore badge slot visibility if badge exists and we are not hidden
|
||||
if (badgeSlot && this.moderationBadgeEl) {
|
||||
badgeSlot.hidden = false;
|
||||
badgeSlot.removeAttribute("aria-hidden");
|
||||
} else if (badgeSlot) {
|
||||
badgeSlot.hidden = true;
|
||||
badgeSlot.setAttribute("aria-hidden", "true");
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ function setupDom(t) {
|
||||
"ResizeObserver",
|
||||
"requestAnimationFrame",
|
||||
"cancelAnimationFrame",
|
||||
"navigator",
|
||||
];
|
||||
|
||||
keys.forEach((key) => {
|
||||
@@ -55,6 +54,14 @@ function setupDom(t) {
|
||||
globalThis[key] = window[key];
|
||||
});
|
||||
|
||||
// Mock navigator safely
|
||||
const originalNavigator = globalThis.navigator;
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
value: window.navigator,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
if (!window.requestAnimationFrame) {
|
||||
window.requestAnimationFrame = (cb) => setTimeout(() => cb(Date.now()), 16);
|
||||
globalThis.requestAnimationFrame = window.requestAnimationFrame;
|
||||
@@ -85,6 +92,12 @@ function setupDom(t) {
|
||||
globalThis[key] = previousValue;
|
||||
}
|
||||
});
|
||||
// Restore navigator
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
value: originalNavigator,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
return { window, document: window.document };
|
||||
|
||||
Reference in new issue
Block a user