mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Promote unstable → beta: share popover copy buttons fix
Wire the video-modal share popover's Copy URL / Copy Magnet / Copy CDN / Share on Nostr actions (were dead — no click delegation). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
commit
d8d1ccb1f6
3 files changed
+178
No files matched your search
@@ -3450,6 +3450,81 @@ export class VideoModal {
|
||||
}
|
||||
}
|
||||
|
||||
// Delegated click handler for the share popover's menu-action buttons
|
||||
// (Copy URL / Copy Magnet / Copy CDN / Share on Nostr). The forced-source
|
||||
// test links self-wire their own copy, so they are skipped here.
|
||||
bindShareMenuActions(panel) {
|
||||
if (!panel || typeof panel.addEventListener !== "function") {
|
||||
return;
|
||||
}
|
||||
panel.addEventListener("click", (event) => {
|
||||
const target = event.target;
|
||||
const button =
|
||||
target && typeof target.closest === "function"
|
||||
? target.closest("button[data-action]")
|
||||
: null;
|
||||
if (!button || button.disabled) {
|
||||
return;
|
||||
}
|
||||
const action = button.dataset?.action || "";
|
||||
if (action === "copy-webtorrent-url" || action === "copy-cdn-url") {
|
||||
return; // self-wired in createVideoShareMenuPanel
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.handleShareMenuAction(action);
|
||||
this.modalSharePopover?.close?.();
|
||||
});
|
||||
}
|
||||
|
||||
handleShareMenuAction(action) {
|
||||
const video = this.activeVideo;
|
||||
switch (action) {
|
||||
case "copy-magnet":
|
||||
this.dispatch("video:copy-magnet", { video });
|
||||
break;
|
||||
case "copy-cdn":
|
||||
this.dispatch("video:copy-cdn", { video });
|
||||
break;
|
||||
case "share-nostr":
|
||||
this.dispatch("video:share-nostr", {
|
||||
video,
|
||||
trigger: this.shareBtn || null,
|
||||
});
|
||||
break;
|
||||
case "share":
|
||||
// "Copy URL" — the shareable deep link to this video.
|
||||
this.dispatch("video:copy-url", {
|
||||
video,
|
||||
url: this.buildShareUrl(video),
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Plain `?v=<nevent>` share link (no forced playback source).
|
||||
buildShareUrl(video) {
|
||||
const eventId =
|
||||
video && typeof video.id === "string" ? video.id.trim() : "";
|
||||
if (!eventId) {
|
||||
return "";
|
||||
}
|
||||
const win = this.window || globalThis;
|
||||
const neventEncode = win?.NostrTools?.nip19?.neventEncode;
|
||||
const loc = win?.location;
|
||||
if (typeof neventEncode !== "function" || !loc) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const nevent = neventEncode({ id: eventId });
|
||||
return `${loc.origin}${loc.pathname}?v=${encodeURIComponent(nevent)}`;
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
setupModalSharePopover() {
|
||||
if (!this.playerModal || !this.shareBtn) {
|
||||
return;
|
||||
@@ -3483,6 +3558,10 @@ export class VideoModal {
|
||||
this.buildForcedSourceShareUrl(this.activeVideo, playback),
|
||||
});
|
||||
if (panel) {
|
||||
// The share menu's Copy URL / Copy Magnet / Copy CDN / Share on Nostr
|
||||
// items only set `data-action` (like the ⋯ menu) — unlike the ⋯ menu,
|
||||
// nothing wired their clicks, so they were dead. Delegate them here.
|
||||
this.bindShareMenuActions(panel);
|
||||
container.appendChild(panel);
|
||||
}
|
||||
return panel;
|
||||
|
||||
@@ -69,6 +69,10 @@ export default class VideoModalController {
|
||||
this.handleCopyCdn(event);
|
||||
});
|
||||
|
||||
this.videoModal.addEventListener("video:copy-url", (event) => {
|
||||
this.handleCopyUrl(event);
|
||||
});
|
||||
|
||||
this.videoModal.addEventListener("video:copy-magnet", () => {
|
||||
if (this.handleCopyMagnetCallback) {
|
||||
this.handleCopyMagnetCallback();
|
||||
@@ -115,6 +119,28 @@ export default class VideoModalController {
|
||||
});
|
||||
}
|
||||
|
||||
handleCopyUrl(event) {
|
||||
const url = event?.detail?.url || "";
|
||||
if (!url) {
|
||||
if (this.showError) {
|
||||
this.showError("Could not generate a share link.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
navigator.clipboard
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
if (this.showSuccess) {
|
||||
this.showSuccess("Video link copied to clipboard!");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (this.showError) {
|
||||
this.showError("Failed to copy the link.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleSourceSwitch(event) {
|
||||
const detail = event?.detail || {};
|
||||
const { source } = detail;
|
||||
|
||||
@@ -308,3 +308,76 @@ test("reaction click with no matching button does not dispatch", async (t) => {
|
||||
modal.handleReactionClick({});
|
||||
assert.equal(fired, false, "no reaction event for an unrelated/empty target");
|
||||
});
|
||||
|
||||
// Share popover (reported: "Copy URL / Copy Magnet / Copy CDN don't work"). The
|
||||
// share menu buttons only set data-action (like the ⋯ menu), but unlike the ⋯
|
||||
// menu nothing wired their clicks, so they were dead. handleShareMenuAction now
|
||||
// routes each to the event videoModalController already handles.
|
||||
//
|
||||
// test_integrity_note:
|
||||
// change_type: ["new_tests"]
|
||||
// scenarios:
|
||||
// - id: SCN-video-modal-share-menu-routing
|
||||
// given: "a video modal with an active video"
|
||||
// when: "handleShareMenuAction runs for a share-menu action"
|
||||
// then: "it dispatches the matching video:* event; forced-source actions do not re-route"
|
||||
// determinism_controls:
|
||||
// - "JSDOM modal via setupModal; synchronous dispatch"
|
||||
// relaxation:
|
||||
// did_relax_any_assertion: false
|
||||
|
||||
test("share menu Copy Magnet routes to video:copy-magnet", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
modal.activeVideo = { id: "e".repeat(64), url: "https://x/v.mp4", magnet: "" };
|
||||
const events = [];
|
||||
modal.addEventListener("video:copy-magnet", (e) => events.push(e.detail));
|
||||
modal.handleShareMenuAction("copy-magnet");
|
||||
assert.equal(events.length, 1, "Copy Magnet dispatches video:copy-magnet");
|
||||
assert.equal(events[0].video.id, "e".repeat(64));
|
||||
});
|
||||
|
||||
test("share menu Copy CDN routes to video:copy-cdn", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
modal.activeVideo = { id: "e".repeat(64), url: "https://x/v.mp4" };
|
||||
const events = [];
|
||||
modal.addEventListener("video:copy-cdn", (e) => events.push(e.detail));
|
||||
modal.handleShareMenuAction("copy-cdn");
|
||||
assert.equal(events.length, 1, "Copy CDN dispatches video:copy-cdn");
|
||||
assert.equal(events[0].video.url, "https://x/v.mp4");
|
||||
});
|
||||
|
||||
test("share menu Copy URL routes to video:copy-url", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
modal.activeVideo = { id: "e".repeat(64) };
|
||||
const events = [];
|
||||
modal.addEventListener("video:copy-url", (e) => events.push(e.detail));
|
||||
modal.handleShareMenuAction("share");
|
||||
assert.equal(events.length, 1, "Copy URL dispatches video:copy-url");
|
||||
assert.equal(typeof events[0].url, "string");
|
||||
});
|
||||
|
||||
test("share menu Share on Nostr routes to video:share-nostr", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
modal.activeVideo = { id: "e".repeat(64) };
|
||||
const events = [];
|
||||
modal.addEventListener("video:share-nostr", (e) => events.push(e.detail));
|
||||
modal.handleShareMenuAction("share-nostr");
|
||||
assert.equal(events.length, 1, "Share on Nostr dispatches video:share-nostr");
|
||||
});
|
||||
|
||||
test("share menu forced-source actions do not re-route through handleShareMenuAction", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
modal.activeVideo = { id: "e".repeat(64) };
|
||||
let fired = false;
|
||||
["video:copy-magnet", "video:copy-cdn", "video:copy-url", "video:share-nostr"].forEach(
|
||||
(name) => modal.addEventListener(name, () => { fired = true; })
|
||||
);
|
||||
modal.handleShareMenuAction("copy-cdn-url");
|
||||
modal.handleShareMenuAction("copy-webtorrent-url");
|
||||
assert.equal(fired, false, "forced-source links self-wire; not routed here");
|
||||
});
|
||||
Reference in new issue
Block a user