mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-03 11:22:25 +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(() => {
|
const submissionsPollId = setInterval(() => {
|
||||||
void app.refreshSubmissionsIndicator({ reason: "poll" });
|
void app.refreshSubmissionsIndicator({ reason: "poll" });
|
||||||
}, 180000);
|
}, 180000);
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ export class SimilarContentCard {
|
|||||||
this.handleModerationHideClick(event);
|
this.handleModerationHideClick(event);
|
||||||
this.boundModerationBlockHandler = (event) =>
|
this.boundModerationBlockHandler = (event) =>
|
||||||
this.handleModerationBlockClick(event);
|
this.handleModerationBlockClick(event);
|
||||||
|
this.boundTrustAuthorHandler = (event) =>
|
||||||
|
this.handleTrustAuthorClick(event);
|
||||||
|
|
||||||
this.build();
|
this.build();
|
||||||
}
|
}
|
||||||
@@ -524,6 +526,43 @@ export class SimilarContentCard {
|
|||||||
return button;
|
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) {
|
handleShowAnywayClick(event) {
|
||||||
event?.preventDefault?.();
|
event?.preventDefault?.();
|
||||||
event?.stopPropagation?.();
|
event?.stopPropagation?.();
|
||||||
@@ -720,6 +759,7 @@ export class SimilarContentCard {
|
|||||||
actions.appendChild(showButton);
|
actions.appendChild(showButton);
|
||||||
this.moderationActionButton = showButton;
|
this.moderationActionButton = showButton;
|
||||||
this.moderationActionButtonMode = "override";
|
this.moderationActionButtonMode = "override";
|
||||||
|
actions.appendChild(this.createModerationTrustAuthorButton());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,8 @@ export class VideoCard {
|
|||||||
this.moderationBadgeSlot = null;
|
this.moderationBadgeSlot = null;
|
||||||
this.hiddenSummaryEl = null;
|
this.hiddenSummaryEl = null;
|
||||||
this.boundShowAnywayHandler = (event) => this.handleShowAnywayClick(event);
|
this.boundShowAnywayHandler = (event) => this.handleShowAnywayClick(event);
|
||||||
|
this.boundTrustAuthorHandler = (event) =>
|
||||||
|
this.handleTrustAuthorClick(event);
|
||||||
this.boundModerationBlockHandler = (event) =>
|
this.boundModerationBlockHandler = (event) =>
|
||||||
this.handleModerationBlockClick(event);
|
this.handleModerationBlockClick(event);
|
||||||
this.boundModerationHideHandler = (event) =>
|
this.boundModerationHideHandler = (event) =>
|
||||||
@@ -1210,6 +1212,44 @@ export class VideoCard {
|
|||||||
return button;
|
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() {
|
createModerationHideButton() {
|
||||||
const { text: label, ariaLabel } = getModerationOverrideActionLabels({
|
const { text: label, ariaLabel } = getModerationOverrideActionLabels({
|
||||||
overrideActive: true,
|
overrideActive: true,
|
||||||
@@ -1638,6 +1678,7 @@ export class VideoCard {
|
|||||||
actions.appendChild(showButton);
|
actions.appendChild(showButton);
|
||||||
this.moderationActionButton = showButton;
|
this.moderationActionButton = showButton;
|
||||||
this.moderationActionButtonMode = "override";
|
this.moderationActionButtonMode = "override";
|
||||||
|
actions.appendChild(this.createModerationTrustAuthorButton());
|
||||||
}
|
}
|
||||||
hasActions = true;
|
hasActions = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ export class VideoModal {
|
|||||||
this.moderationBadgeText = null;
|
this.moderationBadgeText = null;
|
||||||
this.moderationActionsContainer = null;
|
this.moderationActionsContainer = null;
|
||||||
this.moderationPrimaryButton = null;
|
this.moderationPrimaryButton = null;
|
||||||
|
this.moderationTrustAuthorButton = null;
|
||||||
this.moderationBlockButton = null;
|
this.moderationBlockButton = null;
|
||||||
this.moderationPrimaryMode = "";
|
this.moderationPrimaryMode = "";
|
||||||
this.moderationBadgeId = "";
|
this.moderationBadgeId = "";
|
||||||
@@ -268,6 +269,8 @@ export class VideoModal {
|
|||||||
this.handleModerationHideClick.bind(this);
|
this.handleModerationHideClick.bind(this);
|
||||||
this.handleModerationBlockClick =
|
this.handleModerationBlockClick =
|
||||||
this.handleModerationBlockClick.bind(this);
|
this.handleModerationBlockClick.bind(this);
|
||||||
|
this.handleModerationTrustAuthorClick =
|
||||||
|
this.handleModerationTrustAuthorClick.bind(this);
|
||||||
this.handleGlobalModerationOverride =
|
this.handleGlobalModerationOverride =
|
||||||
this.handleGlobalModerationOverride.bind(this);
|
this.handleGlobalModerationOverride.bind(this);
|
||||||
this.handleGlobalModerationBlock =
|
this.handleGlobalModerationBlock =
|
||||||
@@ -556,6 +559,12 @@ export class VideoModal {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.moderationTrustAuthorButton) {
|
||||||
|
this.moderationTrustAuthorButton.removeEventListener(
|
||||||
|
"click",
|
||||||
|
this.handleModerationTrustAuthorClick,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.moderationBlockButton) {
|
if (this.moderationBlockButton) {
|
||||||
this.moderationBlockButton.removeEventListener(
|
this.moderationBlockButton.removeEventListener(
|
||||||
"click",
|
"click",
|
||||||
@@ -568,6 +577,7 @@ export class VideoModal {
|
|||||||
this.moderationActionsContainer = null;
|
this.moderationActionsContainer = null;
|
||||||
this.moderationPrimaryButton = null;
|
this.moderationPrimaryButton = null;
|
||||||
this.moderationPrimaryMode = "";
|
this.moderationPrimaryMode = "";
|
||||||
|
this.moderationTrustAuthorButton = null;
|
||||||
this.moderationBlockButton = null;
|
this.moderationBlockButton = null;
|
||||||
|
|
||||||
const previousScrollRegion = this.scrollRegion;
|
const previousScrollRegion = this.scrollRegion;
|
||||||
@@ -2009,8 +2019,28 @@ export class VideoModal {
|
|||||||
}
|
}
|
||||||
actionsAttached = true;
|
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 {
|
} else {
|
||||||
this.removeModerationPrimaryButton();
|
this.removeModerationPrimaryButton();
|
||||||
|
this.removeModerationTrustAuthorButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.shouldShowModerationBlockAction(context)) {
|
if (this.shouldShowModerationBlockAction(context)) {
|
||||||
@@ -2160,6 +2190,65 @@ export class VideoModal {
|
|||||||
this.moderationPrimaryMode = "";
|
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() {
|
ensureModerationBlockButton() {
|
||||||
if (!this.document) {
|
if (!this.document) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in new issue
Block a user