diff --git a/content/roadmap/02_bitvid_Enhanced_Migration_of_Note_Spec_Logic.md b/content/roadmap/02_bitvid_Enhanced_Migration_of_Note_Spec_Logic.md index 32ad29c9..b76fe39f 100644 --- a/content/roadmap/02_bitvid_Enhanced_Migration_of_Note_Spec_Logic.md +++ b/content/roadmap/02_bitvid_Enhanced_Migration_of_Note_Spec_Logic.md @@ -94,7 +94,12 @@ async handleSubmit(e) { magnet: document.getElementById("magnet")?.value.trim() || "", thumbnail: document.getElementById("thumbnail")?.value.trim() || "", description: document.getElementById("description")?.value.trim() || "", - mode: isDevMode ? "dev" : "live", + mode: + (document.getElementById("mode")?.value || "") + .trim() + .toLowerCase() === "dev" + ? "dev" + : "live", isPrivate: this.isPrivateCheckbox.checked }; diff --git a/js/app.js b/js/app.js index 3dba4573..5730d5b2 100644 --- a/js/app.js +++ b/js/app.js @@ -560,7 +560,11 @@ class Application { editModalOverride: ui.editModal, container: modalContainer, services: { - getMode: () => (isDevMode ? "dev" : "live"), + getMode: ({ video } = {}) => { + const candidate = + typeof video?.mode === "string" ? video.mode.trim().toLowerCase() : ""; + return candidate === "dev" ? "dev" : "live"; + }, sanitizers: { text: (value) => (typeof value === "string" ? value.trim() : ""), url: (value) => (typeof value === "string" ? value.trim() : ""), @@ -5001,6 +5005,9 @@ class Application { const description = normalizeString(legacyPayload?.description || ""); const ws = normalizeString(legacyPayload?.ws || ""); const xs = normalizeString(legacyPayload?.xs || ""); + const rawMode = normalizeString(legacyPayload?.mode || ""); + const normalizedMode = + rawMode && rawMode.toLowerCase() === "dev" ? "dev" : "live"; const enableComments = normalizeBooleanFlag( legacyPayload?.enableComments, true @@ -5017,7 +5024,7 @@ class Application { magnet, thumbnail, description, - mode: isDevMode ? "dev" : "live", + mode: normalizedMode, enableComments, isNsfw, isForKids, diff --git a/js/ui/components/EditModal.js b/js/ui/components/EditModal.js index 1229f665..a6c336d0 100644 --- a/js/ui/components/EditModal.js +++ b/js/ui/components/EditModal.js @@ -19,7 +19,10 @@ export class EditModal { typeof setGlobalModalState === "function" ? setGlobalModalState : () => {}; this.escapeHtml = typeof escapeHtml === "function" ? escapeHtml : (value) => `${value ?? ""}`; this.showError = typeof showError === "function" ? showError : () => {}; - this.getMode = typeof getMode === "function" ? getMode : () => "live"; + this.getMode = + typeof getMode === "function" + ? getMode + : () => (this.activeVideo?.mode ?? "live"); this.eventTarget = eventTarget instanceof EventTarget ? eventTarget : new EventTarget(); this.container = container || null; @@ -312,6 +315,37 @@ export class EditModal { return { isNsfw, isForKids }; } + normalizeMode(value) { + if (typeof value === "string") { + const normalized = value.trim().toLowerCase(); + if (normalized === "dev") { + return "dev"; + } + } + return "live"; + } + + resolveModeForSubmit() { + let candidate = null; + if (typeof this.getMode === "function") { + try { + candidate = this.getMode({ video: this.activeVideo }); + } catch (_error) { + candidate = null; + } + } + + if (typeof candidate === "string" && candidate.trim()) { + return this.normalizeMode(candidate); + } + + if (this.activeVideo && typeof this.activeVideo.mode === "string") { + return this.normalizeMode(this.activeVideo.mode); + } + + return "live"; + } + reset() { if (!this.root) { this.activeVideo = null; @@ -963,7 +997,7 @@ export class EditModal { url: finalUrl, thumbnail: finalThumbnail, description: finalDescription, - mode: this.getMode(), + mode: this.resolveModeForSubmit(), ws: finalWs, xs: finalXs, wsEdited: !shouldUseOriginalWs, diff --git a/tests/nostr-publish-rejection.test.mjs b/tests/nostr-publish-rejection.test.mjs index 28640cda..24dfce57 100644 --- a/tests/nostr-publish-rejection.test.mjs +++ b/tests/nostr-publish-rejection.test.mjs @@ -51,6 +51,8 @@ try { const { nostrClient, setActiveSigner, clearActiveSigner } = await import("../js/nostr.js"); const { subscriptions } = await import("../js/subscriptions.js"); const { userBlocks } = await import("../js/userBlocks.js"); + const { Application } = await import("../js/app.js"); + const { isDevMode } = await import("../js/config.js"); setActiveSigner({ type: "session", @@ -283,6 +285,89 @@ try { } } + async function testPublishVideoNoteDefaultsToLiveModeInDev() { + assert.equal(isDevMode, true, "Test expects isDevMode to be true"); + + let capturedPayload = null; + let capturedPubkey = null; + + const fakeApp = { + pubkey: "f".repeat(64), + nostrService: { + publishVideoNote: async (payload, pubkey) => { + capturedPayload = payload; + capturedPubkey = pubkey; + }, + }, + showError(message) { + throw new Error(`showError invoked: ${message}`); + }, + showSuccess() {}, + showStatus() {}, + loadVideos: async () => {}, + uploadModal: null, + }; + + const publishResult = await Application.prototype.publishVideoNote.call( + fakeApp, + { + legacyFormData: { + title: "Dev Mode Default", + url: "https://cdn.example/dev.mp4", + }, + }, + ); + + assert.equal(publishResult, true, "publishVideoNote should resolve truthy"); + assert.equal( + capturedPubkey, + fakeApp.pubkey, + "publishVideoNote should forward the viewer pubkey", + ); + assert.equal( + capturedPayload?.legacyFormData?.mode, + "live", + "publishVideoNote should default mode to live even in dev mode", + ); + } + + async function testPublishVideoNoteRespectsProvidedMode() { + let capturedPayload = null; + + const fakeApp = { + pubkey: "f".repeat(64), + nostrService: { + publishVideoNote: async (payload) => { + capturedPayload = payload; + }, + }, + showError(message) { + throw new Error(`showError invoked: ${message}`); + }, + showSuccess() {}, + showStatus() {}, + loadVideos: async () => {}, + uploadModal: null, + }; + + await Application.prototype.publishVideoNote.call( + fakeApp, + { + legacyFormData: { + title: "Dev Mode Override", + url: "https://cdn.example/dev.mp4", + mode: "dev", + }, + }, + ); + + assert.equal( + capturedPayload?.legacyFormData?.mode, + "dev", + "publishVideoNote should respect an explicit dev mode value", + ); + } + async function testPublishVideoNormalizesPubkeyForNip71Metadata() { const uppercasePubkey = "AB".repeat(32); const originalSignAndPublish = nostrClient.signAndPublishEvent; @@ -361,6 +446,8 @@ try { await testPublishVideoRejectsWhenAllRelaysFail(); await testPublishSubscriptionListRejectsWhenAllRelaysFail(); await testPublishBlockListRejectsWhenAllRelaysFail(); + await testPublishVideoNoteDefaultsToLiveModeInDev(); + await testPublishVideoNoteRespectsProvidedMode(); await testPublishVideoNormalizesPubkeyForNip71Metadata(); console.log("nostr publish rejection tests passed");