commit b7db84af605f112f326480ebfb6775a93226ecff
parent d4cb5a739d74a4fde1b4d42c5f54e31034173a65
Author: alltheseas <alltheseas@users.noreply.github.com>
Date: Wed, 22 Oct 2025 10:41:10 -0500
Refine relay pool connections and profile experience
Diffstat:
| M | src/html.rs | | | 63 | ++++++++++++++++++++++++++++++++++++++++++++++++++++----------- |
| M | src/relay_pool.rs | | | 41 | +++++++++++++++++------------------------ |
2 files changed, 69 insertions(+), 35 deletions(-)
diff --git a/src/html.rs b/src/html.rs
@@ -1,7 +1,9 @@
use crate::Error;
use crate::{
abbrev::{abbrev_str, abbreviate},
- render::{NoteAndProfileRenderData, ProfileRenderData},
+ render::{
+ is_image_url, NoteAndProfileRenderData, ProfileRenderData, PROFILE_FEED_RECENT_LIMIT,
+ },
Notecrumbs,
};
use ammonia::Builder as HtmlSanitizer;
@@ -414,21 +416,57 @@ const COPY_NPUB_SCRIPT: &str = r#"
(function() {
'use strict';
var buttons = document.querySelectorAll('[data-copy-npub]');
- if (!navigator.clipboard || buttons.length === 0) {
+ if (buttons.length === 0 || !document.body) {
return;
}
+ function copyWithExecCommand(value) {
+ var textarea = document.createElement('textarea');
+ textarea.value = value;
+ textarea.setAttribute('readonly', '');
+ textarea.style.position = 'fixed';
+ textarea.style.top = '-9999px';
+ document.body.appendChild(textarea);
+ textarea.select();
+ var success = false;
+ try {
+ success = document.execCommand('copy');
+ } catch (err) {
+ success = false;
+ }
+ document.body.removeChild(textarea);
+ return success;
+ }
+ function copyText(value) {
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ return navigator.clipboard.writeText(value);
+ }
+ return new Promise(function(resolve, reject) {
+ if (copyWithExecCommand(value)) {
+ resolve();
+ } else {
+ reject(new Error('copy unsupported'));
+ }
+ });
+ }
Array.prototype.forEach.call(buttons, function(button) {
button.addEventListener('click', function(event) {
var value = event.currentTarget.getAttribute('data-copy-npub');
if (!value) {
return;
}
- navigator.clipboard.writeText(value).then(function() {
- event.currentTarget.textContent = 'Copied!';
- setTimeout(function() {
- event.currentTarget.textContent = 'Copy npub';
- }, 1500);
- });
+ copyText(value)
+ .then(function() {
+ event.currentTarget.textContent = 'Copied!';
+ setTimeout(function() {
+ event.currentTarget.textContent = 'Copy npub';
+ }, 1500);
+ })
+ .catch(function() {
+ event.currentTarget.textContent = 'Copy failed';
+ setTimeout(function() {
+ event.currentTarget.textContent = 'Copy npub';
+ }, 1500);
+ });
});
});
}());
@@ -791,10 +829,13 @@ pub fn serve_profile_html(
let note_filter = nostrdb::Filter::new()
.authors(author_ref)
.kinds([1])
- .limit(20)
+ .limit(PROFILE_FEED_RECENT_LIMIT as u64)
.build();
- if let Ok(results) = app.ndb.query(&txn, &[note_filter], 20) {
+ if let Ok(results) = app
+ .ndb
+ .query(&txn, &[note_filter], PROFILE_FEED_RECENT_LIMIT as i32)
+ {
let mut entries = Vec::new();
for res in results {
@@ -824,7 +865,7 @@ pub fn serve_profile_html(
}
entries.sort_by(|a, b| b.0.cmp(&a.0));
- entries.truncate(6);
+ entries.truncate(PROFILE_FEED_RECENT_LIMIT);
for (timestamp_value, note_body_html, note_link_attr) in entries {
let _ = write!(
diff --git a/src/relay_pool.rs b/src/relay_pool.rs
@@ -12,7 +12,7 @@ use tracing::{debug, warn};
pub struct RelayPool {
client: Client,
known_relays: Arc<Mutex<HashSet<String>>>,
- default_relays: Arc<Vec<RelayUrl>>,
+ default_relays: Arc<[RelayUrl]>,
connect_timeout: Duration,
}
@@ -34,21 +34,22 @@ impl RelayPool {
})
.collect();
+ let default_relays = Arc::<[RelayUrl]>::from(parsed_defaults);
let pool = Self {
client,
known_relays: Arc::new(Mutex::new(HashSet::new())),
- default_relays: Arc::new(parsed_defaults),
+ default_relays: default_relays.clone(),
connect_timeout,
};
- pool.ensure_relays(pool.default_relays()).await?;
- pool.connect_known_relays().await?;
+ pool.ensure_relays(pool.default_relays().iter().cloned())
+ .await?;
Ok(pool)
}
- pub fn default_relays(&self) -> Vec<RelayUrl> {
- self.default_relays.as_ref().clone()
+ pub fn default_relays(&self) -> &[RelayUrl] {
+ self.default_relays.as_ref()
}
pub async fn ensure_relays<I>(&self, relays: I) -> Result<(), Error>
@@ -56,24 +57,33 @@ impl RelayPool {
I: IntoIterator<Item = RelayUrl>,
{
let mut new_relays = Vec::new();
+ let mut had_new = false;
{
let mut guard = self.known_relays.lock().await;
for relay in relays {
let key = relay.to_string();
if guard.insert(key) {
new_relays.push(relay);
+ had_new = true;
}
}
}
for relay in new_relays {
debug!("adding relay {}", relay);
- self.client.add_relay(relay.clone()).await?;
+ self.client
+ .add_relay(relay.clone())
+ .await
+ .map_err(|err| Error::Generic(format!("failed to add relay {relay}: {err}")))?;
if let Err(err) = self.client.connect_relay(relay.clone()).await {
warn!("failed to connect relay {}: {}", relay, err);
}
}
+ if had_new {
+ self.client.connect_with_timeout(self.connect_timeout).await;
+ }
+
Ok(())
}
@@ -83,8 +93,6 @@ impl RelayPool {
relays: &[RelayUrl],
timeout: Duration,
) -> Result<ReceiverStream<Event>, Error> {
- self.client.connect_with_timeout(self.connect_timeout).await;
-
if relays.is_empty() {
Ok(self.client.stream_events(filters, Some(timeout)).await?)
} else {
@@ -95,19 +103,4 @@ impl RelayPool {
.await?)
}
}
-
- async fn connect_known_relays(&self) -> Result<(), Error> {
- let relays = {
- let guard = self.known_relays.lock().await;
- guard.iter().cloned().collect::<Vec<_>>()
- };
-
- if relays.is_empty() {
- return Ok(());
- }
-
- self.client.connect_with_timeout(self.connect_timeout).await;
-
- Ok(())
- }
}