From 6aa4b6517a396fb825dd3ec6f1c7c8f24189fc9c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:31:46 -0400 Subject: [PATCH 1/2] Surface audience flags in revert modal --- components/revert-video-modal.html | 18 ++++++++- js/ui/components/RevertModal.js | 61 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/components/revert-video-modal.html b/components/revert-video-modal.html index cedd0daa..151d7bfd 100644 --- a/components/revert-video-modal.html +++ b/components/revert-video-modal.html @@ -62,7 +62,7 @@

Versions are grouped by their d tag or shared videoRootId. This ensures legacy - clients see consistent history when multiple edits were published. + clients see consistent history when multiple edits were published, and keeps audience flags (NSFW / for kids) aligned with the Upload modal.

@@ -117,6 +117,22 @@ +
+

+ Audience flags +

+
+
+
NSFW flag
+
Awaiting selection
+
+
+
For kids flag
+
Awaiting selection
+
+
+
+

NIP-71 event metadata diff --git a/js/ui/components/RevertModal.js b/js/ui/components/RevertModal.js index 5e287b69..3d9dee6b 100644 --- a/js/ui/components/RevertModal.js +++ b/js/ui/components/RevertModal.js @@ -571,6 +571,9 @@ export class RevertModal { } const chips = []; + const nsfwFlag = version.isNsfw === true; + const forKidsFlag = version.isForKids === true; + const conflictingAudienceFlags = nsfwFlag && forKidsFlag; if (version.deleted) { chips.push( 'Marked deleted' @@ -581,6 +584,22 @@ export class RevertModal { 'Private' ); } + if (conflictingAudienceFlags) { + chips.push( + 'NSFW + For kids conflict' + ); + } else { + if (nsfwFlag) { + chips.push( + 'Marked NSFW' + ); + } + if (forKidsFlag) { + chips.push( + 'For kids' + ); + } + } if (version.version !== undefined) { chips.push( `Schema v${escape( @@ -589,6 +608,35 @@ export class RevertModal { ); } + const renderFlagStatus = (value, { + yesLabel = "Yes", + noLabel = "No", + unspecifiedLabel = "Not specified", + } = {}) => { + if (value === true) { + return `${escape(yesLabel)}`; + } + if (value === false) { + return `${escape(noLabel)}`; + } + return this.renderPlaceholder(unspecifiedLabel); + }; + + const nsfwStatusHtml = renderFlagStatus(version.isNsfw, { + yesLabel: "Yes — marked NSFW", + noLabel: "No — not flagged as NSFW", + unspecifiedLabel: "Not specified", + }); + const kidsStatusHtml = renderFlagStatus(version.isForKids, { + yesLabel: "Yes — marked for kids", + noLabel: "No — not marked for kids", + unspecifiedLabel: "Not specified", + }); + + const audienceConflictNotice = conflictingAudienceFlags + ? '

Conflicting flags detected — this revision is marked both NSFW and for kids. Review before reverting.

' + : ""; + const descriptionHtml = description ? `

${escape(description)}

` : '

No description provided.

'; @@ -718,6 +766,11 @@ export class RevertModal { definition("Alt text", altHtml, { span: 2 }), ].join(""); + const audienceRows = [ + definition("NSFW flag", nsfwStatusHtml), + definition("For kids flag", kidsStatusHtml), + ].join(""); + this.details.innerHTML = `
@@ -759,6 +812,14 @@ export class RevertModal { ${descriptionHtml}

+
+

Audience flags

+
+ ${audienceRows} +
+ ${audienceConflictNotice} +
+

NIP-71 event metadata

From bc3966798acab0ca4a2e2d3ed67a57930d2d090f Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:41:58 -0400 Subject: [PATCH 2/2] Enforce audience flag exclusivity in edit modal --- js/ui/components/EditModal.js | 58 +++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/js/ui/components/EditModal.js b/js/ui/components/EditModal.js index f65f0f9a..2deef5d2 100644 --- a/js/ui/components/EditModal.js +++ b/js/ui/components/EditModal.js @@ -249,9 +249,44 @@ export class EditModal { }); } + if (this.fields.isNsfw && this.fields.isForKids) { + this.setupMutuallyExclusiveCheckboxes( + this.fields.isNsfw, + this.fields.isForKids + ); + } + this.nip71FormManager.bindSection(this.nip71SectionKey); } + setupMutuallyExclusiveCheckboxes(firstInput, secondInput) { + if (!firstInput || !secondInput) { + return; + } + + const enforceExclusion = (primary, secondary) => { + if (primary.checked) { + secondary.checked = false; + } + }; + + const handleFirstChange = () => enforceExclusion(firstInput, secondInput); + const handleSecondChange = () => enforceExclusion(secondInput, firstInput); + + if (firstInput.checked && secondInput.checked) { + secondInput.checked = false; + } + + firstInput.addEventListener("change", handleFirstChange); + secondInput.addEventListener("change", handleSecondChange); + } + + sanitizeAudienceFlags(flags = {}) { + const isNsfw = flags?.isNsfw === true; + const isForKids = flags?.isForKids === true && !isNsfw; + return { isNsfw, isForKids }; + } + reset() { if (!this.root) { this.activeVideo = null; @@ -322,10 +357,14 @@ export class EditModal { const enableCommentsValue = typeof video.enableComments === "boolean" ? video.enableComments : true; const isPrivateValue = video.isPrivate === true; - const isNsfwValue = - typeof video.isNsfw === "boolean" ? video.isNsfw : false; - const isForKidsValue = + const rawIsNsfw = typeof video.isNsfw === "boolean" ? video.isNsfw : false; + const rawIsForKids = typeof video.isForKids === "boolean" ? video.isForKids : false; + const { isNsfw: isNsfwValue, isForKids: isForKidsValue } = + this.sanitizeAudienceFlags({ + isNsfw: rawIsNsfw, + isForKids: rawIsForKids, + }); const editContext = { ...video, @@ -361,6 +400,13 @@ export class EditModal { return; } + const sanitizedFlags = this.sanitizeAudienceFlags({ + isNsfw: editContext.isNsfw, + isForKids: editContext.isForKids, + }); + editContext.isNsfw = sanitizedFlags.isNsfw; + editContext.isForKids = sanitizedFlags.isForKids; + const fieldMap = { title: editContext.title || "", url: editContext.url || "", @@ -831,6 +877,12 @@ export class EditModal { } } + ({ isNsfw: finalIsNsfw, isForKids: finalIsForKids } = + this.sanitizeAudienceFlags({ + isNsfw: finalIsNsfw, + isForKids: finalIsForKids, + })); + if (!finalTitle || (!finalUrl && !finalMagnet && !hasImetaSource)) { this.showError( "Title and at least one of URL, Magnet, or a NIP-71 media variant is required."