mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 16:32:28 +00:00
356 lines
11 KiB
HTML
356 lines
11 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Moderation Fixtures • bitvid</title>
|
|
<base href="/" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="stylesheet" href="/css/tailwind.generated.css" />
|
|
<style>
|
|
body {
|
|
background-color: #0f172a;
|
|
color: #f8fafc;
|
|
font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
}
|
|
main {
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
padding: 2rem 1.5rem 4rem;
|
|
}
|
|
h1 {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
section {
|
|
margin-bottom: 2.5rem;
|
|
}
|
|
section h2 {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
section p {
|
|
color: #cbd5f5;
|
|
margin-bottom: 1rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body data-ds="new">
|
|
<main id="moderationFixtureRoot">
|
|
<h1>Moderation Test Fixtures</h1>
|
|
<p>
|
|
These scenarios mirror the QA vectors in <code>docs/moderation/testing.md</code> so
|
|
automated checks can assert blur thresholds, autoplay blocking, and local
|
|
override persistence.
|
|
</p>
|
|
</main>
|
|
<script type="module">
|
|
import { VideoCard } from "/js/ui/components/VideoCard.js";
|
|
|
|
const STORAGE_KEY = "bitvid:moderation:fixture-overrides";
|
|
const fixtures = [
|
|
{
|
|
key: "blur-threshold",
|
|
heading: "Blur threshold (≥3 trusted nudity reports)",
|
|
description:
|
|
"Card should be blurred and autoplay disabled until the viewer overrides it.",
|
|
video: {
|
|
id: "1".repeat(64),
|
|
title: "Fixture: Blurred Preview",
|
|
pubkey: "a".repeat(64),
|
|
thumbnail: "/assets/jpg/video-thumbnail-fallback.jpg",
|
|
},
|
|
moderation: {
|
|
blockAutoplay: true,
|
|
blurThumbnail: true,
|
|
trustedCount: 3,
|
|
reportType: "nudity",
|
|
},
|
|
reporters: [
|
|
{ name: "Jess F1", pubkey: "b".repeat(64), latest: 1_700_000_010 },
|
|
{ name: "Mira F1", pubkey: "c".repeat(64), latest: 1_700_000_020 },
|
|
{ name: "Omar F1", pubkey: "d".repeat(64), latest: 1_700_000_030 },
|
|
],
|
|
},
|
|
{
|
|
key: "autoplay-threshold",
|
|
heading: "Autoplay threshold (≥2 trusted nudity reports)",
|
|
description:
|
|
"Card should block autoplay but stay unblurred so users can inspect the thumbnail.",
|
|
video: {
|
|
id: "2".repeat(64),
|
|
title: "Fixture: Autoplay Blocked",
|
|
pubkey: "e".repeat(64),
|
|
thumbnail: "/assets/jpg/video-thumbnail-fallback.jpg",
|
|
},
|
|
moderation: {
|
|
blockAutoplay: true,
|
|
blurThumbnail: false,
|
|
trustedCount: 2,
|
|
reportType: "nudity",
|
|
},
|
|
reporters: [
|
|
{ name: "Jess F1", pubkey: "b".repeat(64), latest: 1_700_000_010 },
|
|
{ name: "Mira F1", pubkey: "c".repeat(64), latest: 1_700_000_020 },
|
|
],
|
|
},
|
|
{
|
|
key: "show-anyway",
|
|
heading: "\"Show anyway\" override (persists locally)",
|
|
description:
|
|
"Card starts blurred and blocked; activating \"Show anyway\" should persist across reloads.",
|
|
enableOverride: true,
|
|
video: {
|
|
id: "3".repeat(64),
|
|
title: "Fixture: Override Persistence",
|
|
pubkey: "f".repeat(64),
|
|
thumbnail: "/assets/jpg/video-thumbnail-fallback.jpg",
|
|
},
|
|
moderation: {
|
|
blockAutoplay: true,
|
|
blurThumbnail: true,
|
|
trustedCount: 3,
|
|
reportType: "nudity",
|
|
},
|
|
reporters: [
|
|
{ name: "Jess F1", pubkey: "b".repeat(64), latest: 1_700_000_010 },
|
|
{ name: "Mira F1", pubkey: "c".repeat(64), latest: 1_700_000_020 },
|
|
{ name: "Omar F1", pubkey: "d".repeat(64), latest: 1_700_000_030 },
|
|
],
|
|
},
|
|
{
|
|
key: "trusted-mute",
|
|
heading: "Trusted mute (F1 mute list)",
|
|
description:
|
|
"Card should explain that a trusted contact muted this author without enabling overrides.",
|
|
video: {
|
|
id: "4".repeat(64),
|
|
title: "Fixture: Trusted Mute",
|
|
pubkey: "aa".repeat(32),
|
|
thumbnail: "/assets/jpg/video-thumbnail-fallback.jpg",
|
|
},
|
|
moderation: {
|
|
blockAutoplay: false,
|
|
blurThumbnail: false,
|
|
trustedMuted: true,
|
|
trustedMuteCount: 1,
|
|
},
|
|
muters: [{ name: "Jess F1", pubkey: "b".repeat(64) }],
|
|
},
|
|
];
|
|
|
|
window.__MODERATION_FIXTURE_STORAGE_KEY__ = STORAGE_KEY;
|
|
|
|
function loadOverrides() {
|
|
try {
|
|
const raw = window.localStorage?.getItem(STORAGE_KEY);
|
|
if (!raw) {
|
|
return {};
|
|
}
|
|
const parsed = JSON.parse(raw);
|
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function persistOverrides(next) {
|
|
try {
|
|
window.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
} catch {
|
|
/* ignore persistence issues in test fixtures */
|
|
}
|
|
}
|
|
|
|
function buildSummary(fixture) {
|
|
if (!fixture?.moderation?.reportType) {
|
|
return null;
|
|
}
|
|
const latest = Array.isArray(fixture.reporters)
|
|
? fixture.reporters.reduce(
|
|
(max, reporter) =>
|
|
Number.isFinite(reporter?.latest)
|
|
? Math.max(max, Math.floor(reporter.latest))
|
|
: max,
|
|
0,
|
|
)
|
|
: 0;
|
|
return {
|
|
eventId: fixture.video.id,
|
|
totalTrusted: fixture.moderation.trustedCount,
|
|
types: {
|
|
[fixture.moderation.reportType]: {
|
|
trusted: fixture.moderation.trustedCount,
|
|
total: fixture.moderation.trustedCount,
|
|
latest,
|
|
},
|
|
},
|
|
updatedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
function buildModerationState(fixture, overrides, videoId) {
|
|
const overrideEntry = overrides?.[videoId];
|
|
const base = {
|
|
reportType: fixture.moderation.reportType,
|
|
trustedCount: fixture.moderation.trustedCount,
|
|
reporterDisplayNames: Array.isArray(fixture.reporters)
|
|
? fixture.reporters.map((reporter) => reporter.name)
|
|
: [],
|
|
reporterPubkeys: Array.isArray(fixture.reporters)
|
|
? fixture.reporters.map((reporter) => reporter.pubkey)
|
|
: [],
|
|
trustedReporters: Array.isArray(fixture.reporters)
|
|
? fixture.reporters.map((reporter) => ({
|
|
pubkey: reporter.pubkey,
|
|
latest: reporter.latest,
|
|
}))
|
|
: [],
|
|
original: {
|
|
blockAutoplay: fixture.moderation.blockAutoplay === true,
|
|
blurThumbnail: fixture.moderation.blurThumbnail === true,
|
|
},
|
|
};
|
|
|
|
const summary = buildSummary(fixture);
|
|
if (summary) {
|
|
base.summary = summary;
|
|
}
|
|
|
|
if (fixture.moderation.trustedMuted) {
|
|
base.trustedMuted = true;
|
|
const muterPubkeys = Array.isArray(fixture.muters)
|
|
? fixture.muters
|
|
.map((muter) => (typeof muter?.pubkey === "string" ? muter.pubkey : ""))
|
|
.filter(Boolean)
|
|
: [];
|
|
base.trustedMuters = muterPubkeys;
|
|
const muteCount = Number.isFinite(fixture.moderation.trustedMuteCount)
|
|
? Math.max(0, Math.floor(fixture.moderation.trustedMuteCount))
|
|
: muterPubkeys.length;
|
|
base.trustedMuteCount = muteCount;
|
|
base.trustedMuterDisplayNames = Array.isArray(fixture.muters)
|
|
? fixture.muters
|
|
.map((muter) => (typeof muter?.name === "string" ? muter.name : ""))
|
|
.filter(Boolean)
|
|
: [];
|
|
}
|
|
|
|
if (overrideEntry?.showAnyway) {
|
|
base.viewerOverride = {
|
|
showAnyway: true,
|
|
updatedAt: Number.isFinite(overrideEntry.updatedAt)
|
|
? Math.floor(overrideEntry.updatedAt)
|
|
: Date.now(),
|
|
};
|
|
base.blockAutoplay = false;
|
|
base.blurThumbnail = false;
|
|
} else {
|
|
base.blockAutoplay = fixture.moderation.blockAutoplay === true;
|
|
base.blurThumbnail = fixture.moderation.blurThumbnail === true;
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
function setOverride(overrides, videoId, entry) {
|
|
const next = { ...overrides };
|
|
next[videoId] = {
|
|
...entry,
|
|
updatedAt: Number.isFinite(entry?.updatedAt)
|
|
? Math.floor(entry.updatedAt)
|
|
: Date.now(),
|
|
};
|
|
persistOverrides(next);
|
|
return next;
|
|
}
|
|
|
|
function createCard(fixture, overrides) {
|
|
const video = {
|
|
...fixture.video,
|
|
};
|
|
video.moderation = buildModerationState(fixture, overrides, video.id);
|
|
|
|
const card = new VideoCard({
|
|
document,
|
|
video,
|
|
shareUrl: "#",
|
|
formatters: {
|
|
formatTimeAgo: () => "just now",
|
|
formatNumber: (value) => {
|
|
if (typeof value === "number") {
|
|
return value.toLocaleString("en-US");
|
|
}
|
|
return String(value ?? "");
|
|
},
|
|
},
|
|
helpers: {
|
|
isMagnetSupported: () => true,
|
|
escapeHtml: (value) => String(value ?? ""),
|
|
toLocaleString: (value, locales, options) => {
|
|
if (value && typeof value.toLocaleString === "function") {
|
|
return value.toLocaleString(locales || "en-US", options);
|
|
}
|
|
return String(value ?? "");
|
|
},
|
|
},
|
|
assets: {
|
|
fallbackThumbnailSrc: "/assets/jpg/video-thumbnail-fallback.jpg",
|
|
},
|
|
designSystem: {
|
|
getMode: () => "new",
|
|
isNew: () => true,
|
|
},
|
|
});
|
|
|
|
if (fixture.enableOverride) {
|
|
card.onModerationOverride = () => {
|
|
overrides = setOverride(overrides, video.id, { showAnyway: true });
|
|
card.video.moderation = buildModerationState(
|
|
fixture,
|
|
overrides,
|
|
video.id,
|
|
);
|
|
card.refreshModerationUi();
|
|
return true;
|
|
};
|
|
}
|
|
|
|
const root = card.getRoot();
|
|
root.dataset.testId = fixture.key;
|
|
return { card, root };
|
|
}
|
|
|
|
function renderFixtures() {
|
|
const root = document.getElementById("moderationFixtureRoot");
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
let overrides = loadOverrides();
|
|
|
|
fixtures.forEach((fixture) => {
|
|
const section = document.createElement("section");
|
|
|
|
const heading = document.createElement("h2");
|
|
heading.textContent = fixture.heading;
|
|
section.appendChild(heading);
|
|
|
|
if (fixture.description) {
|
|
const description = document.createElement("p");
|
|
description.textContent = fixture.description;
|
|
section.appendChild(description);
|
|
}
|
|
|
|
const { root: cardRoot } = createCard(fixture, overrides);
|
|
section.appendChild(cardRoot);
|
|
root.appendChild(section);
|
|
});
|
|
}
|
|
|
|
renderFixtures();
|
|
</script>
|
|
</body>
|
|
</html>
|