Compare commits

...
6 Commits
Author SHA1 Message Date
reya b0c49c5141 chore: release 2024-11-10 13:23:57 +07:00
reya 2ed2cb3afd chore: update dependencies 2024-11-10 10:43:38 +07:00
reya 2bcda1f2ef feat: support NIP22 for comment 2024-11-10 08:51:07 +07:00
reya ca20bbd298 chore: release 2024-11-08 09:19:51 +07:00
reya c6da06cd4d chore: clean up 2024-11-08 09:02:57 +07:00
reya 50bf6c04c1 chore: use upstream nostr-sdk instead of fork 2024-11-08 07:51:48 +07:00
18 changed files with 1195 additions and 1197 deletions
+18 -18
View File
@@ -20,38 +20,38 @@
"@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-tooltip": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/query-broadcast-client-experimental": "^5.59.16", "@tanstack/query-broadcast-client-experimental": "^5.59.20",
"@tanstack/query-persist-client-core": "^5.59.16", "@tanstack/query-persist-client-core": "^5.59.20",
"@tanstack/react-query": "^5.59.16", "@tanstack/react-query": "^5.59.20",
"@tanstack/react-router": "^1.77.5", "@tanstack/react-router": "^1.81.0",
"@tauri-apps/api": "^2.0.3", "@tauri-apps/api": "^2.1.0",
"@tauri-apps/plugin-clipboard-manager": "^2.0.0", "@tauri-apps/plugin-clipboard-manager": "^2.0.0",
"@tauri-apps/plugin-dialog": "^2.0.1", "@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-fs": "^2.0.1", "@tauri-apps/plugin-fs": "^2.0.2",
"@tauri-apps/plugin-http": "^2.0.1", "@tauri-apps/plugin-http": "^2.0.1",
"@tauri-apps/plugin-os": "^2.0.0", "@tauri-apps/plugin-os": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0", "@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.1", "@tauri-apps/plugin-shell": "^2.0.1",
"@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510", "@tauri-apps/plugin-store": "github:tauri-apps/tauri-plugin-store#a564510",
"@tauri-apps/plugin-updater": "^2.0.0", "@tauri-apps/plugin-updater": "^2.0.0",
"@tauri-apps/plugin-upload": "^2.0.0", "@tauri-apps/plugin-upload": "^2.1.0",
"@tauri-apps/plugin-window-state": "^2.0.0", "@tauri-apps/plugin-window-state": "^2.0.0",
"bitcoin-units": "^1.0.0", "bitcoin-units": "^1.0.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"embla-carousel-react": "^8.3.0", "embla-carousel-react": "^8.3.1",
"i18next": "^23.16.4", "i18next": "^23.16.5",
"i18next-resources-to-backend": "^1.2.1", "i18next-resources-to-backend": "^1.2.1",
"light-bolt11-decoder": "^3.2.0", "light-bolt11-decoder": "^3.2.0",
"minidenticons": "^4.2.1", "minidenticons": "^4.2.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.8",
"nostr-tools": "^2.9.4", "nostr-tools": "^2.10.1",
"react": "19.0.0-rc-cae764ce-20241025", "react": "19.0.0-rc-cae764ce-20241025",
"react-currency-input-field": "^3.8.0", "react-currency-input-field": "^3.8.0",
"react-dom": "19.0.0-rc-cae764ce-20241025", "react-dom": "19.0.0-rc-cae764ce-20241025",
"react-hook-form": "^7.53.1", "react-hook-form": "^7.53.2",
"react-i18next": "^15.1.0", "react-i18next": "^15.1.1",
"react-string-replace": "^1.1.1", "react-string-replace": "^1.1.1",
"rich-textarea": "^0.26.3", "rich-textarea": "^0.26.4",
"use-debounce": "^10.0.4", "use-debounce": "^10.0.4",
"virtua": "^0.34.2" "virtua": "^0.34.2"
}, },
@@ -59,9 +59,9 @@
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@tailwindcss/forms": "^0.5.9", "@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@tanstack/router-devtools": "^1.77.5", "@tanstack/router-devtools": "^1.81.0",
"@tanstack/router-plugin": "^1.76.4", "@tanstack/router-plugin": "^1.79.0",
"@tauri-apps/cli": "^2.0.4", "@tauri-apps/cli": "^2.1.0",
"@types/react": "npm:types-react@19.0.0-rc.1", "@types/react": "npm:types-react@19.0.0-rc.1",
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1",
"@vitejs/plugin-react": "^4.3.3", "@vitejs/plugin-react": "^4.3.3",
@@ -76,7 +76,7 @@
"tailwindcss-content-visibility": "^1.0.0", "tailwindcss-content-visibility": "^1.0.0",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"vite": "^5.4.10", "vite": "^5.4.10",
"vite-tsconfig-paths": "^5.0.1" "vite-tsconfig-paths": "^5.1.1"
}, },
"overrides": { "overrides": {
"@types/react": "npm:types-react@rc", "@types/react": "npm:types-react@rc",
+300 -300
View File
File diff suppressed because it is too large Load Diff
+761 -767
View File
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -33,9 +33,6 @@ tauri-plugin-theme = "2.1.2"
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" } tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] } tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
specta = "^2.0.0-rc.20" specta = "^2.0.0-rc.20"
specta-typescript = "0.0.7" specta-typescript = "0.0.7"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
@@ -49,10 +46,15 @@ regex = "1.10.4"
keyring = { version = "3", features = ["apple-native", "windows-native"] } keyring = { version = "3", features = ["apple-native", "windows-native"] }
keyring-search = { git = "https://github.com/reyamir/keyring-search" } keyring-search = { git = "https://github.com/reyamir/keyring-search" }
tracing-subscriber = { version = "0.3.18", features = ["fmt"] } tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
async-trait = "0.1.83"
webbrowser = "1.0.2"
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
[patch.'https://github.com/rust-nostr/nostr'] [patch.'https://github.com/rust-nostr/nostr']
nostr-sdk = { git = "https://github.com/reyamir/nostr", branch = "feat/open-auth-url", features = ["lmdb", "webln", "all-nips"] } nostr-sdk = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/reyamir/nostr", branch = "feat/open-auth-url" } nostr-connect = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22" }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" } border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
+2 -2
View File
@@ -37,7 +37,7 @@
], ],
"definitions": { "definitions": {
"Capability": { "Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object", "type": "object",
"required": [ "required": [
"identifier", "identifier",
@@ -84,7 +84,7 @@
} }
}, },
"permissions": { "permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PermissionEntry" "$ref": "#/definitions/PermissionEntry"
+2 -2
View File
@@ -37,7 +37,7 @@
], ],
"definitions": { "definitions": {
"Capability": { "Capability": {
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, \"platforms\": [\"macOS\",\"windows\"] } ```", "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
"type": "object", "type": "object",
"required": [ "required": [
"identifier", "identifier",
@@ -84,7 +84,7 @@
} }
}, },
"permissions": { "permissions": {
"description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ```", "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/PermissionEntry" "$ref": "#/definitions/PermissionEntry"
+28 -5
View File
@@ -14,6 +14,17 @@ struct Account {
nostr_connect: Option<String>, nostr_connect: Option<String>,
} }
#[derive(Debug, Clone)]
struct AuthHandler;
#[async_trait::async_trait]
impl AuthUrlHandler for AuthHandler {
async fn on_auth_url(&self, auth_url: Url) -> Result<()> {
webbrowser::open(auth_url.as_str())?;
Ok(())
}
}
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub fn get_accounts() -> Vec<String> { pub fn get_accounts() -> Vec<String> {
@@ -94,19 +105,29 @@ pub async fn connect_account(uri: String, state: State<'_, Nostr>) -> Result<Str
let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?; let remote_npub = remote_user.to_bech32().map_err(|err| err.to_string())?;
// Init nostr connect // Init nostr connect
let nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None) let mut nostr_connect = NostrConnect::new(bunker_uri, app_keys, Duration::from_secs(120), None)
.map_err(|err| err.to_string())?; .map_err(|err| err.to_string())?;
let bunker_uri = nostr_connect // Handle auth url
nostr_connect.auth_url_handler(AuthHandler);
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?;
let reuse_bunker = nostr_connect
.bunker_uri() .bunker_uri()
.await .await
.map_err(|err| err.to_string())?; .map_err(|err| err.to_string())?;
let keyring = Entry::new("Lume Safe Storage", &remote_npub).map_err(|err| err.to_string())?; let mut reuse_uri = reuse_bunker.to_string();
if let Some(secret) = reuse_bunker.secret() {
let replace = format!("&secret={}", secret);
reuse_uri = reuse_uri.replace(replace.as_str(), "");
}
let account = Account { let account = Account {
secret_key: app_secret, secret_key: app_secret,
nostr_connect: Some(bunker_uri.to_string()), nostr_connect: Some(reuse_uri),
}; };
// Save secret key to keyring // Save secret key to keyring
@@ -217,7 +238,9 @@ pub async fn set_signer(
let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?; let app_keys = Keys::from_str(&account.secret_key).map_err(|e| e.to_string())?;
match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) { match NostrConnect::new(uri, app_keys, Duration::from_secs(120), None) {
Ok(signer) => { Ok(mut signer) => {
// Handle auth url
signer.auth_url_handler(AuthHandler);
// Update signer // Update signer
client.set_signer(signer).await; client.set_signer(signer).await;
// Emit to front-end // Emit to front-end
+20 -28
View File
@@ -71,11 +71,11 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
#[specta::specta] #[specta::specta]
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() let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Custom(1111)]) .kind(Kind::Comment)
.event(event_id); .custom_tag(SingleLetterTag::uppercase(Alphabet::E), [event_id]);
let mut events = Events::new(&[filter.clone()]); let mut events = Events::new(&[filter.clone()]);
@@ -523,39 +523,31 @@ pub async fn reply(content: String, to: String, state: State<'_, Nostr>) -> Resu
Err(e) => return Err(e.to_string()), Err(e) => return Err(e.to_string()),
}; };
// Detect root event from reply // Find root event from reply
let root_ids: Vec<&EventId> = reply_to let root_tag = reply_to
.tags .tags
.filter_standardized(TagKind::e()) .find(TagKind::SingleLetter(SingleLetterTag::uppercase(
.filter_map(|t| match t { Alphabet::E,
TagStandard::Event { )));
event_id, marker, ..
} => {
if let Some(mkr) = marker {
match mkr {
Marker::Root => Some(event_id),
Marker::Reply => Some(event_id),
_ => None,
}
} else {
Some(event_id)
}
}
_ => None,
})
.collect();
// Get root event if exist // Get root event if exist
let root = match root_ids.first() { let root = match root_tag {
Some(&id) => client Some(tag) => match tag.content() {
Some(content) => {
let id = EventId::parse(content).map_err(|err| err.to_string())?;
client
.database() .database()
.event_by_id(id) .event_by_id(&id)
.await .await
.map_err(|err| err.to_string())?, .map_err(|err| err.to_string())?
}
None => None,
},
None => None, None => None,
}; };
let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None) let builder = EventBuilder::comment(content, &reply_to, root.as_ref(), None)
.add_tags(tags) .add_tags(tags)
.pow(DEFAULT_DIFFICULTY); .pow(DEFAULT_DIFFICULTY);
+5 -6
View File
@@ -105,9 +105,6 @@ pub async fn create_column(
}); });
} }
} else if let Ok(event_id) = EventId::parse(&id) { } else if let Ok(event_id) = EventId::parse(&id) {
let is_thread = payload.url().to_string().contains("events");
if is_thread {
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let state = webview.state::<Nostr>(); let state = webview.state::<Nostr>();
let client = &state.client; let client = &state.client;
@@ -115,8 +112,11 @@ pub async fn create_column(
let subscription_id = SubscriptionId::new(webview.label()); let subscription_id = SubscriptionId::new(webview.label());
let filter = Filter::new() let filter = Filter::new()
.event(event_id) .custom_tag(
.kinds(vec![Kind::TextNote, Kind::Custom(1111)]) SingleLetterTag::uppercase(Alphabet::E),
[event_id],
)
.kind(Kind::Comment)
.since(Timestamp::now()); .since(Timestamp::now());
if let Err(e) = client if let Err(e) = client
@@ -129,7 +129,6 @@ pub async fn create_column(
} }
} }
} }
}
PageLoadEvent::Finished => { PageLoadEvent::Finished => {
println!("{} finished loading", payload.url()); println!("{} finished loading", payload.url());
} }
+18 -17
View File
@@ -56,13 +56,6 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
// Get words // Get words
let words: Vec<_> = content.split_whitespace().collect(); let words: Vec<_> = content.split_whitespace().collect();
// Get mentions
let mentions = words
.iter()
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
.map(|&s| s.to_string())
.collect::<Vec<_>>();
// Get hashtags // Get hashtags
let hashtags = words let hashtags = words
.iter() .iter()
@@ -70,6 +63,13 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
.map(|&s| s.to_string().replace("#", "").to_lowercase()) .map(|&s| s.to_string().replace("#", "").to_lowercase())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Get mentions
let mentions = words
.iter()
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
.map(|&s| s.to_string())
.collect::<Vec<_>>();
for mention in mentions { for mention in mentions {
let entity = mention.replace("nostr:", "").replace('@', ""); let entity = mention.replace("nostr:", "").replace('@', "");
@@ -92,8 +92,11 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
} }
if entity.starts_with("note") { if entity.starts_with("note") {
if let Ok(event_id) = EventId::from_bech32(&entity) { if let Ok(event_id) = EventId::from_bech32(&entity) {
let hex = event_id.to_hex(); let tag = Tag::from_standardized(TagStandard::Quote {
let tag = Tag::parse(&["e", &hex, "", "mention"]).unwrap(); event_id,
relay_url: None,
public_key: None,
});
tags.push(tag); tags.push(tag);
} else { } else {
continue; continue;
@@ -101,14 +104,12 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
} }
if entity.starts_with("nevent") { if entity.starts_with("nevent") {
if let Ok(event) = Nip19Event::from_bech32(&entity) { if let Ok(event) = Nip19Event::from_bech32(&entity) {
let hex = event.event_id.to_hex(); let relay_url = event.relays.first().map(UncheckedUrl::from);
let relay = event.clone().relays.into_iter().next().unwrap_or("".into()); let tag = Tag::from_standardized(TagStandard::Quote {
let tag = Tag::parse(&["e", &hex, &relay, "mention"]).unwrap(); event_id: event.event_id,
relay_url,
if let Some(author) = event.author { public_key: event.author,
let tag = Tag::public_key(author); });
tags.push(tag);
}
tags.push(tag); tags.push(tag);
} else { } else {
+4 -4
View File
@@ -236,8 +236,8 @@ fn main() {
// Config // Config
let opts = Options::new() let opts = Options::new()
.gossip(true) .gossip(true)
.max_avg_latency(Duration::from_millis(500)) .max_avg_latency(Duration::from_secs(2))
.timeout(Duration::from_secs(5)); .timeout(Duration::from_secs(10));
// Setup nostr client // Setup nostr client
let client = ClientBuilder::default() let client = ClientBuilder::default()
@@ -532,7 +532,7 @@ fn main() {
if let Err(e) = handle_clone.emit("metadata", event.as_json()) { if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
println!("Emit error: {}", e) println!("Emit error: {}", e)
} }
} else if event.kind == Kind::TextNote { } else if event.kind == Kind::Comment {
let payload = RichEvent { let payload = RichEvent {
raw: event.as_json(), raw: event.as_json(),
parsed: if event.kind == Kind::TextNote { parsed: if event.kind == Kind::TextNote {
@@ -544,7 +544,7 @@ fn main() {
if let Err(e) = handle_clone.emit_to( if let Err(e) = handle_clone.emit_to(
EventTarget::labeled(subscription_id.to_string()), EventTarget::labeled(subscription_id.to_string()),
"event", "comment",
payload, payload,
) { ) {
println!("Emit error: {}", e) println!("Emit error: {}", e)
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Lume", "productName": "Lume",
"version": "24.11.5", "version": "24.11.7",
"identifier": "nu.lume.Lume", "identifier": "nu.lume.Lume",
"build": { "build": {
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",
+3 -5
View File
@@ -1,12 +1,12 @@
/* prettier-ignore-start */
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// This file is auto-generated by TanStack Router // This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
@@ -1036,8 +1036,6 @@ export const routeTree = rootRoute
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */
/* ROUTE_MANIFEST_START /* ROUTE_MANIFEST_START
{ {
"routes": { "routes": {
@@ -99,13 +99,8 @@ function ReplyList() {
const res = await commands.getReplies(id); const res = await commands.getReplies(id);
if (res.status === "ok") { if (res.status === "ok") {
const events = res.data const events = res.data.map((item) =>
// Create Lume Events LumeEvent.from(item.raw, item.parsed),
.map((item) => LumeEvent.from(item.raw, item.parsed))
// Filter quote
.filter(
(ev) =>
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
); );
return events; return events;
@@ -179,7 +174,7 @@ function ReplyList() {
useEffect(() => { useEffect(() => {
const unlisten = getCurrentWindow().listen<EventPayload>( const unlisten = getCurrentWindow().listen<EventPayload>(
"event", "comment",
async (data) => { async (data) => {
const event = LumeEvent.from(data.payload.raw, data.payload.parsed); const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
@@ -216,7 +211,7 @@ function ReplyList() {
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<Spinner className="size-4" /> <Spinner className="size-4" />
<span className="text-sm font-medium">Getting replies...</span> <span className="text-sm font-medium">Loading replies...</span>
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
+1 -7
View File
@@ -1,9 +1,3 @@
import { commands } from "@/commands.gen";
import { createFileRoute } from "@tanstack/react-router"; import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/columns/_layout/events/$id")({ export const Route = createFileRoute("/columns/_layout/events/$id")();
beforeLoad: async () => {
const accounts = await commands.getAccounts();
return { accounts };
},
});
@@ -73,7 +73,7 @@ function Newsfeeds() {
: item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed"; : item.tags.find((tag) => tag[0] === "title")?.[1] || "Unnamed";
const label = const label =
item.kind === 3 item.kind === 3
? `newsfeed-${id.slice(0, 5)}` ? `newsfeed-${item.pubkey.slice(0, 5)}`
: item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid(); : item.tags.find((tag) => tag[0] === "d")?.[1] || nanoid();
return ( return (
@@ -120,19 +120,19 @@ function Screen() {
> >
<Tabs.List className="h-11 shrink-0 flex items-center"> <Tabs.List className="h-11 shrink-0 flex items-center">
<Tabs.Trigger <Tabs.Trigger
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50" className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
value="replies" value="replies"
> >
Replies Replies
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50" className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
value="reactions" value="reactions"
> >
Reactions Reactions
</Tabs.Trigger> </Tabs.Trigger>
<Tabs.Trigger <Tabs.Trigger
className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-900 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-800 data-[state=inactive]:opacity-50" className="flex-1 inline-flex h-11 items-center justify-center gap-2 px-3 text-sm font-medium border-b border-neutral-100 dark:border-neutral-700 data-[state=active]:border-neutral-200 dark:data-[state=active]:border-neutral-600 data-[state=inactive]:opacity-50"
value="zaps" value="zaps"
> >
Zaps Zaps
+1 -1
View File
@@ -70,7 +70,7 @@ export const Route = createLazyFileRoute("/new-post/")({
function Screen() { function Screen() {
const { reply_to } = Route.useSearch(); const { reply_to } = Route.useSearch();
const { accounts, initialValue, queryClient } = Route.useRouteContext(); const { accounts, initialValue } = Route.useRouteContext();
const { deferMentionList } = Route.useLoaderData(); const { deferMentionList } = Route.useLoaderData();
const users = useAwaited({ promise: deferMentionList })[0]; const users = useAwaited({ promise: deferMentionList })[0];