Add "Always show creator" overlay button for account-level WoT override

Adds a second entry point (alongside the Safety-tab npub input) for the
per-author moderation override. On blurred/warned video surfaces, an
"Always show creator" button now sits beside "Show anyway". Clicking it
dispatches a `video:trust-author` document CustomEvent; the single app-level
listener in applicationBootstrap.js applies the account-level override and
refreshes affected videos, clearing the web-of-trust warning for every video
from that creator until reset.

Wired into VideoCard, VideoModal (persistent reused button + teardown
cleanup), and SimilarContentCard.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-07-08 17:01:49 -04:00
1 parent a0131c718e
commit 5844a36a54
4 files changed
+188

No files matched your search

+18
View File
@@ -1122,6 +1122,24 @@ export default class ApplicationBootstrap {
),
);
}
// #24: "Always show this creator" overlay button → set the account-level
// web-of-trust override for that author + refresh the feed. Fired by the
// moderation badge on video cards / the player / similar cards.
if (typeof document !== "undefined") {
const onTrustAuthor = (event) => {
const pubkey =
typeof event?.detail?.pubkey === "string" ? event.detail.pubkey : "";
if (!pubkey) {
return;
}
setAuthorModerationOverride(pubkey, { showAnyway: true });
notifyAuthorModerationOverrideChanged(app, pubkey);
};
document.addEventListener("video:trust-author", onTrustAuthor);
nostrUnsubscribes.push(() =>
document.removeEventListener("video:trust-author", onTrustAuthor),
);
}
const submissionsPollId = setInterval(() => {
void app.refreshSubmissionsIndicator({ reason: "poll" });
}, 180000);
+40
View File
@@ -134,6 +134,8 @@ export class SimilarContentCard {
this.handleModerationHideClick(event);
this.boundModerationBlockHandler = (event) =>
this.handleModerationBlockClick(event);
this.boundTrustAuthorHandler = (event) =>
this.handleTrustAuthorClick(event);
this.build();
}
@@ -524,6 +526,43 @@ export class SimilarContentCard {
return button;
}
createModerationTrustAuthorButton() {
const button = this.document.createElement("button");
button.classList.add(
"moderation-badge__action",
"flex-shrink-0",
"text-xs",
"py-1",
"px-2"
);
button.type = "button";
button.dataset.moderationAction = "trust-author";
button.setAttribute("aria-label", "Always show videos from this creator");
button.textContent = "Always show creator";
button.addEventListener("click", this.boundTrustAuthorHandler);
button.classList.add("pointer-events-auto");
return button;
}
handleTrustAuthorClick(event) {
event?.preventDefault?.();
event?.stopPropagation?.();
const pubkey =
this.video?.pubkey || this.video?.author?.pubkey || "";
if (!pubkey || typeof document === "undefined") {
return;
}
try {
document.dispatchEvent(
new CustomEvent("video:trust-author", { detail: { pubkey } })
);
} catch (error) {
// Non-fatal: trust-author is a convenience action.
}
}
handleShowAnywayClick(event) {
event?.preventDefault?.();
event?.stopPropagation?.();
@@ -720,6 +759,7 @@ export class SimilarContentCard {
actions.appendChild(showButton);
this.moderationActionButton = showButton;
this.moderationActionButtonMode = "override";
actions.appendChild(this.createModerationTrustAuthorButton());
}
}
+41
View File
@@ -156,6 +156,8 @@ export class VideoCard {
this.moderationBadgeSlot = null;
this.hiddenSummaryEl = null;
this.boundShowAnywayHandler = (event) => this.handleShowAnywayClick(event);
this.boundTrustAuthorHandler = (event) =>
this.handleTrustAuthorClick(event);
this.boundModerationBlockHandler = (event) =>
this.handleModerationBlockClick(event);
this.boundModerationHideHandler = (event) =>
@@ -1210,6 +1212,44 @@ export class VideoCard {
return button;
}
// Account-level ("trusted creator") action shown alongside "Show anyway":
// turns off the web-of-trust warning for EVERY video by this creator. Fires a
// document event that the app handles (sets the per-author override + refresh)
// — no per-renderer callback plumbing needed.
createModerationTrustAuthorButton() {
const button = this.createElement("button", {
classNames: ["moderation-badge__action", "flex-shrink-0"],
attrs: {
type: "button",
"data-moderation-action": "trust-author",
"aria-describedby": this.getModerationBadgeId(),
"aria-label": "Always show videos from this creator",
},
textContent: "Always show creator",
});
button.addEventListener("click", this.boundTrustAuthorHandler);
return button;
}
handleTrustAuthorClick(event) {
if (event) {
event.preventDefault?.();
event.stopPropagation?.();
}
const pubkey =
this.video?.pubkey || this.video?.author?.pubkey || "";
if (!pubkey || typeof document === "undefined") {
return;
}
try {
document.dispatchEvent(
new CustomEvent("video:trust-author", { detail: { pubkey } }),
);
} catch (error) {
userLogger.warn("[VideoCard] trust-author dispatch failed", error);
}
}
createModerationHideButton() {
const { text: label, ariaLabel } = getModerationOverrideActionLabels({
overrideActive: true,
@@ -1638,6 +1678,7 @@ export class VideoCard {
actions.appendChild(showButton);
this.moderationActionButton = showButton;
this.moderationActionButtonMode = "override";
actions.appendChild(this.createModerationTrustAuthorButton());
}
hasActions = true;
} else {
+89
View File
@@ -251,6 +251,7 @@ export class VideoModal {
this.moderationBadgeText = null;
this.moderationActionsContainer = null;
this.moderationPrimaryButton = null;
this.moderationTrustAuthorButton = null;
this.moderationBlockButton = null;
this.moderationPrimaryMode = "";
this.moderationBadgeId = "";
@@ -268,6 +269,8 @@ export class VideoModal {
this.handleModerationHideClick.bind(this);
this.handleModerationBlockClick =
this.handleModerationBlockClick.bind(this);
this.handleModerationTrustAuthorClick =
this.handleModerationTrustAuthorClick.bind(this);
this.handleGlobalModerationOverride =
this.handleGlobalModerationOverride.bind(this);
this.handleGlobalModerationBlock =
@@ -556,6 +559,12 @@ export class VideoModal {
);
}
}
if (this.moderationTrustAuthorButton) {
this.moderationTrustAuthorButton.removeEventListener(
"click",
this.handleModerationTrustAuthorClick,
);
}
if (this.moderationBlockButton) {
this.moderationBlockButton.removeEventListener(
"click",
@@ -568,6 +577,7 @@ export class VideoModal {
this.moderationActionsContainer = null;
this.moderationPrimaryButton = null;
this.moderationPrimaryMode = "";
this.moderationTrustAuthorButton = null;
this.moderationBlockButton = null;
const previousScrollRegion = this.scrollRegion;
@@ -2009,8 +2019,28 @@ export class VideoModal {
}
actionsAttached = true;
}
if (mode === "override") {
const trustButton = this.ensureModerationTrustAuthorButton();
if (trustButton) {
trustButton.disabled = false;
trustButton.removeAttribute("aria-busy");
if (badgeId) {
trustButton.setAttribute("aria-describedby", badgeId);
} else {
trustButton.removeAttribute("aria-describedby");
}
if (actions && trustButton.parentElement !== actions) {
actions.appendChild(trustButton);
}
actionsAttached = true;
}
} else {
this.removeModerationTrustAuthorButton();
}
} else {
this.removeModerationPrimaryButton();
this.removeModerationTrustAuthorButton();
}
if (this.shouldShowModerationBlockAction(context)) {
@@ -2160,6 +2190,65 @@ export class VideoModal {
this.moderationPrimaryMode = "";
}
ensureModerationTrustAuthorButton() {
if (!this.document) {
return null;
}
if (!this.moderationTrustAuthorButton) {
const button = this.document.createElement("button");
button.type = "button";
button.className = "moderation-badge__action flex-shrink-0";
button.dataset.moderationAction = "trust-author";
button.textContent = "Always show creator";
button.setAttribute(
"aria-label",
"Always show videos from this creator"
);
button.addEventListener("click", this.handleModerationTrustAuthorClick);
this.moderationTrustAuthorButton = button;
}
return this.moderationTrustAuthorButton;
}
removeModerationTrustAuthorButton() {
const button = this.moderationTrustAuthorButton;
if (!button) {
return;
}
button.removeEventListener("click", this.handleModerationTrustAuthorClick);
if (button.parentElement) {
button.parentElement.removeChild(button);
}
this.moderationTrustAuthorButton = null;
}
handleModerationTrustAuthorClick(event) {
if (event) {
event.preventDefault?.();
event.stopPropagation?.();
}
const pubkey =
this.activeVideo?.pubkey ||
this.activeVideo?.author?.pubkey ||
"";
if (!pubkey || typeof document === "undefined") {
return;
}
try {
document.dispatchEvent(
new CustomEvent("video:trust-author", { detail: { pubkey } })
);
} catch (error) {
devLogger?.warn?.("[VideoModal] trust-author dispatch failed", error);
}
}
ensureModerationBlockButton() {
if (!this.document) {
return null;