From d33ca197d79d0fa54684536f5f0a6caba4fcb34d Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Thu, 9 Oct 2025 22:03:58 -0400 Subject: [PATCH] Prevent conflicting audience flags in upload forms --- js/app.js | 6 ++- js/services/r2Service.js | 5 ++- js/ui/components/UploadModal.js | 75 +++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 7 deletions(-) diff --git a/js/app.js b/js/app.js index ff6f2d8b..95a83d2e 100644 --- a/js/app.js +++ b/js/app.js @@ -2391,8 +2391,10 @@ class Application { legacyPayload?.enableComments, true ); - const isNsfw = normalizeBooleanFlag(legacyPayload?.isNsfw, false); - const isForKids = normalizeBooleanFlag(legacyPayload?.isForKids, false); + const rawIsNsfw = normalizeBooleanFlag(legacyPayload?.isNsfw, false); + const rawIsForKids = normalizeBooleanFlag(legacyPayload?.isForKids, false); + const isNsfw = rawIsNsfw; + const isForKids = rawIsNsfw ? false : rawIsForKids; const legacyFormData = { version: 3, diff --git a/js/services/r2Service.js b/js/services/r2Service.js index 8b307c28..f6518809 100644 --- a/js/services/r2Service.js +++ b/js/services/r2Service.js @@ -674,7 +674,10 @@ class R2Service { const xs = String(metadata.xs || "").trim(); const enableComments = metadata.enableComments !== false; const isNsfw = metadata.isNsfw === true; - const isForKids = metadata.isForKids === true; + const isForKids = metadata.isForKids === true && !isNsfw; + + metadata.isNsfw = isNsfw; + metadata.isForKids = isForKids; const accountId = (this.cloudflareSettings?.accountId || "").trim(); const accessKeyId = (this.cloudflareSettings?.accessKeyId || "").trim(); diff --git a/js/ui/components/UploadModal.js b/js/ui/components/UploadModal.js index 15e3bf4f..0b21a96b 100644 --- a/js/ui/components/UploadModal.js +++ b/js/ui/components/UploadModal.js @@ -76,6 +76,7 @@ export class UploadModal { this.r2BaseDomainInput = null; this.nip71FormManager = new Nip71FormManager(); + this.cleanupHandlers = []; } addEventListener(type, listener, options) { @@ -242,6 +243,39 @@ export class UploadModal { input.checked = fallback; } + sanitizeAudienceFlags(flags = {}) { + const isNsfw = flags?.isNsfw === true; + const isForKids = flags?.isForKids === true && !isNsfw; + return { isNsfw, isForKids }; + } + + 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); + + this.cleanupHandlers.push(() => { + firstInput.removeEventListener("change", handleFirstChange); + secondInput.removeEventListener("change", handleSecondChange); + }); + } + buildAutoGeneratedImetaVariant(file) { if (!file) { return null; @@ -318,6 +352,20 @@ export class UploadModal { }); } + if (this.customFormInputs?.isNsfw && this.customFormInputs?.isForKids) { + this.setupMutuallyExclusiveCheckboxes( + this.customFormInputs.isNsfw, + this.customFormInputs.isForKids + ); + } + + if (this.cloudflareIsNsfwInput && this.cloudflareIsForKidsInput) { + this.setupMutuallyExclusiveCheckboxes( + this.cloudflareIsNsfwInput, + this.cloudflareIsForKidsInput + ); + } + this.nip71FormManager.bindSection("custom"); this.nip71FormManager.bindSection("cloudflare"); } @@ -463,6 +511,10 @@ export class UploadModal { } handleCustomSubmit() { + const audienceFlags = this.sanitizeAudienceFlags({ + isNsfw: this.readCheckboxValue(this.customFormInputs.isNsfw, false), + isForKids: this.readCheckboxValue(this.customFormInputs.isForKids, false), + }); const payload = { title: this.customFormInputs.title?.value?.trim() || "", url: this.customFormInputs.url?.value?.trim() || "", @@ -475,8 +527,7 @@ export class UploadModal { this.customFormInputs.enableComments, true ), - isNsfw: this.readCheckboxValue(this.customFormInputs.isNsfw, false), - isForKids: this.readCheckboxValue(this.customFormInputs.isForKids, false), + ...audienceFlags, }; if (this.customFormInputs.isPrivate) { @@ -747,6 +798,10 @@ export class UploadModal { const npub = this.safeEncodeNpub(pubkey) || ""; const file = this.cloudflareFileInput?.files?.[0] || null; + const audienceFlags = this.sanitizeAudienceFlags({ + isNsfw: this.readCheckboxValue(this.cloudflareIsNsfwInput, false), + isForKids: this.readCheckboxValue(this.cloudflareIsForKidsInput, false), + }); const metadata = { title: this.cloudflareTitleInput?.value?.trim() || "", description: this.cloudflareDescriptionInput?.value?.trim() || "", @@ -758,8 +813,7 @@ export class UploadModal { this.cloudflareEnableCommentsInput, true ), - isNsfw: this.readCheckboxValue(this.cloudflareIsNsfwInput, false), - isForKids: this.readCheckboxValue(this.cloudflareIsForKidsInput, false), + ...audienceFlags, }; const nip71Metadata = this.nip71FormManager.collectSection("cloudflare"); @@ -793,6 +847,19 @@ export class UploadModal { } destroy() { + if (Array.isArray(this.cleanupHandlers)) { + this.cleanupHandlers.forEach((cleanup) => { + try { + if (typeof cleanup === "function") { + cleanup(); + } + } catch (error) { + console.warn("[UploadModal] Failed to remove handler", error); + } + }); + this.cleanupHandlers = []; + } + if (Array.isArray(this.r2Unsubscribes)) { this.r2Unsubscribes.forEach((unsubscribe) => { try {