refactor chats (#15)
* refactor * update * update * update * remove nostrprofile struct * update * refactor contacts * prevent double login
This commit is contained in:
509
Cargo.lock
generated
509
Cargo.lock
generated
@@ -97,12 +97,6 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anes"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.18"
|
version = "0.6.18"
|
||||||
@@ -750,9 +744,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.11.3"
|
version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -834,12 +828,6 @@ dependencies = [
|
|||||||
"wayland-client",
|
"wayland-client",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cast"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cbc"
|
name = "cbc"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -958,6 +946,7 @@ dependencies = [
|
|||||||
name = "chats"
|
name = "chats"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"account",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
@@ -987,33 +976,6 @@ dependencies = [
|
|||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"ciborium-ll",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-io"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ciborium-ll"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
|
|
||||||
dependencies = [
|
|
||||||
"ciborium-io",
|
|
||||||
"half",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -1158,7 +1120,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -1317,6 +1279,19 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-graphics-helmer-fork"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"core-graphics-types 0.1.3",
|
||||||
|
"foreign-types",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-graphics-types"
|
name = "core-graphics-types"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
@@ -1428,42 +1403,6 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "criterion"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
|
||||||
dependencies = [
|
|
||||||
"anes",
|
|
||||||
"cast",
|
|
||||||
"ciborium",
|
|
||||||
"clap",
|
|
||||||
"criterion-plot",
|
|
||||||
"is-terminal",
|
|
||||||
"itertools 0.10.5",
|
|
||||||
"num-traits",
|
|
||||||
"once_cell",
|
|
||||||
"oorandom",
|
|
||||||
"plotters",
|
|
||||||
"rayon",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"tinytemplate",
|
|
||||||
"walkdir",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "criterion-plot"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
|
||||||
dependencies = [
|
|
||||||
"cast",
|
|
||||||
"itertools 0.10.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -1531,6 +1470,16 @@ version = "0.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
|
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctrlc"
|
||||||
|
version = "3.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
|
||||||
|
dependencies = [
|
||||||
|
"nix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -1559,7 +1508,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1619,6 +1568,12 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dispatch"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -2320,7 +2275,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
@@ -2375,6 +2330,7 @@ dependencies = [
|
|||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"refineable",
|
"refineable",
|
||||||
"resvg",
|
"resvg",
|
||||||
|
"scap",
|
||||||
"schemars",
|
"schemars",
|
||||||
"seahash",
|
"seahash",
|
||||||
"semantic_version",
|
"semantic_version",
|
||||||
@@ -2397,8 +2353,8 @@ dependencies = [
|
|||||||
"wayland-cursor",
|
"wayland-cursor",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
"wayland-protocols-plasma",
|
"wayland-protocols-plasma",
|
||||||
"windows",
|
"windows 0.61.1",
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
"x11-clipboard",
|
"x11-clipboard",
|
||||||
@@ -2410,7 +2366,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2445,9 +2401,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.5.0"
|
version = "2.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
|
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crunchy",
|
"crunchy",
|
||||||
@@ -2523,12 +2479,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hermit-abi"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -2640,7 +2590,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2657,7 +2607,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -2741,7 +2691,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3018,17 +2968,6 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-terminal"
|
|
||||||
version = "0.4.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi 0.5.0",
|
|
||||||
"libc",
|
|
||||||
"windows-sys 0.59.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-wsl"
|
name = "is-wsl"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -3045,15 +2984,6 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -3402,7 +3332,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
@@ -3478,9 +3408,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.7"
|
version = "0.8.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
|
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
@@ -3583,7 +3513,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
@@ -3608,7 +3538,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3620,7 +3550,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
"lru",
|
"lru",
|
||||||
@@ -3631,7 +3561,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"heed",
|
"heed",
|
||||||
@@ -3644,7 +3574,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3661,7 +3591,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.40.0"
|
version = "0.40.0"
|
||||||
source = "git+https://github.com/rust-nostr/nostr#1347878e90392db54a263d60021fa0410a3b05cb"
|
source = "git+https://github.com/rust-nostr/nostr#f4f3f50aa637b3d288a6f1cf762260005b7b498a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3672,6 +3602,15 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ntapi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@@ -3802,6 +3741,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"malloc_buf",
|
"malloc_buf",
|
||||||
|
"objc_exception",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc-foundation"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||||
|
dependencies = [
|
||||||
|
"block",
|
||||||
|
"objc",
|
||||||
|
"objc_id",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4006,6 +3957,24 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc_exception"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc_id"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||||
|
dependencies = [
|
||||||
|
"objc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@@ -4062,12 +4031,6 @@ dependencies = [
|
|||||||
"zvariant",
|
"zvariant",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "oorandom"
|
|
||||||
version = "11.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -4307,34 +4270,6 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
"plotters-backend",
|
|
||||||
"plotters-svg",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-backend"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "plotters-svg"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
|
||||||
dependencies = [
|
|
||||||
"plotters-backend",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.17.16"
|
version = "0.17.16"
|
||||||
@@ -4485,6 +4420,15 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.30.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.37.4"
|
version = "0.37.4"
|
||||||
@@ -4746,9 +4690,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.10"
|
version = "0.5.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
|
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.0",
|
"bitflags 2.9.0",
|
||||||
]
|
]
|
||||||
@@ -4767,7 +4711,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
@@ -4897,7 +4841,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5194,6 +5138,27 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scap"
|
||||||
|
version = "0.0.8"
|
||||||
|
source = "git+https://github.com/zed-industries/scap?rev=08f0a01417505cc0990b9931a37e5120db92e0d0#08f0a01417505cc0990b9931a37e5120db92e0d0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"cocoa 0.25.0",
|
||||||
|
"core-graphics-helmer-fork",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"screencapturekit",
|
||||||
|
"screencapturekit-sys",
|
||||||
|
"sysinfo",
|
||||||
|
"tao-core-video-sys",
|
||||||
|
"windows 0.61.1",
|
||||||
|
"windows-capture",
|
||||||
|
"x11",
|
||||||
|
"xcb",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.27"
|
version = "0.1.27"
|
||||||
@@ -5240,6 +5205,29 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "screencapturekit"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e"
|
||||||
|
dependencies = [
|
||||||
|
"screencapturekit-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "screencapturekit-sys"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60"
|
||||||
|
dependencies = [
|
||||||
|
"block",
|
||||||
|
"dispatch",
|
||||||
|
"objc",
|
||||||
|
"objc-foundation",
|
||||||
|
"objc_id",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scrypt"
|
name = "scrypt"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -5310,7 +5298,7 @@ checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -5633,7 +5621,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#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
@@ -5806,6 +5794,20 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sysinfo"
|
||||||
|
version = "0.31.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"memchr",
|
||||||
|
"ntapi",
|
||||||
|
"rayon",
|
||||||
|
"windows 0.57.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@@ -5859,6 +5861,18 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb"
|
checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tao-core-video-sys"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"objc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.16"
|
version = "0.12.16"
|
||||||
@@ -6004,16 +6018,6 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tinytemplate"
|
|
||||||
version = "1.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@@ -6042,9 +6046,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.44.1"
|
version = "1.44.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -6312,7 +6316,6 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"common",
|
"common",
|
||||||
"criterion",
|
|
||||||
"gpui",
|
"gpui",
|
||||||
"image",
|
"image",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
@@ -6510,7 +6513,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#c1259c136e41b5870d15271190198ff0075ba35c"
|
source = "git+https://github.com/zed-industries/zed#f0b7f355a29486566a8ed7c2be4acad4b305b881"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -6829,7 +6832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
|
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quick-xml",
|
"quick-xml 0.37.4",
|
||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6943,6 +6946,26 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.57.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.58.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.61.1"
|
version = "0.61.1"
|
||||||
@@ -6950,19 +6973,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-collections",
|
"windows-collections",
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
"windows-future",
|
"windows-future",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
"windows-numerics",
|
"windows-numerics",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-capture"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6001b777f61cafce437201de46a019ed7f4afed3b669f02e5ce4e0759164cb3e"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"ctrlc",
|
||||||
|
"parking_lot",
|
||||||
|
"rayon",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"windows 0.58.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-collections"
|
name = "windows-collections"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement 0.57.0",
|
||||||
|
"windows-interface 0.57.0",
|
||||||
|
"windows-result 0.1.2",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement 0.58.0",
|
||||||
|
"windows-interface 0.58.0",
|
||||||
|
"windows-result 0.2.0",
|
||||||
|
"windows-strings 0.1.0",
|
||||||
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6971,8 +7033,8 @@ version = "0.61.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-implement",
|
"windows-implement 0.60.0",
|
||||||
"windows-interface",
|
"windows-interface 0.59.1",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
"windows-result 0.3.2",
|
"windows-result 0.3.2",
|
||||||
"windows-strings 0.4.0",
|
"windows-strings 0.4.0",
|
||||||
@@ -6984,10 +7046,32 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
checksum = "7a1d6bbefcb7b60acd19828e1bc965da6fcf18a7e39490c5f8be71e54a19ba32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-implement"
|
name = "windows-implement"
|
||||||
version = "0.60.0"
|
version = "0.60.0"
|
||||||
@@ -6999,6 +7083,28 @@ dependencies = [
|
|||||||
"syn 2.0.100",
|
"syn 2.0.100",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-interface"
|
name = "windows-interface"
|
||||||
version = "0.59.1"
|
version = "0.59.1"
|
||||||
@@ -7022,7 +7128,7 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-core",
|
"windows-core 0.61.0",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -7048,6 +7154,15 @@ dependencies = [
|
|||||||
"windows-targets 0.53.0",
|
"windows-targets 0.53.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.6",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-result"
|
name = "windows-result"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -7374,9 +7489,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.7.4"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e97b544156e9bebe1a0ffbc03484fc1ffe3100cbce3ffb17eac35f7cdd7ab36"
|
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -7427,6 +7542,16 @@ version = "0.5.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "x11"
|
||||||
|
version = "2.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x11-clipboard"
|
name = "x11-clipboard"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
@@ -7456,6 +7581,18 @@ version = "0.13.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
|
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xcb"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1e2f212bb1a92cd8caac8051b829a6582ede155ccb60b5d5908b81b100952be"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"libc",
|
||||||
|
"quick-xml 0.30.0",
|
||||||
|
"x11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xcursor"
|
name = "xcursor"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
|||||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = ["parser"] }
|
nostr = { git = "https://github.com/rust-nostr/nostr", features = ["parser"] }
|
||||||
nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-relay-builder = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
|
nostr-keyring = { git = "https://github.com/rust-nostr/nostr" }
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [
|
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [
|
||||||
"lmdb",
|
"lmdb",
|
||||||
"nip96",
|
"nip96",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use common::profile::NostrProfile;
|
|
||||||
use global::{
|
use global::{
|
||||||
constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID},
|
constants::{ALL_MESSAGES_SUB_ID, NEW_MESSAGE_SUB_ID},
|
||||||
get_client,
|
get_client,
|
||||||
@@ -15,12 +14,19 @@ struct GlobalAccount(Entity<Account>);
|
|||||||
impl Global for GlobalAccount {}
|
impl Global for GlobalAccount {}
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
Account::set_global(cx.new(|_| Account { profile: None }), cx);
|
Account::set_global(
|
||||||
|
cx.new(|_| Account {
|
||||||
|
profile: None,
|
||||||
|
loading: false,
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub profile: Option<NostrProfile>,
|
pub profile: Option<Profile>,
|
||||||
|
loading: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
@@ -36,7 +42,13 @@ impl Account {
|
|||||||
where
|
where
|
||||||
S: NostrSigner + 'static,
|
S: NostrSigner + 'static,
|
||||||
{
|
{
|
||||||
let task: Task<Result<NostrProfile, Error>> = cx.background_spawn(async move {
|
if self.loading {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_loading(true, cx);
|
||||||
|
|
||||||
|
let task: Task<Result<Profile, Error>> = cx.background_spawn(async move {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
// Use user's signer for main signer
|
// Use user's signer for main signer
|
||||||
_ = client.set_signer(signer).await;
|
_ = client.set_signer(signer).await;
|
||||||
@@ -44,6 +56,7 @@ impl Account {
|
|||||||
// Verify nostr signer and get public key
|
// Verify nostr signer and get public key
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
log::info!("Logged in with public key: {:?}", public_key);
|
||||||
|
|
||||||
// Fetch user's metadata
|
// Fetch user's metadata
|
||||||
let metadata = client
|
let metadata = client
|
||||||
@@ -51,7 +64,7 @@ impl Account {
|
|||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(NostrProfile::new(public_key, metadata))
|
Ok(Profile::new(public_key, metadata))
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| match task.await {
|
cx.spawn_in(window, async move |this, cx| match task.await {
|
||||||
@@ -59,6 +72,7 @@ impl Account {
|
|||||||
cx.update(|_, cx| {
|
cx.update(|_, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.profile = Some(profile);
|
this.profile = Some(profile);
|
||||||
|
this.set_loading(false, cx);
|
||||||
this.subscribe(cx);
|
this.subscribe(cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
@@ -79,14 +93,14 @@ impl Account {
|
|||||||
let client = get_client();
|
let client = get_client();
|
||||||
let keys = Keys::generate();
|
let keys = Keys::generate();
|
||||||
|
|
||||||
let task: Task<Result<NostrProfile, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<Profile, Error>> = cx.background_spawn(async move {
|
||||||
let public_key = keys.public_key();
|
let public_key = keys.public_key();
|
||||||
// Update signer
|
// Update signer
|
||||||
client.set_signer(keys).await;
|
client.set_signer(keys).await;
|
||||||
// Set metadata
|
// Set metadata
|
||||||
client.set_metadata(&metadata).await?;
|
client.set_metadata(&metadata).await?;
|
||||||
|
|
||||||
Ok(NostrProfile::new(public_key, metadata))
|
Ok(Profile::new(public_key, metadata))
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
@@ -115,7 +129,7 @@ impl Account {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let user = profile.public_key;
|
let user = profile.public_key();
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
let metadata = Filter::new()
|
let metadata = Filter::new()
|
||||||
@@ -164,4 +178,9 @@ impl Account {
|
|||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_loading(&mut self, loading: bool, cx: &mut Context<Self>) {
|
||||||
|
self.loading = loading;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use account::Account;
|
use account::Account;
|
||||||
|
use common::profile::SharedProfile;
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, img, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis,
|
actions, div, img, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis,
|
||||||
@@ -172,7 +173,7 @@ impl ChatSpace {
|
|||||||
.icon(Icon::new(IconName::ChevronDownSmall))
|
.icon(Icon::new(IconName::ChevronDownSmall))
|
||||||
.when_some(
|
.when_some(
|
||||||
Account::global(cx).read(cx).profile.as_ref(),
|
Account::global(cx).read(cx).profile.as_ref(),
|
||||||
|this, profile| this.child(img(profile.avatar.clone()).size_5()),
|
|this, profile| this.child(img(profile.shared_avatar()).size_5()),
|
||||||
)
|
)
|
||||||
.popup_menu(move |this, _, _cx| {
|
.popup_menu(move |this, _, _cx| {
|
||||||
this.menu(
|
this.menu(
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ use gpui::{point, SharedString, TitlebarOptions};
|
|||||||
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
use gpui::{WindowBackgroundAppearance, WindowDecorations};
|
||||||
use nostr_sdk::{
|
use nostr_sdk::{
|
||||||
pool::prelude::ReqExitPolicy, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind,
|
pool::prelude::ReqExitPolicy, Event, EventBuilder, EventId, Filter, JsonUtil, Keys, Kind,
|
||||||
PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions, SubscriptionId, Tag,
|
Metadata, PublicKey, RelayMessage, RelayPoolNotification, SubscribeAutoCloseOptions,
|
||||||
|
SubscriptionId, Tag,
|
||||||
};
|
};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use std::{collections::HashSet, mem, sync::Arc, time::Duration};
|
use std::{collections::HashSet, mem, sync::Arc, time::Duration};
|
||||||
@@ -34,6 +35,8 @@ actions!(coop, [Quit]);
|
|||||||
enum Signal {
|
enum Signal {
|
||||||
/// Receive event
|
/// Receive event
|
||||||
Event(Event),
|
Event(Event),
|
||||||
|
/// Receive metadata
|
||||||
|
Metadata(Box<(PublicKey, Option<Metadata>)>),
|
||||||
/// Receive EOSE
|
/// Receive EOSE
|
||||||
Eose,
|
Eose,
|
||||||
}
|
}
|
||||||
@@ -149,6 +152,14 @@ fn main() {
|
|||||||
event_tx.send(Signal::Event(event)).await.ok();
|
event_tx.send(Signal::Event(event)).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Kind::Metadata => {
|
||||||
|
let metadata = Metadata::from_json(&event.content).ok();
|
||||||
|
|
||||||
|
event_tx
|
||||||
|
.send(Signal::Metadata(Box::new((event.pubkey, metadata))))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
Kind::ContactList => {
|
Kind::ContactList => {
|
||||||
if let Ok(signer) = client.signer().await {
|
if let Ok(signer) = client.signer().await {
|
||||||
if let Ok(public_key) = signer.get_public_key().await {
|
if let Ok(public_key) = signer.get_public_key().await {
|
||||||
@@ -241,14 +252,23 @@ fn main() {
|
|||||||
while let Ok(signal) = event_rx.recv().await {
|
while let Ok(signal) = event_rx.recv().await {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
match signal {
|
match signal {
|
||||||
Signal::Eose => {
|
|
||||||
chats.update(cx, |this, cx| this.load_rooms(window, cx));
|
|
||||||
}
|
|
||||||
Signal::Event(event) => {
|
Signal::Event(event) => {
|
||||||
chats.update(cx, |this, cx| {
|
chats.update(cx, |this, cx| {
|
||||||
this.push_message(event, window, cx)
|
this.push_message(event, window, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Signal::Metadata(data) => {
|
||||||
|
chats.update(cx, |this, cx| {
|
||||||
|
this.add_profile(data.0, data.1, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Signal::Eose => {
|
||||||
|
chats.update(cx, |this, cx| {
|
||||||
|
// This function maybe called multiple times
|
||||||
|
// TODO: only handle the last EOSE signal
|
||||||
|
this.load_rooms(window, cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use chats::{message::RoomMessage, room::Room, ChatRegistry};
|
use chats::{message::RoomMessage, room::Room, ChatRegistry};
|
||||||
use common::utils::nip96_upload;
|
use common::{nip96_upload, profile::SharedProfile};
|
||||||
use global::{constants::IMAGE_SERVICE, get_client};
|
use global::{constants::IMAGE_SERVICE, get_client};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, list, prelude::FluentBuilder, px, relative, svg, white, AnyElement, App, AppContext,
|
div, img, list, prelude::FluentBuilder, px, relative, svg, white, AnyElement, App, AppContext,
|
||||||
@@ -27,7 +27,7 @@ use ui::{
|
|||||||
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
const ALERT: &str = "has not set up Messaging (DM) Relays, so they will NOT receive your messages.";
|
||||||
|
|
||||||
pub fn init(id: &u64, window: &mut Window, cx: &mut App) -> Result<Arc<Entity<Chat>>, Error> {
|
pub fn init(id: &u64, window: &mut Window, cx: &mut App) -> Result<Arc<Entity<Chat>>, Error> {
|
||||||
if let Some(room) = ChatRegistry::global(cx).read(cx).get(id, cx) {
|
if let Some(room) = ChatRegistry::global(cx).read(cx).room(id, cx) {
|
||||||
Ok(Arc::new(Chat::new(id, room, window, cx)))
|
Ok(Arc::new(Chat::new(id, room, window, cx)))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Chat Room not found."))
|
Err(anyhow!("Chat Room not found."))
|
||||||
@@ -137,14 +137,14 @@ impl Chat {
|
|||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
result.into_iter().for_each(|item| {
|
result.into_iter().for_each(|item| {
|
||||||
if !item.1 {
|
if !item.1 {
|
||||||
if let Some(profile) =
|
let profile = this
|
||||||
this.room.read_with(cx, |this, _| this.member(&item.0))
|
.room
|
||||||
{
|
.read_with(cx, |this, _| this.profile_by_pubkey(&item.0, cx));
|
||||||
this.push_system_message(
|
|
||||||
format!("{} {}", profile.name, ALERT),
|
this.push_system_message(
|
||||||
cx,
|
format!("{} {}", profile.shared_name(), ALERT),
|
||||||
);
|
cx,
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -367,7 +367,7 @@ impl Chat {
|
|||||||
this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
this.bg(cx.theme().accent.step(cx, ColorScaleStep::NINE))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(img(item.author.avatar.clone()).size_8().flex_shrink_0())
|
.child(img(item.author.shared_avatar()).size_8().flex_shrink_0())
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -381,17 +381,11 @@ impl Chat {
|
|||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(
|
.child(
|
||||||
div().font_semibold().child(item.author.name.clone()),
|
div().font_semibold().child(item.author.shared_name()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(div().child(item.ago()).text_color(
|
||||||
div()
|
cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
|
||||||
.child(item.created_at.human_readable())
|
)),
|
||||||
.text_color(
|
|
||||||
cx.theme()
|
|
||||||
.base
|
|
||||||
.step(cx, ColorScaleStep::ELEVEN),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(div().text_sm().child(text.element(
|
.child(div().text_sm().child(text.element(
|
||||||
"body".into(),
|
"body".into(),
|
||||||
@@ -445,11 +439,7 @@ impl Panel for Chat {
|
|||||||
|
|
||||||
fn title(&self, cx: &App) -> AnyElement {
|
fn title(&self, cx: &App) -> AnyElement {
|
||||||
self.room.read_with(cx, |this, _| {
|
self.room.read_with(cx, |this, _| {
|
||||||
let facepill: Vec<SharedString> = this
|
let facepill: Vec<SharedString> = this.avatars(cx);
|
||||||
.members
|
|
||||||
.iter()
|
|
||||||
.map(|member| member.avatar.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -461,13 +451,19 @@ impl Panel for Chat {
|
|||||||
.flex_row_reverse()
|
.flex_row_reverse()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.children(facepill.into_iter().enumerate().rev().map(|(ix, face)| {
|
.children(
|
||||||
div()
|
facepill
|
||||||
.when(ix > 0, |div| div.ml_neg_1())
|
.into_iter()
|
||||||
.child(img(face).size_4())
|
.enumerate()
|
||||||
})),
|
.rev()
|
||||||
|
.map(|(ix, facepill)| {
|
||||||
|
div()
|
||||||
|
.when(ix > 0, |div| div.ml_neg_1())
|
||||||
|
.child(img(facepill).size_4())
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.when_some(this.subject(), |this, name| this.child(name))
|
.child(this.display_name(cx))
|
||||||
.into_any()
|
.into_any()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
use common::profile::NostrProfile;
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use common::profile::SharedProfile;
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
|
div, img, prelude::FluentBuilder, px, uniform_list, AnyElement, App, AppContext, Context,
|
||||||
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
|
Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement,
|
||||||
Render, SharedString, Styled, Window,
|
Render, SharedString, Styled, Task, Window,
|
||||||
};
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use ui::{
|
use ui::{
|
||||||
button::Button,
|
button::Button,
|
||||||
@@ -20,7 +24,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Contacts> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Contacts {
|
pub struct Contacts {
|
||||||
contacts: Entity<Option<Vec<NostrProfile>>>,
|
contacts: Option<Vec<Profile>>,
|
||||||
// Panel
|
// Panel
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
closable: bool,
|
closable: bool,
|
||||||
@@ -29,48 +33,42 @@ pub struct Contacts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Contacts {
|
impl Contacts {
|
||||||
pub fn new(_window: &mut Window, cx: &mut App) -> Entity<Self> {
|
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
let contacts = cx.new(|_| None);
|
cx.new(|cx| Self::view(window, cx))
|
||||||
let async_contact = contacts.clone();
|
}
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
fn view(_window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
|
cx.spawn(async move |this, cx| {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let (tx, rx) = oneshot::channel::<Vec<NostrProfile>>();
|
|
||||||
|
|
||||||
cx.background_executor()
|
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
||||||
.spawn(async move {
|
let signer = client.signer().await?;
|
||||||
let signer = client.signer().await.unwrap();
|
let public_key = signer.get_public_key().await?;
|
||||||
let public_key = signer.get_public_key().await.unwrap();
|
let profiles = client.database().contacts(public_key).await?;
|
||||||
|
|
||||||
if let Ok(profiles) = client.database().contacts(public_key).await {
|
Ok(profiles)
|
||||||
let members: Vec<NostrProfile> = profiles
|
});
|
||||||
.into_iter()
|
|
||||||
.map(|profile| {
|
|
||||||
NostrProfile::new(profile.public_key(), profile.metadata())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
_ = tx.send(members);
|
if let Ok(contacts) = task.await {
|
||||||
}
|
cx.update(|cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.contacts = Some(contacts.into_iter().collect_vec());
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
})
|
})
|
||||||
.detach();
|
.ok();
|
||||||
|
|
||||||
if let Ok(contacts) = rx.await {
|
|
||||||
_ = cx.update_entity(&async_contact, |this, cx| {
|
|
||||||
*this = Some(contacts);
|
|
||||||
cx.notify();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
cx.new(|cx| Self {
|
Self {
|
||||||
contacts,
|
contacts: None,
|
||||||
name: "Contacts".into(),
|
name: "Contacts".into(),
|
||||||
closable: true,
|
closable: true,
|
||||||
zoomable: true,
|
zoomable: true,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +109,7 @@ impl Focusable for Contacts {
|
|||||||
impl Render for Contacts {
|
impl Render for Contacts {
|
||||||
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut gpui::Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div().size_full().pt_2().px_2().map(|this| {
|
div().size_full().pt_2().px_2().map(|this| {
|
||||||
if let Some(contacts) = self.contacts.read(cx).clone() {
|
if let Some(contacts) = self.contacts.clone() {
|
||||||
this.child(
|
this.child(
|
||||||
uniform_list(
|
uniform_list(
|
||||||
cx.entity().clone(),
|
cx.entity().clone(),
|
||||||
@@ -141,9 +139,9 @@ impl Render for Contacts {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.child(img(item.avatar).size_6()),
|
.child(img(item.shared_avatar()).size_6()),
|
||||||
)
|
)
|
||||||
.child(item.name),
|
.child(item.shared_name()),
|
||||||
)
|
)
|
||||||
.hover(|this| {
|
.hover(|this| {
|
||||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use account::Account;
|
use account::Account;
|
||||||
use common::utils::create_qr;
|
use common::create_qr;
|
||||||
use global::get_client_keys;
|
use global::get_client_keys;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, relative, AnyElement, App, AppContext, Context, Entity,
|
div, img, prelude::FluentBuilder, relative, AnyElement, App, AppContext, Context, Entity,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use account::Account;
|
use account::Account;
|
||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use common::utils::nip96_upload;
|
use common::nip96_upload;
|
||||||
use global::{constants::IMAGE_SERVICE, get_client};
|
use global::{constants::IMAGE_SERVICE, get_client};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, px, relative, AnyElement, App, AppContext, Context, Entity,
|
div, img, prelude::FluentBuilder, px, relative, AnyElement, App, AppContext, Context, Entity,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use async_utility::task::spawn;
|
use async_utility::task::spawn;
|
||||||
use common::utils::nip96_upload;
|
use common::nip96_upload;
|
||||||
use global::{constants::IMAGE_SERVICE, get_client};
|
use global::{constants::IMAGE_SERVICE, get_client};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
div, img, prelude::FluentBuilder, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
use anyhow::Error;
|
||||||
use chats::{
|
use chats::{
|
||||||
room::{Room, RoomKind},
|
room::{Room, RoomKind},
|
||||||
ChatRegistry,
|
ChatRegistry,
|
||||||
};
|
};
|
||||||
use common::{profile::NostrProfile, utils::random_name};
|
use common::{profile::SharedProfile, random_name};
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
|
div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
|
||||||
@@ -14,7 +15,11 @@ use nostr_sdk::prelude::*;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
use std::{collections::HashSet, rc::Rc, time::Duration};
|
use std::{
|
||||||
|
collections::{BTreeSet, HashSet},
|
||||||
|
rc::Rc,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonRounded},
|
button::{Button, ButtonRounded},
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
@@ -33,7 +38,7 @@ impl_internal_actions!(contacts, [SelectContact]);
|
|||||||
pub struct Compose {
|
pub struct Compose {
|
||||||
title_input: Entity<TextInput>,
|
title_input: Entity<TextInput>,
|
||||||
user_input: Entity<TextInput>,
|
user_input: Entity<TextInput>,
|
||||||
contacts: Entity<Vec<NostrProfile>>,
|
contacts: Entity<Vec<Profile>>,
|
||||||
selected: Entity<HashSet<PublicKey>>,
|
selected: Entity<HashSet<PublicKey>>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
is_loading: bool,
|
is_loading: bool,
|
||||||
@@ -80,26 +85,17 @@ impl Compose {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
let client = get_client();
|
|
||||||
let (tx, rx) = oneshot::channel::<Vec<NostrProfile>>();
|
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
|
||||||
let signer = client.signer().await.unwrap();
|
|
||||||
let public_key = signer.get_public_key().await.unwrap();
|
|
||||||
|
|
||||||
if let Ok(profiles) = client.database().contacts(public_key).await {
|
|
||||||
let members: Vec<NostrProfile> = profiles
|
|
||||||
.into_iter()
|
|
||||||
.map(|profile| NostrProfile::new(profile.public_key(), profile.metadata()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
_ = tx.send(members);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
if let Ok(contacts) = rx.await {
|
let task: Task<Result<BTreeSet<Profile>, Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let public_key = signer.get_public_key().await?;
|
||||||
|
let profiles = client.database().contacts(public_key).await?;
|
||||||
|
|
||||||
|
Ok(profiles)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Ok(contacts) = task.await {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.contacts.update(cx, |this, cx| {
|
this.contacts.update(cx, |this, cx| {
|
||||||
@@ -107,6 +103,7 @@ impl Compose {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.ok()
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -174,7 +171,7 @@ impl Compose {
|
|||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
let chats = ChatRegistry::global(cx);
|
let chats = ChatRegistry::global(cx);
|
||||||
let room = Room::new(&event, RoomKind::Ongoing);
|
let room = Room::new(&event).kind(RoomKind::Ongoing);
|
||||||
|
|
||||||
chats.update(cx, |chats, cx| {
|
chats.update(cx, |chats, cx| {
|
||||||
match chats.push(room, cx) {
|
match chats.push(room, cx) {
|
||||||
@@ -215,7 +212,7 @@ impl Compose {
|
|||||||
// Show loading spinner
|
// Show loading spinner
|
||||||
self.set_loading(true, cx);
|
self.set_loading(true, cx);
|
||||||
|
|
||||||
let task: Task<Result<NostrProfile, anyhow::Error>> = if content.contains("@") {
|
let task: Task<Result<Profile, anyhow::Error>> = if content.contains("@") {
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let profile = nip05::profile(&content, None).await?;
|
let profile = nip05::profile(&content, None).await?;
|
||||||
let public_key = profile.public_key;
|
let public_key = profile.public_key;
|
||||||
@@ -225,7 +222,7 @@ impl Compose {
|
|||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(NostrProfile::new(public_key, metadata))
|
Ok(Profile::new(public_key, metadata))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let Ok(public_key) = PublicKey::parse(&content) else {
|
let Ok(public_key) = PublicKey::parse(&content) else {
|
||||||
@@ -240,7 +237,7 @@ impl Compose {
|
|||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(NostrProfile::new(public_key, metadata))
|
Ok(Profile::new(public_key, metadata))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -249,7 +246,7 @@ impl Compose {
|
|||||||
Ok(profile) => {
|
Ok(profile) => {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
let public_key = profile.public_key;
|
let public_key = profile.public_key();
|
||||||
|
|
||||||
this.contacts.update(cx, |this, cx| {
|
this.contacts.update(cx, |this, cx| {
|
||||||
this.insert(0, profile);
|
this.insert(0, profile);
|
||||||
@@ -441,7 +438,7 @@ impl Render for Compose {
|
|||||||
|
|
||||||
for ix in range {
|
for ix in range {
|
||||||
let item = contacts.get(ix).unwrap().clone();
|
let item = contacts.get(ix).unwrap().clone();
|
||||||
let is_select = selected.contains(&item.public_key);
|
let is_select = selected.contains(&item.public_key());
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
div()
|
div()
|
||||||
@@ -458,12 +455,10 @@ impl Render for Compose {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.child(
|
.child(div().flex_shrink_0().child(
|
||||||
div().flex_shrink_0().child(
|
img(item.shared_avatar()).size_6(),
|
||||||
img(item.avatar).size_6(),
|
))
|
||||||
),
|
.child(item.shared_name()),
|
||||||
)
|
|
||||||
.child(item.name),
|
|
||||||
)
|
)
|
||||||
.when(is_select, |this| {
|
.when(is_select, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
@@ -484,7 +479,7 @@ impl Render for Compose {
|
|||||||
.on_click(move |_, window, cx| {
|
.on_click(move |_, window, cx| {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(SelectContact(
|
Box::new(SelectContact(
|
||||||
item.public_key,
|
item.public_key(),
|
||||||
)),
|
)),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -138,31 +138,18 @@ impl Sidebar {
|
|||||||
|
|
||||||
for room in rooms {
|
for room in rooms {
|
||||||
let room = room.read(cx);
|
let room = room.read(cx);
|
||||||
let room_id = room.id;
|
let id = room.id;
|
||||||
let ago = room.last_seen().ago();
|
let ago = room.ago();
|
||||||
let Some(member) = room.first_member() else {
|
let label = room.display_name(cx);
|
||||||
continue;
|
let img = room.display_image(cx).map(img);
|
||||||
};
|
|
||||||
|
|
||||||
let label = if room.is_group() {
|
let item = FolderItem::new(id as usize)
|
||||||
room.subject().unwrap_or("Unnamed".into())
|
|
||||||
} else {
|
|
||||||
member.name.clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let img = if !room.is_group() {
|
|
||||||
Some(img(member.avatar.clone()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let item = FolderItem::new(room_id as usize)
|
|
||||||
.label(label)
|
.label(label)
|
||||||
.description(ago)
|
.description(ago)
|
||||||
.img(img)
|
.img(img)
|
||||||
.on_click({
|
.on_click({
|
||||||
cx.listener(move |this, _, window, cx| {
|
cx.listener(move |this, _, window, cx| {
|
||||||
this.open_room(room_id, window, cx);
|
this.open_room(id, window, cx);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
|||||||
publish.workspace = true
|
publish.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
account = { path = "../account" }
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
global = { path = "../global" }
|
global = { path = "../global" }
|
||||||
ui = { path = "../ui" }
|
ui = { path = "../ui" }
|
||||||
|
|||||||
5
crates/chats/src/constants.rs
Normal file
5
crates/chats/src/constants.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub(crate) const NOW: &str = "now";
|
||||||
|
pub(crate) const SECONDS_IN_MINUTE: i64 = 60;
|
||||||
|
pub(crate) const MINUTES_IN_HOUR: i64 = 60;
|
||||||
|
pub(crate) const HOURS_IN_DAY: i64 = 24;
|
||||||
|
pub(crate) const DAYS_IN_MONTH: i64 = 30;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{cmp::Reverse, collections::HashMap};
|
use std::{cmp::Reverse, collections::HashMap};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::{anyhow, Error};
|
||||||
use common::{last_seen::LastSeen, utils::room_hash};
|
use common::room_hash;
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task, Window};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@@ -11,6 +11,7 @@ use smallvec::{smallvec, SmallVec};
|
|||||||
|
|
||||||
use crate::room::Room;
|
use crate::room::Room;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
|
|
||||||
@@ -22,35 +23,56 @@ struct GlobalChatRegistry(Entity<ChatRegistry>);
|
|||||||
|
|
||||||
impl Global for GlobalChatRegistry {}
|
impl Global for GlobalChatRegistry {}
|
||||||
|
|
||||||
|
/// Main registry for managing chat rooms and user profiles
|
||||||
|
///
|
||||||
|
/// The ChatRegistry is responsible for:
|
||||||
|
/// - Managing chat rooms and their states
|
||||||
|
/// - Tracking user profiles
|
||||||
|
/// - Loading room data from the lmdb
|
||||||
|
/// - Handling messages and room creation
|
||||||
pub struct ChatRegistry {
|
pub struct ChatRegistry {
|
||||||
|
/// Collection of all chat rooms
|
||||||
rooms: Vec<Entity<Room>>,
|
rooms: Vec<Entity<Room>>,
|
||||||
|
|
||||||
|
/// Map of user public keys to their profile metadata
|
||||||
|
profiles: Entity<HashMap<PublicKey, Option<Metadata>>>,
|
||||||
|
|
||||||
|
/// Indicates if rooms are currently being loaded
|
||||||
loading: bool,
|
loading: bool,
|
||||||
|
|
||||||
|
/// Subscriptions for observing changes
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
subscriptions: SmallVec<[Subscription; 1]>,
|
subscriptions: SmallVec<[Subscription; 1]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatRegistry {
|
impl ChatRegistry {
|
||||||
pub fn global(cx: &mut App) -> Entity<Self> {
|
/// Retrieve the global ChatRegistry instance
|
||||||
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
cx.global::<GlobalChatRegistry>().0.clone()
|
cx.global::<GlobalChatRegistry>().0.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the global ChatRegistry instance
|
||||||
pub fn set_global(state: Entity<Self>, cx: &mut App) {
|
pub fn set_global(state: Entity<Self>, cx: &mut App) {
|
||||||
cx.set_global(GlobalChatRegistry(state));
|
cx.set_global(GlobalChatRegistry(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new ChatRegistry instance
|
||||||
fn new(cx: &mut Context<Self>) -> Self {
|
fn new(cx: &mut Context<Self>) -> Self {
|
||||||
|
let profiles = cx.new(|_| HashMap::new());
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
|
// Observe new Room creations to collect profile metadata
|
||||||
subscriptions.push(cx.observe_new::<Room>(|this, _, cx| {
|
subscriptions.push(cx.observe_new::<Room>(|this, _, cx| {
|
||||||
let load_metadata = this.load_metadata(cx);
|
let task = this.metadata(cx);
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |_, cx| {
|
||||||
if let Ok(profiles) = load_metadata.await {
|
if let Ok(data) = task.await {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
this.update(cx, |this, cx| {
|
for (public_key, metadata) in data.into_iter() {
|
||||||
this.update_members(profiles, cx);
|
Self::global(cx).update(cx, |this, cx| {
|
||||||
})
|
this.add_profile(public_key, metadata, cx);
|
||||||
.ok();
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -61,24 +83,73 @@ impl ChatRegistry {
|
|||||||
Self {
|
Self {
|
||||||
rooms: vec![],
|
rooms: vec![],
|
||||||
loading: true,
|
loading: true,
|
||||||
|
profiles,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the global loading status
|
||||||
|
pub fn loading(&self) -> bool {
|
||||||
|
self.loading
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a room by its ID.
|
||||||
|
pub fn room(&self, id: &u64, cx: &App) -> Option<Entity<Room>> {
|
||||||
|
self.rooms
|
||||||
|
.iter()
|
||||||
|
.find(|model| model.read(cx).id == *id)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all rooms grouped by their kind.
|
||||||
|
pub fn rooms(&self, cx: &App) -> HashMap<RoomKind, Vec<&Entity<Room>>> {
|
||||||
|
let mut groups = HashMap::new();
|
||||||
|
groups.insert(RoomKind::Ongoing, Vec::new());
|
||||||
|
groups.insert(RoomKind::Trusted, Vec::new());
|
||||||
|
groups.insert(RoomKind::Unknown, Vec::new());
|
||||||
|
|
||||||
|
for room in self.rooms.iter() {
|
||||||
|
let kind = room.read(cx).kind;
|
||||||
|
groups.entry(kind).or_insert_with(Vec::new).push(room);
|
||||||
|
}
|
||||||
|
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get rooms by their kind.
|
||||||
|
pub fn rooms_by_kind(&self, kind: RoomKind, cx: &App) -> Vec<&Entity<Room>> {
|
||||||
|
self.rooms
|
||||||
|
.iter()
|
||||||
|
.filter(|room| room.read(cx).kind == kind)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the IDs of all rooms.
|
||||||
|
pub fn room_ids(&self, cx: &mut Context<Self>) -> Vec<u64> {
|
||||||
|
self.rooms.iter().map(|room| room.read(cx).id).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load all rooms from the lmdb.
|
||||||
|
///
|
||||||
|
/// This method:
|
||||||
|
/// 1. Fetches all private direct messages from the lmdb
|
||||||
|
/// 2. Groups them by ID
|
||||||
|
/// 3. Determines each room's type based on message frequency and trust status
|
||||||
|
/// 4. Creates Room entities for each unique room
|
||||||
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn load_rooms(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let client = get_client();
|
type Rooms = Vec<(Event, usize, bool)>;
|
||||||
let room_ids = self.room_ids(cx);
|
|
||||||
|
|
||||||
type LoadResult = Result<Vec<(Event, usize, bool)>, Error>;
|
let task: Task<Result<Rooms, Error>> = cx.background_spawn(async move {
|
||||||
|
let client = get_client();
|
||||||
let task: Task<LoadResult> = cx.background_spawn(async move {
|
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
let public_key = signer.get_public_key().await?;
|
let public_key = signer.get_public_key().await?;
|
||||||
|
|
||||||
|
// Get messages sent by the user
|
||||||
let send = Filter::new()
|
let send = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.author(public_key);
|
.author(public_key);
|
||||||
|
|
||||||
|
// Get messages received by the user
|
||||||
let recv = Filter::new()
|
let recv = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.pubkey(public_key);
|
.pubkey(public_key);
|
||||||
@@ -89,26 +160,26 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
let mut room_map: HashMap<u64, (Event, usize, bool)> = HashMap::new();
|
let mut room_map: HashMap<u64, (Event, usize, bool)> = HashMap::new();
|
||||||
|
|
||||||
|
// Process each event and group by room hash
|
||||||
for event in events
|
for event in events
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
.filter(|ev| ev.tags.public_keys().peekable().peek().is_some())
|
||||||
{
|
{
|
||||||
let hash = room_hash(&event);
|
let hash = room_hash(&event);
|
||||||
|
|
||||||
if !room_ids.iter().any(|id| id == &hash) {
|
let filter = Filter::new().kind(Kind::ContactList).pubkey(event.pubkey);
|
||||||
let filter = Filter::new().kind(Kind::ContactList).pubkey(event.pubkey);
|
let is_trust = client.database().count(filter).await? >= 1;
|
||||||
let is_trust = client.database().count(filter).await? >= 1;
|
|
||||||
|
|
||||||
room_map
|
room_map
|
||||||
.entry(hash)
|
.entry(hash)
|
||||||
.and_modify(|(_, count, trusted)| {
|
.and_modify(|(_, count, trusted)| {
|
||||||
*count += 1;
|
*count += 1;
|
||||||
*trusted = is_trust;
|
*trusted = is_trust;
|
||||||
})
|
})
|
||||||
.or_insert((event, 1, is_trust));
|
.or_insert((event, 1, is_trust));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort rooms by creation date (newest first)
|
||||||
let result: Vec<(Event, usize, bool)> = room_map
|
let result: Vec<(Event, usize, bool)> = room_map
|
||||||
.into_values()
|
.into_values()
|
||||||
.sorted_by_key(|(ev, _, _)| Reverse(ev.created_at))
|
.sorted_by_key(|(ev, _, _)| Reverse(ev.created_at))
|
||||||
@@ -119,26 +190,31 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
if let Ok(events) = task.await {
|
if let Ok(events) = task.await {
|
||||||
let rooms: Vec<Entity<Room>> = events
|
|
||||||
.into_iter()
|
|
||||||
.map(|(event, count, trusted)| {
|
|
||||||
let kind = if count > 2 {
|
|
||||||
// If frequency count is greater than 2, mark this room as ongoing
|
|
||||||
RoomKind::Ongoing
|
|
||||||
} else if trusted {
|
|
||||||
RoomKind::Trusted
|
|
||||||
} else {
|
|
||||||
RoomKind::Unknown
|
|
||||||
};
|
|
||||||
|
|
||||||
cx.new(|_| Room::new(&event, kind)).unwrap()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
cx.update(|_, cx| {
|
cx.update(|_, cx| {
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
|
let ids = this.room_ids(cx);
|
||||||
|
let rooms: Vec<Entity<Room>> = events
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(event, count, trusted)| {
|
||||||
|
let hash = room_hash(&event);
|
||||||
|
if !ids.iter().any(|this| this == &hash) {
|
||||||
|
let kind = if count > 2 {
|
||||||
|
// If frequency count is greater than 2, mark this room as ongoing
|
||||||
|
RoomKind::Ongoing
|
||||||
|
} else if trusted {
|
||||||
|
RoomKind::Trusted
|
||||||
|
} else {
|
||||||
|
RoomKind::Unknown
|
||||||
|
};
|
||||||
|
Some(cx.new(|_| Room::new(&event).kind(kind)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
this.rooms.extend(rooms);
|
this.rooms.extend(rooms);
|
||||||
this.rooms.sort_by_key(|r| Reverse(r.read(cx).last_seen()));
|
this.rooms.sort_by_key(|r| Reverse(r.read(cx).created_at));
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
@@ -151,48 +227,40 @@ impl ChatRegistry {
|
|||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the IDs of all rooms.
|
/// Add a user profile to the registry
|
||||||
pub fn room_ids(&self, cx: &mut Context<Self>) -> Vec<u64> {
|
///
|
||||||
self.rooms.iter().map(|room| room.read(cx).id).collect()
|
/// Only adds the profile if it doesn't already exist or is currently none
|
||||||
|
pub fn add_profile(
|
||||||
|
&mut self,
|
||||||
|
public_key: PublicKey,
|
||||||
|
metadata: Option<Metadata>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.profiles.update(cx, |this, _cx| {
|
||||||
|
this.entry(public_key)
|
||||||
|
.and_modify(|entry| {
|
||||||
|
if entry.is_none() {
|
||||||
|
*entry = metadata.clone();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_insert_with(|| metadata);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all rooms.
|
/// Get a user profile by public key
|
||||||
pub fn rooms(&self, cx: &App) -> HashMap<RoomKind, Vec<&Entity<Room>>> {
|
pub fn profile(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||||
let mut groups = HashMap::new();
|
let metadata = if let Some(profile) = self.profiles.read(cx).get(public_key) {
|
||||||
groups.insert(RoomKind::Ongoing, Vec::new());
|
profile.clone().unwrap_or_default()
|
||||||
groups.insert(RoomKind::Trusted, Vec::new());
|
} else {
|
||||||
groups.insert(RoomKind::Unknown, Vec::new());
|
Metadata::default()
|
||||||
|
};
|
||||||
|
|
||||||
for room in self.rooms.iter() {
|
Profile::new(*public_key, metadata)
|
||||||
let kind = room.read(cx).kind();
|
|
||||||
groups.entry(kind).or_insert_with(Vec::new).push(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
groups
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get rooms by their kind.
|
/// Add a new room to the registry
|
||||||
pub fn rooms_by_kind(&self, kind: RoomKind, cx: &App) -> Vec<&Entity<Room>> {
|
///
|
||||||
self.rooms
|
/// Returns an error if the room already exists
|
||||||
.iter()
|
|
||||||
.filter(|room| room.read(cx).kind() == kind)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the loading status of the rooms.
|
|
||||||
pub fn loading(&self) -> bool {
|
|
||||||
self.loading
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a room by its ID.
|
|
||||||
pub fn get(&self, id: &u64, cx: &App) -> Option<Entity<Room>> {
|
|
||||||
self.rooms
|
|
||||||
.iter()
|
|
||||||
.find(|model| model.read(cx).id == *id)
|
|
||||||
.cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push a room to the list.
|
|
||||||
pub fn push(&mut self, room: Room, cx: &mut Context<Self>) -> Result<(), anyhow::Error> {
|
pub fn push(&mut self, room: Room, cx: &mut Context<Self>) -> Result<(), anyhow::Error> {
|
||||||
let room = cx.new(|_| room);
|
let room = cx.new(|_| room);
|
||||||
|
|
||||||
@@ -210,21 +278,24 @@ impl ChatRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a message to a room.
|
/// Push a new message to a room
|
||||||
|
///
|
||||||
|
/// If the room doesn't exist, it will be created.
|
||||||
|
/// Updates room ordering based on the most recent messages.
|
||||||
pub fn push_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn push_message(&mut self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let id = room_hash(&event);
|
let id = room_hash(&event);
|
||||||
|
|
||||||
if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) {
|
if let Some(room) = self.rooms.iter().find(|room| room.read(cx).id == id) {
|
||||||
room.update(cx, |this, cx| {
|
room.update(cx, |this, cx| {
|
||||||
this.set_last_seen(LastSeen(event.created_at), cx);
|
this.created_at(event.created_at, cx);
|
||||||
this.emit_message(event, window, cx);
|
this.emit_message(event, window, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-sort rooms by last seen
|
// Re-sort rooms by last seen
|
||||||
self.rooms
|
self.rooms
|
||||||
.sort_by_key(|room| Reverse(room.read(cx).last_seen()));
|
.sort_by_key(|room| Reverse(room.read(cx).created_at));
|
||||||
} else {
|
} else {
|
||||||
let new_room = cx.new(|_| Room::new(&event, RoomKind::default()));
|
let new_room = cx.new(|_| Room::new(&event));
|
||||||
|
|
||||||
// Push the new room to the front of the list
|
// Push the new room to the front of the list
|
||||||
self.rooms.insert(0, new_room);
|
self.rooms.insert(0, new_room);
|
||||||
|
|||||||
@@ -1,36 +1,101 @@
|
|||||||
use common::{last_seen::LastSeen, profile::NostrProfile};
|
use chrono::{Local, TimeZone};
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
/// # Message
|
||||||
|
///
|
||||||
|
/// Represents a message in the application.
|
||||||
|
///
|
||||||
|
/// ## Fields
|
||||||
|
///
|
||||||
|
/// - `id`: The unique identifier for the message
|
||||||
|
/// - `content`: The text content of the message
|
||||||
|
/// - `author`: Profile information about who created the message
|
||||||
|
/// - `mentions`: List of profiles mentioned in the message
|
||||||
|
/// - `created_at`: Timestamp when the message was created
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
pub id: EventId,
|
pub id: EventId,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
pub author: NostrProfile,
|
pub author: Profile,
|
||||||
pub mentions: Vec<NostrProfile>,
|
pub mentions: Vec<Profile>,
|
||||||
pub created_at: LastSeen,
|
pub created_at: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn new(
|
/// Creates a new message with the provided details
|
||||||
id: EventId,
|
///
|
||||||
content: String,
|
/// # Arguments
|
||||||
author: NostrProfile,
|
///
|
||||||
mentions: Vec<NostrProfile>,
|
/// * `id` - Unique event identifier
|
||||||
created_at: Timestamp,
|
/// * `content` - Message text content
|
||||||
) -> Self {
|
/// * `author` - Profile of the message author
|
||||||
let created_at = LastSeen(created_at);
|
/// * `created_at` - When the message was created
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A new `Message` instance
|
||||||
|
pub fn new(id: EventId, content: String, author: Profile, created_at: Timestamp) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
content,
|
content,
|
||||||
author,
|
author,
|
||||||
mentions,
|
|
||||||
created_at,
|
created_at,
|
||||||
|
mentions: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds or replaces mentions in the message
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `mentions` - New list of mentioned profiles
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The same message with updated mentions
|
||||||
|
pub fn with_mentions(mut self, mentions: impl IntoIterator<Item = Profile>) -> Self {
|
||||||
|
self.mentions.extend(mentions);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats the message timestamp as a human-readable relative time
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A formatted string like "Today at 12:30 PM", "Yesterday at 3:45 PM",
|
||||||
|
/// or a date and time for older messages
|
||||||
|
pub fn ago(&self) -> SharedString {
|
||||||
|
let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {
|
||||||
|
chrono::LocalResult::Single(time) => time,
|
||||||
|
_ => return "Invalid timestamp".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = Local::now();
|
||||||
|
let input_date = input_time.date_naive();
|
||||||
|
let now_date = now.date_naive();
|
||||||
|
let yesterday_date = (now - chrono::Duration::days(1)).date_naive();
|
||||||
|
|
||||||
|
let time_format = input_time.format("%H:%M %p");
|
||||||
|
|
||||||
|
match input_date {
|
||||||
|
date if date == now_date => format!("Today at {time_format}"),
|
||||||
|
date if date == yesterday_date => format!("Yesterday at {time_format}"),
|
||||||
|
_ => format!("{}, {time_format}", input_time.format("%d/%m/%y")),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # RoomMessage
|
||||||
|
///
|
||||||
|
/// Represents different types of messages that can appear in a room.
|
||||||
|
///
|
||||||
|
/// ## Variants
|
||||||
|
///
|
||||||
|
/// - `User`: A message sent by a user
|
||||||
|
/// - `System`: A message generated by the system
|
||||||
|
/// - `Announcement`: A special message type used for room announcements
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum RoomMessage {
|
pub enum RoomMessage {
|
||||||
/// User message
|
/// User message
|
||||||
@@ -43,14 +108,37 @@ pub enum RoomMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RoomMessage {
|
impl RoomMessage {
|
||||||
|
/// Creates a new user message
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `message` - The message content
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `RoomMessage::User` variant
|
||||||
pub fn user(message: Message) -> Self {
|
pub fn user(message: Message) -> Self {
|
||||||
Self::User(Box::new(message))
|
Self::User(Box::new(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new system message
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content` - The system message content
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `RoomMessage::System` variant
|
||||||
pub fn system(content: SharedString) -> Self {
|
pub fn system(content: SharedString) -> Self {
|
||||||
Self::System(content)
|
Self::System(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new announcement placeholder
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A `RoomMessage::Announcement` variant
|
||||||
pub fn announcement() -> Self {
|
pub fn announcement() -> Self {
|
||||||
Self::Announcement
|
Self::Announcement
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
use std::{collections::HashSet, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use account::Account;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use common::{
|
use chrono::{Local, TimeZone};
|
||||||
last_seen::LastSeen,
|
use common::{compare, profile::SharedProfile, room_hash};
|
||||||
profile::NostrProfile,
|
|
||||||
utils::{compare, room_hash},
|
|
||||||
};
|
|
||||||
use global::get_client;
|
use global::get_client;
|
||||||
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
|
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use smallvec::SmallVec;
|
|
||||||
|
|
||||||
use crate::message::{Message, RoomMessage};
|
use crate::{
|
||||||
|
constants::{DAYS_IN_MONTH, HOURS_IN_DAY, MINUTES_IN_HOUR, NOW, SECONDS_IN_MINUTE},
|
||||||
|
message::{Message, RoomMessage},
|
||||||
|
ChatRegistry,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct IncomingEvent {
|
pub struct IncomingEvent {
|
||||||
@@ -29,15 +30,13 @@ pub enum RoomKind {
|
|||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub last_seen: LastSeen,
|
pub created_at: Timestamp,
|
||||||
/// Subject of the room
|
/// Subject of the room
|
||||||
pub subject: Option<SharedString>,
|
pub subject: Option<SharedString>,
|
||||||
/// All members of the room
|
/// All members of the room
|
||||||
pub members: Arc<SmallVec<[NostrProfile; 2]>>,
|
pub members: Arc<Vec<PublicKey>>,
|
||||||
/// Kind
|
/// Kind
|
||||||
pub kind: RoomKind,
|
pub kind: RoomKind,
|
||||||
/// All public keys of the room members
|
|
||||||
pubkeys: Vec<PublicKey>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<IncomingEvent> for Room {}
|
impl EventEmitter<IncomingEvent> for Room {}
|
||||||
@@ -49,10 +48,25 @@ impl PartialEq for Room {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Room {
|
impl Room {
|
||||||
/// Create a new room from an Nostr Event
|
/// Creates a new Room instance from a Nostr event
|
||||||
pub fn new(event: &Event, kind: RoomKind) -> Self {
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `event` - The Nostr event containing chat information
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A new Room instance with information extracted from the event
|
||||||
|
pub fn new(event: &Event) -> Self {
|
||||||
let id = room_hash(event);
|
let id = room_hash(event);
|
||||||
let last_seen = LastSeen(event.created_at);
|
let created_at = event.created_at;
|
||||||
|
|
||||||
|
// Get all pubkeys from the event's tags
|
||||||
|
let mut pubkeys: Vec<PublicKey> = event.tags.public_keys().cloned().collect();
|
||||||
|
pubkeys.push(event.pubkey);
|
||||||
|
|
||||||
|
// Convert pubkeys into members
|
||||||
|
let members = Arc::new(pubkeys.into_iter().unique().sorted().collect());
|
||||||
|
|
||||||
// Get the subject from the event's tags
|
// Get the subject from the event's tags
|
||||||
let subject = if let Some(tag) = event.tags.find(TagKind::Subject) {
|
let subject = if let Some(tag) = event.tags.find(TagKind::Subject) {
|
||||||
@@ -61,112 +75,264 @@ impl Room {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all public keys from the event's tags
|
|
||||||
let mut pubkeys = vec![];
|
|
||||||
pubkeys.extend(event.tags.public_keys().collect::<HashSet<_>>());
|
|
||||||
pubkeys.push(event.pubkey);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
last_seen,
|
created_at,
|
||||||
subject,
|
subject,
|
||||||
kind,
|
members,
|
||||||
members: Arc::new(SmallVec::with_capacity(pubkeys.len())),
|
kind: RoomKind::Unknown,
|
||||||
pubkeys,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get room's id
|
/// Sets the kind of the room
|
||||||
pub fn id(&self) -> u64 {
|
///
|
||||||
self.id
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `kind` - The kind of room to set
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The room with the updated kind
|
||||||
|
pub fn kind(mut self, kind: RoomKind) -> Self {
|
||||||
|
self.kind = kind;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get room's member by public key
|
/// Calculates a human-readable representation of the time passed since room creation
|
||||||
pub fn member(&self, public_key: &PublicKey) -> Option<NostrProfile> {
|
///
|
||||||
self.members
|
/// # Returns
|
||||||
.iter()
|
///
|
||||||
.find(|m| &m.public_key == public_key)
|
/// A SharedString representing the relative time since room creation:
|
||||||
.cloned()
|
/// - "now" for less than a minute
|
||||||
}
|
/// - "Xm" for minutes
|
||||||
|
/// - "Xh" for hours
|
||||||
/// Get room's first member's public key
|
/// - "Xd" for days
|
||||||
pub fn first_member(&self) -> Option<&NostrProfile> {
|
/// - Month and day (e.g. "Jan 15") for older dates
|
||||||
self.members.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect room's member's public keys
|
|
||||||
pub fn public_keys(&self) -> Vec<PublicKey> {
|
|
||||||
self.pubkeys.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get room's display name
|
|
||||||
pub fn subject(&self) -> Option<SharedString> {
|
|
||||||
self.subject.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get room's kind
|
|
||||||
pub fn kind(&self) -> RoomKind {
|
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine if room is a group
|
|
||||||
pub fn is_group(&self) -> bool {
|
|
||||||
self.members.len() > 2
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get room's last seen
|
|
||||||
pub fn last_seen(&self) -> LastSeen {
|
|
||||||
self.last_seen
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set room's last seen
|
|
||||||
pub fn set_last_seen(&mut self, last_seen: LastSeen, cx: &mut Context<Self>) {
|
|
||||||
self.last_seen = last_seen;
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get room's last seen as ago format
|
|
||||||
pub fn ago(&self) -> SharedString {
|
pub fn ago(&self) -> SharedString {
|
||||||
self.last_seen.ago()
|
let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {
|
||||||
|
chrono::LocalResult::Single(time) => time,
|
||||||
|
_ => return "1m".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = Local::now();
|
||||||
|
let duration = now.signed_duration_since(input_time);
|
||||||
|
|
||||||
|
match duration {
|
||||||
|
d if d.num_seconds() < SECONDS_IN_MINUTE => NOW.into(),
|
||||||
|
d if d.num_minutes() < MINUTES_IN_HOUR => format!("{}m", d.num_minutes()),
|
||||||
|
d if d.num_hours() < HOURS_IN_DAY => format!("{}h", d.num_hours()),
|
||||||
|
d if d.num_days() < DAYS_IN_MONTH => format!("{}d", d.num_days()),
|
||||||
|
_ => input_time.format("%b %d").to_string(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_members(&mut self, profiles: Vec<NostrProfile>, cx: &mut Context<Self>) {
|
/// Gets the profile for a specific public key
|
||||||
// Update the room's name if it's not already set
|
///
|
||||||
if self.subject.is_none() {
|
/// # Arguments
|
||||||
// Merge all members into a single name
|
///
|
||||||
|
/// * `public_key` - The public key to get the profile for
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The Profile associated with the given public key
|
||||||
|
pub fn profile_by_pubkey(&self, public_key: &PublicKey, cx: &App) -> Profile {
|
||||||
|
ChatRegistry::global(cx).read(cx).profile(public_key, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the first member in the room that isn't the current user
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The Profile of the first member in the room
|
||||||
|
pub fn first_member(&self, cx: &App) -> Profile {
|
||||||
|
let account = Account::global(cx).read(cx);
|
||||||
|
let profile = account.profile.clone().unwrap();
|
||||||
|
|
||||||
|
if let Some(public_key) = self
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.filter(|&pubkey| pubkey != &profile.public_key())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.first()
|
||||||
|
{
|
||||||
|
self.profile_by_pubkey(public_key, cx)
|
||||||
|
} else {
|
||||||
|
profile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all avatars for members in the room
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A vector of SharedString containing all members' avatars
|
||||||
|
pub fn avatars(&self, cx: &App) -> Vec<SharedString> {
|
||||||
|
let profiles: Vec<Profile> = self
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.map(|pubkey| ChatRegistry::global(cx).read(cx).profile(pubkey, cx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
profiles
|
||||||
|
.iter()
|
||||||
|
.map(|member| member.shared_avatar())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a formatted string of member names
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A SharedString containing formatted member names:
|
||||||
|
/// - For a group chat: "name1, name2, +X" where X is the number of additional members
|
||||||
|
/// - For a direct message: just the name of the other person
|
||||||
|
pub fn names(&self, cx: &App) -> SharedString {
|
||||||
|
if self.is_group() {
|
||||||
|
let profiles = self
|
||||||
|
.members
|
||||||
|
.iter()
|
||||||
|
.map(|pubkey| ChatRegistry::global(cx).read(cx).profile(pubkey, cx))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut name = profiles
|
let mut name = profiles
|
||||||
.iter()
|
.iter()
|
||||||
.take(2)
|
.take(2)
|
||||||
.map(|profile| profile.name.to_string())
|
.map(|profile| profile.shared_name())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
// Create a specific name for group
|
|
||||||
if profiles.len() > 2 {
|
if profiles.len() > 2 {
|
||||||
name = format!("{}, +{}", name, profiles.len() - 2);
|
name = format!("{}, +{}", name, profiles.len() - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.subject = Some(name.into());
|
name.into()
|
||||||
};
|
} else {
|
||||||
|
self.first_member(cx).shared_name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update the room's members
|
/// Gets the display name for the room
|
||||||
self.members = Arc::new(profiles.into());
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A SharedString representing the display name:
|
||||||
|
/// - The subject of the room if it exists
|
||||||
|
/// - Otherwise, the formatted names of the members
|
||||||
|
pub fn display_name(&self, cx: &App) -> SharedString {
|
||||||
|
if let Some(subject) = self.subject.as_ref() {
|
||||||
|
subject.clone()
|
||||||
|
} else {
|
||||||
|
self.names(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the display image for the room
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// An Option<SharedString> containing the avatar:
|
||||||
|
/// - For a direct message: the other person's avatar
|
||||||
|
/// - For a group chat: None
|
||||||
|
pub fn display_image(&self, cx: &App) -> Option<SharedString> {
|
||||||
|
if !self.is_group() {
|
||||||
|
Some(self.first_member(cx).shared_avatar())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the room is a group chat
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// true if the room has more than 2 members, false otherwise
|
||||||
|
pub fn is_group(&self) -> bool {
|
||||||
|
self.members.len() > 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the creation timestamp of the room
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `created_at` - The new Timestamp to set
|
||||||
|
/// * `cx` - The context to notify about the update
|
||||||
|
pub fn created_at(&mut self, created_at: Timestamp, cx: &mut Context<Self>) {
|
||||||
|
self.created_at = created_at;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify messaging_relays for all room's members
|
/// Fetches metadata for all members in the room
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The context for the background task
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A Task that resolves to Result<Vec<(PublicKey, Option<Metadata>)>, Error>
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub fn metadata(
|
||||||
|
&self,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<Result<Vec<(PublicKey, Option<Metadata>)>, Error>> {
|
||||||
|
let client = get_client();
|
||||||
|
let public_keys = self.members.clone();
|
||||||
|
|
||||||
|
cx.background_spawn(async move {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for public_key in public_keys.iter() {
|
||||||
|
let metadata = client.database().metadata(*public_key).await?;
|
||||||
|
output.push((*public_key, metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks which members have inbox relays set up
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A Task that resolves to Result<Vec<(PublicKey, bool)>, Error> where
|
||||||
|
/// the boolean indicates if the member has inbox relays configured
|
||||||
pub fn messaging_relays(&self, cx: &App) -> Task<Result<Vec<(PublicKey, bool)>, Error>> {
|
pub fn messaging_relays(&self, cx: &App) -> Task<Result<Vec<(PublicKey, bool)>, Error>> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let pubkeys = self.public_keys();
|
let pubkeys = Arc::clone(&self.members);
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let mut result = Vec::with_capacity(pubkeys.len());
|
let mut result = Vec::with_capacity(pubkeys.len());
|
||||||
|
|
||||||
for pubkey in pubkeys.into_iter() {
|
for pubkey in pubkeys.iter() {
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.author(pubkey)
|
.author(*pubkey)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
let is_ready = client
|
let is_ready = client
|
||||||
@@ -177,17 +343,27 @@ impl Room {
|
|||||||
.and_then(|events| events.first_owned())
|
.and_then(|events| events.first_owned())
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
result.push((pubkey, is_ready));
|
result.push((*pubkey, is_ready));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send message to all room's members
|
/// Sends a message to all members in the room
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content` - The content of the message to send
|
||||||
|
/// * `cx` - The App context
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A Task that resolves to Result<Vec<String>, Error> where the
|
||||||
|
/// strings contain error messages for any failed sends
|
||||||
pub fn send_message(&self, content: String, cx: &App) -> Task<Result<Vec<String>, Error>> {
|
pub fn send_message(&self, content: String, cx: &App) -> Task<Result<Vec<String>, Error>> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let pubkeys = self.public_keys();
|
let pubkeys = self.members.clone();
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let signer = client.signer().await?;
|
let signer = client.signer().await?;
|
||||||
@@ -218,48 +394,29 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load metadata for all members
|
/// Loads all messages for this room from the database
|
||||||
pub fn load_metadata(&self, cx: &mut Context<Self>) -> Task<Result<Vec<NostrProfile>, Error>> {
|
///
|
||||||
let client = get_client();
|
/// # Arguments
|
||||||
let pubkeys = self.public_keys();
|
///
|
||||||
|
/// * `cx` - The App context
|
||||||
cx.background_spawn(async move {
|
///
|
||||||
let signer = client.signer().await?;
|
/// # Returns
|
||||||
let signer_pubkey = signer.get_public_key().await?;
|
///
|
||||||
let mut profiles = Vec::with_capacity(pubkeys.len());
|
/// A Task that resolves to Result<Vec<RoomMessage>, Error> containing
|
||||||
|
/// all messages for this room
|
||||||
for public_key in pubkeys.into_iter() {
|
|
||||||
let metadata = client
|
|
||||||
.database()
|
|
||||||
.metadata(public_key)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
// Convert metadata to profile
|
|
||||||
let profile = NostrProfile::new(public_key, metadata);
|
|
||||||
|
|
||||||
if public_key == signer_pubkey {
|
|
||||||
// Room's owner always push to the end of the vector
|
|
||||||
profiles.push(profile);
|
|
||||||
} else {
|
|
||||||
profiles.insert(0, profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(profiles)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load room messages
|
|
||||||
pub fn load_messages(&self, cx: &App) -> Task<Result<Vec<RoomMessage>, Error>> {
|
pub fn load_messages(&self, cx: &App) -> Task<Result<Vec<RoomMessage>, Error>> {
|
||||||
let client = get_client();
|
let client = get_client();
|
||||||
let pubkeys = self.public_keys();
|
let pubkeys = Arc::clone(&self.members);
|
||||||
let members = Arc::clone(&self.members);
|
|
||||||
|
let profiles: Vec<Profile> = pubkeys
|
||||||
|
.iter()
|
||||||
|
.map(|pubkey| ChatRegistry::global(cx).read(cx).profile(pubkey, cx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::PrivateDirectMessage)
|
.kind(Kind::PrivateDirectMessage)
|
||||||
.authors(pubkeys.clone())
|
.authors(pubkeys.to_vec())
|
||||||
.pubkeys(pubkeys.clone());
|
.pubkeys(pubkeys.to_vec());
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
@@ -282,14 +439,16 @@ impl Room {
|
|||||||
|
|
||||||
for event in events.into_iter() {
|
for event in events.into_iter() {
|
||||||
let mut mentions = vec![];
|
let mut mentions = vec![];
|
||||||
|
let id = event.id;
|
||||||
|
let created_at = event.created_at;
|
||||||
let content = event.content.clone();
|
let content = event.content.clone();
|
||||||
let tokens = parser.parse(&content);
|
let tokens = parser.parse(&content);
|
||||||
|
|
||||||
let author = members
|
let author = profiles
|
||||||
.iter()
|
.iter()
|
||||||
.find(|profile| profile.public_key == event.pubkey)
|
.find(|profile| profile.public_key() == event.pubkey)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| NostrProfile::new(event.pubkey, Metadata::default()));
|
.unwrap_or_else(|| Profile::new(event.pubkey, Metadata::default()));
|
||||||
|
|
||||||
let pubkey_tokens = tokens
|
let pubkey_tokens = tokens
|
||||||
.filter_map(|token| match token {
|
.filter_map(|token| match token {
|
||||||
@@ -303,22 +462,16 @@ impl Room {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for pubkey in pubkey_tokens {
|
for pubkey in pubkey_tokens {
|
||||||
if let Some(profile) =
|
mentions.push(
|
||||||
members.iter().find(|profile| profile.public_key == pubkey)
|
profiles
|
||||||
{
|
.iter()
|
||||||
mentions.push(profile.clone());
|
.find(|profile| profile.public_key() == pubkey)
|
||||||
} else {
|
.cloned()
|
||||||
let metadata = client
|
.unwrap_or_else(|| Profile::new(pubkey, Metadata::default())),
|
||||||
.database()
|
);
|
||||||
.metadata(pubkey)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
mentions.push(NostrProfile::new(pubkey, metadata));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = Message::new(event.id, content, author, mentions, event.created_at);
|
let message = Message::new(id, content, author, created_at).with_mentions(mentions);
|
||||||
let room_message = RoomMessage::user(message);
|
let room_message = RoomMessage::user(message);
|
||||||
|
|
||||||
messages.push(room_message);
|
messages.push(room_message);
|
||||||
@@ -328,22 +481,37 @@ impl Room {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emit message to GPUI
|
/// Emits a message event to the GPUI
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `event` - The Nostr event to emit
|
||||||
|
/// * `window` - The Window to emit the event to
|
||||||
|
/// * `cx` - The context for the room
|
||||||
|
///
|
||||||
|
/// # Effects
|
||||||
|
///
|
||||||
|
/// Processes the event and emits an IncomingEvent to the UI when complete
|
||||||
pub fn emit_message(&self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn emit_message(&self, event: Event, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let client = get_client();
|
let pubkeys = self.members.clone();
|
||||||
let members = Arc::clone(&self.members);
|
let profiles: Vec<Profile> = pubkeys
|
||||||
|
.iter()
|
||||||
|
.map(|pubkey| ChatRegistry::global(cx).read(cx).profile(pubkey, cx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let task: Task<Result<RoomMessage, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<RoomMessage, Error>> = cx.background_spawn(async move {
|
||||||
let parser = NostrParser::new();
|
let parser = NostrParser::new();
|
||||||
|
let id = event.id;
|
||||||
|
let created_at = event.created_at;
|
||||||
let content = event.content.clone();
|
let content = event.content.clone();
|
||||||
let tokens = parser.parse(&content);
|
let tokens = parser.parse(&content);
|
||||||
let mut mentions = vec![];
|
let mut mentions = vec![];
|
||||||
|
|
||||||
let author = members
|
let author = profiles
|
||||||
.iter()
|
.iter()
|
||||||
.find(|profile| profile.public_key == event.pubkey)
|
.find(|profile| profile.public_key() == event.pubkey)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| NostrProfile::new(event.pubkey, Metadata::default()));
|
.unwrap_or_else(|| Profile::new(event.pubkey, Metadata::default()));
|
||||||
|
|
||||||
let pubkey_tokens = tokens
|
let pubkey_tokens = tokens
|
||||||
.filter_map(|token| match token {
|
.filter_map(|token| match token {
|
||||||
@@ -357,23 +525,16 @@ impl Room {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
for pubkey in pubkey_tokens {
|
for pubkey in pubkey_tokens {
|
||||||
if let Some(profile) = members
|
mentions.push(
|
||||||
.iter()
|
profiles
|
||||||
.find(|profile| profile.public_key == event.pubkey)
|
.iter()
|
||||||
{
|
.find(|profile| profile.public_key() == pubkey)
|
||||||
mentions.push(profile.clone());
|
.cloned()
|
||||||
} else {
|
.unwrap_or_else(|| Profile::new(pubkey, Metadata::default())),
|
||||||
let metadata = client
|
);
|
||||||
.database()
|
|
||||||
.metadata(pubkey)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
mentions.push(NostrProfile::new(pubkey, metadata));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = Message::new(event.id, content, author, mentions, event.created_at);
|
let message = Message::new(id, content, author, created_at).with_mentions(mentions);
|
||||||
let room_message = RoomMessage::user(message);
|
let room_message = RoomMessage::user(message);
|
||||||
|
|
||||||
Ok(room_message)
|
Ok(room_message)
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
use chrono::{Local, TimeZone};
|
|
||||||
use gpui::SharedString;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
|
|
||||||
const NOW: &str = "now";
|
|
||||||
const SECONDS_IN_MINUTE: i64 = 60;
|
|
||||||
const MINUTES_IN_HOUR: i64 = 60;
|
|
||||||
const HOURS_IN_DAY: i64 = 24;
|
|
||||||
const DAYS_IN_MONTH: i64 = 30;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct LastSeen(pub Timestamp);
|
|
||||||
|
|
||||||
impl LastSeen {
|
|
||||||
pub fn ago(&self) -> SharedString {
|
|
||||||
let now = Local::now();
|
|
||||||
let input_time = match Local.timestamp_opt(self.0.as_u64() as i64, 0) {
|
|
||||||
chrono::LocalResult::Single(time) => time,
|
|
||||||
_ => return "Invalid timestamp".into(),
|
|
||||||
};
|
|
||||||
let duration = now.signed_duration_since(input_time);
|
|
||||||
|
|
||||||
match duration {
|
|
||||||
d if d.num_seconds() < SECONDS_IN_MINUTE => NOW.into(),
|
|
||||||
d if d.num_minutes() < MINUTES_IN_HOUR => format!("{}m", d.num_minutes()),
|
|
||||||
d if d.num_hours() < HOURS_IN_DAY => format!("{}h", d.num_hours()),
|
|
||||||
d if d.num_days() < DAYS_IN_MONTH => format!("{}d", d.num_days()),
|
|
||||||
_ => input_time.format("%b %d").to_string(),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn human_readable(&self) -> SharedString {
|
|
||||||
let now = Local::now();
|
|
||||||
let input_time = match Local.timestamp_opt(self.0.as_u64() as i64, 0) {
|
|
||||||
chrono::LocalResult::Single(time) => time,
|
|
||||||
_ => return "Invalid timestamp".into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let input_date = input_time.date_naive();
|
|
||||||
let now_date = now.date_naive();
|
|
||||||
let yesterday_date = (now - chrono::Duration::days(1)).date_naive();
|
|
||||||
|
|
||||||
let time_format = input_time.format("%H:%M %p");
|
|
||||||
|
|
||||||
match input_date {
|
|
||||||
date if date == now_date => format!("Today at {time_format}"),
|
|
||||||
date if date == yesterday_date => format!("Yesterday at {time_format}"),
|
|
||||||
_ => format!("{}, {time_format}", input_time.format("%d/%m/%y")),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, created_at: Timestamp) {
|
|
||||||
self.0 = created_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,78 @@
|
|||||||
pub mod last_seen;
|
use std::{
|
||||||
|
collections::HashSet,
|
||||||
|
hash::{DefaultHasher, Hash, Hasher},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use global::constants::NIP96_SERVER;
|
||||||
|
use gpui::Image;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use nostr_sdk::prelude::*;
|
||||||
|
use qrcode_generator::QrCodeEcc;
|
||||||
|
use rnglib::{Language, RNG};
|
||||||
|
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod utils;
|
|
||||||
|
pub async fn nip96_upload(client: &Client, file: Vec<u8>) -> anyhow::Result<Url, anyhow::Error> {
|
||||||
|
let signer = client.signer().await?;
|
||||||
|
let server_url = Url::parse(NIP96_SERVER)?;
|
||||||
|
|
||||||
|
let config: ServerConfig = nip96::get_server_config(server_url, None).await?;
|
||||||
|
let url = nip96::upload_data(&signer, &config, file, None, None).await?;
|
||||||
|
|
||||||
|
Ok(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn room_hash(event: &Event) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
let mut pubkeys: Vec<&PublicKey> = vec![];
|
||||||
|
|
||||||
|
// Add all public keys from event
|
||||||
|
pubkeys.push(&event.pubkey);
|
||||||
|
pubkeys.extend(event.tags.public_keys().collect::<Vec<_>>());
|
||||||
|
|
||||||
|
// Generate unique hash
|
||||||
|
pubkeys
|
||||||
|
.into_iter()
|
||||||
|
.unique()
|
||||||
|
.sorted()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.hash(&mut hasher);
|
||||||
|
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn device_pubkey(event: &Event) -> Result<PublicKey, anyhow::Error> {
|
||||||
|
let n_tag = event.tags.find(TagKind::custom("n")).context("Invalid")?;
|
||||||
|
let hex = n_tag.content().context("Invalid")?;
|
||||||
|
let pubkey = PublicKey::parse(hex)?;
|
||||||
|
|
||||||
|
Ok(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_name(length: usize) -> String {
|
||||||
|
let rng = RNG::from(&Language::Roman);
|
||||||
|
rng.generate_names(length, true).join("-").to_lowercase()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_qr(data: &str) -> Result<Arc<Image>, anyhow::Error> {
|
||||||
|
let qr = qrcode_generator::to_png_to_vec_from_str(data, QrCodeEcc::Medium, 256)?;
|
||||||
|
let img = Arc::new(Image {
|
||||||
|
format: gpui::ImageFormat::Png,
|
||||||
|
bytes: qr.clone(),
|
||||||
|
id: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compare<T>(a: &[T], b: &[T]) -> bool
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
|
let a: HashSet<_> = a.iter().collect();
|
||||||
|
let b: HashSet<_> = b.iter().collect();
|
||||||
|
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,27 +2,14 @@ use global::constants::IMAGE_SERVICE;
|
|||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
pub trait SharedProfile {
|
||||||
pub struct NostrProfile {
|
fn shared_avatar(&self) -> SharedString;
|
||||||
pub public_key: PublicKey,
|
fn shared_name(&self) -> SharedString;
|
||||||
pub avatar: SharedString,
|
|
||||||
pub name: SharedString,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NostrProfile {
|
impl SharedProfile for Profile {
|
||||||
pub fn new(public_key: PublicKey, metadata: Metadata) -> Self {
|
fn shared_avatar(&self) -> SharedString {
|
||||||
let name = Self::extract_name(&public_key, &metadata);
|
self.metadata()
|
||||||
let avatar = Self::extract_avatar(&metadata);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
public_key,
|
|
||||||
name,
|
|
||||||
avatar,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_avatar(metadata: &Metadata) -> SharedString {
|
|
||||||
metadata
|
|
||||||
.picture
|
.picture
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|picture| !picture.is_empty())
|
.filter(|picture| !picture.is_empty())
|
||||||
@@ -36,20 +23,20 @@ impl NostrProfile {
|
|||||||
.unwrap_or_else(|| "brand/avatar.png".into())
|
.unwrap_or_else(|| "brand/avatar.png".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_name(public_key: &PublicKey, metadata: &Metadata) -> SharedString {
|
fn shared_name(&self) -> SharedString {
|
||||||
if let Some(display_name) = metadata.display_name.as_ref() {
|
if let Some(display_name) = self.metadata().display_name.as_ref() {
|
||||||
if !display_name.is_empty() {
|
if !display_name.is_empty() {
|
||||||
return display_name.into();
|
return display_name.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = metadata.name.as_ref() {
|
if let Some(name) = self.metadata().name.as_ref() {
|
||||||
if !name.is_empty() {
|
if !name.is_empty() {
|
||||||
return name.into();
|
return name.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pubkey = public_key.to_hex();
|
let pubkey = self.public_key().to_hex();
|
||||||
|
|
||||||
format!("{}:{}", &pubkey[0..4], &pubkey[pubkey.len() - 4..]).into()
|
format!("{}:{}", &pubkey[0..4], &pubkey[pubkey.len() - 4..]).into()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
use anyhow::Context;
|
|
||||||
use global::constants::NIP96_SERVER;
|
|
||||||
use gpui::Image;
|
|
||||||
use itertools::Itertools;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use qrcode_generator::QrCodeEcc;
|
|
||||||
use rnglib::{Language, RNG};
|
|
||||||
use std::{
|
|
||||||
collections::HashSet,
|
|
||||||
hash::{DefaultHasher, Hash, Hasher},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn nip96_upload(client: &Client, file: Vec<u8>) -> anyhow::Result<Url, anyhow::Error> {
|
|
||||||
let signer = client.signer().await?;
|
|
||||||
let server_url = Url::parse(NIP96_SERVER)?;
|
|
||||||
|
|
||||||
let config: ServerConfig = nip96::get_server_config(server_url, None).await?;
|
|
||||||
let url = nip96::upload_data(&signer, &config, file, None, None).await?;
|
|
||||||
|
|
||||||
Ok(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn room_hash(event: &Event) -> u64 {
|
|
||||||
let mut hasher = DefaultHasher::new();
|
|
||||||
let mut pubkeys: Vec<&PublicKey> = vec![];
|
|
||||||
|
|
||||||
// Add all public keys from event
|
|
||||||
pubkeys.push(&event.pubkey);
|
|
||||||
pubkeys.extend(
|
|
||||||
event
|
|
||||||
.tags
|
|
||||||
.public_keys()
|
|
||||||
.unique()
|
|
||||||
.sorted()
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate unique hash
|
|
||||||
pubkeys
|
|
||||||
.into_iter()
|
|
||||||
.unique()
|
|
||||||
.sorted()
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.hash(&mut hasher);
|
|
||||||
|
|
||||||
hasher.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn device_pubkey(event: &Event) -> Result<PublicKey, anyhow::Error> {
|
|
||||||
let n_tag = event.tags.find(TagKind::custom("n")).context("Invalid")?;
|
|
||||||
let hex = n_tag.content().context("Invalid")?;
|
|
||||||
let pubkey = PublicKey::parse(hex)?;
|
|
||||||
|
|
||||||
Ok(pubkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn random_name(length: usize) -> String {
|
|
||||||
let rng = RNG::from(&Language::Roman);
|
|
||||||
rng.generate_names(length, true).join("-").to_lowercase()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_qr(data: &str) -> Result<Arc<Image>, anyhow::Error> {
|
|
||||||
let qr = qrcode_generator::to_png_to_vec_from_str(data, QrCodeEcc::Medium, 256)?;
|
|
||||||
let img = Arc::new(Image {
|
|
||||||
format: gpui::ImageFormat::Png,
|
|
||||||
bytes: qr.clone(),
|
|
||||||
id: 1,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(img)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compare<T>(a: &[T], b: &[T]) -> bool
|
|
||||||
where
|
|
||||||
T: Eq + Hash,
|
|
||||||
{
|
|
||||||
let a: HashSet<_> = a.iter().collect();
|
|
||||||
let b: HashSet<_> = b.iter().collect();
|
|
||||||
|
|
||||||
a == b
|
|
||||||
}
|
|
||||||
@@ -19,9 +19,12 @@ pub fn get_client() -> &'static Client {
|
|||||||
// Client options
|
// Client options
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
// NIP-65
|
// NIP-65
|
||||||
|
// Coop is don't really need to enable this option,
|
||||||
|
// but this will help the client discover user's messaging relays efficiently.
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
// Skip all very slow relays
|
// Skip all very slow relays
|
||||||
.max_avg_latency(Duration::from_secs(2));
|
// Note: max delay is 800ms
|
||||||
|
.max_avg_latency(Duration::from_millis(800));
|
||||||
|
|
||||||
// Setup Nostr Client
|
// Setup Nostr Client
|
||||||
ClientBuilder::default().database(lmdb).opts(opts).build()
|
ClientBuilder::default().database(lmdb).opts(opts).build()
|
||||||
|
|||||||
@@ -24,10 +24,3 @@ uuid = "1.10"
|
|||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
image = "0.25.1"
|
image = "0.25.1"
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
criterion = "0.5"
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "text_benchmark"
|
|
||||||
harness = false
|
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
use common::profile::NostrProfile;
|
|
||||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
||||||
use gpui::SharedString;
|
|
||||||
use nostr_sdk::prelude::*;
|
|
||||||
use ui::text::render_plain_text_mut;
|
|
||||||
|
|
||||||
fn create_test_profiles() -> Vec<NostrProfile> {
|
|
||||||
let mut profiles = Vec::new();
|
|
||||||
|
|
||||||
// Create a few test profiles
|
|
||||||
for i in 0..5 {
|
|
||||||
let keypair = Keys::generate();
|
|
||||||
let profile = NostrProfile {
|
|
||||||
public_key: keypair.public_key(),
|
|
||||||
name: SharedString::from(format!("user{}", i)),
|
|
||||||
avatar: SharedString::from(format!("avatar{}", i)),
|
|
||||||
// Add other required fields based on NostrProfile definition
|
|
||||||
// This is a simplified version - adjust based on your actual NostrProfile struct
|
|
||||||
};
|
|
||||||
profiles.push(profile);
|
|
||||||
}
|
|
||||||
|
|
||||||
profiles
|
|
||||||
}
|
|
||||||
|
|
||||||
fn benchmark_plain_text(c: &mut Criterion) {
|
|
||||||
let profiles = create_test_profiles();
|
|
||||||
|
|
||||||
// Simple text without any links or entities
|
|
||||||
let simple_text = "This is a simple text message without any links or entities.";
|
|
||||||
|
|
||||||
// Text with URLs
|
|
||||||
let text_with_urls =
|
|
||||||
"Check out https://example.com and https://nostr.com for more information.";
|
|
||||||
|
|
||||||
// Text with nostr entities
|
|
||||||
let text_with_nostr = "I found this note nostr:note1qw5uy7hsqs4jcsvmjc2rj5t6f5uuenwg3yapm5l58srprspvshlspr4mh3 from npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft";
|
|
||||||
|
|
||||||
// Mixed content with urls and nostr entities
|
|
||||||
let mixed_content = "Check out https://example.com and my profile nostr:npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft along with this event nevent1qw5uy7hsqs4jcsvmjc2rj5t6f5uuenwg3yapm5l58srprspvshlspr4mh3";
|
|
||||||
|
|
||||||
// Long text with multiple links and entities
|
|
||||||
let long_text = "Here's a long message with multiple links like https://example1.com, https://example2.com, and https://example3.com. It also has nostr entities like npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft, note1qw5uy7hsqs4jcsvmjc2rj5t6f5uuenwg3yapm5l58srprspvshlspr4mh3, and nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuerpd46hxtnfdupzp8xummjw3exgcnqvmpw35xjueqvdnyqystngfxk5hsnfd9h8jtr8a4klacnp".repeat(3);
|
|
||||||
|
|
||||||
// Benchmark with simple text
|
|
||||||
c.bench_function("render_plain_text_simple", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let mut link_ranges = Vec::new();
|
|
||||||
let mut link_urls = Vec::new();
|
|
||||||
|
|
||||||
render_plain_text_mut(
|
|
||||||
black_box(simple_text),
|
|
||||||
black_box(&profiles),
|
|
||||||
&mut text,
|
|
||||||
&mut highlights,
|
|
||||||
&mut link_ranges,
|
|
||||||
&mut link_urls,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Benchmark with URLs
|
|
||||||
c.bench_function("render_plain_text_urls", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let mut link_ranges = Vec::new();
|
|
||||||
let mut link_urls = Vec::new();
|
|
||||||
|
|
||||||
render_plain_text_mut(
|
|
||||||
black_box(text_with_urls),
|
|
||||||
black_box(&profiles),
|
|
||||||
&mut text,
|
|
||||||
&mut highlights,
|
|
||||||
&mut link_ranges,
|
|
||||||
&mut link_urls,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Benchmark with nostr entities
|
|
||||||
c.bench_function("render_plain_text_nostr", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let mut link_ranges = Vec::new();
|
|
||||||
let mut link_urls = Vec::new();
|
|
||||||
|
|
||||||
render_plain_text_mut(
|
|
||||||
black_box(text_with_nostr),
|
|
||||||
black_box(&profiles),
|
|
||||||
&mut text,
|
|
||||||
&mut highlights,
|
|
||||||
&mut link_ranges,
|
|
||||||
&mut link_urls,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Benchmark with mixed content
|
|
||||||
c.bench_function("render_plain_text_mixed", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let mut link_ranges = Vec::new();
|
|
||||||
let mut link_urls = Vec::new();
|
|
||||||
|
|
||||||
render_plain_text_mut(
|
|
||||||
black_box(mixed_content),
|
|
||||||
black_box(&profiles),
|
|
||||||
&mut text,
|
|
||||||
&mut highlights,
|
|
||||||
&mut link_ranges,
|
|
||||||
&mut link_urls,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Benchmark with long text
|
|
||||||
c.bench_function("render_plain_text_long", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut text = String::new();
|
|
||||||
let mut highlights = Vec::new();
|
|
||||||
let mut link_ranges = Vec::new();
|
|
||||||
let mut link_urls = Vec::new();
|
|
||||||
|
|
||||||
render_plain_text_mut(
|
|
||||||
black_box(&long_text),
|
|
||||||
black_box(&profiles),
|
|
||||||
&mut text,
|
|
||||||
&mut highlights,
|
|
||||||
&mut link_ranges,
|
|
||||||
&mut link_urls,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, benchmark_plain_text);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -161,7 +161,7 @@ impl Element for Switch {
|
|||||||
if !self.disabled
|
if !self.disabled
|
||||||
&& prev_checked
|
&& prev_checked
|
||||||
.borrow()
|
.borrow()
|
||||||
.map_or(false, |prev| prev != checked)
|
.is_some_and(|prev| prev != checked)
|
||||||
{
|
{
|
||||||
let dur = Duration::from_secs_f64(0.15);
|
let dur = Duration::from_secs_f64(0.15);
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use common::profile::NostrProfile;
|
use common::profile::SharedProfile;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, AnyView, App, ElementId, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
AnyElement, AnyView, App, ElementId, FontWeight, HighlightStyle, InteractiveText, IntoElement,
|
||||||
SharedString, StyledText, UnderlineStyle, Window,
|
SharedString, StyledText, UnderlineStyle, Window,
|
||||||
@@ -43,7 +43,7 @@ pub struct RichText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RichText {
|
impl RichText {
|
||||||
pub fn new(content: String, profiles: &[NostrProfile]) -> Self {
|
pub fn new(content: String, profiles: &[Profile]) -> Self {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut highlights = Vec::new();
|
let mut highlights = Vec::new();
|
||||||
let mut link_ranges = Vec::new();
|
let mut link_ranges = Vec::new();
|
||||||
@@ -154,7 +154,7 @@ impl RichText {
|
|||||||
|
|
||||||
pub fn render_plain_text_mut(
|
pub fn render_plain_text_mut(
|
||||||
content: &str,
|
content: &str,
|
||||||
profiles: &[NostrProfile],
|
profiles: &[Profile],
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
highlights: &mut Vec<(Range<usize>, Highlight)>,
|
||||||
link_ranges: &mut Vec<Range<usize>>,
|
link_ranges: &mut Vec<Range<usize>>,
|
||||||
@@ -164,9 +164,9 @@ pub fn render_plain_text_mut(
|
|||||||
text.push_str(content);
|
text.push_str(content);
|
||||||
|
|
||||||
// Create a profile lookup using PublicKey directly
|
// Create a profile lookup using PublicKey directly
|
||||||
let profile_lookup: HashMap<&PublicKey, &NostrProfile> = profiles
|
let profile_lookup: HashMap<PublicKey, Profile> = profiles
|
||||||
.iter()
|
.iter()
|
||||||
.map(|profile| (&profile.public_key, profile))
|
.map(|profile| (profile.public_key(), profile.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Process regular URLs using linkify
|
// Process regular URLs using linkify
|
||||||
@@ -263,18 +263,18 @@ pub fn render_plain_text_mut(
|
|||||||
let profile_match = if entity_without_prefix.starts_with("npub") {
|
let profile_match = if entity_without_prefix.starts_with("npub") {
|
||||||
PublicKey::from_bech32(entity_without_prefix)
|
PublicKey::from_bech32(entity_without_prefix)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|pubkey| profile_lookup.get(&pubkey).copied())
|
.and_then(|pubkey| profile_lookup.get(&pubkey).cloned())
|
||||||
} else if entity_without_prefix.starts_with("nprofile") {
|
} else if entity_without_prefix.starts_with("nprofile") {
|
||||||
Nip19Profile::from_bech32(entity_without_prefix)
|
Nip19Profile::from_bech32(entity_without_prefix)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|profile| profile_lookup.get(&profile.public_key).copied())
|
.and_then(|profile| profile_lookup.get(&profile.public_key).cloned())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(profile) = profile_match {
|
if let Some(profile) = profile_match {
|
||||||
// Profile found - create a mention
|
// Profile found - create a mention
|
||||||
let display_name = format!("@{}", profile.name);
|
let display_name = format!("@{}", profile.shared_name());
|
||||||
|
|
||||||
// Replace mention with profile name
|
// Replace mention with profile name
|
||||||
text.replace_range(range.clone(), &display_name);
|
text.replace_range(range.clone(), &display_name);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.85"
|
channel = "1.86"
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
components = ["rustfmt", "clippy"]
|
components = ["rustfmt", "clippy"]
|
||||||
targets = [
|
targets = [
|
||||||
|
|||||||
Reference in New Issue
Block a user