update
This commit is contained in:
20
src-tauri/Cargo.lock
generated
20
src-tauri/Cargo.lock
generated
@@ -3081,7 +3081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3480,7 +3480,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -3510,7 +3510,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -3524,7 +3524,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heed",
|
"heed",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3537,7 +3537,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3555,7 +3555,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"atomic-destructor",
|
"atomic-destructor",
|
||||||
@@ -3575,7 +3575,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-signer"
|
name = "nostr-signer"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3588,7 +3588,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-zapper"
|
name = "nostr-zapper"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3732,7 +3732,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nwc"
|
name = "nwc"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#a398775dd1013482f785816e6b9fe99f7668416a"
|
source = "git+https://github.com/rust-nostr/nostr#370d044e5b0005b534dc82b48fe69acebac02d0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -7223,7 +7223,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
wss://relay.damus.io,
|
wss://relay.damus.io,
|
||||||
wss://relay.nostr.net,
|
|
||||||
wss://relay.primal.net,
|
wss://relay.primal.net,
|
||||||
wss://nostr.fmt.wiz.biz,
|
wss://nostr.fmt.wiz.biz,
|
||||||
|
|||||||
@@ -31,7 +31,28 @@ pub async fn get_event(id: String, state: State<'_, Nostr>) -> Result<RichEvent,
|
|||||||
|
|
||||||
Ok(RichEvent { raw, parsed })
|
Ok(RichEvent { raw, parsed })
|
||||||
} else {
|
} else {
|
||||||
Err("Event not found".to_string())
|
match client
|
||||||
|
.fetch_events(
|
||||||
|
vec![Filter::new().id(event_id)],
|
||||||
|
Some(Duration::from_secs(5)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(events) => {
|
||||||
|
if let Some(event) = events.iter().next() {
|
||||||
|
let raw = event.as_json();
|
||||||
|
let parsed = if event.kind == Kind::TextNote {
|
||||||
|
Some(parse_event(&event.content).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(RichEvent { raw, parsed })
|
||||||
|
} else {
|
||||||
|
Err("Event not found.".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(err.to_string()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
@@ -49,13 +70,16 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
|
|||||||
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
|
||||||
let filter = Filter::new().kinds(vec![Kind::TextNote]).event(event_id);
|
|
||||||
|
let filter = Filter::new()
|
||||||
|
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
||||||
|
.event(event_id);
|
||||||
|
|
||||||
match client
|
match client
|
||||||
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, true).await),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +122,7 @@ pub async fn get_all_events_by_author(
|
|||||||
.limit(limit as usize);
|
.limit(limit as usize);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +153,7 @@ pub async fn get_all_events_by_authors(
|
|||||||
.authors(authors);
|
.authors(authors);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +182,7 @@ pub async fn get_all_events_by_hashtags(
|
|||||||
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +206,7 @@ pub async fn get_local_events(
|
|||||||
.until(as_of);
|
.until(as_of);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,11 +229,8 @@ pub async fn get_global_events(
|
|||||||
.limit(FETCH_LIMIT)
|
.limit(FETCH_LIMIT)
|
||||||
.until(as_of);
|
.until(as_of);
|
||||||
|
|
||||||
match client
|
match client.database().query(vec![filter]).await {
|
||||||
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +355,13 @@ pub async fn is_reposted(id: String, state: State<'_, Nostr>) -> Result<bool, St
|
|||||||
|
|
||||||
let authors: Vec<PublicKey> = accounts
|
let authors: Vec<PublicKey> = accounts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|acc| PublicKey::from_str(acc).unwrap())
|
.filter_map(|acc| {
|
||||||
|
if let Ok(pk) = PublicKey::from_str(acc) {
|
||||||
|
Some(pk)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
@@ -452,7 +479,7 @@ pub async fn search(query: String, state: State<'_, Nostr>) -> Result<Vec<RichEv
|
|||||||
let filter = Filter::new().search(query);
|
let filter = Filter::new().search(query);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,34 @@ pub async fn get_profile(id: String, state: State<'_, Nostr>) -> Result<String,
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
match client.database().profile(public_key).await {
|
let filter = Filter::new()
|
||||||
Ok(profile) => Ok(profile.metadata().as_json()),
|
.author(public_key)
|
||||||
|
.kind(Kind::Metadata)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
match client.database().query(vec![filter.clone()]).await {
|
||||||
|
Ok(events) => {
|
||||||
|
if let Some(event) = events.iter().next() {
|
||||||
|
let metadata = Metadata::from_json(&event.content).map_err(|e| e.to_string())?;
|
||||||
|
Ok(metadata.as_json())
|
||||||
|
} else {
|
||||||
|
match client
|
||||||
|
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(events) => {
|
||||||
|
if let Some(event) = events.iter().next() {
|
||||||
|
let metadata =
|
||||||
|
Metadata::from_json(&event.content).map_err(|e| e.to_string())?;
|
||||||
|
Ok(metadata.as_json())
|
||||||
|
} else {
|
||||||
|
Err("Profile not found.".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(err.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +229,13 @@ pub async fn set_group(
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_keys: Vec<PublicKey> = users
|
let public_keys: Vec<PublicKey> = users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|u| PublicKey::from_str(u).unwrap())
|
.filter_map(|u| {
|
||||||
|
if let Ok(pk) = PublicKey::from_str(u) {
|
||||||
|
Some(pk)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let label = title.to_lowercase().replace(" ", "-");
|
let label = title.to_lowercase().replace(" ", "-");
|
||||||
let mut tags: Vec<Tag> = vec![Tag::title(title)];
|
let mut tags: Vec<Tag> = vec![Tag::title(title)];
|
||||||
@@ -237,7 +269,7 @@ pub async fn set_group(
|
|||||||
.authors(public_keys)
|
.authors(public_keys)
|
||||||
.limit(500);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(report) = client.sync(filter, SyncOptions::default()).await {
|
if let Ok(report) = client.sync(filter, &SyncOptions::default()).await {
|
||||||
println!("Received: {}", report.received.len());
|
println!("Received: {}", report.received.len());
|
||||||
handle.emit("synchronized", ()).unwrap();
|
handle.emit("synchronized", ()).unwrap();
|
||||||
};
|
};
|
||||||
@@ -283,7 +315,7 @@ pub async fn get_all_groups(state: State<'_, Nostr>) -> Result<Vec<RichEvent>, S
|
|||||||
let filter = Filter::new().kind(Kind::FollowSet).authors(authors);
|
let filter = Filter::new().kind(Kind::FollowSet).authors(authors);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,7 +363,7 @@ pub async fn set_interest(
|
|||||||
.hashtags(hashtags)
|
.hashtags(hashtags)
|
||||||
.limit(500);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(report) = client.sync(filter, SyncOptions::default()).await {
|
if let Ok(report) = client.sync(filter, &SyncOptions::default()).await {
|
||||||
println!("Received: {}", report.received.len());
|
println!("Received: {}", report.received.len());
|
||||||
handle.emit("synchronized", ()).unwrap();
|
handle.emit("synchronized", ()).unwrap();
|
||||||
};
|
};
|
||||||
@@ -381,7 +413,7 @@ pub async fn get_all_interests(state: State<'_, Nostr>) -> Result<Vec<RichEvent>
|
|||||||
.authors(authors);
|
.authors(authors);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client.database().query(vec![filter]).await {
|
||||||
Ok(events) => Ok(process_event(client, events).await),
|
Ok(events) => Ok(process_event(client, events, false).await),
|
||||||
Err(e) => Err(e.to_string()),
|
Err(e) => Err(e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -560,14 +592,20 @@ pub async fn get_notifications(id: String, state: State<'_, Nostr>) -> Result<Ve
|
|||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
let public_key = PublicKey::from_str(&id).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let filter = Filter::new().pubkey(public_key).kinds(vec![
|
let filter = Filter::new()
|
||||||
Kind::TextNote,
|
.pubkey(public_key)
|
||||||
Kind::Repost,
|
.kinds(vec![
|
||||||
Kind::Reaction,
|
Kind::TextNote,
|
||||||
Kind::ZapReceipt,
|
Kind::Repost,
|
||||||
]);
|
Kind::Reaction,
|
||||||
|
Kind::ZapReceipt,
|
||||||
|
])
|
||||||
|
.limit(200);
|
||||||
|
|
||||||
match client.database().query(vec![filter]).await {
|
match client
|
||||||
|
.fetch_events(vec![filter], Some(Duration::from_secs(5)))
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
|
Ok(events) => Ok(events.into_iter().map(|ev| ev.as_json()).collect()),
|
||||||
Err(err) => Err(err.to_string()),
|
Err(err) => Err(err.to_string()),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use nostr_sdk::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
@@ -52,20 +53,18 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
//
|
//
|
||||||
if let Ok(events) = client
|
if let Ok(events) = client
|
||||||
.database()
|
.database()
|
||||||
.query(vec![Filter::new().kinds(vec![
|
.query(vec![Filter::new()
|
||||||
Kind::ContactList,
|
.authors(public_keys)
|
||||||
Kind::FollowSet,
|
.kinds(vec![Kind::ContactList, Kind::FollowSet])])
|
||||||
Kind::MuteList,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::TextNote,
|
|
||||||
])])
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let pubkeys: Vec<PublicKey> = events
|
let set: HashSet<PublicKey> = events
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|ev| ev.tags.public_keys().copied())
|
.flat_map(|ev| ev.tags.public_keys().copied())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let pubkeys: Vec<PublicKey> = set.into_iter().collect();
|
||||||
|
|
||||||
for chunk in pubkeys.chunks(500) {
|
for chunk in pubkeys.chunks(500) {
|
||||||
if chunk.is_empty() {
|
if chunk.is_empty() {
|
||||||
break;
|
break;
|
||||||
@@ -78,10 +77,10 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
let events = Filter::new()
|
let events = Filter::new()
|
||||||
.authors(authors.clone())
|
.authors(authors.clone())
|
||||||
.kinds(vec![Kind::TextNote, Kind::Repost])
|
.kinds(vec![Kind::TextNote, Kind::Repost])
|
||||||
.limit(1000);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(output) = client
|
if let Ok(output) = client
|
||||||
.sync_with(&bootstrap_relays, events, SyncOptions::default())
|
.sync_with(&bootstrap_relays, events, &SyncOptions::default())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
NegentropyEvent {
|
NegentropyEvent {
|
||||||
@@ -94,21 +93,20 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
|
|
||||||
// NEG: Sync metadata
|
// NEG: Sync metadata
|
||||||
//
|
//
|
||||||
let metadata = Filter::new()
|
let events = Filter::new()
|
||||||
.authors(authors)
|
.authors(authors.clone())
|
||||||
.kinds(vec![
|
.kinds(vec![
|
||||||
Kind::Metadata,
|
Kind::Metadata,
|
||||||
Kind::ContactList,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
Kind::InterestSet,
|
||||||
|
Kind::Interests,
|
||||||
Kind::FollowSet,
|
Kind::FollowSet,
|
||||||
Kind::MuteList,
|
Kind::EventDeletion,
|
||||||
Kind::RelaySet,
|
Kind::Custom(30315),
|
||||||
])
|
])
|
||||||
.limit(1000);
|
.limit(500);
|
||||||
|
|
||||||
if let Ok(output) = client
|
if let Ok(output) = client
|
||||||
.sync_with(&bootstrap_relays, metadata, SyncOptions::default())
|
.sync_with(&bootstrap_relays, events, &SyncOptions::default())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
NegentropyEvent {
|
NegentropyEvent {
|
||||||
@@ -120,62 +118,6 @@ pub fn sync_all(accounts: Vec<String>, app_handle: AppHandle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEG: Sync notification
|
|
||||||
//
|
|
||||||
let notification = Filter::new()
|
|
||||||
.pubkeys(public_keys.clone())
|
|
||||||
.kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Reaction,
|
|
||||||
Kind::ZapReceipt,
|
|
||||||
])
|
|
||||||
.limit(500);
|
|
||||||
|
|
||||||
if let Ok(output) = client
|
|
||||||
.sync_with(&bootstrap_relays, notification, SyncOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Notification,
|
|
||||||
total_event: output.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEG: Sync metadata
|
|
||||||
//
|
|
||||||
let metadata = Filter::new().authors(public_keys.clone()).kinds(vec![
|
|
||||||
Kind::Metadata,
|
|
||||||
Kind::ContactList,
|
|
||||||
Kind::Interests,
|
|
||||||
Kind::InterestSet,
|
|
||||||
Kind::FollowSet,
|
|
||||||
Kind::RelayList,
|
|
||||||
Kind::MuteList,
|
|
||||||
Kind::EventDeletion,
|
|
||||||
Kind::Bookmarks,
|
|
||||||
Kind::BookmarkSet,
|
|
||||||
Kind::Emojis,
|
|
||||||
Kind::EmojiSet,
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Custom(30315),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if let Ok(output) = client
|
|
||||||
.sync_with(&bootstrap_relays, metadata, SyncOptions::default())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
NegentropyEvent {
|
|
||||||
kind: NegentropyKind::Others,
|
|
||||||
total_event: output.received.len() as i32,
|
|
||||||
}
|
|
||||||
.emit(&app_handle)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,10 +177,7 @@ pub async fn sync_account(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Ok(output) = client
|
if let Ok(output) = client.sync_with(&bootstrap_relays, filter, &opts).await {
|
||||||
.sync_with(&bootstrap_relays, filter, opts.clone())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Success: {:?}", output.success);
|
println!("Success: {:?}", output.success);
|
||||||
println!("Failed: {:?}", output.failed);
|
println!("Failed: {:?}", output.failed);
|
||||||
|
|
||||||
@@ -279,37 +218,13 @@ pub async fn sync_account(
|
|||||||
])
|
])
|
||||||
.limit(10000);
|
.limit(10000);
|
||||||
|
|
||||||
if let Ok(output) = client
|
if let Ok(output) = client.sync_with(&bootstrap_relays, filter, &opts).await {
|
||||||
.sync_with(&bootstrap_relays, filter, opts.clone())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Success: {:?}", output.success);
|
println!("Success: {:?}", output.success);
|
||||||
println!("Failed: {:?}", output.failed);
|
println!("Failed: {:?}", output.failed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let event_ids = client
|
|
||||||
.database()
|
|
||||||
.query(vec![Filter::new().kinds(vec![
|
|
||||||
Kind::TextNote,
|
|
||||||
Kind::Repost,
|
|
||||||
Kind::Bookmarks,
|
|
||||||
Kind::BookmarkSet,
|
|
||||||
])])
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
if !event_ids.is_empty() {
|
|
||||||
let ids: Vec<EventId> = event_ids.iter().map(|ev| ev.id).collect();
|
|
||||||
let filter = Filter::new().events(ids);
|
|
||||||
|
|
||||||
if let Ok(output) = client.sync_with(&bootstrap_relays, filter, opts).await {
|
|
||||||
println!("Success: {:?}", output.success);
|
|
||||||
println!("Failed: {:?}", output.failed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let config_dir = app_handle
|
let config_dir = app_handle
|
||||||
.path()
|
.path()
|
||||||
.app_config_dir()
|
.app_config_dir()
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use tauri::window::Effect;
|
|||||||
use tauri::TitleBarStyle;
|
use tauri::TitleBarStyle;
|
||||||
use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl};
|
use tauri::{LogicalPosition, LogicalSize, Manager, WebviewUrl};
|
||||||
use tauri::{WebviewBuilder, WebviewWindowBuilder};
|
use tauri::{WebviewBuilder, WebviewWindowBuilder};
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
use tauri_plugin_decorum::WebviewWindowExt;
|
use tauri_plugin_decorum::WebviewWindowExt;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Type)]
|
#[derive(Serialize, Deserialize, Type)]
|
||||||
@@ -35,9 +36,9 @@ pub struct Column {
|
|||||||
height: f32,
|
height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result<String, String> {
|
pub async fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result<String, String> {
|
||||||
match app_handle.get_window("main") {
|
match app_handle.get_window("main") {
|
||||||
Some(main_window) => match app_handle.get_webview(&column.label) {
|
Some(main_window) => match app_handle.get_webview(&column.label) {
|
||||||
Some(_) => Ok(column.label),
|
Some(_) => Ok(column.label),
|
||||||
@@ -63,18 +64,18 @@ pub fn create_column(column: Column, app_handle: tauri::AppHandle) -> Result<Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn close_column(label: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
|
pub async fn close_column(label: String, app_handle: tauri::AppHandle) -> Result<bool, String> {
|
||||||
match app_handle.get_webview(&label) {
|
match app_handle.get_webview(&label) {
|
||||||
Some(webview) => Ok(webview.close().is_ok()),
|
Some(webview) => Ok(webview.close().is_ok()),
|
||||||
None => Err("Cannot close, column not found.".into()),
|
None => Err("Cannot close, column not found.".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn update_column(
|
pub async fn update_column(
|
||||||
label: String,
|
label: String,
|
||||||
width: f32,
|
width: f32,
|
||||||
height: f32,
|
height: f32,
|
||||||
@@ -98,9 +99,9 @@ pub fn update_column(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command(async)]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
pub async fn reload_column(label: String, app_handle: tauri::AppHandle) -> Result<(), String> {
|
||||||
match app_handle.get_webview(&label) {
|
match app_handle.get_webview(&label) {
|
||||||
Some(webview) => {
|
Some(webview) => {
|
||||||
webview.eval("location.reload(true)").unwrap();
|
webview.eval("location.reload(true)").unwrap();
|
||||||
@@ -124,6 +125,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<Strin
|
|||||||
|
|
||||||
Ok(current_window.label().to_string())
|
Ok(current_window.label().to_string())
|
||||||
} else {
|
} else {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let new_window = WebviewWindowBuilder::new(
|
let new_window = WebviewWindowBuilder::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&window.label,
|
&window.label,
|
||||||
@@ -147,27 +149,7 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<Strin
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Restore native border
|
#[cfg(target_os = "windows")]
|
||||||
new_window.add_border(None);
|
|
||||||
|
|
||||||
Ok(new_window.label().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command(async)]
|
|
||||||
#[specta::specta]
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<String, String> {
|
|
||||||
if let Some(current_window) = app_handle.get_window(&window.label) {
|
|
||||||
if current_window.is_visible().unwrap_or_default() {
|
|
||||||
let _ = current_window.set_focus();
|
|
||||||
} else {
|
|
||||||
let _ = current_window.show();
|
|
||||||
let _ = current_window.set_focus();
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(current_window.label().to_string())
|
|
||||||
} else {
|
|
||||||
let new_window = WebviewWindowBuilder::new(
|
let new_window = WebviewWindowBuilder::new(
|
||||||
&app_handle,
|
&app_handle,
|
||||||
&window.label,
|
&window.label,
|
||||||
@@ -190,58 +172,14 @@ pub fn open_window(window: Window, app_handle: tauri::AppHandle) -> Result<Strin
|
|||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Set decoration
|
|
||||||
new_window.create_overlay_titlebar().unwrap();
|
|
||||||
|
|
||||||
Ok(new_window.label().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub fn reopen_lume(app: tauri::AppHandle) {
|
|
||||||
if let Some(window) = app.get_window("main") {
|
|
||||||
if window.is_visible().unwrap_or_default() {
|
|
||||||
let _ = window.set_focus();
|
|
||||||
} else {
|
|
||||||
let _ = window.show();
|
|
||||||
let _ = window.set_focus();
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
let window =
|
|
||||||
WebviewWindowBuilder::from_config(&app, app.config().app.windows.first().unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Set decoration
|
// Set decoration
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
window.create_overlay_titlebar().unwrap();
|
new_window.create_overlay_titlebar().unwrap();
|
||||||
|
|
||||||
// Restore native border
|
// Restore native border
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
window.add_border(None);
|
new_window.add_border(None);
|
||||||
|
|
||||||
// Set a custom inset to the traffic lights
|
Ok(new_window.label().to_string())
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
window.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let win = window.clone();
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
window.on_window_event(move |event| {
|
|
||||||
if let tauri::WindowEvent::ThemeChanged(_) = event {
|
|
||||||
win.set_traffic_lights_inset(7.0, 10.0).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[specta::specta]
|
|
||||||
pub fn quit() {
|
|
||||||
std::process::exit(0)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -143,29 +143,33 @@ pub fn get_all_accounts() -> Vec<String> {
|
|||||||
accounts.into_iter().collect()
|
accounts.into_iter().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn process_event(client: &Client, events: Events) -> Vec<RichEvent> {
|
pub async fn process_event(client: &Client, events: Events, is_reply: bool) -> Vec<RichEvent> {
|
||||||
// Remove event thread if event is TextNote
|
// Remove event thread if event is TextNote
|
||||||
let events: Vec<Event> = events
|
let events: Vec<Event> = if !is_reply {
|
||||||
.into_iter()
|
events
|
||||||
.filter_map(|ev| {
|
.into_iter()
|
||||||
if ev.kind == Kind::TextNote {
|
.filter_map(|ev| {
|
||||||
let tags = ev
|
if ev.kind == Kind::TextNote {
|
||||||
.tags
|
let tags = ev
|
||||||
.iter()
|
.tags
|
||||||
.filter(|t| t.is_reply() || t.is_root())
|
.iter()
|
||||||
.filter_map(|t| t.content())
|
.filter(|t| t.is_reply() || t.is_root())
|
||||||
.collect::<Vec<_>>();
|
.filter_map(|t| t.content())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if tags.is_empty() {
|
if tags.is_empty() {
|
||||||
Some(ev)
|
Some(ev)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
Some(ev)
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
Some(ev)
|
.collect()
|
||||||
}
|
} else {
|
||||||
})
|
events.into_iter().collect()
|
||||||
.collect();
|
};
|
||||||
|
|
||||||
// Get deletion request by event's authors
|
// Get deletion request by event's authors
|
||||||
let ids: Vec<EventId> = events.iter().map(|ev| ev.id).collect();
|
let ids: Vec<EventId> = events.iter().map(|ev| ev.id).collect();
|
||||||
|
|||||||
@@ -156,8 +156,6 @@ fn main() {
|
|||||||
reload_column,
|
reload_column,
|
||||||
close_column,
|
close_column,
|
||||||
open_window,
|
open_window,
|
||||||
reopen_lume,
|
|
||||||
quit
|
|
||||||
])
|
])
|
||||||
.events(collect_events![Subscription, NegentropyEvent]);
|
.events(collect_events![Subscription, NegentropyEvent]);
|
||||||
|
|
||||||
@@ -207,12 +205,12 @@ fn main() {
|
|||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
.max_avg_latency(Duration::from_millis(800))
|
.max_avg_latency(Duration::from_millis(500))
|
||||||
.automatic_authentication(false)
|
.automatic_authentication(false)
|
||||||
.connection_timeout(Some(Duration::from_secs(20)))
|
.connection_timeout(Some(Duration::from_secs(20)))
|
||||||
.send_timeout(Some(Duration::from_secs(10)))
|
.send_timeout(Some(Duration::from_secs(10)))
|
||||||
.wait_for_send(false)
|
.wait_for_send(false)
|
||||||
.timeout(Duration::from_secs(20));
|
.timeout(Duration::from_secs(12));
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
@@ -370,29 +368,31 @@ fn main() {
|
|||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let state = handle_clone.state::<Nostr>();
|
let state = handle_clone.state::<Nostr>();
|
||||||
let client = &state.client;
|
let client = &state.client;
|
||||||
let accounts = state.accounts.lock().unwrap().clone();
|
let accounts = get_all_accounts();
|
||||||
|
|
||||||
let public_keys: Vec<PublicKey> = accounts
|
if !accounts.is_empty() {
|
||||||
.iter()
|
let public_keys: Vec<PublicKey> = accounts
|
||||||
.filter_map(|acc| {
|
.iter()
|
||||||
if let Ok(pk) = PublicKey::from_str(acc) {
|
.filter_map(|acc| {
|
||||||
Some(pk)
|
if let Ok(pk) = PublicKey::from_str(acc) {
|
||||||
} else {
|
Some(pk)
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
})
|
}
|
||||||
.collect();
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// Subscribe for new notification
|
// Subscribe for new notification
|
||||||
if let Ok(e) = client
|
if let Ok(e) = client
|
||||||
.subscribe_with_id(
|
.subscribe_with_id(
|
||||||
SubscriptionId::new(NOTIFICATION_SUB_ID),
|
SubscriptionId::new(NOTIFICATION_SUB_ID),
|
||||||
vec![Filter::new().pubkeys(public_keys).since(Timestamp::now())],
|
vec![Filter::new().pubkeys(public_keys).since(Timestamp::now())],
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
println!("Subscribed for notification on {} relays", e.success.len())
|
println!("Subscribed for notification on {} relays", e.success.len())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let allow_notification = match handle_clone.notification().request_permission() {
|
let allow_notification = match handle_clone.notification().request_permission() {
|
||||||
@@ -408,7 +408,6 @@ fn main() {
|
|||||||
|
|
||||||
let notification_id = SubscriptionId::new(NOTIFICATION_SUB_ID);
|
let notification_id = SubscriptionId::new(NOTIFICATION_SUB_ID);
|
||||||
let mut notifications = client.pool().notifications();
|
let mut notifications = client.pool().notifications();
|
||||||
let mut new_events: Vec<EventId> = Vec::new();
|
|
||||||
|
|
||||||
while let Ok(notification) = notifications.recv().await {
|
while let Ok(notification) = notifications.recv().await {
|
||||||
match notification {
|
match notification {
|
||||||
@@ -426,17 +425,6 @@ fn main() {
|
|||||||
println!("Error: {}", e);
|
println!("Error: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for https://github.com/rust-nostr/nostr/issues/509
|
|
||||||
// TODO: remove
|
|
||||||
let _ = client
|
|
||||||
.fetch_events(
|
|
||||||
vec![Filter::new()
|
|
||||||
.kind(Kind::TextNote)
|
|
||||||
.limit(0)],
|
|
||||||
Some(Duration::from_secs(5)),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if allow_notification {
|
if allow_notification {
|
||||||
if let Err(e) = &handle_clone
|
if let Err(e) = &handle_clone
|
||||||
.notification()
|
.notification()
|
||||||
@@ -469,22 +457,8 @@ fn main() {
|
|||||||
event,
|
event,
|
||||||
} = message
|
} = message
|
||||||
{
|
{
|
||||||
let tags: Vec<String> = event
|
|
||||||
.tags
|
|
||||||
.public_keys()
|
|
||||||
.filter_map(|pk| {
|
|
||||||
if let Ok(bech32) = pk.to_bech32() {
|
|
||||||
Some(bech32)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Handle events from notification subscription
|
// Handle events from notification subscription
|
||||||
if subscription_id == notification_id
|
if subscription_id == notification_id {
|
||||||
&& tags.iter().any(|item| accounts.iter().any(|i| i == item))
|
|
||||||
{
|
|
||||||
// Send native notification
|
// Send native notification
|
||||||
if allow_notification {
|
if allow_notification {
|
||||||
let author = client
|
let author = client
|
||||||
@@ -501,37 +475,22 @@ fn main() {
|
|||||||
&handle_clone,
|
&handle_clone,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
let payload = RichEvent {
|
||||||
|
raw: event.as_json(),
|
||||||
|
parsed: if event.kind == Kind::TextNote {
|
||||||
|
Some(parse_event(&event.content).await)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let payload = RichEvent {
|
if let Err(e) = handle_clone.emit_to(
|
||||||
raw: event.as_json(),
|
|
||||||
parsed: if event.kind == Kind::TextNote {
|
|
||||||
Some(parse_event(&event.content).await)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
handle_clone
|
|
||||||
.emit_to(
|
|
||||||
EventTarget::labeled(subscription_id.to_string()),
|
EventTarget::labeled(subscription_id.to_string()),
|
||||||
"event",
|
"event",
|
||||||
payload,
|
payload,
|
||||||
)
|
) {
|
||||||
.unwrap();
|
println!("Emitter error: {}", e)
|
||||||
|
|
||||||
if state
|
|
||||||
.subscriptions
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.any(|i| i == &subscription_id)
|
|
||||||
{
|
|
||||||
new_events.push(event.id);
|
|
||||||
|
|
||||||
if new_events.len() > 5 {
|
|
||||||
handle_clone.emit("synchronized", ()).unwrap();
|
|
||||||
new_events.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
58
src/app.tsx
58
src/app.tsx
@@ -1,31 +1,24 @@
|
|||||||
import { experimental_createPersister } from "@tanstack/query-persist-client-core";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||||
import { type } from "@tauri-apps/plugin-os";
|
import { type } from "@tauri-apps/plugin-os";
|
||||||
import { createStore } from "@tauri-apps/plugin-store";
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { newQueryStorage } from "./commons";
|
|
||||||
import type { LumeEvent } from "./system";
|
import type { LumeEvent } from "./system";
|
||||||
|
|
||||||
import { routeTree } from "./routes.gen"; // auto generated file
|
import { routeTree } from "./routes.gen"; // auto generated file
|
||||||
import "./app.css"; // global styles
|
import "./app.css"; // global styles
|
||||||
|
|
||||||
const platform = type();
|
declare module "@tanstack/react-router" {
|
||||||
// @ts-expect-error, required: https://github.com/tauri-apps/plugins-workspace/pull/1860
|
interface Register {
|
||||||
const store = await createStore(".cache", { autoSave: 250 });
|
router: typeof router;
|
||||||
const queryClient = new QueryClient({
|
}
|
||||||
defaultOptions: {
|
interface HistoryState {
|
||||||
queries: {
|
events?: LumeEvent[];
|
||||||
gcTime: 1000 * 30,
|
}
|
||||||
persister: experimental_createPersister({
|
}
|
||||||
storage: newQueryStorage(store),
|
|
||||||
maxAge: 1000 * 60 * 60 * 12,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const platform = type();
|
||||||
|
const queryClient = new QueryClient();
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: { queryClient, platform },
|
context: { queryClient, platform },
|
||||||
@@ -36,28 +29,11 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register things for typesafety
|
const rootElement = document.getElementById("root");
|
||||||
declare module "@tanstack/react-router" {
|
const root = ReactDOM.createRoot(rootElement as unknown as HTMLElement);
|
||||||
interface Register {
|
|
||||||
router: typeof router;
|
|
||||||
}
|
|
||||||
interface HistoryState {
|
|
||||||
events?: LumeEvent[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
root.render(
|
||||||
return <RouterProvider router={router} />;
|
<StrictMode>
|
||||||
}
|
<RouterProvider router={router} />
|
||||||
|
</StrictMode>,
|
||||||
// biome-ignore lint/style/noNonNullAssertion: idk
|
);
|
||||||
const rootElement = document.getElementById("root")!;
|
|
||||||
|
|
||||||
if (!rootElement.innerHTML) {
|
|
||||||
const root = ReactDOM.createRoot(rootElement);
|
|
||||||
root.render(
|
|
||||||
<StrictMode>
|
|
||||||
<App />
|
|
||||||
</StrictMode>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -495,12 +495,6 @@ async openWindow(window: Window) : Promise<Result<string, string>> {
|
|||||||
if(e instanceof Error) throw e;
|
if(e instanceof Error) throw e;
|
||||||
else return { status: "error", error: e as any };
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async reopenLume() : Promise<void> {
|
|
||||||
await TAURI_INVOKE("reopen_lume");
|
|
||||||
},
|
|
||||||
async quit() : Promise<void> {
|
|
||||||
await TAURI_INVOKE("quit");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,20 @@ import type { LumeColumn } from "@/types";
|
|||||||
import { CaretDown, Check } from "@phosphor-icons/react";
|
import { CaretDown, Check } from "@phosphor-icons/react";
|
||||||
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
import { Menu, MenuItem, PredefinedMenuItem } from "@tauri-apps/api/menu";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { User } from "./user";
|
import { User } from "./user";
|
||||||
|
|
||||||
export function Column({ column }: { column: LumeColumn }) {
|
export function Column({ column }: { column: LumeColumn }) {
|
||||||
const webviewLabel = useMemo(() => `column-${column.label}`, [column.label]);
|
const webviewLabel = useMemo(() => `column-${column.label}`, [column.label]);
|
||||||
|
|
||||||
const [rect, ref] = useRect();
|
const [rect, ref] = useRect();
|
||||||
const [_error, setError] = useState<string>(null);
|
const [_error, setError] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -33,10 +39,8 @@ export function Column({ column }: { column: LumeColumn }) {
|
|||||||
})();
|
})();
|
||||||
}, [rect]);
|
}, [rect]);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const isCreated = window.sessionStorage.getItem(webviewLabel);
|
if (ref.current) {
|
||||||
|
|
||||||
if (!isCreated) {
|
|
||||||
const initialRect = ref.current.getBoundingClientRect();
|
const initialRect = ref.current.getBoundingClientRect();
|
||||||
|
|
||||||
commands
|
commands
|
||||||
@@ -51,7 +55,6 @@ export function Column({ column }: { column: LumeColumn }) {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
console.log("webview is created: ", webviewLabel);
|
console.log("webview is created: ", webviewLabel);
|
||||||
window.sessionStorage.setItem(webviewLabel, "");
|
|
||||||
} else {
|
} else {
|
||||||
setError(res.error);
|
setError(res.error);
|
||||||
}
|
}
|
||||||
@@ -172,7 +175,11 @@ function Header({
|
|||||||
<div
|
<div
|
||||||
contentEditable
|
contentEditable
|
||||||
suppressContentEditableWarning={true}
|
suppressContentEditableWarning={true}
|
||||||
onBlur={(e) => setTitle(e.currentTarget.textContent)}
|
onBlur={(e) => {
|
||||||
|
if (e.currentTarget.textContent) {
|
||||||
|
setTitle(e.currentTarget.textContent);
|
||||||
|
}
|
||||||
|
}}
|
||||||
className="text-[12px] font-semibold focus:outline-none"
|
className="text-[12px] font-semibold focus:outline-none"
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export function Hashtag({ tag }: { tag: string }) {
|
export function Hashtag({ tag }: { tag: string }) {
|
||||||
return (
|
return (
|
||||||
<span className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal">
|
<span className="leading-normal cursor-default text-blue-500 hover:text-blue-600 font-normal">
|
||||||
{tag}
|
{tag.includes("#") ? tag : `#${tag}`}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import * as ScrollArea from "@radix-ui/react-scroll-area";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { useEffect, useRef } from "react";
|
import { type RefObject, useEffect, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/events/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/events/$id")({
|
||||||
@@ -35,7 +35,7 @@ function Screen() {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
<RootEvent />
|
<RootEvent />
|
||||||
<ReplyList />
|
<ReplyList />
|
||||||
</Virtualizer>
|
</Virtualizer>
|
||||||
@@ -64,10 +64,10 @@ function RootEvent() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isError) {
|
if (isError || !event) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 text-sm text-red-500">
|
<div className="flex items-center gap-2 text-sm text-red-500">
|
||||||
{error.message}
|
{error?.message || "Event not found within your current relay set"}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -181,15 +181,21 @@ function ReplyList() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
events.subscription
|
events.subscription
|
||||||
.emit({ label, kind: "Subscribe", event_id: id })
|
.emit({
|
||||||
|
label: label || id,
|
||||||
|
kind: "Subscribe",
|
||||||
|
event_id: id,
|
||||||
|
contacts: null,
|
||||||
|
})
|
||||||
.then(() => console.log("Subscribe: ", label));
|
.then(() => console.log("Subscribe: ", label));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
events.subscription
|
events.subscription
|
||||||
.emit({
|
.emit({
|
||||||
label,
|
label: label || id,
|
||||||
kind: "Unsubscribe",
|
kind: "Unsubscribe",
|
||||||
event_id: id,
|
event_id: id,
|
||||||
|
contacts: null,
|
||||||
})
|
})
|
||||||
.then(() => console.log("Unsubscribe: ", label));
|
.then(() => console.log("Unsubscribe: ", label));
|
||||||
};
|
};
|
||||||
@@ -229,7 +235,7 @@ function ReplyList() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{!data.length ? (
|
{!data?.length ? (
|
||||||
<div className="flex items-center justify-center w-full">
|
<div className="flex items-center justify-center w-full">
|
||||||
<div className="flex flex-col items-center justify-center gap-2 py-4">
|
<div className="flex flex-col items-center justify-center gap-2 py-4">
|
||||||
<h3 className="text-3xl">👋</h3>
|
<h3 className="text-3xl">👋</h3>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/global")({
|
export const Route = createFileRoute("/columns/_layout/global")({
|
||||||
@@ -27,7 +27,7 @@ export function Screen() {
|
|||||||
queryKey: ["events", "global", label],
|
queryKey: ["events", "global", label],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
const until = pageParam > 0 ? pageParam.toString() : null;
|
||||||
const res = await commands.getGlobalEvents(until);
|
const res = await commands.getGlobalEvents(until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
@@ -36,7 +36,13 @@ export function Screen() {
|
|||||||
|
|
||||||
return toLumeEvents(res.data);
|
return toLumeEvents(res.data);
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => {
|
||||||
|
const lastEvent = lastPage.at(-1);
|
||||||
|
|
||||||
|
if (lastEvent) {
|
||||||
|
return lastEvent.created_at - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
@@ -47,14 +53,17 @@ export function Screen() {
|
|||||||
(event: LumeEvent) => {
|
(event: LumeEvent) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Repost:
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RepostNote
|
<RepostNote
|
||||||
key={event.id}
|
key={repostId + event.id}
|
||||||
event={event}
|
event={event}
|
||||||
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<TextNote
|
<TextNote
|
||||||
@@ -76,9 +85,9 @@ export function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
||||||
@@ -92,7 +101,7 @@ export function Screen() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
🎉 Yo. You're catching up on all latest notes.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/groups/$id")({
|
||||||
@@ -29,7 +29,7 @@ export function Screen() {
|
|||||||
queryKey: ["events", "groups", params.id],
|
queryKey: ["events", "groups", params.id],
|
||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
const until = pageParam > 0 ? pageParam.toString() : null;
|
||||||
const res = await commands.getAllEventsByAuthors(group, until);
|
const res = await commands.getAllEventsByAuthors(group, until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
@@ -38,7 +38,13 @@ export function Screen() {
|
|||||||
|
|
||||||
return toLumeEvents(res.data);
|
return toLumeEvents(res.data);
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => {
|
||||||
|
const lastEvent = lastPage.at(-1);
|
||||||
|
|
||||||
|
if (lastEvent) {
|
||||||
|
return lastEvent.created_at - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
enabled: group?.length > 0,
|
enabled: group?.length > 0,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
@@ -50,14 +56,17 @@ export function Screen() {
|
|||||||
(event: LumeEvent) => {
|
(event: LumeEvent) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Repost:
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RepostNote
|
<RepostNote
|
||||||
key={event.id}
|
key={repostId + event.id}
|
||||||
event={event}
|
event={event}
|
||||||
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<TextNote
|
<TextNote
|
||||||
@@ -81,7 +90,7 @@ export function Screen() {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
||||||
@@ -95,7 +104,7 @@ export function Screen() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
🎉 Yo. You're catching up on all latest notes.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/interests/$id")({
|
||||||
@@ -30,7 +30,7 @@ export function Screen() {
|
|||||||
initialPageParam: 0,
|
initialPageParam: 0,
|
||||||
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
queryFn: async ({ pageParam }: { pageParam: number }) => {
|
||||||
const tags = hashtags.map((tag) => tag.toLowerCase().replace("#", ""));
|
const tags = hashtags.map((tag) => tag.toLowerCase().replace("#", ""));
|
||||||
const until = pageParam > 0 ? pageParam.toString() : undefined;
|
const until = pageParam > 0 ? pageParam.toString() : null;
|
||||||
const res = await commands.getAllEventsByHashtags(tags, until);
|
const res = await commands.getAllEventsByHashtags(tags, until);
|
||||||
|
|
||||||
if (res.status === "error") {
|
if (res.status === "error") {
|
||||||
@@ -39,7 +39,13 @@ export function Screen() {
|
|||||||
|
|
||||||
return toLumeEvents(res.data);
|
return toLumeEvents(res.data);
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => {
|
||||||
|
const lastEvent = lastPage.at(-1);
|
||||||
|
|
||||||
|
if (lastEvent) {
|
||||||
|
return lastEvent.created_at - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
@@ -50,14 +56,17 @@ export function Screen() {
|
|||||||
(event: LumeEvent) => {
|
(event: LumeEvent) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Repost:
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RepostNote
|
<RepostNote
|
||||||
key={event.id}
|
key={repostId + event.id}
|
||||||
event={event}
|
event={event}
|
||||||
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<TextNote
|
<TextNote
|
||||||
@@ -79,9 +88,9 @@ export function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
||||||
@@ -95,7 +104,7 @@ export function Screen() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
🎉 Yo. You're catching up on all latest notes.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const Route = createLazyFileRoute("/columns/_layout/launchpad/$id")({
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { id } = Route.useParams();
|
const { id } = Route.useParams();
|
||||||
const { data: isSync } = useQuery({
|
const { isLoading, data: isSync } = useQuery({
|
||||||
queryKey: ["is-sync", id],
|
queryKey: ["is-sync", id],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await commands.isAccountSync(id);
|
const res = await commands.isAccountSync(id);
|
||||||
@@ -40,7 +40,9 @@ function Screen() {
|
|||||||
className="overflow-hidden size-full"
|
className="overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
<ScrollArea.Viewport className="relative h-full px-3 pb-3">
|
||||||
{!isSync ? (
|
{isLoading ? (
|
||||||
|
<Spinner className="size-4" />
|
||||||
|
) : !isSync ? (
|
||||||
<SyncProgress />
|
<SyncProgress />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import { ArrowDown } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useEffect, useRef } from "react";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { type RefObject, useCallback, useEffect, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/newsfeed/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/newsfeed/$id")({
|
||||||
@@ -17,7 +18,7 @@ export const Route = createLazyFileRoute("/columns/_layout/newsfeed/$id")({
|
|||||||
export function Screen() {
|
export function Screen() {
|
||||||
const contacts = Route.useLoaderData();
|
const contacts = Route.useLoaderData();
|
||||||
const search = Route.useSearch();
|
const search = Route.useSearch();
|
||||||
|
const { queryClient } = Route.useRouteContext();
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
isLoading,
|
isLoading,
|
||||||
@@ -38,7 +39,13 @@ export function Screen() {
|
|||||||
|
|
||||||
return toLumeEvents(res.data);
|
return toLumeEvents(res.data);
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => lastPage?.at?.(-1)?.created_at - 1,
|
getNextPageParam: (lastPage) => {
|
||||||
|
const lastEvent = lastPage.at(-1);
|
||||||
|
|
||||||
|
if (lastEvent) {
|
||||||
|
return lastEvent.created_at - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
select: (data) => data?.pages.flat(),
|
select: (data) => data?.pages.flat(),
|
||||||
enabled: contacts?.length > 0,
|
enabled: contacts?.length > 0,
|
||||||
});
|
});
|
||||||
@@ -52,14 +59,17 @@ export function Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Repost:
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RepostNote
|
<RepostNote
|
||||||
key={event.id}
|
key={repostId + event.id}
|
||||||
event={event}
|
event={event}
|
||||||
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<TextNote
|
<TextNote
|
||||||
@@ -95,6 +105,18 @@ export function Screen() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unlisten = listen("synchronized", async () => {
|
||||||
|
await queryClient.refetchQueries({
|
||||||
|
queryKey: ["events", "newsfeed", search.label],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unlisten.then((f) => f());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea.Root
|
<ScrollArea.Root
|
||||||
type={"scroll"}
|
type={"scroll"}
|
||||||
@@ -103,9 +125,9 @@ export function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isFetching && !isLoading && !isFetchingNextPage ? (
|
{isFetching && !isLoading && !isFetchingNextPage ? (
|
||||||
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
<div className="z-50 fixed top-0 left-0 w-full h-14 flex items-center justify-center px-3">
|
||||||
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
<div className="w-max h-8 pl-2 pr-3 inline-flex items-center justify-center gap-1.5 rounded-full shadow-lg text-sm font-medium text-white bg-black dark:text-black dark:bg-white">
|
||||||
@@ -119,7 +141,7 @@ export function Screen() {
|
|||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
🎉 Yo. You're catching up on all latest notes.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { type ReactNode, useEffect, useMemo, useRef } from "react";
|
import {
|
||||||
|
type ReactNode,
|
||||||
|
type RefObject,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/notification/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/notification/$id")({
|
||||||
@@ -110,7 +116,7 @@ function Screen() {
|
|||||||
<div className="px-3 h-full overflow-y-auto">
|
<div className="px-3 h-full overflow-y-auto">
|
||||||
<Tabs.Root
|
<Tabs.Root
|
||||||
defaultValue="replies"
|
defaultValue="replies"
|
||||||
className="flex flex-col h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="flex flex-col h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Tabs.List className="h-11 shrink-0 flex items-center">
|
<Tabs.List className="h-11 shrink-0 flex items-center">
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
@@ -138,62 +144,74 @@ function Screen() {
|
|||||||
className="min-h-0 flex-1 overflow-x-hidden"
|
className="min-h-0 flex-1 overflow-x-hidden"
|
||||||
>
|
>
|
||||||
<Tab value="replies">
|
<Tab value="replies">
|
||||||
{data.texts.map((event) => (
|
{!data?.texts ? (
|
||||||
<TextNote key={event.id} event={event} />
|
<div>Empty</div>
|
||||||
))}
|
) : (
|
||||||
|
data.texts.map((event) => (
|
||||||
|
<TextNote key={event.id} event={event} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="reactions">
|
<Tab value="reactions">
|
||||||
{[...data.reactions.entries()].map(([root, events]) => (
|
{!data?.reactions ? (
|
||||||
<div
|
<div>Empty</div>
|
||||||
key={root}
|
) : (
|
||||||
className="p-3 w-full border-b-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
[...data.reactions.entries()].map(([root, events]) => (
|
||||||
>
|
<div
|
||||||
<div className="flex flex-col flex-1 min-w-0 gap-2">
|
key={root}
|
||||||
<div className="flex items-center gap-2 pb-2">
|
className="p-3 w-full border-b-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
||||||
<RootNote id={root} />
|
>
|
||||||
</div>
|
<div className="flex flex-col flex-1 min-w-0 gap-2">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex items-center gap-2 pb-2">
|
||||||
{events.map((event) => (
|
<RootNote id={root} />
|
||||||
<User.Provider key={event.id} pubkey={event.pubkey}>
|
</div>
|
||||||
<User.Root className="shrink-0 flex rounded-full h-7 bg-neutral-100 dark:bg-neutral-900 p-[2px]">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<User.Avatar className="flex-1 rounded-full size-6" />
|
{events.map((event) => (
|
||||||
<div className="inline-flex items-center justify-center flex-1 text-xs truncate rounded-full size-6">
|
<User.Provider key={event.id} pubkey={event.pubkey}>
|
||||||
{event.kind === Kind.Reaction ? (
|
<User.Root className="shrink-0 flex rounded-full h-7 bg-neutral-100 dark:bg-neutral-900 p-[2px]">
|
||||||
event.content === "+" ? (
|
<User.Avatar className="flex-1 rounded-full size-6" />
|
||||||
"👍"
|
<div className="inline-flex items-center justify-center flex-1 text-xs truncate rounded-full size-6">
|
||||||
|
{event.kind === Kind.Reaction ? (
|
||||||
|
event.content === "+" ? (
|
||||||
|
"👍"
|
||||||
|
) : (
|
||||||
|
event.content
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
event.content
|
<RepostIcon className="text-teal-400 size-4 dark:text-teal-600" />
|
||||||
)
|
)}
|
||||||
) : (
|
</div>
|
||||||
<RepostIcon className="text-teal-400 size-4 dark:text-teal-600" />
|
</User.Root>
|
||||||
)}
|
</User.Provider>
|
||||||
</div>
|
))}
|
||||||
</User.Root>
|
</div>
|
||||||
</User.Provider>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
))}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab value="zaps">
|
<Tab value="zaps">
|
||||||
{[...data.zaps.entries()].map(([root, events]) => (
|
{!data?.zaps ? (
|
||||||
<div
|
<div>Empty</div>
|
||||||
key={root}
|
) : (
|
||||||
className="p-3 w-full border-b-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
[...data.zaps.entries()].map(([root, events]) => (
|
||||||
>
|
<div
|
||||||
<div className="flex flex-col flex-1 min-w-0 gap-2">
|
key={root}
|
||||||
<div className="flex items-center gap-2 pb-2">
|
className="p-3 w-full border-b-[.5px] border-neutral-300 dark:border-neutral-700 hover:border-blue-500"
|
||||||
<RootNote id={root} />
|
>
|
||||||
</div>
|
<div className="flex flex-col flex-1 min-w-0 gap-2">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex items-center gap-2 pb-2">
|
||||||
{events.map((event) => (
|
<RootNote id={root} />
|
||||||
<ZapReceipt key={event.id} event={event} />
|
</div>
|
||||||
))}
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
|
{events.map((event) => (
|
||||||
|
<ZapReceipt key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
))}
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
className="flex select-none touch-none p-0.5 duration-[160ms] ease-out data-[orientation=vertical]:w-2"
|
||||||
@@ -208,13 +226,15 @@ function Screen() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tab({ value, children }: { value: string; children: ReactNode[] }) {
|
function Tab({ value, children }: { value: string; children: ReactNode }) {
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs.Content value={value} className="size-full">
|
<Tabs.Content value={value} className="size-full">
|
||||||
<ScrollArea.Viewport ref={ref} className="h-full">
|
<ScrollArea.Viewport ref={ref} className="h-full">
|
||||||
<Virtualizer scrollRef={ref}>{children}</Virtualizer>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
|
{children}
|
||||||
|
</Virtualizer>
|
||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
);
|
);
|
||||||
@@ -311,10 +331,16 @@ function TextNote({ event }: { event: LumeEvent }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ZapReceipt({ event }: { event: LumeEvent }) {
|
function ZapReceipt({ event }: { event: LumeEvent }) {
|
||||||
const amount = useMemo(
|
const amount = useMemo(() => {
|
||||||
() => decodeZapInvoice(event.tags).bitcoinFormatted ?? "0",
|
const decoded = decodeZapInvoice(event.tags);
|
||||||
[event.id],
|
|
||||||
);
|
if (decoded) {
|
||||||
|
return decoded.bitcoinFormatted;
|
||||||
|
} else {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
}, [event.id]);
|
||||||
|
|
||||||
const sender = useMemo(
|
const sender = useMemo(
|
||||||
() => event.tags.find((tag) => tag[0] === "P")?.[1],
|
() => event.tags.find((tag) => tag[0] === "P")?.[1],
|
||||||
[event.id],
|
[event.id],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
useRouter,
|
useRouter,
|
||||||
useRouterState,
|
useRouterState,
|
||||||
} from "@tanstack/react-router";
|
} from "@tanstack/react-router";
|
||||||
import { useRef } from "react";
|
import { type RefObject, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/replies/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/replies/$id")({
|
||||||
@@ -20,7 +20,7 @@ function Screen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 h-full">
|
<div className="px-3 h-full">
|
||||||
<div className="size-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700">
|
<div className="size-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700">
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="h-10 shrink-0 border-b border-black/5 dark:border-white/5 flex items-center justify-between px-2">
|
<div className="h-10 shrink-0 border-b border-black/5 dark:border-white/5 flex items-center justify-between px-2">
|
||||||
<button
|
<button
|
||||||
@@ -38,10 +38,14 @@ function Screen() {
|
|||||||
className="overflow-hidden size-full flex-1"
|
className="overflow-hidden size-full flex-1"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport ref={ref} className="h-full p-3">
|
<ScrollArea.Viewport ref={ref} className="h-full p-3">
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{events.map((event) => (
|
{!events ? (
|
||||||
<ReplyNote key={event.id} event={event} />
|
<div>Empty</div>
|
||||||
))}
|
) : (
|
||||||
|
events.map((event) => (
|
||||||
|
<ReplyNote key={event.id} event={event} />
|
||||||
|
))
|
||||||
|
)}
|
||||||
</Virtualizer>
|
</Virtualizer>
|
||||||
</ScrollArea.Viewport>
|
</ScrollArea.Viewport>
|
||||||
<ScrollArea.Scrollbar
|
<ScrollArea.Scrollbar
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import { MagnifyingGlass } from "@phosphor-icons/react";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
import { message } from "@tauri-apps/plugin-dialog";
|
||||||
import { useCallback, useRef, useState, useTransition } from "react";
|
import {
|
||||||
|
type RefObject,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useTransition,
|
||||||
|
} from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/search")({
|
export const Route = createLazyFileRoute("/columns/_layout/search")({
|
||||||
@@ -97,7 +103,7 @@ function Screen() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 h-full">
|
<div className="px-3 h-full">
|
||||||
<div className="flex flex-col gap-3 size-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700">
|
<div className="flex flex-col gap-3 size-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700">
|
||||||
<div className="h-9 shrink-0 pt-6 px-3 flex items-center gap-2">
|
<div className="h-9 shrink-0 pt-6 px-3 flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
name="search"
|
name="search"
|
||||||
@@ -127,7 +133,7 @@ function Screen() {
|
|||||||
className="overflow-hidden size-full flex-1"
|
className="overflow-hidden size-full flex-1"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport ref={ref} className="relative h-full">
|
<ScrollArea.Viewport ref={ref} className="relative h-full">
|
||||||
<Virtualizer scrollRef={ref}>
|
<Virtualizer scrollRef={ref as unknown as RefObject<HTMLElement>}>
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<div className="w-full h-[200px] flex gap-2 items-center justify-center">
|
<div className="w-full h-[200px] flex gap-2 items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import * as ScrollArea from "@radix-ui/react-scroll-area";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import { type ReactNode, memo, useMemo, useRef } from "react";
|
import { type ReactNode, type RefObject, memo, useMemo, useRef } from "react";
|
||||||
import reactStringReplace from "react-string-replace";
|
import reactStringReplace from "react-string-replace";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
@@ -29,7 +29,10 @@ function Screen() {
|
|||||||
className="overflow-hidden size-full"
|
className="overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport ref={ref} className="relative h-full px-3 pb-3">
|
<ScrollArea.Viewport ref={ref} className="relative h-full px-3 pb-3">
|
||||||
<Virtualizer scrollRef={ref} overscan={0}>
|
<Virtualizer
|
||||||
|
scrollRef={ref as unknown as RefObject<HTMLElement>}
|
||||||
|
overscan={0}
|
||||||
|
>
|
||||||
{!contacts ? (
|
{!contacts ? (
|
||||||
<div className="w-full h-24 flex items-center justify-center">
|
<div className="w-full h-24 flex items-center justify-center">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
@@ -77,7 +80,7 @@ function StoryItem({ contact }: { contact: string }) {
|
|||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 flex flex-col w-full h-[300px] bg-white dark:bg-black rounded-xl border-[.5px] border-neutral-300 dark:border-neutral-700">
|
<div className="mb-3 flex flex-col w-full h-[300px] bg-white dark:bg-neutral-800 rounded-xl border-[.5px] border-neutral-300 dark:border-neutral-700">
|
||||||
<div className="h-12 shrink-0 px-2 flex items-center justify-between border-b border-neutral-100 dark:border-white/5">
|
<div className="h-12 shrink-0 px-2 flex items-center justify-between border-b border-neutral-100 dark:border-white/5">
|
||||||
<User.Provider pubkey={contact}>
|
<User.Provider pubkey={contact}>
|
||||||
<User.Root className="inline-flex items-center gap-2">
|
<User.Root className="inline-flex items-center gap-2">
|
||||||
@@ -102,7 +105,10 @@ function StoryItem({ contact }: { contact: string }) {
|
|||||||
className="flex-1 min-h-0 overflow-hidden size-full"
|
className="flex-1 min-h-0 overflow-hidden size-full"
|
||||||
>
|
>
|
||||||
<ScrollArea.Viewport ref={ref} className="relative h-full px-2 pt-2">
|
<ScrollArea.Viewport ref={ref} className="relative h-full px-2 pt-2">
|
||||||
<Virtualizer scrollRef={ref} overscan={0}>
|
<Virtualizer
|
||||||
|
scrollRef={ref as unknown as RefObject<HTMLElement>}
|
||||||
|
overscan={0}
|
||||||
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
@@ -111,7 +117,7 @@ function StoryItem({ contact }: { contact: string }) {
|
|||||||
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
||||||
{error.message}
|
{error.message}
|
||||||
</div>
|
</div>
|
||||||
) : !events.length ? (
|
) : !events?.length ? (
|
||||||
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
<div className="w-full h-[calc(300px-48px)] flex items-center justify-center text-sm">
|
||||||
This user didn't have any new notes in the last few days.
|
This user didn't have any new notes in the last few days.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Kind, type NostrEvent } from "@/types";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/trending")({
|
export const Route = createLazyFileRoute("/columns/_layout/trending")({
|
||||||
@@ -71,15 +71,18 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref} overscan={1}>
|
<Virtualizer
|
||||||
|
scrollRef={ref as unknown as RefObject<HTMLElement>}
|
||||||
|
overscan={1}
|
||||||
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center w-full h-16 gap-2">
|
<div className="flex items-center justify-center w-full h-16 gap-2">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !data.length ? (
|
) : !data?.length ? (
|
||||||
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
<div className="mb-3 flex items-center justify-center h-20 text-sm">
|
||||||
🎉 Yo. You're catching up on all latest notes.
|
🎉 Yo. You're catching up on all latest notes.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Kind } from "@/types";
|
|||||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||||
import { useCallback, useRef } from "react";
|
import { type RefObject, useCallback, useRef } from "react";
|
||||||
import { Virtualizer } from "virtua";
|
import { Virtualizer } from "virtua";
|
||||||
|
|
||||||
export const Route = createLazyFileRoute("/columns/_layout/users/$id")({
|
export const Route = createLazyFileRoute("/columns/_layout/users/$id")({
|
||||||
@@ -36,14 +36,17 @@ function Screen() {
|
|||||||
(event: LumeEvent) => {
|
(event: LumeEvent) => {
|
||||||
if (!event) return;
|
if (!event) return;
|
||||||
switch (event.kind) {
|
switch (event.kind) {
|
||||||
case Kind.Repost:
|
case Kind.Repost: {
|
||||||
|
const repostId = event.repostId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RepostNote
|
<RepostNote
|
||||||
key={event.id}
|
key={repostId + event.id}
|
||||||
event={event}
|
event={event}
|
||||||
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="border-b-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<TextNote
|
<TextNote
|
||||||
@@ -65,9 +68,12 @@ function Screen() {
|
|||||||
>
|
>
|
||||||
<ScrollArea.Viewport
|
<ScrollArea.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="relative h-full bg-white dark:bg-black rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
className="relative h-full bg-white dark:bg-neutral-800 rounded-t-xl shadow shadow-neutral-300/50 dark:shadow-none border-[.5px] border-neutral-300 dark:border-neutral-700"
|
||||||
>
|
>
|
||||||
<Virtualizer scrollRef={ref} overscan={0}>
|
<Virtualizer
|
||||||
|
scrollRef={ref as unknown as RefObject<HTMLElement>}
|
||||||
|
overscan={0}
|
||||||
|
>
|
||||||
<User.Provider pubkey={id}>
|
<User.Provider pubkey={id}>
|
||||||
<User.Root className="relative">
|
<User.Root className="relative">
|
||||||
<User.Cover className="object-cover w-full h-44 rounded-t-lg gradient-mask-b-0" />
|
<User.Cover className="object-cover w-full h-44 rounded-t-lg gradient-mask-b-0" />
|
||||||
@@ -88,7 +94,7 @@ function Screen() {
|
|||||||
<Spinner className="size-5" />
|
<Spinner className="size-5" />
|
||||||
<span className="text-sm font-medium">Loading...</span>
|
<span className="text-sm font-medium">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
) : !events.length ? (
|
) : !events?.length ? (
|
||||||
<div className="flex items-center justify-center">
|
<div className="flex items-center justify-center">
|
||||||
Yo. You're catching up on all the things happening around you.
|
Yo. You're catching up on all the things happening around you.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ export class LumeEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static from(raw: string, parsed?: Meta) {
|
static from(raw: string, parsed?: Meta | null) {
|
||||||
const nostrEvent: NostrEvent = JSON.parse(raw);
|
const nostrEvent: NostrEvent = JSON.parse(raw);
|
||||||
|
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export interface NostrEvent {
|
|||||||
tags: string[][];
|
tags: string[][];
|
||||||
content: string;
|
content: string;
|
||||||
sig: string;
|
sig: string;
|
||||||
meta: Meta;
|
meta: Meta | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventWithReplies extends NostrEvent {
|
export interface EventWithReplies extends NostrEvent {
|
||||||
|
|||||||
Reference in New Issue
Block a user