mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Expand video modal moderation tests
This commit is contained in:
1 parent
13e3963072
commit
5b65d51262
5 files changed
+655
-1
No files matched your search
@@ -59,7 +59,50 @@
|
||||
class="video-modal__ambient-canvas"
|
||||
></canvas>
|
||||
</div>
|
||||
<div class="video-modal__video">
|
||||
<div class="video-modal__video" data-video-surface>
|
||||
<div
|
||||
class="video-modal__moderation-overlay"
|
||||
data-moderation-overlay
|
||||
hidden
|
||||
>
|
||||
<div
|
||||
class="moderation-badge flex flex-wrap items-center gap-sm"
|
||||
data-moderation-badge="true"
|
||||
data-variant="warning"
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
<span class="moderation-badge__icon" aria-hidden="true">
|
||||
<svg
|
||||
class="moderation-badge__icon-mark"
|
||||
viewBox="0 0 20 20"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
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"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="moderation-badge__text whitespace-nowrap"
|
||||
data-moderation-text
|
||||
>
|
||||
Content reported by trusted contacts
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="moderation-badge__action flex-shrink-0"
|
||||
data-moderation-action="override"
|
||||
>
|
||||
Show anyway
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<video
|
||||
id="modalVideo"
|
||||
class="video-modal__media"
|
||||
|
||||
@@ -1438,6 +1438,36 @@
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#playerModal .video-modal__video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#playerModal .video-modal__video[data-visual-state="blurred"] .video-modal__media {
|
||||
@apply transition duration-base ease-in-out;
|
||||
filter: blur(var(--blur-thumbnail-blur));
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
#playerModal .video-modal__moderation-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: var(--z-modal-content);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: clamp(var(--space-sm), 3vw, var(--space-xl));
|
||||
background: color-mix(in srgb, var(--color-overlay-strong) 62%, transparent);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#playerModal .video-modal__moderation-overlay[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#playerModal .video-modal__moderation-overlay .moderation-badge {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.card[data-owner-is-viewer="true"][data-url-health-state="offline"][data-stream-health-state="unhealthy"] {
|
||||
@apply border border-critical border-opacity-40 bg-card-critical;
|
||||
}
|
||||
|
||||
@@ -848,6 +848,38 @@ class Application {
|
||||
"video:share",
|
||||
this.boundVideoModalShareHandler
|
||||
);
|
||||
this.boundVideoModalModerationOverrideHandler = (event) => {
|
||||
const detail = event?.detail || {};
|
||||
const targetVideo =
|
||||
detail && typeof detail.video === "object"
|
||||
? detail.video
|
||||
: this.currentVideo || null;
|
||||
if (!targetVideo) {
|
||||
const trigger = detail?.trigger;
|
||||
if (trigger) {
|
||||
trigger.disabled = false;
|
||||
trigger.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const handled = this.handleModerationOverride({
|
||||
video: targetVideo,
|
||||
card: detail?.card || null,
|
||||
});
|
||||
|
||||
if (handled === false) {
|
||||
const trigger = detail?.trigger;
|
||||
if (trigger) {
|
||||
trigger.disabled = false;
|
||||
trigger.removeAttribute("aria-busy");
|
||||
}
|
||||
}
|
||||
};
|
||||
this.videoModal.addEventListener(
|
||||
"video:moderation-override",
|
||||
this.boundVideoModalModerationOverrideHandler,
|
||||
);
|
||||
this.boundVideoModalTagActivateHandler = (event) => {
|
||||
const detail = event?.detail || {};
|
||||
const nativeEvent = detail?.nativeEvent || null;
|
||||
@@ -7473,6 +7505,24 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
const doc =
|
||||
(this.videoModal && this.videoModal.document) ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
if (doc && typeof doc.dispatchEvent === "function") {
|
||||
try {
|
||||
doc.dispatchEvent(
|
||||
new CustomEvent("video:moderation-override", {
|
||||
detail: { video: target },
|
||||
}),
|
||||
);
|
||||
} catch (eventError) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to dispatch moderation override event:",
|
||||
eventError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7528,6 +7578,24 @@ class Application {
|
||||
}
|
||||
}
|
||||
|
||||
const doc =
|
||||
(this.videoModal && this.videoModal.document) ||
|
||||
(typeof document !== "undefined" ? document : null);
|
||||
if (doc && typeof doc.dispatchEvent === "function") {
|
||||
try {
|
||||
doc.dispatchEvent(
|
||||
new CustomEvent("video:moderation-hide", {
|
||||
detail: { video: target },
|
||||
}),
|
||||
);
|
||||
} catch (eventError) {
|
||||
devLogger.warn(
|
||||
"[Application] Failed to dispatch moderation hide event:",
|
||||
eventError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -10625,6 +10693,13 @@ class Application {
|
||||
);
|
||||
this.boundVideoModalShareHandler = null;
|
||||
}
|
||||
if (this.boundVideoModalModerationOverrideHandler) {
|
||||
this.videoModal.removeEventListener(
|
||||
"video:moderation-override",
|
||||
this.boundVideoModalModerationOverrideHandler,
|
||||
);
|
||||
this.boundVideoModalModerationOverrideHandler = null;
|
||||
}
|
||||
if (this.boundVideoModalTagActivateHandler) {
|
||||
this.videoModal.removeEventListener(
|
||||
"tag:activate",
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
unsubscribeFromVideoViewCount,
|
||||
formatViewCount,
|
||||
} from "../../viewCounter.js";
|
||||
import { normalizeVideoModerationContext } from "../moderationUiHelpers.js";
|
||||
import {
|
||||
formatAbsoluteTimestamp,
|
||||
formatTimeAgo,
|
||||
@@ -99,6 +100,7 @@ export class VideoModal {
|
||||
this.modalBackdrop = null;
|
||||
this.scrollRegion = null;
|
||||
this.modalVideo = null;
|
||||
this.videoStage = null;
|
||||
this.modalStatus = null;
|
||||
this.modalProgress = null;
|
||||
this.modalProgressStatus = null;
|
||||
@@ -234,19 +236,43 @@ export class VideoModal {
|
||||
this.detachAmbientBackground = null;
|
||||
|
||||
this.activeVideo = null;
|
||||
this.activeModerationContext = null;
|
||||
this.tagPreferenceStateResolver = null;
|
||||
|
||||
this.moderationOverlay = null;
|
||||
this.moderationBadge = null;
|
||||
this.moderationBadgeText = null;
|
||||
this.moderationOverrideButton = null;
|
||||
this.moderationBadgeId = "";
|
||||
|
||||
this.handleCopyRequest = this.handleCopyRequest.bind(this);
|
||||
this.handleShareRequest = this.handleShareRequest.bind(this);
|
||||
this.handleCreatorNavigation = this.handleCreatorNavigation.bind(this);
|
||||
this.handleModalMoreButtonClick = this.handleModalMoreButtonClick.bind(this);
|
||||
this.handleReactionClick = this.handleReactionClick.bind(this);
|
||||
this.handleVideoTagActivate = this.handleVideoTagActivate.bind(this);
|
||||
this.handleModerationOverrideClick =
|
||||
this.handleModerationOverrideClick.bind(this);
|
||||
this.handleGlobalModerationOverride =
|
||||
this.handleGlobalModerationOverride.bind(this);
|
||||
this.handleGlobalModerationHide =
|
||||
this.handleGlobalModerationHide.bind(this);
|
||||
|
||||
this.MODAL_LOADING_POSTER = "assets/gif/please-stand-by.gif";
|
||||
this.DEFAULT_PROFILE_AVATAR = "assets/svg/default-profile.svg";
|
||||
|
||||
this.setMediaLoader(mediaLoader);
|
||||
|
||||
if (this.document) {
|
||||
this.document.addEventListener(
|
||||
"video:moderation-override",
|
||||
this.handleGlobalModerationOverride,
|
||||
);
|
||||
this.document.addEventListener(
|
||||
"video:moderation-hide",
|
||||
this.handleGlobalModerationHide,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log(message, ...args) {
|
||||
@@ -401,6 +427,17 @@ export class VideoModal {
|
||||
|
||||
this.teardownAmbientGlow({ clear: false });
|
||||
|
||||
if (this.moderationOverrideButton) {
|
||||
this.moderationOverrideButton.removeEventListener(
|
||||
"click",
|
||||
this.handleModerationOverrideClick,
|
||||
);
|
||||
}
|
||||
this.moderationOverlay = null;
|
||||
this.moderationBadge = null;
|
||||
this.moderationBadgeText = null;
|
||||
this.moderationOverrideButton = null;
|
||||
|
||||
const previousScrollRegion = this.scrollRegion;
|
||||
if (previousScrollRegion && this.modalNavScrollHandler) {
|
||||
previousScrollRegion.removeEventListener(
|
||||
@@ -483,6 +520,20 @@ export class VideoModal {
|
||||
: [];
|
||||
this.videoTagsData = null;
|
||||
|
||||
this.videoStage = playerModal.querySelector(".video-modal__video") || null;
|
||||
this.moderationOverlay =
|
||||
this.videoStage?.querySelector("[data-moderation-overlay]") || null;
|
||||
this.moderationBadge =
|
||||
this.moderationOverlay?.querySelector("[data-moderation-badge='true']") || null;
|
||||
this.moderationBadgeText =
|
||||
this.moderationOverlay?.querySelector("[data-moderation-text]") || null;
|
||||
const overrideButton =
|
||||
this.moderationOverlay?.querySelector("[data-moderation-action='override']") || null;
|
||||
if (overrideButton) {
|
||||
overrideButton.addEventListener("click", this.handleModerationOverrideClick);
|
||||
this.moderationOverrideButton = overrideButton;
|
||||
}
|
||||
|
||||
this.modalVideo = playerModal.querySelector("#modalVideo") || null;
|
||||
this.modalStatus = playerModal.querySelector("#modalStatus") || null;
|
||||
this.modalProgress = playerModal.querySelector("#modalProgress") || null;
|
||||
@@ -569,6 +620,8 @@ export class VideoModal {
|
||||
this.setupModalZapPopover();
|
||||
this.setupModalMorePopover();
|
||||
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
|
||||
this.detachCommentEventHandlers();
|
||||
|
||||
const commentsRoot =
|
||||
@@ -2021,6 +2074,30 @@ export class VideoModal {
|
||||
this.removeCommentRetryButton();
|
||||
this.invokeCommentTeardown();
|
||||
|
||||
if (this.document) {
|
||||
this.document.removeEventListener(
|
||||
"video:moderation-override",
|
||||
this.handleGlobalModerationOverride,
|
||||
);
|
||||
this.document.removeEventListener(
|
||||
"video:moderation-hide",
|
||||
this.handleGlobalModerationHide,
|
||||
);
|
||||
}
|
||||
|
||||
if (this.moderationOverrideButton) {
|
||||
this.moderationOverrideButton.removeEventListener(
|
||||
"click",
|
||||
this.handleModerationOverrideClick,
|
||||
);
|
||||
}
|
||||
this.moderationOverlay = null;
|
||||
this.moderationBadge = null;
|
||||
this.moderationBadgeText = null;
|
||||
this.moderationOverrideButton = null;
|
||||
this.moderationBadgeId = "";
|
||||
this.activeModerationContext = null;
|
||||
|
||||
this.commentProfiles = new Map();
|
||||
this.commentNodes = new Map();
|
||||
this.commentChildLists = new Map();
|
||||
@@ -2638,6 +2715,7 @@ export class VideoModal {
|
||||
|
||||
open(video, options = {}) {
|
||||
this.activeVideo = video || null;
|
||||
this.moderationBadgeId = "";
|
||||
if (!this.playerModal) {
|
||||
return;
|
||||
}
|
||||
@@ -2656,10 +2734,12 @@ export class VideoModal {
|
||||
}
|
||||
this.setGlobalModalState("player", true);
|
||||
this.applyLoadingPoster();
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
}
|
||||
|
||||
close() {
|
||||
this.activeVideo = null;
|
||||
this.applyModerationOverlay(null);
|
||||
if (this.playerModal) {
|
||||
this.playerModal.classList.add("hidden");
|
||||
this.playerModal.setAttribute("hidden", "");
|
||||
@@ -3794,6 +3874,300 @@ export class VideoModal {
|
||||
if (creator !== undefined) {
|
||||
this.updateCreator(creator);
|
||||
}
|
||||
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
}
|
||||
|
||||
getModerationBadgeId() {
|
||||
if (this.moderationBadgeId) {
|
||||
return this.moderationBadgeId;
|
||||
}
|
||||
|
||||
const baseId =
|
||||
typeof this.activeVideo?.id === "string" && this.activeVideo.id
|
||||
? this.activeVideo.id
|
||||
: "";
|
||||
if (!baseId) {
|
||||
this.moderationBadgeId = "";
|
||||
return this.moderationBadgeId;
|
||||
}
|
||||
|
||||
const sanitized = baseId.replace(/[^a-zA-Z0-9_-]/g, "");
|
||||
if (!sanitized) {
|
||||
this.moderationBadgeId = "";
|
||||
return this.moderationBadgeId;
|
||||
}
|
||||
|
||||
this.moderationBadgeId = `video-modal-${sanitized}-moderation`;
|
||||
return this.moderationBadgeId;
|
||||
}
|
||||
|
||||
buildModerationReasonText(context) {
|
||||
if (!context) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const reasons = [];
|
||||
|
||||
if (context.trustedMuted) {
|
||||
const muteCount = Math.max(1, Number(context.trustedMuteCount) || 0);
|
||||
const muteLabel = muteCount === 1 ? "trusted contact" : "trusted contacts";
|
||||
reasons.push(
|
||||
`muted by ${muteCount === 1 ? "a" : muteCount} ${muteLabel}`,
|
||||
);
|
||||
}
|
||||
|
||||
const typeLabel = context.friendlyType || "this video";
|
||||
const reportCount = Math.max(0, Number(context.trustedCount) || 0);
|
||||
if (reportCount > 0) {
|
||||
const friendLabel = reportCount === 1 ? "friend" : "friends";
|
||||
reasons.push(`${reportCount} ${friendLabel} reported ${typeLabel}`);
|
||||
} else if (!context.trustedMuted) {
|
||||
reasons.push(
|
||||
context.friendlyType ? `reports of ${typeLabel}` : "reports",
|
||||
);
|
||||
}
|
||||
|
||||
if (!reasons.length) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const combined = reasons.join(" · ");
|
||||
return combined.charAt(0).toUpperCase() + combined.slice(1);
|
||||
}
|
||||
|
||||
buildModerationBadgeText(context) {
|
||||
if (!context) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const blurSource = (context.originalBlurReason || context.blurReason || "")
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
const suppressBlurLabel =
|
||||
blurSource === "trusted-mute" || blurSource === "trusted-mute-hide";
|
||||
|
||||
const parts = [];
|
||||
if (!suppressBlurLabel) {
|
||||
parts.push("Blurred");
|
||||
}
|
||||
|
||||
const reason = this.buildModerationReasonText(context);
|
||||
if (reason) {
|
||||
parts.push(reason);
|
||||
}
|
||||
|
||||
if (!parts.length) {
|
||||
return "Blurred due to reports";
|
||||
}
|
||||
|
||||
return parts.join(" · ");
|
||||
}
|
||||
|
||||
refreshActiveVideoModeration({ video = this.activeVideo } = {}) {
|
||||
if (!video) {
|
||||
this.activeModerationContext = null;
|
||||
this.applyModerationOverlay(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const context = normalizeVideoModerationContext(video.moderation);
|
||||
this.activeModerationContext = context;
|
||||
this.applyModerationOverlay(context);
|
||||
}
|
||||
|
||||
applyModerationOverlay(context) {
|
||||
const stage = this.videoStage;
|
||||
const overlay = this.moderationOverlay;
|
||||
const badge = this.moderationBadge;
|
||||
const textEl = this.moderationBadgeText;
|
||||
const button = this.moderationOverrideButton;
|
||||
|
||||
const shouldBlur = Boolean(context?.activeBlur && !context?.overrideActive);
|
||||
|
||||
if (!shouldBlur) {
|
||||
if (stage && stage.dataset.visualState === "blurred") {
|
||||
delete stage.dataset.visualState;
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.setAttribute("hidden", "");
|
||||
}
|
||||
if (badge) {
|
||||
badge.dataset.variant = context?.overrideActive ? "neutral" : "warning";
|
||||
if (badge.dataset.moderationState) {
|
||||
delete badge.dataset.moderationState;
|
||||
}
|
||||
badge.removeAttribute("title");
|
||||
if (badge.id && !this.activeVideo) {
|
||||
badge.removeAttribute("id");
|
||||
}
|
||||
if (badge.hasAttribute("aria-label")) {
|
||||
badge.removeAttribute("aria-label");
|
||||
}
|
||||
}
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (stage) {
|
||||
stage.dataset.visualState = "blurred";
|
||||
}
|
||||
|
||||
if (overlay) {
|
||||
overlay.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
if (this.modalVideo && typeof this.modalVideo.pause === "function") {
|
||||
try {
|
||||
this.modalVideo.pause();
|
||||
} catch (error) {
|
||||
// ignore pause failures (e.g., in unsupported environments)
|
||||
}
|
||||
}
|
||||
|
||||
if (badge) {
|
||||
const state = context?.trustedMuted ? "trusted-mute" : "blocked";
|
||||
badge.dataset.variant = "warning";
|
||||
badge.dataset.moderationState = state;
|
||||
|
||||
const badgeId = this.getModerationBadgeId();
|
||||
if (badgeId) {
|
||||
badge.id = badgeId;
|
||||
}
|
||||
|
||||
const textContent = this.buildModerationBadgeText(context);
|
||||
if (textEl) {
|
||||
textEl.textContent = textContent;
|
||||
}
|
||||
|
||||
const muteNames = Array.isArray(context.trustedMuteDisplayNames)
|
||||
? context.trustedMuteDisplayNames
|
||||
.map((name) => (typeof name === "string" ? name.trim() : ""))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const reporterNames = Array.isArray(context.reporterDisplayNames)
|
||||
? context.reporterDisplayNames
|
||||
.map((name) => (typeof name === "string" ? name.trim() : ""))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const allNames = [...muteNames, ...reporterNames];
|
||||
const uniqueNames = [];
|
||||
const seenNameKeys = new Set();
|
||||
for (const name of allNames) {
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
const key = name.toLowerCase();
|
||||
if (seenNameKeys.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seenNameKeys.add(key);
|
||||
uniqueNames.push(name);
|
||||
}
|
||||
|
||||
if (uniqueNames.length) {
|
||||
const joined = uniqueNames.join(", ");
|
||||
const hasMuted = muteNames.length > 0;
|
||||
const hasReporters = reporterNames.length > 0;
|
||||
const prefix = hasMuted && hasReporters
|
||||
? "Muted/Reported by"
|
||||
: hasMuted
|
||||
? "Muted by"
|
||||
: "Reported by";
|
||||
badge.title = `${prefix} ${joined}`;
|
||||
const baseLabel = textEl?.textContent?.trim() || textContent || "";
|
||||
if (baseLabel) {
|
||||
badge.setAttribute("aria-label", `${baseLabel}. ${prefix} ${joined}.`);
|
||||
} else {
|
||||
badge.setAttribute("aria-label", `${prefix} ${joined}.`);
|
||||
}
|
||||
} else {
|
||||
badge.removeAttribute("title");
|
||||
const baseLabel = textEl?.textContent?.trim();
|
||||
if (baseLabel) {
|
||||
badge.setAttribute("aria-label", `${baseLabel}.`);
|
||||
} else {
|
||||
badge.removeAttribute("aria-label");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
const badgeId = this.getModerationBadgeId();
|
||||
if (badgeId) {
|
||||
button.setAttribute("aria-describedby", badgeId);
|
||||
} else {
|
||||
button.removeAttribute("aria-describedby");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleModerationOverrideClick(event) {
|
||||
event?.preventDefault?.();
|
||||
event?.stopPropagation?.();
|
||||
|
||||
const button = event?.currentTarget || this.moderationOverrideButton;
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.setAttribute("aria-busy", "true");
|
||||
}
|
||||
|
||||
if (!this.activeVideo) {
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = {
|
||||
video: this.activeVideo,
|
||||
event,
|
||||
trigger: button || null,
|
||||
context: this.activeModerationContext || null,
|
||||
};
|
||||
|
||||
const handled = this.dispatch("video:moderation-override", detail);
|
||||
if (!handled && button) {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
}
|
||||
|
||||
handleGlobalModerationOverride(event) {
|
||||
const detail = event?.detail || {};
|
||||
const video = detail && typeof detail === "object" ? detail.video : null;
|
||||
if (!video || !video.id || !this.activeVideo || this.activeVideo.id !== video.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeVideo !== video) {
|
||||
this.activeVideo = video;
|
||||
this.moderationBadgeId = "";
|
||||
}
|
||||
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
}
|
||||
|
||||
handleGlobalModerationHide(event) {
|
||||
const detail = event?.detail || {};
|
||||
const video = detail && typeof detail === "object" ? detail.video : null;
|
||||
if (!video || !video.id || !this.activeVideo || this.activeVideo.id !== video.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeVideo !== video) {
|
||||
this.activeVideo = video;
|
||||
this.moderationBadgeId = "";
|
||||
}
|
||||
|
||||
this.refreshActiveVideoModeration({ video: this.activeVideo });
|
||||
}
|
||||
|
||||
renderVideoDescription(description) {
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { setupModal } from "./video-modal-accessibility.test.mjs";
|
||||
|
||||
test("VideoModal blurs and restores playback when moderation overlay toggles", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
const document = modal.document;
|
||||
const window = document.defaultView;
|
||||
|
||||
try {
|
||||
await t.test("trusted reports blur the active video", async () => {
|
||||
const videoElement = modal.getVideoElement();
|
||||
assert.ok(videoElement, "modal video element should exist");
|
||||
|
||||
let pauseCalls = 0;
|
||||
const originalPause = videoElement.pause;
|
||||
videoElement.pause = function patchedPause() {
|
||||
pauseCalls += 1;
|
||||
if (typeof originalPause === "function") {
|
||||
return originalPause.call(this);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const video = {
|
||||
id: "event-trusted-blur",
|
||||
title: "Blurred video",
|
||||
moderation: {
|
||||
blurThumbnail: true,
|
||||
trustedCount: 3,
|
||||
reporterDisplayNames: ["Alice", "Bob"],
|
||||
summary: {
|
||||
types: {
|
||||
nudity: { trusted: 3 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
modal.open(video);
|
||||
|
||||
const stage = document.querySelector(".video-modal__video");
|
||||
assert.ok(stage, "video stage should exist");
|
||||
assert.equal(stage.dataset.visualState, "blurred");
|
||||
|
||||
const overlay = document.querySelector("[data-moderation-overlay]");
|
||||
assert.ok(overlay, "moderation overlay should be rendered");
|
||||
assert.equal(overlay.hasAttribute("hidden"), false);
|
||||
|
||||
assert.ok(pauseCalls > 0, "video pause should be invoked when blur is applied");
|
||||
|
||||
const textEl = overlay.querySelector("[data-moderation-text]");
|
||||
assert.ok(textEl, "moderation text element should exist");
|
||||
assert.match(
|
||||
textEl.textContent,
|
||||
/blurred/i,
|
||||
"overlay text should describe the blur state",
|
||||
);
|
||||
|
||||
let overrideEvent = null;
|
||||
modal.addEventListener("video:moderation-override", (event) => {
|
||||
overrideEvent = event;
|
||||
});
|
||||
|
||||
const showButton = overlay.querySelector(
|
||||
"[data-moderation-action='override']",
|
||||
);
|
||||
assert.ok(showButton, "Show anyway button should be present");
|
||||
showButton.click();
|
||||
|
||||
assert.ok(overrideEvent, "clicking the CTA should emit override event");
|
||||
assert.equal(overrideEvent.detail.video, video);
|
||||
|
||||
video.moderation.blurThumbnail = false;
|
||||
video.moderation.viewerOverride = { showAnyway: true };
|
||||
|
||||
const overrideSignal = new window.CustomEvent("video:moderation-override", {
|
||||
detail: { video },
|
||||
});
|
||||
document.dispatchEvent(overrideSignal);
|
||||
|
||||
assert.equal(stage.dataset.visualState, undefined);
|
||||
assert.equal(overlay.hasAttribute("hidden"), true);
|
||||
|
||||
delete video.moderation.viewerOverride;
|
||||
video.moderation.blurThumbnail = true;
|
||||
|
||||
const hideSignal = new window.CustomEvent("video:moderation-hide", {
|
||||
detail: { video },
|
||||
});
|
||||
document.dispatchEvent(hideSignal);
|
||||
|
||||
assert.equal(stage.dataset.visualState, "blurred");
|
||||
assert.equal(overlay.hasAttribute("hidden"), false);
|
||||
|
||||
videoElement.pause = originalPause;
|
||||
modal.close();
|
||||
});
|
||||
|
||||
await t.test("trusted mute blur state matches moderation context", async () => {
|
||||
const video = {
|
||||
id: "event-trusted-mute",
|
||||
title: "Trusted mute video",
|
||||
moderation: {
|
||||
blurThumbnail: true,
|
||||
trustedMuted: true,
|
||||
trustedMuteCount: 2,
|
||||
trustedMuteDisplayNames: ["Carol", "Dave"],
|
||||
},
|
||||
};
|
||||
|
||||
modal.open(video);
|
||||
|
||||
const stage = document.querySelector(".video-modal__video");
|
||||
assert.ok(stage, "video stage should exist");
|
||||
assert.equal(stage.dataset.visualState, "blurred");
|
||||
|
||||
const overlay = document.querySelector("[data-moderation-overlay]");
|
||||
assert.ok(overlay, "moderation overlay should be rendered");
|
||||
assert.equal(overlay.hasAttribute("hidden"), false);
|
||||
|
||||
const badge = overlay.querySelector("[data-moderation-badge='true']");
|
||||
assert.ok(badge, "moderation badge should exist");
|
||||
assert.equal(badge.dataset.moderationState, "trusted-mute");
|
||||
|
||||
modal.close();
|
||||
});
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user