mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
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:
1 parent
a0131c718e
commit
5844a36a54
4 files changed
+188
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user