diff --git a/www/js/init-ndk.mjs b/www/js/init-ndk.mjs index 9166a1b..155d319 100644 --- a/www/js/init-ndk.mjs +++ b/www/js/init-ndk.mjs @@ -392,6 +392,7 @@ function handleWorkerMessage(event) { failed: message.failed, latestRelay: message.latestRelay, latestError: message.latestError, + relayUrls: message.relayUrls || [], } })); } else if (message.type === 'startupStatus') { diff --git a/www/js/post-interactions.mjs b/www/js/post-interactions.mjs index c853c06..d2332ad 100644 --- a/www/js/post-interactions.mjs +++ b/www/js/post-interactions.mjs @@ -917,43 +917,65 @@ export function formatTimeAgo(timestamp) { // Store timestamps for real-time updates const timeAgoElements = new Map(); // element -> { timestamp, eventId } -// Track relay publish counts per event ID from broadcastProgress events. -// Keyed by event ID → number of successful relay publishes. -const relayCountByEventId = new Map(); +// Track relay publish info per event ID from broadcastProgress events. +// Keyed by event ID → { count, urls } where urls is an array of relay URLs. +const relayInfoByEventId = new Map(); -// Listen for broadcast progress events to capture final relay counts. +// Listen for broadcast progress events to capture final relay counts and URLs. // The worker emits ndkBroadcastProgress with phase 'done' containing the -// total successful count. We store it so post cards can show "21r - 1h". +// total successful count and the list of relay URLs. We store it so post +// cards can show "21r - 1h" with a hover tooltip listing the relays. if (typeof window !== 'undefined') { window.addEventListener('ndkBroadcastProgress', (event) => { const d = event.detail; if (!d || d.phase !== 'done') return; const eventId = d.eventId; if (!eventId) return; - relayCountByEventId.set(eventId, d.successful || 0); + relayInfoByEventId.set(eventId, { + count: d.successful || 0, + urls: Array.isArray(d.relayUrls) ? d.relayUrls : [], + }); // Update any already-rendered time elements for this event. timeAgoElements.forEach((info, element) => { if (info.eventId === eventId && element.isConnected) { - element.textContent = formatTimeAgoWithRelays(info.timestamp, info.eventId); + updateTimeAgoElement(element, info); } }); }); } /** - * Format time ago with optional relay count prefix. - * Shows "21r - 1h" when a relay count is available, else just "1h". - * @param {number} timestamp - Unix timestamp in seconds - * @param {string} [eventId] - Optional event ID to look up relay count + * Build the hover tooltip text for a relay list. + * Shows "Published to N relays:" followed by the URL list, truncated. + * @param {number} count - Number of successful relays + * @param {string[]} urls - Array of relay URLs * @returns {string} */ -function formatTimeAgoWithRelays(timestamp, eventId) { - const timeStr = formatTimeAgo(timestamp); - if (eventId && relayCountByEventId.has(eventId)) { - const count = relayCountByEventId.get(eventId); - return `${count}r - ${timeStr}`; +function buildRelayTooltip(count, urls) { + if (!count || count === 0) return ''; + const header = `Published to ${count} relay${count === 1 ? '' : 's'}:`; + if (!urls || urls.length === 0) return header; + // Show up to 50 relay URLs in the tooltip; beyond that, show a summary. + if (urls.length <= 50) { + return header + '\n' + urls.join('\n'); + } + return header + '\n' + urls.slice(0, 50).join('\n') + `\n… and ${urls.length - 50} more`; +} + +/** + * Update a time-ago element's text and tooltip from its stored info. + * @param {HTMLElement} element - The element to update + * @param {{timestamp: number, eventId: string}} info - Stored info + */ +function updateTimeAgoElement(element, info) { + const timeStr = formatTimeAgo(info.timestamp); + if (info.eventId && relayInfoByEventId.has(info.eventId)) { + const relayInfo = relayInfoByEventId.get(info.eventId); + element.textContent = `${relayInfo.count}r - ${timeStr}`; + element.title = buildRelayTooltip(relayInfo.count, relayInfo.urls); + } else { + element.textContent = timeStr; } - return timeStr; } /** @@ -964,7 +986,7 @@ function formatTimeAgoWithRelays(timestamp, eventId) { */ export function registerTimeAgo(element, timestamp, eventId) { timeAgoElements.set(element, { timestamp, eventId }); - element.textContent = formatTimeAgoWithRelays(timestamp, eventId); + updateTimeAgoElement(element, { timestamp, eventId }); } /** @@ -974,7 +996,7 @@ export function registerTimeAgo(element, timestamp, eventId) { export function updateTimeAgos() { timeAgoElements.forEach((info, element) => { if (element.isConnected) { - element.textContent = formatTimeAgoWithRelays(info.timestamp, info.eventId); + updateTimeAgoElement(element, info); } else { // Clean up detached elements timeAgoElements.delete(element); diff --git a/www/js/version.json b/www/js/version.json index ed0ad9c..712b981 100644 --- a/www/js/version.json +++ b/www/js/version.json @@ -1,5 +1,5 @@ { - "VERSION": "v0.7.71", - "VERSION_NUMBER": "0.7.71", - "BUILD_DATE": "2026-06-30T13:33:08.793Z" + "VERSION": "v0.7.72", + "VERSION_NUMBER": "0.7.72", + "BUILD_DATE": "2026-06-30T13:37:36.886Z" } diff --git a/www/ndk-worker.js b/www/ndk-worker.js index 3745e97..39d52c0 100644 --- a/www/ndk-worker.js +++ b/www/ndk-worker.js @@ -6315,6 +6315,7 @@ async function handlePublish(requestId, event, port) { successful: publishedSoFar.size, failed: totalFailed, latestRelay: null, + relayUrls: Array.from(publishedSoFar), }); // Clean up listeners so the NDKEvent can be garbage-collected. try { @@ -6330,6 +6331,9 @@ async function handlePublish(requestId, event, port) { // We use the relaySet result to count successful relays. if (!isBroadcast) { const successCount = relaySet ? relaySet.size : 0; + const successUrls = relaySet + ? Array.from(relaySet).map(r => r?.url || '').filter(Boolean) + : []; broadcast({ type: 'broadcastProgress', sessionId: publishSessionId, @@ -6340,6 +6344,7 @@ async function handlePublish(requestId, event, port) { successful: successCount, failed: 0, latestRelay: null, + relayUrls: successUrls, }); }