diff --git a/web/superball-shared.css b/web/superball-shared.css
index f0417ee..0891859 100644
--- a/web/superball-shared.css
+++ b/web/superball-shared.css
@@ -418,6 +418,8 @@ small {
border-radius: var(--border-radius);
filter: grayscale(100%);
transition: filter 0.3s ease;
+ object-fit: cover;
+ object-position: center;
}
#thrower-banner:hover {
@@ -431,6 +433,8 @@ small {
border: var(--border-width) solid var(--primary-color);
filter: grayscale(100%);
transition: filter 0.3s ease;
+ object-fit: cover;
+ object-position: center;
}
#thrower-icon:hover {
@@ -449,6 +453,8 @@ small {
border: var(--border-width) solid var(--primary-color);
filter: grayscale(100%);
transition: filter 0.3s ease;
+ object-fit: cover;
+ object-position: center;
}
#profile-picture:hover {
diff --git a/web/superball.html b/web/superball.html
index 0a621aa..0d0dc4c 100644
--- a/web/superball.html
+++ b/web/superball.html
@@ -542,7 +542,7 @@
${thrower.icon ? `
-

+
` : ''}
@@ -762,16 +762,17 @@
select.removeChild(select.lastChild);
}
- // Add discovered throwers
+ // Add discovered throwers with real-time online status check
discoveredThrowers.forEach(thrower => {
const option = document.createElement('option');
option.value = thrower.pubkey;
+ // Always check current online status when populating
const onlineStatus = isThrowerOnline(thrower) ? 'š¢' : 'š“';
option.textContent = `${onlineStatus} ${thrower.name} (${thrower.pubkey.substring(0, 8)}...)`;
select.appendChild(option);
});
- console.log('INFO', `Populated thrower dropdown for bounce ${bounceId} with ${discoveredThrowers.length} throwers`);
+ console.log('INFO', `Populated thrower dropdown for bounce ${bounceId} with ${discoveredThrowers.length} throwers (with current online status)`);
}
// Update all existing thrower dropdowns with current online status
@@ -904,7 +905,7 @@
return null;
}
- // Populate relay dropdown with relays the selected thrower can read from
+ // Populate relay dropdown with relays the selected thrower can throw to (write to)
function populateRelayDropdown(bounceId, throwerPubkey) {
const relaySelect = document.getElementById(`relay-select-${bounceId}`);
const relayInput = document.getElementById(`bounce-relays-${bounceId}`);
@@ -927,17 +928,15 @@
return;
}
- // Get relays the thrower can read from (read or both)
- const readableRelays = thrower.relayList.relays.filter(r => r.type === 'read' || r.type === 'both');
- // Get relays the thrower can write to (write or both)
+ // Get relays the thrower can write to (write or both) - these are the relays it can throw to
const writableRelays = thrower.relayList.relays.filter(r => r.type === 'write' || r.type === 'both');
- if (readableRelays.length === 0) {
- relaySelect.innerHTML = '
';
- // Show manual option since thrower has no readable relays
+ if (writableRelays.length === 0) {
+ relaySelect.innerHTML = '
';
+ // Show manual option since thrower has no writable relays
const manualOption = relaySelect.querySelector('option[value="__manual__"]');
manualOption.classList.remove('hidden');
- console.log('WARN', `Thrower ${thrower.name} cannot read from any relays`);
+ console.log('WARN', `Thrower ${thrower.name} cannot throw to any relays`);
return;
}
@@ -949,8 +948,8 @@
relaySelect.appendChild(allRelaysOption);
}
- // Add readable relays to dropdown
- readableRelays.forEach(relay => {
+ // Add writable relays to dropdown (these are the relays the thrower can throw to)
+ writableRelays.forEach(relay => {
const option = document.createElement('option');
option.value = relay.url;
option.textContent = relay.url;
@@ -967,7 +966,7 @@
relayInput.value = '';
relayManualDiv.classList.add('hidden');
- console.log('INFO', `Populated relay dropdown for bounce ${bounceId} with ${readableRelays.length} readable relays and ${writableRelays.length} writable relays from ${thrower.name}`);
+ console.log('INFO', `Populated relay dropdown for bounce ${bounceId} with ${writableRelays.length} writable relays that ${thrower.name} can throw to`);
}
// Clear relay dropdown
@@ -1142,7 +1141,7 @@
@@ -71,7 +71,7 @@
-
+
-
+
@@ -616,35 +616,95 @@
}
- // Add new relay
+ // Add new relay (supports comma-separated list)
function addRelay() {
- const url = document.getElementById('new-relay-url').value.trim();
+ const input = document.getElementById('new-relay-url').value.trim();
const type = document.getElementById('new-relay-type').value;
- if (!url) {
+ if (!input) {
showStatus('relay-status', 'Please enter a relay URL', 'error');
return;
}
- if (!url.startsWith('wss://') && !url.startsWith('ws://')) {
- showStatus('relay-status', 'Relay URL must start with wss:// or ws://', 'error');
- return;
- }
+ // Check if input contains commas (multiple URLs)
+ const urls = input.includes(',') ?
+ input.split(',').map(url => url.trim()).filter(url => url.length > 0) :
+ [input];
- // Check for duplicates
- if (currentRelays.some(r => r.url === url)) {
- showStatus('relay-status', 'Relay already exists', 'error');
- return;
- }
+ const results = {
+ added: [],
+ failed: [],
+ duplicates: []
+ };
- currentRelays.push({ url, type });
+ // Process each URL
+ urls.forEach(url => {
+ // Validate URL format
+ if (!url.startsWith('wss://') && !url.startsWith('ws://')) {
+ results.failed.push({ url, reason: 'Must start with wss:// or ws://' });
+ return;
+ }
+
+ // Check for duplicates
+ if (currentRelays.some(r => r.url === url)) {
+ results.duplicates.push(url);
+ return;
+ }
+
+ // Add relay
+ currentRelays.push({ url, type, authStatus: 'unknown', lastTested: null });
+ results.added.push(url);
+ });
+
+ // Update display
displayRelayList();
// Clear form
document.getElementById('new-relay-url').value = '';
document.getElementById('new-relay-type').value = '';
- showStatus('relay-status', 'Relay added (remember to save)', 'info');
+ // Provide detailed feedback
+ const messages = [];
+
+ if (results.added.length > 0) {
+ const typeText = type ? ` (${type === 'read' ? 'Read only' : type === 'write' ? 'Write only' : 'Both'})` : ' (Both)';
+ if (results.added.length === 1) {
+ messages.push(`ā
Added: ${results.added[0]}${typeText}`);
+ } else {
+ messages.push(`ā
Added ${results.added.length} relays${typeText}:`);
+ results.added.forEach(url => messages.push(` ⢠${url}`));
+ }
+ }
+
+ if (results.duplicates.length > 0) {
+ if (results.duplicates.length === 1) {
+ messages.push(`ā ļø Duplicate skipped: ${results.duplicates[0]}`);
+ } else {
+ messages.push(`ā ļø ${results.duplicates.length} duplicates skipped:`);
+ results.duplicates.forEach(url => messages.push(` ⢠${url}`));
+ }
+ }
+
+ if (results.failed.length > 0) {
+ if (results.failed.length === 1) {
+ messages.push(`ā Failed: ${results.failed[0].url} (${results.failed[0].reason})`);
+ } else {
+ messages.push(`ā ${results.failed.length} failed:`);
+ results.failed.forEach(item => messages.push(` ⢠${item.url} (${item.reason})`));
+ }
+ }
+
+ // Show comprehensive status
+ const statusMessage = messages.join('\n');
+ const statusType = results.added.length > 0 ? 'info' :
+ results.failed.length > 0 ? 'error' : 'info';
+
+ // Add reminder to save if any were added
+ const finalMessage = results.added.length > 0 ?
+ statusMessage + '\n\nš¾ Remember to save your relay configuration!' :
+ statusMessage;
+
+ showStatus('relay-status', finalMessage, statusType);
}
// Remove relay
@@ -1067,7 +1127,7 @@
version: '1.0.0',
privacyPolicy: '',
termsOfService: '',
- refreshRate: 60,
+ refreshRate: 300,
content: event.content || ''
};
@@ -1084,7 +1144,7 @@
else if (tag[0] === 'version') currentThrowerInfo.version = tag[1] || '1.0.0';
else if (tag[0] === 'privacy_policy') currentThrowerInfo.privacyPolicy = tag[1] || '';
else if (tag[0] === 'terms_of_service') currentThrowerInfo.termsOfService = tag[1] || '';
- else if (tag[0] === 'refresh_rate') currentThrowerInfo.refreshRate = parseInt(tag[1]) || 60;
+ else if (tag[0] === 'refresh_rate') currentThrowerInfo.refreshRate = parseInt(tag[1]) || 300;
});
lastThrowerInfoPublish = event.created_at;
@@ -1103,7 +1163,7 @@
version: '1.0.0',
privacyPolicy: '',
termsOfService: '',
- refreshRate: 60,
+ refreshRate: 300,
content: ''
};
displayThrowerInfo(currentThrowerInfo);
@@ -1148,7 +1208,7 @@
document.getElementById('edit-version').value = currentThrowerInfo.version || '1.0.0';
document.getElementById('edit-privacy-policy').value = currentThrowerInfo.privacyPolicy || '';
document.getElementById('edit-terms-service').value = currentThrowerInfo.termsOfService || '';
- document.getElementById('edit-refresh-rate').value = currentThrowerInfo.refreshRate || 60;
+ document.getElementById('edit-refresh-rate').value = currentThrowerInfo.refreshRate || 300;
document.getElementById('edit-thrower-content').value = currentThrowerInfo.content || '';
}
@@ -1172,7 +1232,7 @@
const version = document.getElementById('edit-version').value.trim();
const privacyPolicy = document.getElementById('edit-privacy-policy').value.trim();
const termsOfService = document.getElementById('edit-terms-service').value.trim();
- const refreshRate = parseInt(document.getElementById('edit-refresh-rate').value) || 60;
+ const refreshRate = parseInt(document.getElementById('edit-refresh-rate').value) || 300;
const content = document.getElementById('edit-thrower-content').value.trim();
try {