Merge pull request #648 from PR0M3TH3AN/codex/improve-zap-button-menu-responsiveness

Improve zap popover responsiveness
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-18 14:55:19 -04:00
commit afa0f7a7c6
7 files changed
+594 -61

No files matched your search

+109 -2
View File
@@ -585,14 +585,18 @@ class Application {
getCurrentVideo: () => this.currentVideo,
nwcSettings: this.nwcSettingsService,
isUserLoggedIn: () => this.isUserLoggedIn(),
hasSessionActor: () =>
Boolean(
typeof nostrClient?.sessionActor?.pubkey === "string" &&
nostrClient.sessionActor.pubkey.trim()
),
splitAndZap: (...args) => this.splitAndZap(...args),
payments: this.payments,
callbacks: {
onSuccess: (message) => this.showSuccess(message),
onError: (message) => this.showError(message),
},
requestWalletPane: () =>
this.profileController?.showWalletPane?.(),
requestWalletPane: () => this.openWalletPane(),
});
this.boundVideoModalCloseHandler = () => {
this.hideModal();
@@ -1307,6 +1311,47 @@ class Application {
}
}
openWalletPane() {
if (
!this.profileController ||
typeof this.profileController.showWalletPane !== "function"
) {
devLogger.warn(
"[Application] Wallet pane requested before profile controller initialized.",
);
return Promise.resolve(false);
}
try {
const result = this.profileController.showWalletPane();
if (result && typeof result.then === "function") {
return result.catch((error) => {
devLogger.error("[Application] Failed to open wallet pane:", error);
throw error;
});
}
return Promise.resolve(result);
} catch (error) {
devLogger.error("[Application] Failed to open wallet pane:", error);
return Promise.reject(error);
}
}
dispatchAuthChange(detail = {}) {
if (typeof window === "undefined" || typeof window.dispatchEvent !== "function") {
return false;
}
try {
const payload = { ...(typeof detail === "object" && detail ? detail : {}) };
window.dispatchEvent(new CustomEvent("bitvid:auth-changed", { detail: payload }));
return true;
} catch (error) {
devLogger.warn("[Application] Failed to dispatch auth change event:", error);
return false;
}
}
normalizeModalTrigger(candidate) {
if (!candidate) {
return null;
@@ -3095,6 +3140,23 @@ class Application {
identityChanged: Boolean(detail?.identityChanged),
};
if (this.zapController) {
try {
this.zapController.setVisibility(
Boolean(this.currentVideo?.lightningAddress),
);
} catch (error) {
devLogger.warn("[Application] Failed to refresh zap visibility after login:", error);
}
}
this.dispatchAuthChange({
status: "login",
loggedIn: true,
pubkey: loginContext.pubkey || null,
previousPubkey: loginContext.previousPubkey || null,
});
const nwcPromise = Promise.resolve()
.then(() => this.nwcSettingsService.onLogin(loginContext))
.catch((error) => {
@@ -3186,6 +3248,51 @@ class Application {
this.applyLoggedOutUiState();
if (this.videoModal?.closeZapDialog) {
try {
this.videoModal.closeZapDialog({ silent: true, restoreFocus: false });
} catch (error) {
devLogger.warn("Failed to close zap dialog during logout:", error);
}
}
if (this.zapController) {
try {
this.zapController.resetState();
this.zapController.setVisibility(Boolean(this.currentVideo?.lightningAddress));
} catch (error) {
devLogger.warn("Failed to reset zap controller during logout:", error);
}
}
if (typeof this.nostrService?.clearVideoSubscription === "function") {
try {
this.nostrService.clearVideoSubscription();
} catch (error) {
devLogger.warn("Failed to clear video subscription during logout:", error);
}
}
if (typeof this.nostrService?.resetVideosCache === "function") {
try {
this.nostrService.resetVideosCache();
} catch (error) {
devLogger.warn("Failed to reset cached videos during logout:", error);
}
}
await this.renderVideoList({
videos: [],
metadata: { reason: "auth:logout" },
});
this.dispatchAuthChange({
status: "logout",
loggedIn: false,
pubkey: detail?.pubkey || null,
previousPubkey: detail?.previousPubkey || null,
});
try {
await this.loadVideos(true);
} catch (error) {
+349 -28
View File
@@ -112,6 +112,8 @@ function logZapError(stage, details = {}, error) {
userLogger.error("[zap] Channel zap failure", summary, error);
}
let currentChannelProfileEvent = null;
let currentChannelProfileSnapshot = null;
let currentChannelProfileHasExplicitPayload = false;
let cachedZapButton = null;
let cachedChannelShareButton = null;
@@ -133,6 +135,7 @@ let zapControlsOpen = false;
let zapPopover = null;
let zapPopoverTrigger = null;
let zapShouldFocusOnOpen = false;
let zapPopoverOpenPromise = null;
let channelMenuPopover = null;
let channelMenuOpen = false;
let currentVideoLoadToken = 0;
@@ -144,6 +147,33 @@ const PROFILE_EVENT_CACHE_TTL_MS = 5 * 60 * 1000;
const channelProfileMetadataCache = new Map();
function hasExplicitChannelProfilePayload(profile) {
return (
profile &&
typeof profile === "object" &&
Object.keys(profile).length > 0
);
}
function touchChannelProfileCacheEntry(pubkey) {
if (typeof pubkey !== "string" || !pubkey) {
return false;
}
const existing = channelProfileMetadataCache.get(pubkey);
if (!existing) {
return false;
}
channelProfileMetadataCache.set(pubkey, {
timestamp: Date.now(),
profile: { ...existing.profile },
event: existing.event ? { ...existing.event } : null
});
return true;
}
const SUPPORTED_BANNER_REFERRER_POLICIES = new Set([
"no-referrer",
"origin",
@@ -469,7 +499,11 @@ function setChannelZapVisibility(visible) {
typeof app?.isUserLoggedIn === "function"
? app.isUserLoggedIn()
: Boolean(app?.normalizeHexPubkey?.(app?.pubkey));
const shouldShow = !!visible && isLoggedIn;
const sessionActorPubkey = getSessionActorPubkey();
const hasSessionActor = sessionActorPubkey.length > 0;
const sessionOnly = !isLoggedIn && hasSessionActor;
const shouldShow = !!visible && (isLoggedIn || sessionOnly);
const requiresLogin = shouldShow && !isLoggedIn;
if (
shouldShow &&
@@ -480,7 +514,10 @@ function setChannelZapVisibility(visible) {
zapButton.toggleAttribute("hidden", !shouldShow);
zapButton.disabled = !shouldShow;
zapButton.setAttribute("aria-disabled", (!shouldShow).toString());
zapButton.setAttribute(
"aria-disabled",
(requiresLogin || !shouldShow).toString()
);
zapButton.setAttribute("aria-hidden", (!shouldShow).toString());
zapButton.setAttribute("aria-expanded", "false");
if (shouldShow) {
@@ -492,18 +529,24 @@ function setChannelZapVisibility(visible) {
clearZapReceipts();
setZapStatus("", "neutral");
}
if (requiresLogin) {
zapButton.dataset.requiresLogin = "true";
} else {
delete zapButton.dataset.requiresLogin;
}
closeZapControls();
if (controls) {
controls.setAttribute("aria-hidden", "true");
controls.hidden = true;
}
const shouldEnableInputs = shouldShow && !requiresLogin;
if (amountInput) {
amountInput.disabled = !shouldShow;
amountInput.disabled = !shouldEnableInputs;
}
if (sendButton) {
sendButton.disabled = !shouldShow;
sendButton.setAttribute("aria-hidden", (!shouldShow).toString());
if (shouldShow) {
sendButton.disabled = !shouldEnableInputs;
sendButton.setAttribute("aria-hidden", (!shouldEnableInputs).toString());
if (shouldEnableInputs) {
sendButton.removeAttribute("tabindex");
sendButton.removeAttribute("aria-busy");
delete sendButton.dataset.state;
@@ -525,12 +568,31 @@ function setChannelZapVisibility(visible) {
typeof settings?.nwcUri === "string" ? settings.nwcUri.trim() : "";
return uri.length > 0;
})();
setZapWalletPromptVisible(shouldShow && !hasWallet);
if (shouldShow) {
setZapWalletPromptVisible(shouldEnableInputs && !hasWallet);
if (shouldEnableInputs) {
setupZapWalletLink();
}
}
function isSessionActorWithoutLogin() {
const app = getApp();
const isLoggedIn =
typeof app?.isUserLoggedIn === "function"
? app.isUserLoggedIn()
: Boolean(app?.normalizeHexPubkey?.(app?.pubkey) || app?.pubkey);
if (isLoggedIn) {
return false;
}
return getSessionActorPubkey().length > 0;
}
function getSessionActorPubkey() {
return typeof nostrClient?.sessionActor?.pubkey === "string"
? nostrClient.sessionActor.pubkey.trim()
: "";
}
function getChannelShareButton() {
if (
cachedChannelShareButton &&
@@ -804,6 +866,8 @@ function cacheZapPanelElements(panel) {
cachedZapWalletLink = null;
cachedZapCloseBtn = null;
cachedZapSendBtn = null;
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
return;
}
@@ -910,7 +974,20 @@ function setupZapWalletLink() {
event?.preventDefault?.();
const app = getApp();
if (typeof app?.openWalletPane === "function") {
app.openWalletPane();
Promise.resolve()
.then(() => app.openWalletPane())
.catch((error) => {
devLogger.warn(
"[zap] Failed to open wallet pane from channel view:",
error
);
app?.showError?.("Wallet settings are not available right now.");
});
} else {
devLogger.warn(
"[zap] Wallet pane requested but application did not expose openWalletPane()."
);
app?.showError?.("Wallet settings are not available right now.");
}
});
link.dataset.initialized = "true";
@@ -936,24 +1013,61 @@ function isZapControlsOpen() {
function openZapControls({ focus = false } = {}) {
const zapButton = getChannelZapButton();
if (!zapButton || !zapPopover) {
if (!zapButton) {
zapShouldFocusOnOpen = false;
return false;
}
zapShouldFocusOnOpen = focus;
const result = zapPopover.open();
if (result && typeof result.then === "function") {
result.catch(() => {}).finally(() => {
if (!zapPopover || zapPopoverTrigger !== zapButton) {
const initialized = setupZapButton({ force: true });
if (!initialized || !zapPopover || zapPopoverTrigger !== zapButton) {
zapShouldFocusOnOpen = false;
});
return false;
}
}
if (zapPopoverOpenPromise) {
if (focus) {
zapShouldFocusOnOpen = true;
}
return true;
}
zapShouldFocusOnOpen = focus;
let result;
try {
result = zapPopover.open();
} catch (error) {
zapShouldFocusOnOpen = false;
zapPopoverOpenPromise = null;
devLogger.warn("[zap] Failed to open zap popover", error);
return false;
}
if (result && typeof result.then === "function") {
const pending = result
.catch((error) => {
devLogger.warn("[zap] Zap popover open rejected", error);
return false;
})
.finally(() => {
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
});
zapPopoverOpenPromise = pending;
return true;
}
zapShouldFocusOnOpen = false;
zapPopoverOpenPromise = null;
return Boolean(result);
}
function closeZapControls({ focusButton = false } = {}) {
const zapButton = getChannelZapButton();
zapShouldFocusOnOpen = false;
zapPopoverOpenPromise = null;
if (zapPopover) {
const result = zapPopover.close({ restoreFocus: focusButton });
@@ -1636,10 +1750,27 @@ function handleZapButtonClick(event) {
return;
}
const popoverIsOpen =
typeof zapPopover?.isOpen === "function"
? zapPopover.isOpen()
: isZapControlsOpen();
if (isSessionActorWithoutLogin()) {
const app = getApp();
const message = "Log in with your Nostr profile to send zaps.";
app?.showError?.(message);
return;
}
if (!zapPopover || zapPopoverTrigger !== zapButton) {
setupZapButton({ force: true });
}
if (!zapPopover) {
return;
}
if (zapPopoverOpenPromise) {
zapShouldFocusOnOpen = true;
return;
}
const popoverIsOpen = isZapControlsOpen();
if (!popoverIsOpen) {
openZapControls({ focus: true });
@@ -1667,11 +1798,27 @@ async function handleZapSend(event) {
return;
}
if (!zapPopover || zapPopoverTrigger !== zapButton) {
setupZapButton({ force: true });
}
if (!isZapControlsOpen()) {
if (zapPopoverOpenPromise) {
zapShouldFocusOnOpen = true;
return;
}
openZapControls({ focus: true });
return;
}
if (zapPopoverOpenPromise) {
return;
}
if (!zapPopover) {
return;
}
if (!currentChannelLightningAddress) {
const message = "This creator has not configured a Lightning address yet.";
setZapStatus(message, "error");
@@ -2174,6 +2321,8 @@ export async function initChannelProfileView() {
currentChannelNpub = null;
currentChannelLightningAddress = "";
currentChannelProfileEvent = null;
currentChannelProfileSnapshot = null;
currentChannelProfileHasExplicitPayload = false;
setCachedPlatformLightningAddress("");
resetZapRetryState();
clearZapReceipts();
@@ -2296,6 +2445,9 @@ function setupZapButton({ force = false } = {}) {
zapPopover.destroy();
zapPopover = null;
zapPopoverTrigger = null;
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
delete zapButton.dataset.zapPopoverWarmBound;
}
const documentRef =
@@ -2413,13 +2565,22 @@ function setupZapButton({ force = false } = {}) {
});
if (!popover) {
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
return false;
}
const originalOpen = popover.open?.bind(popover);
if (originalOpen) {
popover.open = async (...args) => {
const result = await originalOpen(...args);
let result;
try {
result = await originalOpen(...args);
} catch (error) {
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
throw error;
}
if (result) {
zapControlsOpen = true;
const controls = getZapControlsContainer();
@@ -2454,6 +2615,7 @@ function setupZapButton({ force = false } = {}) {
zapButton.setAttribute("aria-expanded", "false");
}
zapShouldFocusOnOpen = false;
zapPopoverOpenPromise = null;
return result;
};
}
@@ -2466,6 +2628,8 @@ function setupZapButton({ force = false } = {}) {
zapPopover = null;
zapPopoverTrigger = null;
}
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
cacheZapPanelElements(null);
};
}
@@ -2473,6 +2637,49 @@ function setupZapButton({ force = false } = {}) {
zapPopover = popover;
zapPopoverTrigger = zapButton;
const warmZapPopover = () => {
if (!zapPopover || zapPopover !== popover) {
return;
}
if (zapButton.hasAttribute("hidden")) {
return;
}
if (typeof popover.preload !== "function") {
return;
}
try {
popover.preload();
} catch (error) {
devLogger.warn("[zap] Failed to preload zap controls", error);
}
};
if (
typeof popover.preload === "function" &&
!zapButton.hasAttribute("hidden")
) {
if (
typeof window !== "undefined" &&
typeof window.requestIdleCallback === "function"
) {
window.requestIdleCallback(() => warmZapPopover(), { timeout: 250 });
} else {
setTimeout(() => {
warmZapPopover();
}, 0);
}
}
if (zapButton.dataset.zapPopoverWarmBound !== "true") {
const handleWarm = () => {
warmZapPopover();
};
zapButton.addEventListener("pointerenter", handleWarm, { once: true });
zapButton.addEventListener("pointerdown", handleWarm, { once: true });
zapButton.addEventListener("focus", handleWarm, { once: true });
zapButton.dataset.zapPopoverWarmBound = "true";
}
if (zapButton.dataset.initialized !== "true") {
zapButton.addEventListener("click", handleZapButtonClick);
zapButton.dataset.initialized = "true";
@@ -2581,7 +2788,30 @@ function normalizeChannelProfileMetadata(raw = {}) {
function rememberChannelProfile(pubkey, { profile = {}, event = null } = {}) {
if (typeof pubkey !== "string" || !pubkey) {
return;
return false;
}
const hasPayload = hasExplicitChannelProfilePayload(profile);
if (!hasPayload) {
touchChannelProfileCacheEntry(pubkey);
return false;
}
const incomingEventTimestamp =
typeof event?.created_at === "number" ? event.created_at : 0;
const existing = channelProfileMetadataCache.get(pubkey);
const existingEventTimestamp =
typeof existing?.event?.created_at === "number"
? existing.event.created_at
: 0;
if (
existingEventTimestamp &&
incomingEventTimestamp &&
incomingEventTimestamp < existingEventTimestamp
) {
touchChannelProfileCacheEntry(pubkey);
return false;
}
channelProfileMetadataCache.set(pubkey, {
@@ -2589,6 +2819,8 @@ function rememberChannelProfile(pubkey, { profile = {}, event = null } = {}) {
profile: normalizeChannelProfileMetadata(profile),
event: event ? { ...event } : null
});
return true;
}
function getCachedChannelProfile(pubkey) {
@@ -3062,6 +3294,38 @@ function applyChannelProfileMetadata({
}
const normalized = normalizeChannelProfileMetadata(profile);
const hasExplicitProfilePayload =
hasExplicitChannelProfilePayload(profile);
const incomingEventTimestamp =
typeof event?.created_at === "number" ? event.created_at : 0;
const existingEventTimestamp =
typeof currentChannelProfileEvent?.created_at === "number"
? currentChannelProfileEvent.created_at
: 0;
if (currentChannelProfileSnapshot) {
const isOlderEvent =
incomingEventTimestamp &&
existingEventTimestamp &&
incomingEventTimestamp < existingEventTimestamp;
if (isOlderEvent) {
return;
}
if (!hasExplicitProfilePayload) {
if (currentChannelProfileHasExplicitPayload) {
return;
}
const shouldIgnoreEmptyPayload =
!incomingEventTimestamp ||
incomingEventTimestamp <= existingEventTimestamp;
if (shouldIgnoreEmptyPayload) {
return;
}
}
}
const expectedLoadToken =
loadToken !== null ? loadToken : currentProfileLoadToken;
@@ -3152,6 +3416,9 @@ function applyChannelProfileMetadata({
currentChannelProfileEvent = null;
}
currentChannelProfileSnapshot = { ...normalized };
currentChannelProfileHasExplicitPayload = hasExplicitProfilePayload;
if (lightningAddress) {
if (lightningAddress !== previousLightning) {
fetchLightningMetadata(lightningAddress)
@@ -3264,18 +3531,27 @@ async function loadUserProfile(pubkey) {
return;
}
rememberChannelProfile(pubkey, result);
const cachedUpdated = rememberChannelProfile(pubkey, result);
if (typeof app?.setProfileCacheEntry === "function") {
if (cachedUpdated && typeof app?.setProfileCacheEntry === "function") {
try {
app.setProfileCacheEntry(pubkey, {
...result.profile,
lightningAddress:
const profileForCache =
result && typeof result.profile === "object" && result.profile
? { ...result.profile }
: {};
if (!profileForCache.lightningAddress) {
const lightningFallback =
result.profile?.lightningAddress ||
result.profile?.lud16 ||
result.profile?.lud06 ||
""
});
"";
if (lightningFallback) {
profileForCache.lightningAddress = lightningFallback;
}
}
app.setProfileCacheEntry(pubkey, profileForCache);
} catch (error) {
userLogger.warn(
"Failed to persist channel profile metadata to cache:",
@@ -3469,6 +3745,51 @@ window.addEventListener("bitvid:access-control-updated", () => {
});
});
window.addEventListener("bitvid:auth-changed", (event) => {
const hashParams = new URLSearchParams(window.location.hash.slice(1));
if (hashParams.get("view") !== "channel-profile") {
return;
}
if (!currentChannelHex) {
return;
}
const activeNpub = hashParams.get("npub");
if (currentChannelNpub && activeNpub && activeNpub !== currentChannelNpub) {
return;
}
setChannelZapVisibility(Boolean(currentChannelLightningAddress));
syncChannelShareButtonState();
const detail = event?.detail;
if (!detail || typeof detail !== "object") {
return;
}
if (detail.status === "logout") {
zapInFlight = false;
zapPopoverOpenPromise = null;
zapShouldFocusOnOpen = false;
resetZapRetryState();
setZapStatus("", "neutral");
clearZapReceipts();
const amountInput = getZapAmountInput();
if (amountInput) {
amountInput.value = "";
}
return;
}
if (detail.status === "login") {
zapInFlight = false;
resetZapRetryState();
setZapStatus("", "neutral");
return;
}
});
/**
* Keep only the newest version of each video root.
*/
+7
View File
@@ -218,6 +218,13 @@ export class NostrService {
this.emit("videos:cache", { size: map.size });
}
resetVideosCache() {
const map = new Map();
this.videosMap = map;
setStoredVideosMap(map);
this.emit("videos:cache", { size: 0 });
}
shouldIncludeVideo(video, {
blacklistedEventIds = new Set(),
isAuthorBlocked = () => false,
+96 -28
View File
@@ -77,7 +77,10 @@ export class VideoModal {
this.modalZapWalletLink = null;
this.modalZapDialogOpen = false;
this.modalZapPending = false;
this.modalZapRequiresLogin = false;
this.modalZapPopover = null;
this.modalZapOpenPromise = null;
this.modalZapPendingToggle = null;
this.modalMorePopover = null;
this.modalMoreMenuPanel = null;
@@ -237,6 +240,8 @@ export class VideoModal {
this.modalMorePopover.destroy();
}
this.modalZapPopover = null;
this.modalZapOpenPromise = null;
this.modalZapPendingToggle = null;
this.modalMorePopover = null;
this.modalMoreMenuPanel = null;
@@ -414,11 +419,24 @@ export class VideoModal {
return;
}
if (this.modalZapRequiresLogin) {
this.dispatch("zap:open", {
video: this.activeVideo,
requiresLogin: true,
});
return;
}
const popoverIsOpen =
typeof this.modalZapPopover?.isOpen === "function"
? this.modalZapPopover.isOpen()
: this.modalZapDialogOpen === true;
if (this.modalZapOpenPromise) {
this.modalZapPendingToggle = "close";
return;
}
if (popoverIsOpen) {
if (this.modalZapPending) {
return;
@@ -428,6 +446,7 @@ export class VideoModal {
return;
}
this.modalZapPendingToggle = null;
this.openZapDialog();
this.dispatch("zap:open", { video: this.activeVideo });
});
@@ -578,6 +597,8 @@ export class VideoModal {
const wasOpen = popover.isOpen?.() === true;
const result = originalClose(rest);
if (wasOpen && result) {
this.modalZapPendingToggle = null;
this.modalZapOpenPromise = null;
this.modalZapDialog.dataset.state = "closed";
this.modalZapDialog.setAttribute("aria-hidden", "true");
this.modalZapDialog.hidden = true;
@@ -966,13 +987,29 @@ export class VideoModal {
this.shareBtn.classList.toggle("cursor-not-allowed", !enabled);
}
setZapVisibility(visible) {
const shouldShow = !!visible;
setZapVisibility(visible, options = {}) {
let config;
if (typeof visible === "object" && visible !== null) {
config = { ...visible };
} else {
config = {
visible,
...(options && typeof options === "object" ? options : {}),
};
}
const shouldShow = !!config.visible;
const requiresLogin = shouldShow && !!config.requiresLogin;
this.modalZapRequiresLogin = requiresLogin;
if (this.modalZapBtn) {
this.modalZapBtn.toggleAttribute("hidden", !shouldShow);
const disableButton = !shouldShow || this.modalZapPending;
const disableButton =
!shouldShow || (this.modalZapPending && !requiresLogin);
this.modalZapBtn.disabled = disableButton;
this.modalZapBtn.setAttribute("aria-disabled", (!shouldShow).toString());
const ariaDisabledValue =
requiresLogin || !shouldShow ? "true" : "false";
this.modalZapBtn.setAttribute("aria-disabled", ariaDisabledValue);
this.modalZapBtn.setAttribute("aria-hidden", (!shouldShow).toString());
this.modalZapBtn.setAttribute("aria-expanded", "false");
if (shouldShow) {
@@ -980,16 +1017,26 @@ export class VideoModal {
} else {
this.modalZapBtn.setAttribute("tabindex", "-1");
}
if (this.modalZapPending) {
this.modalZapBtn.setAttribute("aria-busy", "true");
this.modalZapBtn.classList.add("opacity-50", "pointer-events-none");
} else {
if (requiresLogin) {
this.modalZapBtn.dataset.requiresLogin = "true";
this.modalZapBtn.removeAttribute("aria-busy");
this.modalZapBtn.classList.remove("opacity-50", "pointer-events-none");
} else {
delete this.modalZapBtn.dataset.requiresLogin;
if (this.modalZapPending) {
this.modalZapBtn.setAttribute("aria-busy", "true");
this.modalZapBtn.classList.add("opacity-50", "pointer-events-none");
} else {
this.modalZapBtn.removeAttribute("aria-busy");
this.modalZapBtn.classList.remove(
"opacity-50",
"pointer-events-none",
);
}
}
}
if (!shouldShow) {
if (!shouldShow || requiresLogin) {
this.closeZapDialog({ silent: true, restoreFocus: false });
}
}
@@ -1007,8 +1054,12 @@ export class VideoModal {
}
async openZapDialog() {
if (this.modalZapPopover?.open) {
try {
if (this.modalZapOpenPromise) {
return this.modalZapOpenPromise;
}
const runOpen = async () => {
if (this.modalZapPopover?.open) {
const opened = await this.modalZapPopover.open();
if (opened) {
this.modalZapDialogOpen = true;
@@ -1019,27 +1070,43 @@ export class VideoModal {
}
this.focusZapAmount();
}
return;
} catch (error) {
this.log("[VideoModal] Failed to open zap popover", error);
return opened;
}
}
if (!this.modalZapDialog) {
return;
}
if (!this.modalZapDialog) {
return false;
}
this.modalZapDialog.hidden = false;
this.modalZapDialog.dataset.state = "open";
this.modalZapDialog.setAttribute("aria-hidden", "false");
this.modalZapDialogOpen = true;
if (this.modalZapBtn) {
this.modalZapBtn.setAttribute("aria-expanded", "true");
}
this.focusZapAmount();
this.modalZapDialog.hidden = false;
this.modalZapDialog.dataset.state = "open";
this.modalZapDialog.setAttribute("aria-hidden", "false");
this.modalZapDialogOpen = true;
if (this.modalZapBtn) {
this.modalZapBtn.setAttribute("aria-expanded", "true");
}
this.focusZapAmount();
return true;
};
const promise = runOpen().catch((error) => {
this.log("[VideoModal] Failed to open zap popover", error);
return false;
});
this.modalZapOpenPromise = promise.finally(() => {
const shouldClose = this.modalZapPendingToggle === "close";
this.modalZapOpenPromise = null;
this.modalZapPendingToggle = null;
if (shouldClose && !this.modalZapPending) {
this.closeZapDialog();
}
});
return this.modalZapOpenPromise;
}
closeZapDialog({ silent = false, restoreFocus } = {}) {
this.modalZapPendingToggle = null;
if (this.modalZapPopover?.close) {
const options = { silent };
if (restoreFocus !== undefined) {
@@ -1292,11 +1359,12 @@ export class VideoModal {
}
if (this.modalZapBtn) {
if (isPending) {
const buttonHidden = this.modalZapBtn.hasAttribute("hidden");
if (isPending && !this.modalZapRequiresLogin) {
this.modalZapBtn.disabled = true;
this.modalZapBtn.setAttribute("aria-busy", "true");
this.modalZapBtn.classList.add("opacity-50", "pointer-events-none");
} else if (!this.modalZapBtn.hasAttribute("hidden")) {
} else if (!buttonHidden) {
this.modalZapBtn.disabled = false;
this.modalZapBtn.removeAttribute("aria-busy");
this.modalZapBtn.classList.remove("opacity-50", "pointer-events-none");
+5
View File
@@ -671,6 +671,10 @@ export function createPopover(trigger, render, options = {}) {
return panel;
}
function preload() {
return Boolean(ensurePanel());
}
async function updatePosition() {
if (!anchor || !panel) {
return;
@@ -918,6 +922,7 @@ export function createPopover(trigger, render, options = {}) {
update: updatePosition,
isOpen: () => isOpen,
getPanel: () => panel,
preload,
};
return api;
+9
View File
@@ -4016,6 +4016,15 @@ export class ProfileModalController {
return;
}
const parentElement = modalRoot.parentElement;
if (parentElement) {
if (parentElement.lastElementChild !== modalRoot) {
parentElement.appendChild(modalRoot);
}
} else if (typeof document !== "undefined" && document.body) {
document.body.appendChild(modalRoot);
}
modalRoot.classList.remove("hidden");
modalRoot.setAttribute("aria-hidden", "false");
this.setGlobalModalState("profile", true);
+19 -3
View File
@@ -33,6 +33,7 @@ export default class ZapController {
nwcSettings,
getActiveNwcSettings,
isUserLoggedIn,
hasSessionActor,
hasActiveWalletConnection,
splitAndZap,
payments = {},
@@ -58,6 +59,8 @@ export default class ZapController {
}
this.isUserLoggedIn =
typeof isUserLoggedIn === "function" ? isUserLoggedIn : () => false;
this.hasSessionActor =
typeof hasSessionActor === "function" ? hasSessionActor : () => false;
if (
this.nwcSettings &&
typeof this.nwcSettings.hasActiveWalletConnection === "function"
@@ -89,11 +92,20 @@ export default class ZapController {
setVisibility(visible) {
const lightningVisible = !!visible;
const shouldShow = lightningVisible && this.isUserLoggedIn();
const loggedIn = this.isUserLoggedIn();
const sessionActorAvailable = this.hasSessionActor();
const shouldShow =
lightningVisible && (loggedIn || sessionActorAvailable);
const requiresLogin = shouldShow && !loggedIn;
const hasWallet = this.hasActiveWalletConnection();
if (this.videoModal) {
this.videoModal.setZapVisibility(shouldShow);
this.videoModal.setWalletPromptVisible(shouldShow && !hasWallet);
this.videoModal.setZapVisibility({
visible: shouldShow,
requiresLogin,
});
this.videoModal.setWalletPromptVisible(
shouldShow && !requiresLogin && !hasWallet,
);
}
}
@@ -117,6 +129,10 @@ export default class ZapController {
open() {
this.resetFeedback();
if (!this.isUserLoggedIn()) {
this.notifyError("Log in with your Nostr profile to send zaps.");
return;
}
this.applyDefaultAmount();
this.preloadLightningMetadata().catch((error) => {
devLogger.warn("[zap] Preload metadata error:", error);