Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa1d5720b0 | |||
| 32a58211cc | |||
| 4915d4fd60 | |||
| 53f3e77a5d | |||
| e52d584e0c | |||
| b0d1521c49 | |||
| 9dbac5308d |
137
Cargo.lock
generated
@@ -202,11 +202,11 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ar_archive_writer"
|
name = "ar_archive_writer"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348"
|
checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"object",
|
"object 0.39.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -635,7 +635,7 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
"object",
|
"object 0.37.3",
|
||||||
"rustc-demangle",
|
"rustc-demangle",
|
||||||
"windows-link 0.2.1",
|
"windows-link 0.2.1",
|
||||||
]
|
]
|
||||||
@@ -648,9 +648,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bech32"
|
name = "bech32"
|
||||||
version = "0.11.1"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f"
|
checksum = "efbd3e1070bbdf4cd88a75264e18e8a26f7cb5c6949eadf0ceb85fb159cf08f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
@@ -1087,6 +1087,7 @@ dependencies = [
|
|||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"person",
|
"person",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
@@ -1231,7 +1232,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui_util",
|
"gpui_util",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1715,7 +1716,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2611,7 +2612,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -2693,7 +2694,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_linux"
|
name = "gpui_linux"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
@@ -2745,7 +2746,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macos"
|
name = "gpui_macos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_macos",
|
"accesskit_macos",
|
||||||
@@ -2794,7 +2795,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -2805,7 +2806,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_platform"
|
name = "gpui_platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2818,7 +2819,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_shared_string"
|
name = "gpui_shared_string"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2828,7 +2829,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -2839,7 +2840,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_util"
|
name = "gpui_util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"log",
|
"log",
|
||||||
@@ -2849,7 +2850,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_web"
|
name = "gpui_web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
@@ -2872,7 +2873,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_wgpu"
|
name = "gpui_wgpu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -2902,7 +2903,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_windows"
|
name = "gpui_windows"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_windows",
|
"accesskit_windows",
|
||||||
@@ -3192,7 +3193,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -3212,7 +3213,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -3226,9 +3227,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hybrid-array"
|
name = "hybrid-array"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
@@ -4019,7 +4020,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
@@ -4212,8 +4213,8 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -4237,10 +4238,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-blossom"
|
name = "nostr-blossom"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
|
"bitcoin_hashes",
|
||||||
"nostr",
|
"nostr",
|
||||||
"opaquerr",
|
"opaquerr",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -4249,8 +4251,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -4263,8 +4265,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"btreecap",
|
"btreecap",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -4274,8 +4276,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip"
|
name = "nostr-gossip"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nostr",
|
"nostr",
|
||||||
"opaquerr",
|
"opaquerr",
|
||||||
@@ -4283,8 +4285,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-gossip-memory"
|
name = "nostr-gossip-memory"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -4295,8 +4297,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"flume 0.12.0",
|
"flume 0.12.0",
|
||||||
@@ -4309,8 +4311,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-memory"
|
name = "nostr-memory"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"btreecap",
|
"btreecap",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -4320,8 +4322,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.45.0-alpha.7"
|
version = "0.45.0-alpha.8"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#c15db19db55df326d56bbe11543583e0bd0276e5"
|
source = "git+https://github.com/rust-nostr/nostr#83cb556fa59259eb30f3df63f153f42aab77519b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -4333,6 +4335,7 @@ dependencies = [
|
|||||||
"nostr-database",
|
"nostr-database",
|
||||||
"nostr-gossip",
|
"nostr-gossip",
|
||||||
"opaquerr",
|
"opaquerr",
|
||||||
|
"rand 0.10.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -4729,6 +4732,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.39.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -4922,7 +4934,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4942,7 +4954,6 @@ dependencies = [
|
|||||||
"nostr-sdk",
|
"nostr-sdk",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"state",
|
"state",
|
||||||
"urlencoding",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5734,7 +5745,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
@@ -5816,7 +5827,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -6131,7 +6142,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "scheduler"
|
name = "scheduler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-task",
|
"async-task",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
@@ -6757,7 +6768,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless 0.9.3",
|
"heapless 0.9.3",
|
||||||
"log",
|
"log",
|
||||||
@@ -7710,12 +7721,6 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "urlencoding"
|
|
||||||
version = "2.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "usvg"
|
name = "usvg"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@@ -7758,7 +7763,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -7797,9 +7802,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag"
|
name = "value-bag"
|
||||||
version = "1.13.1"
|
version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ef73bfbaf3216cb59c205d7176bee1194e0d84348979da31f4a71fefe3c2054e"
|
checksum = "068e763e8279de7ab94b6afebded2cb701678af094feb1c12ccb061b4783c1be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"value-bag-serde1",
|
"value-bag-serde1",
|
||||||
"value-bag-sval2",
|
"value-bag-sval2",
|
||||||
@@ -7807,9 +7812,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag-serde1"
|
name = "value-bag-serde1"
|
||||||
version = "1.13.1"
|
version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b92170db3db8a6354f12a5b7f13a5453928433e08fc46aee51eedfa8f7a28a1"
|
checksum = "417d6197dd0ee696783d6be4276ac6ea74b985e00024c85ccfb37aff4f2bed82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"erased-serde",
|
"erased-serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
@@ -7818,9 +7823,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "value-bag-sval2"
|
name = "value-bag-sval2"
|
||||||
version = "1.13.1"
|
version = "1.13.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf9832097ca044466ae3f1aa43a943d4e450b7c3b8cdf65363875df07da79a0"
|
checksum = "c61f7251ecde2c9ed431bbe0659853e7991753447447bbf1ae59d8b31c578d4e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sval",
|
"sval",
|
||||||
"sval_buffer",
|
"sval_buffer",
|
||||||
@@ -9088,9 +9093,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xcursor"
|
name = "xcursor"
|
||||||
version = "0.3.10"
|
version = "0.3.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xim-ctext"
|
name = "xim-ctext"
|
||||||
@@ -9491,7 +9496,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "zlog"
|
name = "zlog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -9508,7 +9513,7 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing"
|
name = "ztracing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -9519,7 +9524,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing_macro"
|
name = "ztracing_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#f851d82e880c152d0f41e4a60d363aa82b4b8114"
|
source = "git+https://github.com/zed-industries/zed#5e1fd392f67e27fa1da91bad43eef7db1a5dec23"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
|
|||||||
62
Cargo.toml-E
@@ -1,62 +0,0 @@
|
|||||||
[workspace]
|
|
||||||
resolver = "2"
|
|
||||||
members = ["crates/*", "desktop", "web"]
|
|
||||||
default-members = ["desktop"]
|
|
||||||
|
|
||||||
[workspace.package]
|
|
||||||
version = "1.0.0-beta5"
|
|
||||||
edition = "2024"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# GPUI
|
|
||||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland"] }
|
|
||||||
gpui_linux = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_windows = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_macos = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
|
|
||||||
# Nostr
|
|
||||||
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-blossom = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44" ] }
|
|
||||||
|
|
||||||
# Others
|
|
||||||
anyhow = "1.0.44"
|
|
||||||
chrono = { version = "0.4.38", features = ["wasmbind"] }
|
|
||||||
futures = "0.3"
|
|
||||||
itertools = "0.13.0"
|
|
||||||
log = "0.4"
|
|
||||||
oneshot = "0.1.10"
|
|
||||||
flume = { version = "0.11.1", default-features = false, features = ["async", "select"] }
|
|
||||||
rust-embed = { version = "8.5", features = ["include-exclude"] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
schemars = "1"
|
|
||||||
smallvec = "1.14.0"
|
|
||||||
smol = "2"
|
|
||||||
webbrowser = "1.0.4"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter"] }
|
|
||||||
errno = { version = "0.3.14", default-features = false }
|
|
||||||
instant = "0.1"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
# Use stacker's psm version which may have better WASM support
|
|
||||||
psm = { git = "https://github.com/rust-lang/stacker", branch = "master" }
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
strip = true
|
|
||||||
opt-level = "z"
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[profile.profiling]
|
|
||||||
inherits = "release"
|
|
||||||
debug = true
|
|
||||||
120
README.md
@@ -1,125 +1,5 @@
|
|||||||

|
|
||||||
|
|
||||||
<p>
|
|
||||||
<a href="https://github.com/reyakov/coop/actions/workflows/rust.yml">
|
|
||||||
<img alt="Actions" src="https://github.com/reyakov/coop/actions/workflows/rust.yml/badge.svg">
|
|
||||||
</a>
|
|
||||||
<img alt="GitHub repo size" src="https://img.shields.io/github/repo-size/reyakov/coop">
|
|
||||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues-raw/reyakov/coop">
|
|
||||||
<img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/reyakov/coop">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
Coop is a simple, fast, and reliable nostr client for secure messaging across all platforms.
|
Coop is a simple, fast, and reliable nostr client for secure messaging across all platforms.
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
<p float="left">
|
|
||||||
<img src="/docs/mac_01.png" width="250" />
|
|
||||||
<img src="/docs/mac_02.png" width="250" />
|
|
||||||
<img src="/docs/mac_03.png" width="250" />
|
|
||||||
<img src="/docs/mac_04.png" width="250" />
|
|
||||||
<img src="/docs/mac_05.png" width="250" />
|
|
||||||
<img src="/docs/mac_06.png" width="250" />
|
|
||||||
<img src="/docs/mac_07.png" width="250" />
|
|
||||||
<img src="/docs/mac_08.png" width="250" />
|
|
||||||
<img src="/docs/mac_09.png" width="250" />
|
|
||||||
<img src="/docs/linux_01.png" width="250" />
|
|
||||||
<img src="/docs/linux_02.png" width="250" />
|
|
||||||
<img src="/docs/linux_03.png" width="250" />
|
|
||||||
<img src="/docs/linux_04.png" width="250" />
|
|
||||||
<img src="/docs/linux_05.png" width="250" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
To install Coop, follow these steps:
|
|
||||||
|
|
||||||
1. **Download the Latest Release**:
|
|
||||||
|
|
||||||
- Visit the [Coop Releases page on GitHub](https://github.com/reyakov/coop/releases).
|
|
||||||
- Download the package that matches your operating system (Windows, macOS, or Linux).
|
|
||||||
|
|
||||||
2. **Install**:
|
|
||||||
|
|
||||||
- **Windows**: Run the downloaded `.exe` installer and follow the on-screen instructions.
|
|
||||||
- **macOS**: Open the downloaded `.dmg` file and drag Coop to your Applications folder.
|
|
||||||
- **Linux**: Run the downloaded `.flatpak` or `.snap` installer and follow the on-screen instructions.
|
|
||||||
|
|
||||||
3. **Run Coop**:
|
|
||||||
- Launch Coop from your Applications folder (macOS) or by double-clicking the executable (Windows/Linux).
|
|
||||||
|
|
||||||
For more detailed instructions, refer to the [Release Notes](#) on GitHub.
|
|
||||||
|
|
||||||
### Developing Coop
|
|
||||||
|
|
||||||
Coop is built using Rust and GPUI. All Nostr related stuffs handled by [Rust Nostr SDK](https://github.com/rust-nostr/nostr)
|
|
||||||
|
|
||||||
#### Prerequisites
|
|
||||||
|
|
||||||
- **Rust Toolchain**: Ensure you have Rust installed. If not, you can install it using [rustup](https://rustup.rs/).
|
|
||||||
- **Cargo**: Rust's package manager, which comes bundled with the Rust installation.
|
|
||||||
- **Git**: To clone the repository and manage version control.
|
|
||||||
|
|
||||||
#### Setting Up the Development Environment
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/reyakov/coop.git
|
|
||||||
cd coop
|
|
||||||
```
|
|
||||||
|
|
||||||
2.1 Install Linux dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./script/linux
|
|
||||||
```
|
|
||||||
|
|
||||||
2.2 Install FreeBSD dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./script/freebsd
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Install Rust dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Run the app:
|
|
||||||
```bash
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Building for Production
|
|
||||||
|
|
||||||
To build Coop for production, use the following command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --release
|
|
||||||
```
|
|
||||||
|
|
||||||
This will generate an optimized binary in the `target/release` directory.
|
|
||||||
|
|
||||||
#### Contributing Code
|
|
||||||
|
|
||||||
If you'd like to contribute to Coop, please follow these steps:
|
|
||||||
|
|
||||||
1. Fork the repository.
|
|
||||||
2. Create a new branch for your feature or bugfix.
|
|
||||||
3. Make your changes and ensure all tests pass.
|
|
||||||
4. Submit a pull request with a detailed description of your changes.
|
|
||||||
|
|
||||||
For more information, see the [Contributing](#contributing) section.
|
|
||||||
|
|
||||||
#### Additional Resources
|
|
||||||
|
|
||||||
- [Rust Nostr](https://github.com/rust-nostr/nostr/)
|
|
||||||
- [GPUI](https://www.gpui.rs/)
|
|
||||||
- [GPUI Components](https://github.com/longbridge/gpui-component/)
|
|
||||||
- [Coop Issue Tracker](https://github.com/reyakov/coop/issues/)
|
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
Copyright (C) 2025 Ren Amamiya & other Coop contributors
|
Copyright (C) 2025 Ren Amamiya & other Coop contributors
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
use std::collections::{BTreeSet, HashMap, HashSet, hash_map};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, LazyLock, RwLock};
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
|
|
||||||
@@ -80,6 +80,9 @@ pub struct ChatRegistry {
|
|||||||
/// Chat rooms
|
/// Chat rooms
|
||||||
rooms: Vec<Entity<Room>>,
|
rooms: Vec<Entity<Room>>,
|
||||||
|
|
||||||
|
/// O(1) room lookup by room ID
|
||||||
|
room_index: HashMap<u64, Entity<Room>>,
|
||||||
|
|
||||||
/// Events that failed to unwrap for any reason
|
/// Events that failed to unwrap for any reason
|
||||||
trash: Entity<BTreeSet<FailedMessage>>,
|
trash: Entity<BTreeSet<FailedMessage>>,
|
||||||
|
|
||||||
@@ -170,6 +173,7 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
|
room_index: HashMap::new(),
|
||||||
trash: cx.new(|_| BTreeSet::default()),
|
trash: cx.new(|_| BTreeSet::default()),
|
||||||
seen: Arc::new(RwLock::new(HashMap::default())),
|
seen: Arc::new(RwLock::new(HashMap::default())),
|
||||||
event_map: Arc::new(RwLock::new(HashMap::default())),
|
event_map: Arc::new(RwLock::new(HashMap::default())),
|
||||||
@@ -436,12 +440,9 @@ impl ChatRegistry {
|
|||||||
self.tracking.load(Ordering::Acquire)
|
self.tracking.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a weak reference to a room by its ID.
|
/// Get a weak reference to a room by its ID
|
||||||
pub fn room(&self, id: &u64, cx: &App) -> Option<WeakEntity<Room>> {
|
pub fn room(&self, id: &u64, _cx: &App) -> Option<WeakEntity<Room>> {
|
||||||
self.rooms
|
self.room_index.get(id).map(|room| room.downgrade())
|
||||||
.iter()
|
|
||||||
.find(|this| &this.read(cx).id == id)
|
|
||||||
.map(|this| this.downgrade())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all rooms based on the filter.
|
/// Get all rooms based on the filter.
|
||||||
@@ -511,7 +512,11 @@ impl ChatRegistry {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let room: Room = room.into().organize(&public_key);
|
let room: Room = room.into().organize(&public_key);
|
||||||
self.rooms.insert(0, cx.new(|_| room));
|
let room_id = room.id;
|
||||||
|
let entity = cx.new(|_| room);
|
||||||
|
|
||||||
|
self.room_index.insert(room_id, entity.clone());
|
||||||
|
self.rooms.insert(0, entity);
|
||||||
|
|
||||||
cx.emit(ChatEvent::Ping);
|
cx.emit(ChatEvent::Ping);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -524,9 +529,11 @@ impl ChatRegistry {
|
|||||||
// Get the room's ID.
|
// Get the room's ID.
|
||||||
let id = room.read(cx).id;
|
let id = room.read(cx).id;
|
||||||
|
|
||||||
// If the room is new, add it to the registry.
|
// If the room is new, add it to the registry and index.
|
||||||
if !self.rooms.iter().any(|r| r.read(cx).id == id) {
|
if let hash_map::Entry::Vacant(e) = self.room_index.entry(id) {
|
||||||
self.rooms.insert(0, room.to_owned());
|
let entity = room.to_owned();
|
||||||
|
e.insert(entity.clone());
|
||||||
|
self.rooms.insert(0, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit the open room event deferred to avoid re-entrant reads
|
// Emit the open room event deferred to avoid re-entrant reads
|
||||||
@@ -537,18 +544,24 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
/// Close a room.
|
/// Close a room.
|
||||||
pub fn close_room(&mut self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn close_room(&mut self, id: u64, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.rooms.iter().any(|r| r.read(cx).id == id) {
|
if self.room_index.contains_key(&id) {
|
||||||
|
self.room_index.remove(&id);
|
||||||
|
self.rooms.retain(|r| r.read(cx).id != id);
|
||||||
cx.defer_in(window, move |_this, _window, cx| {
|
cx.defer_in(window, move |_this, _window, cx| {
|
||||||
cx.emit(ChatEvent::CloseRoom(id));
|
cx.emit(ChatEvent::CloseRoom(id));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort rooms by their created at.
|
/// Sort rooms by their created at. Only notifies if order changed.
|
||||||
pub fn sort(&mut self, cx: &mut Context<Self>) {
|
pub fn sort(&mut self, cx: &mut Context<Self>) {
|
||||||
|
let before: Vec<_> = self.rooms.iter().map(|ev| ev.read(cx).id).collect();
|
||||||
self.rooms.sort_by_key(|ev| Reverse(ev.read(cx).created_at));
|
self.rooms.sort_by_key(|ev| Reverse(ev.read(cx).created_at));
|
||||||
|
let after: Vec<_> = self.rooms.iter().map(|ev| ev.read(cx).id).collect();
|
||||||
|
if before != after {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Finding rooms based on a query.
|
/// Finding rooms based on a query.
|
||||||
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
pub fn find(&self, query: &str, cx: &App) -> Vec<Entity<Room>> {
|
||||||
@@ -574,6 +587,7 @@ impl ChatRegistry {
|
|||||||
/// Reset the registry.
|
/// Reset the registry.
|
||||||
pub fn reset(&mut self, cx: &mut Context<Self>) {
|
pub fn reset(&mut self, cx: &mut Context<Self>) {
|
||||||
self.rooms.clear();
|
self.rooms.clear();
|
||||||
|
self.room_index.clear();
|
||||||
self.trash.update(cx, |this, cx| {
|
self.trash.update(cx, |this, cx| {
|
||||||
this.clear();
|
this.clear();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -601,7 +615,9 @@ impl ChatRegistry {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let new_room_id = new_room.id;
|
let new_room_id = new_room.id;
|
||||||
self.rooms.push(cx.new(|_| new_room));
|
let entity = cx.new(|_| new_room);
|
||||||
|
self.room_index.insert(new_room_id, entity.clone());
|
||||||
|
self.rooms.push(entity);
|
||||||
|
|
||||||
let new_index = self.rooms.len();
|
let new_index = self.rooms.len();
|
||||||
room_map.insert(new_room_id, new_index);
|
room_map.insert(new_room_id, new_index);
|
||||||
@@ -713,7 +729,7 @@ impl ChatRegistry {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match self.rooms.iter().find(|e| e.read(cx).id == message.room) {
|
match self.room_index.get(&message.room).cloned() {
|
||||||
Some(room) => {
|
Some(room) => {
|
||||||
room.update(cx, |this, cx| {
|
room.update(cx, |this, cx| {
|
||||||
if this.kind == RoomKind::Request && message.rumor.pubkey == public_key {
|
if this.kind == RoomKind::Request && message.rumor.pubkey == public_key {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use instant::Duration;
|
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
use common::EventExt;
|
use common::EventExt;
|
||||||
use device::DeviceRegistry;
|
use device::DeviceRegistry;
|
||||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task};
|
||||||
|
use instant::Duration;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Person, PersonRegistry};
|
use person::{Person, PersonRegistry};
|
||||||
@@ -271,8 +271,8 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the members of the room
|
/// Returns the members of the room
|
||||||
pub fn members(&self) -> Vec<PublicKey> {
|
pub fn members(&self) -> &[PublicKey] {
|
||||||
self.members.clone()
|
&self.members
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the room has more than two members (group)
|
/// Checks if the room has more than two members (group)
|
||||||
@@ -356,7 +356,7 @@ impl Room {
|
|||||||
pub fn connect(&self, cx: &App) -> Task<Result<(), Error>> {
|
pub fn connect(&self, cx: &App) -> Task<Result<(), Error>> {
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
let client = nostr.read(cx).client();
|
let client = nostr.read(cx).client();
|
||||||
let members = self.members();
|
let members = self.members().to_vec();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let opts = SubscribeAutoCloseOptions::default()
|
let opts = SubscribeAutoCloseOptions::default()
|
||||||
@@ -419,12 +419,23 @@ impl Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct a rumor event for direct message
|
// Construct a rumor event for direct message
|
||||||
pub fn rumor<S, I>(&self, content: S, replies: I, cx: &App) -> Option<UnsignedEvent>
|
pub fn rumor<S, I>(
|
||||||
|
&self,
|
||||||
|
content: S,
|
||||||
|
replies: I,
|
||||||
|
reaction: bool,
|
||||||
|
cx: &App,
|
||||||
|
) -> Option<UnsignedEvent>
|
||||||
where
|
where
|
||||||
S: Into<String>,
|
S: Into<String>,
|
||||||
I: IntoIterator<Item = EventId>,
|
I: IntoIterator<Item = EventId>,
|
||||||
{
|
{
|
||||||
let kind = Kind::PrivateDirectMessage;
|
let kind = if reaction {
|
||||||
|
Kind::Reaction
|
||||||
|
} else {
|
||||||
|
Kind::PrivateDirectMessage
|
||||||
|
};
|
||||||
|
|
||||||
let content: String = content.into();
|
let content: String = content.into();
|
||||||
let replies: Vec<EventId> = replies.into_iter().collect();
|
let replies: Vec<EventId> = replies.into_iter().collect();
|
||||||
|
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ serde.workspace = true
|
|||||||
|
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
pulldown-cmark = "0.13.1"
|
pulldown-cmark = "0.13.1"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, LazyLock, RwLock};
|
||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
@@ -17,6 +17,7 @@ use gpui::{
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{Person, PersonRegistry};
|
use person::{Person, PersonRegistry};
|
||||||
|
use regex::Regex;
|
||||||
use settings::{AppSettings, SignerKind};
|
use settings::{AppSettings, SignerKind};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{NostrRegistry, upload};
|
use state::{NostrRegistry, upload};
|
||||||
@@ -35,6 +36,14 @@ use ui::{
|
|||||||
|
|
||||||
use crate::text::RenderedText;
|
use crate::text::RenderedText;
|
||||||
|
|
||||||
|
const REACTION_EMOJIS: &[&str] = &["👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀"];
|
||||||
|
const COMPACT_REACTION_EMOJIS: &[&str] = &["👍", "❤️", "👀"];
|
||||||
|
|
||||||
|
/// Regex matching strings that consist entirely of emoji characters,
|
||||||
|
/// zero-width joiners, variation selectors, and keycap combiners.
|
||||||
|
static EMOJI_RE: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"^[\p{Emoji}\u{200D}\u{FE0F}\u{20E3}]+$").unwrap());
|
||||||
|
|
||||||
mod actions;
|
mod actions;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
@@ -56,6 +65,12 @@ pub struct ChatPanel {
|
|||||||
/// All messages (sorted by created_at)
|
/// All messages (sorted by created_at)
|
||||||
messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
|
|
||||||
|
/// O(1) message lookup by EventId
|
||||||
|
message_index: HashMap<EventId, usize>,
|
||||||
|
|
||||||
|
/// All reactions
|
||||||
|
reactions: BTreeMap<EventId, Vec<(SharedString, PublicKey)>>,
|
||||||
|
|
||||||
/// Mapping message ids to their rendered texts
|
/// Mapping message ids to their rendered texts
|
||||||
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
|
||||||
|
|
||||||
@@ -160,6 +175,8 @@ impl ChatPanel {
|
|||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
id,
|
id,
|
||||||
messages,
|
messages,
|
||||||
|
message_index: HashMap::new(),
|
||||||
|
reactions: BTreeMap::new(),
|
||||||
room,
|
room,
|
||||||
list_state,
|
list_state,
|
||||||
input,
|
input,
|
||||||
@@ -224,23 +241,29 @@ impl ChatPanel {
|
|||||||
while let Ok(status) = rx.recv_async().await {
|
while let Ok(status) = rx.recv_async().await {
|
||||||
{
|
{
|
||||||
let mut map = reports.write().unwrap();
|
let mut map = reports.write().unwrap();
|
||||||
for reports in map.values_mut() {
|
let status_id = match &*status {
|
||||||
for report in reports.iter_mut() {
|
SendStatus::Ok { id, .. } => *id,
|
||||||
|
SendStatus::Failed { id, .. } => *id,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the matching report and update it (exit early on first match)
|
||||||
|
'outer: for reports_list in map.values_mut() {
|
||||||
|
for report in reports_list.iter_mut() {
|
||||||
let Some(output) = report.output.as_mut() else {
|
let Some(output) = report.output.as_mut() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
if *output.id() != status_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
match &*status {
|
match &*status {
|
||||||
SendStatus::Ok { id, relay } => {
|
SendStatus::Ok { relay, .. } => {
|
||||||
if output.id() == id {
|
|
||||||
output.success.insert(relay.clone(), EventSendStatus::Sent);
|
output.success.insert(relay.clone(), EventSendStatus::Sent);
|
||||||
}
|
}
|
||||||
}
|
SendStatus::Failed { relay, message, .. } => {
|
||||||
SendStatus::Failed { id, relay, message } => {
|
|
||||||
if output.id() == id {
|
|
||||||
output.failed.insert(relay.clone(), message.clone());
|
output.failed.insert(relay.clone(), message.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break 'outer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,8 +282,12 @@ impl ChatPanel {
|
|||||||
move |this, _room, event, window, cx| {
|
move |this, _room, event, window, cx| {
|
||||||
match event {
|
match event {
|
||||||
RoomEvent::Incoming(message) => {
|
RoomEvent::Incoming(message) => {
|
||||||
|
if message.rumor.kind == Kind::Reaction {
|
||||||
|
this.insert_reaction(&message.rumor, cx);
|
||||||
|
} else {
|
||||||
this.insert_message(message, false, cx);
|
this.insert_message(message, false, cx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
RoomEvent::Reload => {
|
RoomEvent::Reload => {
|
||||||
// Defer to avoid re-entrant read on Room while
|
// Defer to avoid re-entrant read on Room while
|
||||||
// emit_refresh holds a write lock (via refresh_rooms).
|
// emit_refresh holds a write lock (via refresh_rooms).
|
||||||
@@ -331,24 +358,60 @@ impl ChatPanel {
|
|||||||
// Get the message which includes all attachments
|
// Get the message which includes all attachments
|
||||||
let content = self.get_input_value(cx);
|
let content = self.get_input_value(cx);
|
||||||
|
|
||||||
|
// Get the replies to this message
|
||||||
|
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
|
||||||
|
|
||||||
// Return if message is empty
|
// Return if message is empty
|
||||||
if content.trim().is_empty() {
|
if content.trim().is_empty() {
|
||||||
window.push_notification("Cannot send an empty message", cx);
|
window.push_notification("Cannot send an empty message", cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.send_message(&content, window, cx);
|
// If replying to exactly one message with only a valid emoji,
|
||||||
|
// send as a reaction instead of a text message
|
||||||
|
if replies.len() == 1 && EMOJI_RE.is_match(&content) && self.attachments.read(cx).is_empty()
|
||||||
|
{
|
||||||
|
for reply in &replies {
|
||||||
|
self.send_reaction(&content, reply, window, cx);
|
||||||
|
}
|
||||||
|
self.clear(window, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_message(&content, replies, false, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_reaction(
|
||||||
|
&mut self,
|
||||||
|
emoji: &str,
|
||||||
|
target: &EventId,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
// Return if emoji is empty
|
||||||
|
if emoji.trim().is_empty() {
|
||||||
|
window.push_notification("Cannot send an empty reaction", cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_message(emoji, vec![*target], true, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a message to all members of the chat
|
/// Send a message to all members of the chat
|
||||||
fn send_message(&mut self, value: &str, window: &mut Window, cx: &mut Context<Self>) {
|
fn send_message(
|
||||||
|
&mut self,
|
||||||
|
value: &str,
|
||||||
|
replies: Vec<EventId>,
|
||||||
|
reaction: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
if value.trim().is_empty() {
|
if value.trim().is_empty() {
|
||||||
window.push_notification("Cannot send an empty message", cx);
|
window.push_notification("Cannot send an empty message", cx);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let room = self.room.clone();
|
let room = self.room.clone();
|
||||||
let replies: Vec<EventId> = self.replies_to.read(cx).iter().copied().collect();
|
|
||||||
let content = value.to_string();
|
let content = value.to_string();
|
||||||
let sent_ids = self.sent_ids.clone();
|
let sent_ids = self.sent_ids.clone();
|
||||||
|
|
||||||
@@ -356,8 +419,10 @@ impl ChatPanel {
|
|||||||
let Some(room_entity) = room.upgrade() else {
|
let Some(room_entity) = room.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create rumor and send task
|
||||||
let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| {
|
let (rumor, send_task) = match room_entity.read_with(cx, |room, cx| {
|
||||||
let rumor = room.rumor(content.clone(), replies, cx)?;
|
let rumor = room.rumor(content.clone(), replies.clone(), reaction, cx)?;
|
||||||
let send_task = room.send(rumor.clone(), cx)?;
|
let send_task = room.send(rumor.clone(), cx)?;
|
||||||
Some((rumor, send_task))
|
Some((rumor, send_task))
|
||||||
}) {
|
}) {
|
||||||
@@ -371,9 +436,15 @@ impl ChatPanel {
|
|||||||
let id = rumor.id.expect("rumor must have an id");
|
let id = rumor.id.expect("rumor must have an id");
|
||||||
|
|
||||||
// Insert optimistic message and clear input
|
// Insert optimistic message and clear input
|
||||||
|
if rumor.kind != Kind::Reaction {
|
||||||
self.insert_message(&rumor, true, cx);
|
self.insert_message(&rumor, true, cx);
|
||||||
self.insert_reports(id, vec![], cx);
|
|
||||||
self.clear(window, cx);
|
self.clear(window, cx);
|
||||||
|
} else {
|
||||||
|
self.insert_reaction(&rumor, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update reports
|
||||||
|
self.insert_reports(id, vec![], cx);
|
||||||
|
|
||||||
// Spawn a single task to await the send and update reports
|
// Spawn a single task to await the send and update reports
|
||||||
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
self.tasks.push(cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -428,6 +499,10 @@ impl ChatPanel {
|
|||||||
|
|
||||||
if let Err(pos) = self.messages.binary_search(&msg) {
|
if let Err(pos) = self.messages.binary_search(&msg) {
|
||||||
self.messages.insert(pos, msg);
|
self.messages.insert(pos, msg);
|
||||||
|
// Rebuild message index after insertion (indices from pos to end shift)
|
||||||
|
for (i, message) in self.messages.iter().enumerate().skip(pos) {
|
||||||
|
self.message_index.insert(message.id, i);
|
||||||
|
}
|
||||||
self.list_state.splice(old_len..old_len, 1);
|
self.list_state.splice(old_len..old_len, 1);
|
||||||
|
|
||||||
if scroll {
|
if scroll {
|
||||||
@@ -444,23 +519,56 @@ impl ChatPanel {
|
|||||||
/// Convert and insert a vector of nostr events into the chat panel
|
/// Convert and insert a vector of nostr events into the chat panel
|
||||||
fn insert_messages(&mut self, events: &[UnsignedEvent], cx: &mut Context<Self>) {
|
fn insert_messages(&mut self, events: &[UnsignedEvent], cx: &mut Context<Self>) {
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
|
if event.kind == Kind::Reaction {
|
||||||
|
self.insert_reaction(event, cx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// Bulk inserting messages, so no need to scroll to the latest message
|
// Bulk inserting messages, so no need to scroll to the latest message
|
||||||
self.insert_message(event, false, cx);
|
self.insert_message(event, false, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a message has any reports
|
/// Insert a reaction into the chat panel
|
||||||
fn has_reports(&self, id: &EventId, _cx: &App) -> bool {
|
fn insert_reaction(&mut self, event: &UnsignedEvent, cx: &mut Context<Self>) {
|
||||||
self.reports_by_id.read().unwrap().get(id).is_some()
|
if event.kind != Kind::Reaction {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sent_reports(&self, id: &EventId, _cx: &App) -> Option<Vec<SendReport>> {
|
for id in event.tags.event_ids() {
|
||||||
|
self.reactions
|
||||||
|
.entry(id)
|
||||||
|
.or_default()
|
||||||
|
.push((SharedString::from(&event.content), event.pubkey));
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a message has any reports
|
||||||
|
fn has_reports(&self, id: &EventId) -> bool {
|
||||||
|
self.reports_by_id.read().unwrap().contains_key(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone reports for a message (used for modal display, not called during render)
|
||||||
|
fn sent_reports(&self, id: &EventId) -> Option<Vec<SendReport>> {
|
||||||
self.reports_by_id.read().unwrap().get(id).cloned()
|
self.reports_by_id.read().unwrap().get(id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a message by its ID
|
/// Get a message by its ID (O(1) lookup)
|
||||||
fn message(&self, id: &EventId) -> Option<&Message> {
|
fn message(&self, id: &EventId) -> Option<&Message> {
|
||||||
self.messages.iter().find(|msg| &msg.id == id)
|
self.message_index
|
||||||
|
.get(id)
|
||||||
|
.and_then(|&ix| self.messages.get(ix))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reaction by its target ID (returns reference, no allocation)
|
||||||
|
fn reaction(&self, id: &EventId) -> &[(SharedString, PublicKey)] {
|
||||||
|
self.reactions.get(id).map(|v| v.as_slice()).unwrap_or(&[])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a message has any reactions
|
||||||
|
fn has_reaction(&self, id: &EventId) -> bool {
|
||||||
|
self.reactions.contains_key(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Scroll to a message by its ID
|
/// Scroll to a message by its ID
|
||||||
@@ -575,7 +683,10 @@ impl ChatPanel {
|
|||||||
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
match command {
|
match command {
|
||||||
Command::Insert(content) => {
|
Command::Insert(content) => {
|
||||||
self.send_message(content, window, cx);
|
self.input.update(cx, |this, cx| {
|
||||||
|
let new_value = format!("{} {}", this.value(), content);
|
||||||
|
this.set_value(new_value, window, cx);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Command::ChangeSubject(subject) => {
|
Command::ChangeSubject(subject) => {
|
||||||
if self
|
if self
|
||||||
@@ -834,7 +945,8 @@ impl ChatPanel {
|
|||||||
|
|
||||||
let replies = message.replies_to.as_slice();
|
let replies = message.replies_to.as_slice();
|
||||||
let has_replies = !replies.is_empty();
|
let has_replies = !replies.is_empty();
|
||||||
let has_reports = self.has_reports(&id, cx);
|
let has_reactions = self.has_reaction(&id);
|
||||||
|
let has_reports = self.has_reports(&id);
|
||||||
|
|
||||||
// Hide avatar setting
|
// Hide avatar setting
|
||||||
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
let hide_avatar = AppSettings::get_hide_avatar(cx);
|
||||||
@@ -879,12 +991,7 @@ impl ChatPanel {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.text_color(cx.theme().text_placeholder)
|
.text_color(cx.theme().text_placeholder)
|
||||||
.child(
|
.child(div().font_semibold().child(author.name()))
|
||||||
div()
|
|
||||||
.font_semibold()
|
|
||||||
.text_color(cx.theme().text)
|
|
||||||
.child(author.name()),
|
|
||||||
)
|
|
||||||
.child(message.created_at.to_human_time())
|
.child(message.created_at.to_human_time())
|
||||||
.when(has_reports, |this| {
|
.when(has_reports, |this| {
|
||||||
this.child(self.render_sent_reports(&id, cx))
|
this.child(self.render_sent_reports(&id, cx))
|
||||||
@@ -895,7 +1002,10 @@ impl ChatPanel {
|
|||||||
this.children(self.render_message_replies(replies, cx))
|
this.children(self.render_message_replies(replies, cx))
|
||||||
})
|
})
|
||||||
.child(rendered_text)
|
.child(rendered_text)
|
||||||
.child(self.render_media(&message.media, cx)),
|
.child(self.render_media(&message.media, cx))
|
||||||
|
.when(has_reactions, |this| {
|
||||||
|
this.child(self.render_reactions(&id, cx))
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -990,13 +1100,9 @@ impl ChatPanel {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.px_2()
|
.px_2()
|
||||||
.border_l_2()
|
.border_l_2()
|
||||||
.border_color(cx.theme().element_selected)
|
.border_color(cx.theme().element_active)
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.child(
|
.child(div().font_semibold().child(author.name()))
|
||||||
div()
|
|
||||||
.text_color(cx.theme().text_accent)
|
|
||||||
.child(author.name()),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
@@ -1017,8 +1123,45 @@ impl ChatPanel {
|
|||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_reactions(&self, id: &EventId, cx: &App) -> impl IntoElement {
|
||||||
|
let current_user = NostrRegistry::global(cx).read(cx).current_user();
|
||||||
|
let reactions = self.reaction(id);
|
||||||
|
|
||||||
|
// Group reactions by emoji and collect authors for each
|
||||||
|
let mut grouped: BTreeMap<SharedString, Vec<PublicKey>> = BTreeMap::new();
|
||||||
|
for (emoji, author) in reactions {
|
||||||
|
grouped.entry(emoji.clone()).or_default().push(*author);
|
||||||
|
}
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.mt_2()
|
||||||
|
.gap_1()
|
||||||
|
.children(grouped.into_iter().map(|(emoji, authors)| {
|
||||||
|
let count = authors.len();
|
||||||
|
let has_reacted = current_user
|
||||||
|
.map(|pk| authors.contains(&pk))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.py_0p5()
|
||||||
|
.px_1()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.text_xs()
|
||||||
|
.border_1()
|
||||||
|
.when(has_reacted, |this| {
|
||||||
|
this.text_color(cx.theme().secondary_foreground)
|
||||||
|
.bg(cx.theme().secondary_background)
|
||||||
|
.border_color(cx.theme().secondary_active)
|
||||||
|
})
|
||||||
|
.when(!has_reacted, |this| this.border_color(cx.theme().border))
|
||||||
|
.child(emoji)
|
||||||
|
.child(SharedString::from(count.to_string()))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn render_sent_reports(&self, id: &EventId, cx: &App) -> impl IntoElement {
|
fn render_sent_reports(&self, id: &EventId, cx: &App) -> impl IntoElement {
|
||||||
let reports = self.sent_reports(id, cx);
|
let reports = self.sent_reports(id);
|
||||||
|
|
||||||
let pending = reports
|
let pending = reports
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1192,6 +1335,29 @@ impl ChatPanel {
|
|||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.bg(cx.theme().background)
|
.bg(cx.theme().background)
|
||||||
|
.children({
|
||||||
|
let mut items = vec![];
|
||||||
|
|
||||||
|
for emoji in COMPACT_REACTION_EMOJIS {
|
||||||
|
items.push(
|
||||||
|
Button::new(*emoji)
|
||||||
|
.label(*emoji)
|
||||||
|
.tooltip(*emoji)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.on_click({
|
||||||
|
let emoji = *emoji;
|
||||||
|
let id = *id;
|
||||||
|
cx.listener(move |this, _event, window, cx| {
|
||||||
|
this.send_reaction(emoji, &id, window, cx);
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items
|
||||||
|
})
|
||||||
|
.child(div().flex_shrink_0().h_4().w_px().bg(cx.theme().border))
|
||||||
.child(
|
.child(
|
||||||
Button::new("reply")
|
Button::new("reply")
|
||||||
.icon(IconName::Reply)
|
.icon(IconName::Reply)
|
||||||
@@ -1305,7 +1471,7 @@ impl ChatPanel {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
.child(SharedString::from("Replying to:"))
|
.child("Replying to:")
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_color(cx.theme().text_accent)
|
.text_color(cx.theme().text_accent)
|
||||||
@@ -1402,15 +1568,10 @@ impl ChatPanel {
|
|||||||
.ghost()
|
.ghost()
|
||||||
.large()
|
.large()
|
||||||
.dropdown_menu_with_anchor(gpui::Anchor::BottomLeft, move |this, _window, _cx| {
|
.dropdown_menu_with_anchor(gpui::Anchor::BottomLeft, move |this, _window, _cx| {
|
||||||
this.horizontal()
|
let menu = this.horizontal();
|
||||||
.menu("👍", Box::new(Command::Insert("👍")))
|
REACTION_EMOJIS.iter().fold(menu, |this, emoji| {
|
||||||
.menu("👎", Box::new(Command::Insert("👎")))
|
this.menu(*emoji, Box::new(Command::Insert(emoji)))
|
||||||
.menu("😄", Box::new(Command::Insert("😄")))
|
})
|
||||||
.menu("🎉", Box::new(Command::Insert("🎉")))
|
|
||||||
.menu("😕", Box::new(Command::Insert("😕")))
|
|
||||||
.menu("❤️", Box::new(Command::Insert("❤️")))
|
|
||||||
.menu("🚀", Box::new(Command::Insert("🚀")))
|
|
||||||
.menu("👀", Box::new(Command::Insert("👀")))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,3 @@ anyhow.workspace = true
|
|||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
flume.workspace = true
|
flume.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
urlencoding = "2.1.3"
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use instant::Duration;
|
|
||||||
|
|
||||||
use anyhow::{Error, anyhow};
|
use anyhow::{Error, anyhow};
|
||||||
use common::EventExt;
|
use common::EventExt;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Task, Window};
|
use gpui::{App, AppContext, Context, Entity, Global, Task, Window};
|
||||||
|
use instant::Duration;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{Announcement, BOOTSTRAP_RELAYS, NostrRegistry, TIMEOUT};
|
use state::{Announcement, BOOTSTRAP_RELAYS, NostrRegistry, TIMEOUT};
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ use gpui::SharedString;
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use state::Announcement;
|
use state::Announcement;
|
||||||
|
|
||||||
const IMAGE_RESIZER: &str = "https://wsrv.nl";
|
|
||||||
|
|
||||||
/// Person
|
/// Person
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Person {
|
pub struct Person {
|
||||||
@@ -111,13 +109,7 @@ impl Person {
|
|||||||
.picture
|
.picture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|picture| !picture.is_empty())
|
.filter(|picture| !picture.is_empty())
|
||||||
.map(|picture| {
|
.map(|picture| picture.into())
|
||||||
let encoded_picture = urlencoding::encode(picture);
|
|
||||||
let url = format!(
|
|
||||||
"{IMAGE_RESIZER}/?url={encoded_picture}&w=100&h=100&fit=cover&mask=circle&n=-1"
|
|
||||||
);
|
|
||||||
url.into()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "brand/avatar.png".into())
|
.unwrap_or_else(|| "brand/avatar.png".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use anyhow::Error;
|
|||||||
use futures::io::AsyncReadExt;
|
use futures::io::AsyncReadExt;
|
||||||
use gpui::http_client::{AsyncBody, HttpClient};
|
use gpui::http_client::{AsyncBody, HttpClient};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[allow(async_fn_in_trait)]
|
#[allow(async_fn_in_trait)]
|
||||||
pub trait NostrAddress {
|
pub trait NostrAddress {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl Render for DragPanel {
|
|||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.whitespace_nowrap()
|
.whitespace_nowrap()
|
||||||
.rounded(cx.theme().radius)
|
.rounded(cx.theme().radius)
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.text_color(cx.theme().text)
|
.text_color(cx.theme().text)
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.when(cx.theme().shadow, |this| this.shadow_xs())
|
.when(cx.theme().shadow, |this| this.shadow_xs())
|
||||||
@@ -312,6 +312,7 @@ impl TabPanel {
|
|||||||
|
|
||||||
cx.emit(PanelEvent::ZoomOut);
|
cx.emit(PanelEvent::ZoomOut);
|
||||||
cx.emit(PanelEvent::LayoutChanged);
|
cx.emit(PanelEvent::LayoutChanged);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detach_panel(
|
fn detach_panel(
|
||||||
@@ -321,10 +322,22 @@ impl TabPanel {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let panel_view = panel.view();
|
let panel_view = panel.view();
|
||||||
|
let removed_ix = self.panels.iter().position(|p| p.view() == panel_view);
|
||||||
self.panels.retain(|p| p.view() != panel_view);
|
self.panels.retain(|p| p.view() != panel_view);
|
||||||
|
|
||||||
if self.active_ix >= self.panels.len() {
|
if self.active_ix >= self.panels.len() {
|
||||||
self.set_active_ix(self.panels.len().saturating_sub(1), window, cx)
|
self.set_active_ix(self.panels.len().saturating_sub(1), window, cx)
|
||||||
|
} else if let Some(removed_ix) = removed_ix {
|
||||||
|
if removed_ix < self.active_ix {
|
||||||
|
self.active_ix = self.active_ix.saturating_sub(1);
|
||||||
|
} else if removed_ix == self.active_ix {
|
||||||
|
// The active panel was removed and another panel shifted into
|
||||||
|
// its position. Activate the new panel at the same index.
|
||||||
|
if let Some(new_active) = self.panels.get(self.active_ix) {
|
||||||
|
new_active.set_active(true, cx);
|
||||||
|
}
|
||||||
|
self.focus_active_panel(window, cx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +626,7 @@ impl TabPanel {
|
|||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
.text_xs()
|
.text_sm()
|
||||||
.child(panel.title(cx)),
|
.child(panel.title(cx)),
|
||||||
)
|
)
|
||||||
.when(state.draggable, |this| {
|
.when(state.draggable, |this| {
|
||||||
@@ -687,8 +700,8 @@ impl TabPanel {
|
|||||||
.on_click(cx.listener({
|
.on_click(cx.listener({
|
||||||
let panel = panel.clone();
|
let panel = panel.clone();
|
||||||
move |view, _ev, window, cx| {
|
move |view, _ev, window, cx| {
|
||||||
|
cx.stop_propagation();
|
||||||
view.remove_panel(&panel, window, cx);
|
view.remove_panel(&panel, window, cx);
|
||||||
view.set_active_ix(ix, window, cx);
|
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use instant::Duration;
|
|
||||||
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -7,6 +6,7 @@ use gpui::{
|
|||||||
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
|
||||||
RenderOnce, SharedString, StyleRefinement, Styled, Window, anchored, div, hsla, point, px,
|
RenderOnce, SharedString, StyleRefinement, Styled, Window, anchored, div, hsla, point, px,
|
||||||
};
|
};
|
||||||
|
use instant::Duration;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
use crate::actions::{Cancel, Confirm};
|
use crate::actions::{Cancel, Confirm};
|
||||||
@@ -359,8 +359,8 @@ impl RenderOnce for Modal {
|
|||||||
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top;
|
||||||
let x = bounds.center().x - self.width / 2.;
|
let x = bounds.center().x - self.width / 2.;
|
||||||
|
|
||||||
let mut padding_right = px(8.);
|
let mut padding_right = px(16.);
|
||||||
let mut padding_left = px(8.);
|
let mut padding_left = px(16.);
|
||||||
|
|
||||||
if let Some(pl) = self.style.padding.left {
|
if let Some(pl) = self.style.padding.left {
|
||||||
padding_left = pl.to_pixels(self.width.into(), window.rem_size());
|
padding_left = pl.to_pixels(self.width.into(), window.rem_size());
|
||||||
@@ -452,8 +452,8 @@ impl RenderOnce for Modal {
|
|||||||
.when_some(self.max_width, |this, w| this.max_w(w))
|
.when_some(self.max_width, |this, w| this.max_w(w))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.px_2()
|
.px_4()
|
||||||
.h_4()
|
.h_8()
|
||||||
.w_full()
|
.w_full()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|||||||
@@ -183,12 +183,10 @@ impl RenderOnce for Tab {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.h(TABBAR_HEIGHT)
|
.h(TABBAR_HEIGHT)
|
||||||
|
.relative()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.text_color(fg)
|
.text_color(fg)
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.when(!self.selected && !self.disabled, |this| {
|
|
||||||
this.hover(|this| this.text_color(cx.theme().secondary_foreground))
|
|
||||||
})
|
|
||||||
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
.when_some(self.prefix, |this, prefix| this.child(prefix))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
@@ -222,5 +220,21 @@ impl RenderOnce for Tab {
|
|||||||
this.on_click(move |event, window, cx| on_click(event, window, cx))
|
this.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.bottom_0()
|
||||||
|
.left_0()
|
||||||
|
.right_0()
|
||||||
|
.h_0p5()
|
||||||
|
.when(self.selected && !self.disabled, |this| {
|
||||||
|
this.bg(cx.theme().element_active)
|
||||||
|
})
|
||||||
|
.when(!self.selected && !self.disabled, |this| {
|
||||||
|
this.invisible().group_hover("", |this| {
|
||||||
|
this.visible().bg(cx.theme().secondary_background)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use theme::ActiveTheme;
|
|||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::indicator::Indicator;
|
use ui::indicator::Indicator;
|
||||||
use ui::{Icon, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
use ui::{Disableable, Icon, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};
|
||||||
|
|
||||||
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
|
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
|
||||||
cx.new(|cx| Screening::new(public_key, window, cx))
|
cx.new(|cx| Screening::new(public_key, window, cx))
|
||||||
@@ -263,7 +263,7 @@ impl Screening {
|
|||||||
let contacts = contacts.clone();
|
let contacts = contacts.clone();
|
||||||
let total = contacts.len();
|
let total = contacts.len();
|
||||||
|
|
||||||
this.title(SharedString::from("Mutual contacts")).child(
|
this.title("Mutual contacts").child(
|
||||||
v_flex().gap_1().pb_2().child(
|
v_flex().gap_1().pb_2().child(
|
||||||
uniform_list("contacts", total, move |range, _window, cx| {
|
uniform_list("contacts", total, move |range, _window, cx| {
|
||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
@@ -342,7 +342,7 @@ impl Render for Screening {
|
|||||||
.h_7()
|
.h_7()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.rounded_full()
|
.rounded_full()
|
||||||
.bg(cx.theme().surface_background)
|
.bg(cx.theme().elevated_surface_background)
|
||||||
.text_sm()
|
.text_sm()
|
||||||
.truncate()
|
.truncate()
|
||||||
.text_ellipsis()
|
.text_ellipsis()
|
||||||
@@ -355,7 +355,8 @@ impl Render for Screening {
|
|||||||
.gap_1()
|
.gap_1()
|
||||||
.child(
|
.child(
|
||||||
Button::new("njump")
|
Button::new("njump")
|
||||||
.label("View on njump.me")
|
.icon(IconName::Link)
|
||||||
|
.label("njump.me")
|
||||||
.secondary()
|
.secondary()
|
||||||
.small()
|
.small()
|
||||||
.rounded()
|
.rounded()
|
||||||
@@ -386,10 +387,7 @@ impl Render for Screening {
|
|||||||
.text_sm()
|
.text_sm()
|
||||||
.child(status_badge(Some(self.followed), cx))
|
.child(status_badge(Some(self.followed), cx))
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex().text_sm().child("Contact").child(
|
||||||
.text_sm()
|
|
||||||
.child(SharedString::from("Contact"))
|
|
||||||
.child(
|
|
||||||
div()
|
div()
|
||||||
.line_clamp(1)
|
.line_clamp(1)
|
||||||
.text_color(cx.theme().text_muted)
|
.text_color(cx.theme().text_muted)
|
||||||
@@ -415,7 +413,7 @@ impl Render for Screening {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.child(SharedString::from("Activity on Public Relays"))
|
.child("Activity on Public Relays")
|
||||||
.child(
|
.child(
|
||||||
Button::new("active")
|
Button::new("active")
|
||||||
.icon(IconName::Info)
|
.icon(IconName::Info)
|
||||||
@@ -483,26 +481,9 @@ impl Render for Screening {
|
|||||||
.items_start()
|
.items_start()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(status_badge(Some(mutuals > 0), cx))
|
.child(status_badge(Some(mutuals > 0), cx))
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.text_sm()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_0p5()
|
.text_sm()
|
||||||
.child(SharedString::from("Mutual contacts"))
|
|
||||||
.child(
|
|
||||||
Button::new("mutuals")
|
|
||||||
.icon(IconName::Info)
|
|
||||||
.xsmall()
|
|
||||||
.ghost()
|
|
||||||
.rounded()
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, _, window, cx| {
|
|
||||||
this.mutual_contacts(window, cx);
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.line_clamp(1)
|
.line_clamp(1)
|
||||||
@@ -514,6 +495,17 @@ impl Render for Screening {
|
|||||||
SharedString::from(NO_MUTUAL)
|
SharedString::from(NO_MUTUAL)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("mutuals")
|
||||||
|
.icon(IconName::Info)
|
||||||
|
.xsmall()
|
||||||
|
.ghost()
|
||||||
|
.rounded()
|
||||||
|
.disabled(mutuals == 0)
|
||||||
|
.on_click(cx.listener(move |this, _, window, cx| {
|
||||||
|
this.mutual_contacts(window, cx);
|
||||||
|
})),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -164,9 +164,7 @@ impl RenderOnce for RoomEntry {
|
|||||||
)
|
)
|
||||||
.on_cancel(move |_event, window, cx| {
|
.on_cancel(move |_event, window, cx| {
|
||||||
window.dispatch_action(Box::new(ClosePanel), cx);
|
window.dispatch_action(Box::new(ClosePanel), cx);
|
||||||
// Prevent closing the modal on click
|
true
|
||||||
// modal will be automatically closed after closing panel
|
|
||||||
false
|
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,6 +354,14 @@ impl Sidebar {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
self.new_requests = false;
|
self.new_requests = false;
|
||||||
|
|
||||||
|
// Reset search state when switching to inbox/requests
|
||||||
|
self.reset(window, cx);
|
||||||
|
|
||||||
|
// Clear the find input value
|
||||||
|
self.find_input.update(cx, |this, cx| {
|
||||||
|
this.set_value("", window, cx);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_list_items(
|
fn render_list_items(
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "coop"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
publish.workspace = true
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "coop"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[package.metadata.packager]
|
|
||||||
name = "Coop"
|
|
||||||
product-name = "Coop"
|
|
||||||
description = "Chat Freely, Stay Private on Nostr"
|
|
||||||
identifier = "su.reya.coop"
|
|
||||||
category = "SocialNetworking"
|
|
||||||
version = "1.0.0-beta5"
|
|
||||||
out-dir = "../dist"
|
|
||||||
before-packaging-command = "cargo build --release"
|
|
||||||
resources = ["Cargo.toml", "src"]
|
|
||||||
icons = [
|
|
||||||
"resources/32x32.png",
|
|
||||||
"resources/128x128.png",
|
|
||||||
"resources/128x128@2x.png",
|
|
||||||
"resources/icon.icns",
|
|
||||||
"resources/icon.ico",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
assets = { path = "../crates/assets" }
|
|
||||||
workspace = { path = "../crates/workspace" }
|
|
||||||
ui = { path = "../crates/ui" }
|
|
||||||
theme = { path = "../crates/theme" }
|
|
||||||
common = { path = "../crates/common" }
|
|
||||||
state = { path = "../crates/state" }
|
|
||||||
device = { path = "../crates/device" }
|
|
||||||
chat = { path = "../crates/chat" }
|
|
||||||
settings = { path = "../crates/settings" }
|
|
||||||
auto_update = { path = "../crates/auto_update" }
|
|
||||||
person = { path = "../crates/person" }
|
|
||||||
|
|
||||||
gpui.workspace = true
|
|
||||||
gpui_platform.workspace = true
|
|
||||||
gpui_linux.workspace = true
|
|
||||||
gpui_windows.workspace = true
|
|
||||||
gpui_macos.workspace = true
|
|
||||||
reqwest_client.workspace = true
|
|
||||||
|
|
||||||
log.workspace = true
|
|
||||||
tracing-subscriber.workspace = true
|
|
||||||
BIN
docs/coop.png
|
Before Width: | Height: | Size: 769 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 374 KiB |
|
Before Width: | Height: | Size: 535 KiB |
|
Before Width: | Height: | Size: 386 KiB |
|
Before Width: | Height: | Size: 432 KiB |
BIN
docs/mac_01.png
|
Before Width: | Height: | Size: 446 KiB |
BIN
docs/mac_02.png
|
Before Width: | Height: | Size: 862 KiB |
BIN
docs/mac_03.png
|
Before Width: | Height: | Size: 809 KiB |
BIN
docs/mac_04.png
|
Before Width: | Height: | Size: 634 KiB |
BIN
docs/mac_05.png
|
Before Width: | Height: | Size: 803 KiB |
BIN
docs/mac_06.png
|
Before Width: | Height: | Size: 651 KiB |
BIN
docs/mac_07.png
|
Before Width: | Height: | Size: 663 KiB |
BIN
docs/mac_08.png
|
Before Width: | Height: | Size: 696 KiB |
BIN
docs/mac_09.png
|
Before Width: | Height: | Size: 506 KiB |