Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bef1a2c6c | |||
| cd26244538 | |||
|
|
ca622d1262 | ||
|
|
5011becacb | ||
|
|
17f92d767e | ||
| be660cb14b | |||
|
|
8fca202c05 |
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
rustup: [stable, nightly]
|
||||
rustup: [stable]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
126
Cargo.lock
generated
126
Cargo.lock
generated
@@ -233,7 +233,7 @@ dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand 2.3.0",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@@ -246,7 +246,7 @@ checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"blocking",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -259,7 +259,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix 1.0.8",
|
||||
@@ -286,7 +286,7 @@ checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"blocking",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -303,7 +303,7 @@ dependencies = [
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"rustix 1.0.8",
|
||||
]
|
||||
|
||||
@@ -392,7 +392,7 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"crc32fast",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"pin-project",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
@@ -749,7 +749,7 @@ dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"piper",
|
||||
]
|
||||
|
||||
@@ -777,18 +777,18 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.23.1"
|
||||
version = "1.23.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.10.0"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4"
|
||||
checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -904,9 +904,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.31"
|
||||
version = "1.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
|
||||
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -1123,7 +1123,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collections"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"rustc-hash 2.1.1",
|
||||
@@ -1544,7 +1544,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "derive_refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1718,7 +1718,7 @@ dependencies = [
|
||||
"cc",
|
||||
"memchr",
|
||||
"rustc_version",
|
||||
"toml 0.9.4",
|
||||
"toml 0.9.5",
|
||||
"vswhom",
|
||||
"winreg",
|
||||
]
|
||||
@@ -2210,9 +2210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.0"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand 2.3.0",
|
||||
"futures-core",
|
||||
@@ -2436,7 +2436,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"as-raw-xcb-connection",
|
||||
@@ -2529,7 +2529,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@@ -2541,7 +2541,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gpui_tokio"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"tokio",
|
||||
@@ -2551,15 +2551,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.17.0"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71b01d27060ad58be4663b9e4ac9e2d4806918e8876af8912afbddd1a91d5eaa"
|
||||
checksum = "12101ecc8225ea6d675bc70263074eab6169079621c2186fe0c66590b2df9681"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.11"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
@@ -2602,9 +2602,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
@@ -2772,7 +2772,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "http_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -2782,6 +2782,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"reqwest 0.12.15",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
@@ -2791,7 +2792,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "http_client_tls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls-platform-verifier",
|
||||
@@ -3107,7 +3108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.4",
|
||||
"hashbrown 0.15.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -3593,7 +3594,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen 0.71.1",
|
||||
@@ -3721,7 +3722,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"codespan-reporting 0.12.0",
|
||||
"half",
|
||||
"hashbrown 0.15.4",
|
||||
"hashbrown 0.15.5",
|
||||
"hexf-parse",
|
||||
"indexmap",
|
||||
"log",
|
||||
@@ -3831,7 +3832,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64",
|
||||
@@ -3854,7 +3855,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-connect"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"nostr",
|
||||
@@ -3866,7 +3867,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-database"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"flatbuffers",
|
||||
"lru",
|
||||
@@ -3877,7 +3878,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-lmdb"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"flume",
|
||||
@@ -3891,7 +3892,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-relay-pool"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"async-wsocket",
|
||||
@@ -3907,7 +3908,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "nostr-sdk"
|
||||
version = "0.43.0"
|
||||
source = "git+https://github.com/rust-nostr/nostr#9d91709c35f743361d68c16349d33979329ebd84"
|
||||
source = "git+https://github.com/rust-nostr/nostr#0ac295f66052dc822f425a53daaa16101a8ef3bd"
|
||||
dependencies = [
|
||||
"async-utility",
|
||||
"nostr",
|
||||
@@ -4219,7 +4220,7 @@ dependencies = [
|
||||
"cipher",
|
||||
"digest",
|
||||
"endi",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"futures-util",
|
||||
"getrandom 0.3.3",
|
||||
"hkdf",
|
||||
@@ -4983,7 +4984,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"derive_refineable",
|
||||
"workspace-hack",
|
||||
@@ -5061,6 +5062,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
@@ -5140,7 +5142,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "reqwest_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5376,7 +5378,7 @@ dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework 3.2.0",
|
||||
"security-framework 3.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5413,7 +5415,7 @@ dependencies = [
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework 3.2.0",
|
||||
"security-framework 3.3.0",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs 0.26.11",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -5439,9 +5441,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
@@ -5646,9 +5648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.2.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
|
||||
checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation 0.10.1",
|
||||
@@ -5676,7 +5678,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
||||
[[package]]
|
||||
name = "semantic_version"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -5918,9 +5920,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
@@ -5951,7 +5953,7 @@ dependencies = [
|
||||
"async-net",
|
||||
"async-process",
|
||||
"blocking",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6071,7 +6073,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
[[package]]
|
||||
name = "sum_tree"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"log",
|
||||
@@ -6294,9 +6296,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "taffy"
|
||||
version = "0.8.3"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aaef0ac998e6527d6d0d5582f7e43953bb17221ac75bb8eb2fcc2db3396db1c"
|
||||
checksum = "a13e5d13f79d558b5d353a98072ca8ca0e99da429467804de959aa8c83c9a004"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"grid",
|
||||
@@ -6655,9 +6657,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.4"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
|
||||
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -6702,9 +6704,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
@@ -7105,7 +7107,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
[[package]]
|
||||
name = "util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed#ea7c3a23fb2eb0f2c5d811eec3337897886482ee"
|
||||
source = "git+https://github.com/zed-industries/zed#2d9cd2ac8888a144ef41e59c9820ffbecee66ed1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-fs",
|
||||
@@ -8312,7 +8314,7 @@ dependencies = [
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite 2.6.0",
|
||||
"futures-lite 2.6.1",
|
||||
"hex",
|
||||
"nix 0.30.1",
|
||||
"ordered-stream",
|
||||
@@ -8434,9 +8436,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.2"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
|
||||
checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
|
||||
@@ -4,12 +4,12 @@ members = ["crates/*"]
|
||||
default-members = ["crates/coop"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[workspace.metadata.i18n]
|
||||
available-locales = ["en", "zh-CN", "zh-TW", "ru", "vi", "ja", "es", "pt", "ko"]
|
||||
available-locales = ["en"]
|
||||
default-locale = "en"
|
||||
load-path = "locales"
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ product-name = "Coop"
|
||||
description = "Chat Freely, Stay Private on Nostr"
|
||||
identifier = "su.reya.coop"
|
||||
category = "SocialNetworking"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
out-dir = "../../dist"
|
||||
before-packaging-command = "cargo build --release"
|
||||
resources = ["Cargo.toml", "src"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "$APP_ID",
|
||||
"runtime": "org.freedesktop.Platform",
|
||||
"runtime-version": "23.08",
|
||||
"runtime-version": "24.08",
|
||||
"sdk": "org.freedesktop.Sdk",
|
||||
"sdk-extensions": ["org.freedesktop.Sdk.Extension.rust-stable"],
|
||||
"command": "coop",
|
||||
|
||||
@@ -27,8 +27,10 @@ use ui::button::{Button, ButtonVariants};
|
||||
use ui::dock_area::dock::DockPlacement;
|
||||
use ui::dock_area::panel::PanelView;
|
||||
use ui::dock_area::{ClosePanel, DockArea, DockItem};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::popup_menu::PopupMenuExt;
|
||||
use ui::tooltip::Tooltip;
|
||||
use ui::{h_flex, ContextModal, IconName, Root, Sizable, StyledExt};
|
||||
|
||||
use crate::views::compose::compose_button;
|
||||
@@ -109,55 +111,9 @@ impl ChatSpace {
|
||||
subscriptions.push(cx.observe_in(
|
||||
&client_keys,
|
||||
window,
|
||||
|_this: &mut Self, state, window, cx| {
|
||||
|this: &mut Self, state, window, cx| {
|
||||
if !state.read(cx).has_keys() {
|
||||
let title = SharedString::new(t!("startup.client_keys_warning"));
|
||||
let desc = SharedString::new(t!("startup.client_keys_desc"));
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.overlay_closable(false)
|
||||
.show_close(false)
|
||||
.keyboard(false)
|
||||
.confirm()
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("startup.create_new_keys"))
|
||||
.ok_text(t!("common.allow")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.h_40()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(title.clone()),
|
||||
)
|
||||
.child(desc.clone()),
|
||||
)
|
||||
.on_cancel(|_, _window, cx| {
|
||||
ClientKeys::global(cx).update(cx, |this, cx| {
|
||||
this.new_keys(cx);
|
||||
});
|
||||
// true: Close modal
|
||||
true
|
||||
})
|
||||
.on_ok(|_, window, cx| {
|
||||
ClientKeys::global(cx).update(cx, |this, cx| {
|
||||
this.load(window, cx);
|
||||
});
|
||||
// true: Close modal
|
||||
true
|
||||
})
|
||||
});
|
||||
this.render_client_keys_modal(window, cx);
|
||||
}
|
||||
},
|
||||
));
|
||||
@@ -299,8 +255,13 @@ impl ChatSpace {
|
||||
}
|
||||
|
||||
fn on_sign_out(&mut self, _ev: &Logout, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let registry = Registry::global(cx);
|
||||
let identity = Identity::global(cx);
|
||||
// TODO: save current session?
|
||||
|
||||
registry.update(cx, |this, cx| {
|
||||
this.reset(cx);
|
||||
});
|
||||
|
||||
identity.update(cx, |this, cx| {
|
||||
this.unload(window, cx);
|
||||
});
|
||||
@@ -324,14 +285,84 @@ impl ChatSpace {
|
||||
});
|
||||
}
|
||||
|
||||
fn render_client_keys_modal(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let title = SharedString::new(t!("startup.client_keys_warning"));
|
||||
let desc = SharedString::new(t!("startup.client_keys_desc"));
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.overlay_closable(false)
|
||||
.show_close(false)
|
||||
.keyboard(false)
|
||||
.confirm()
|
||||
.button_props(
|
||||
ModalButtonProps::default()
|
||||
.cancel_text(t!("startup.create_new_keys"))
|
||||
.ok_text(t!("common.allow")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.h_40()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.font_semibold()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(title.clone()),
|
||||
)
|
||||
.child(desc.clone()),
|
||||
)
|
||||
.on_cancel(|_, _window, cx| {
|
||||
ClientKeys::global(cx).update(cx, |this, cx| {
|
||||
this.new_keys(cx);
|
||||
});
|
||||
// true: Close modal
|
||||
true
|
||||
})
|
||||
.on_ok(|_, window, cx| {
|
||||
ClientKeys::global(cx).update(cx, |this, cx| {
|
||||
this.load(window, cx);
|
||||
});
|
||||
// true: Close modal
|
||||
true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn render_titlebar_left_side(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_cx: &Context<Self>,
|
||||
cx: &Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let compose_button = compose_button().into_any_element();
|
||||
let registry = Registry::read_global(cx);
|
||||
let loading = registry.loading;
|
||||
|
||||
h_flex().gap_1().child(compose_button)
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(compose_button())
|
||||
.when(loading, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.id("downloading")
|
||||
.px_4()
|
||||
.h_6()
|
||||
.gap_1()
|
||||
.text_xs()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().elevated_surface_background)
|
||||
.child(shared_t!("loading.label"))
|
||||
.child(Indicator::new().xsmall())
|
||||
.tooltip(|window, cx| {
|
||||
Tooltip::new(t!("loading.tooltip"), window, cx).into()
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_titlebar_right_side(
|
||||
@@ -342,7 +373,7 @@ impl ChatSpace {
|
||||
) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let need_backup = Identity::read_global(cx).need_backup();
|
||||
let relay_ready = Identity::read_global(cx).relay_ready();
|
||||
let has_dm_relays = Identity::read_global(cx).has_dm_relays();
|
||||
|
||||
let updating = AutoUpdater::read_global(cx).status.is_updating();
|
||||
let updated = AutoUpdater::read_global(cx).status.is_updated();
|
||||
@@ -377,7 +408,7 @@ impl ChatSpace {
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when_some(relay_ready, |this, status| {
|
||||
.when_some(has_dm_relays, |this, status| {
|
||||
this.when(!status, |this| this.child(messaging_relays::relay_button()))
|
||||
})
|
||||
.when_some(need_backup, |this, keys| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use global::constants::{
|
||||
APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT,
|
||||
SEARCH_RELAYS, WAIT_FOR_FINISH,
|
||||
};
|
||||
use global::{gift_wrap_sub_id, nostr_client, starting_time, NostrSignal};
|
||||
use global::{nostr_client, processed_events, starting_time, NostrSignal};
|
||||
use gpui::{
|
||||
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
||||
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||
@@ -50,9 +50,7 @@ fn main() {
|
||||
let (signal_tx, signal_rx) = channel::bounded::<NostrSignal>(2048);
|
||||
let (mta_tx, mta_rx) = channel::bounded::<PublicKey>(1024);
|
||||
let (event_tx, event_rx) = channel::bounded::<Event>(2048);
|
||||
|
||||
let signal_tx_clone = signal_tx.clone();
|
||||
let mta_tx_clone = mta_tx.clone();
|
||||
|
||||
app.background_executor()
|
||||
.spawn(async move {
|
||||
@@ -64,9 +62,7 @@ fn main() {
|
||||
// Handle Nostr notifications.
|
||||
//
|
||||
// Send the redefined signal back to GPUI via channel.
|
||||
if let Err(e) =
|
||||
handle_nostr_notifications(client, &signal_tx_clone, &mta_tx_clone, &event_tx).await
|
||||
{
|
||||
if let Err(e) = handle_nostr_notifications(&signal_tx_clone, &event_tx).await {
|
||||
log::error!("Failed to handle Nostr notifications: {e}");
|
||||
}
|
||||
})
|
||||
@@ -75,6 +71,7 @@ fn main() {
|
||||
app.background_executor()
|
||||
.spawn(async move {
|
||||
let duration = Duration::from_millis(METADATA_BATCH_TIMEOUT);
|
||||
let mut processed_pubkeys: BTreeSet<PublicKey> = BTreeSet::new();
|
||||
let mut batch: BTreeSet<PublicKey> = BTreeSet::new();
|
||||
|
||||
/// Internal events for the metadata batching system
|
||||
@@ -102,20 +99,23 @@ fn main() {
|
||||
|
||||
match smol::future::or(recv(), timeout()).await {
|
||||
BatchEvent::NewKeys(public_key) => {
|
||||
batch.insert(public_key);
|
||||
// Process immediately if batch limit reached
|
||||
// Prevent duplicate keys from being processed
|
||||
if processed_pubkeys.insert(public_key) {
|
||||
batch.insert(public_key);
|
||||
}
|
||||
// Process the batch if it's full
|
||||
if batch.len() >= METADATA_BATCH_LIMIT {
|
||||
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
|
||||
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
|
||||
}
|
||||
}
|
||||
BatchEvent::Timeout => {
|
||||
if !batch.is_empty() {
|
||||
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
|
||||
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
|
||||
}
|
||||
}
|
||||
BatchEvent::Closed => {
|
||||
if !batch.is_empty() {
|
||||
sync_data_for_pubkeys(client, std::mem::take(&mut batch)).await;
|
||||
sync_data_for_pubkeys(std::mem::take(&mut batch)).await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -243,7 +243,7 @@ fn main() {
|
||||
while let Ok(signal) = signal_rx.recv().await {
|
||||
cx.update(|window, cx| {
|
||||
let registry = Registry::global(cx);
|
||||
let identity = Identity::read_global(cx);
|
||||
let identity = Identity::global(cx);
|
||||
|
||||
match signal {
|
||||
// Load chat rooms and stop the loading status
|
||||
@@ -267,15 +267,6 @@ fn main() {
|
||||
}
|
||||
});
|
||||
}
|
||||
// Load chat rooms without setting as finished
|
||||
NostrSignal::Eose(subscription_id) => {
|
||||
// Only load chat rooms if the subscription matches the gift wrap subscription
|
||||
if gift_wrap_sub_id() == &subscription_id {
|
||||
registry.update(cx, |this, cx| {
|
||||
this.load_rooms(window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Add the new metadata to the registry or update the existing one
|
||||
NostrSignal::Metadata(event) => {
|
||||
registry.update(cx, |this, cx| {
|
||||
@@ -284,12 +275,17 @@ fn main() {
|
||||
}
|
||||
// Convert the gift wrapped message to a message
|
||||
NostrSignal::GiftWrap(event) => {
|
||||
if let Some(public_key) = identity.public_key() {
|
||||
if let Some(public_key) = identity.read(cx).public_key() {
|
||||
registry.update(cx, |this, cx| {
|
||||
this.event_to_message(public_key, event, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
NostrSignal::DmRelaysFound => {
|
||||
identity.update(cx, |this, cx| {
|
||||
this.set_has_dm_relays(cx);
|
||||
});
|
||||
}
|
||||
NostrSignal::Notice(_msg) => {
|
||||
// window.push_notification(msg, cx);
|
||||
}
|
||||
@@ -356,67 +352,102 @@ async fn connect(client: &Client) -> Result<(), Error> {
|
||||
}
|
||||
|
||||
async fn handle_nostr_notifications(
|
||||
client: &Client,
|
||||
signal_tx: &Sender<NostrSignal>,
|
||||
mta_tx: &Sender<PublicKey>,
|
||||
event_tx: &Sender<Event>,
|
||||
) -> Result<(), Error> {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let client = nostr_client();
|
||||
let auto_close = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let mut notifications = client.notifications();
|
||||
let mut processed_events: BTreeSet<EventId> = BTreeSet::new();
|
||||
let mut processed_dm_relays: BTreeSet<PublicKey> = BTreeSet::new();
|
||||
|
||||
while let Ok(notification) = notifications.recv().await {
|
||||
let RelayPoolNotification::Message { message, .. } = notification else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match message {
|
||||
RelayMessage::Event { event, .. } => {
|
||||
if processed_events.contains(&event.id) {
|
||||
continue;
|
||||
}
|
||||
// Skip events that have already been processed
|
||||
processed_events.insert(event.id);
|
||||
let RelayMessage::Event { event, .. } = message else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match event.kind {
|
||||
Kind::GiftWrap => {
|
||||
event_tx.send(event.into_owned()).await.ok();
|
||||
}
|
||||
Kind::Metadata => {
|
||||
signal_tx
|
||||
.send(NostrSignal::Metadata(event.into_owned()))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Kind::ContactList => {
|
||||
if let Ok(true) = check_author(client, &event).await {
|
||||
for public_key in event.tags.public_keys().copied() {
|
||||
mta_tx.send(public_key).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::RelayList => {
|
||||
if processed_dm_relays.contains(&event.pubkey) {
|
||||
continue;
|
||||
}
|
||||
// Skip public keys that have already been processed
|
||||
processed_dm_relays.insert(event.pubkey);
|
||||
// Skip events that have already been processed
|
||||
if !processed_events().write().await.insert(event.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let filter = Filter::new()
|
||||
.author(event.pubkey)
|
||||
.kind(Kind::InboxRelays)
|
||||
.limit(1);
|
||||
match event.kind {
|
||||
Kind::RelayList => {
|
||||
// Get metadata for event's pubkey that matches the current user's pubkey
|
||||
if let Ok(true) = is_from_current_user(&event).await {
|
||||
let sub_id = SubscriptionId::new("metadata");
|
||||
let filter = Filter::new()
|
||||
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::InboxRelays])
|
||||
.author(event.pubkey)
|
||||
.limit(10);
|
||||
|
||||
client.subscribe(filter, Some(opts)).await.ok();
|
||||
}
|
||||
_ => {}
|
||||
client
|
||||
.subscribe_with_id(sub_id, filter, Some(auto_close))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
RelayMessage::EndOfStoredEvents(subscription_id) => {
|
||||
Kind::InboxRelays => {
|
||||
if let Ok(true) = is_from_current_user(&event).await {
|
||||
// Get all inbox relays
|
||||
let relays = event
|
||||
.tags
|
||||
.filter_standardized(TagKind::Relay)
|
||||
.filter_map(|t| {
|
||||
if let TagStandard::Relay(url) = t {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
if !relays.is_empty() {
|
||||
// Add relays to nostr client
|
||||
for relay in relays.iter() {
|
||||
_ = client.add_relay(relay).await;
|
||||
_ = client.connect_relay(relay).await;
|
||||
}
|
||||
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(event.pubkey);
|
||||
let sub_id = SubscriptionId::new("gift-wrap");
|
||||
|
||||
// Notify the UI that the current user has set up the DM relays
|
||||
signal_tx.send(NostrSignal::DmRelaysFound).await.ok();
|
||||
|
||||
if client
|
||||
.subscribe_with_id_to(relays.clone(), sub_id, filter, None)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
log::info!("Subscribing to messages in: {relays:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::ContactList => {
|
||||
if let Ok(true) = is_from_current_user(&event).await {
|
||||
let public_keys: Vec<PublicKey> = event.tags.public_keys().copied().collect();
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||
let lens = public_keys.len() * kinds.len();
|
||||
let filter = Filter::new().limit(lens).authors(public_keys).kinds(kinds);
|
||||
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Kind::Metadata => {
|
||||
signal_tx
|
||||
.send(NostrSignal::Eose(subscription_id.into_owned()))
|
||||
.await?;
|
||||
.send(NostrSignal::Metadata(event.into_owned()))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Kind::GiftWrap => {
|
||||
event_tx.send(event.into_owned()).await.ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -425,28 +456,28 @@ async fn handle_nostr_notifications(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn check_author(client: &Client, event: &Event) -> Result<bool, Error> {
|
||||
async fn is_from_current_user(event: &Event) -> Result<bool, Error> {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
|
||||
Ok(public_key == event.pubkey)
|
||||
}
|
||||
|
||||
async fn sync_data_for_pubkeys(client: &Client, public_keys: BTreeSet<PublicKey>) {
|
||||
async fn sync_data_for_pubkeys(public_keys: BTreeSet<PublicKey>) {
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||
|
||||
let filter = Filter::new()
|
||||
.limit(public_keys.len() * kinds.len())
|
||||
.authors(public_keys)
|
||||
.kinds(kinds);
|
||||
|
||||
if let Err(e) = client
|
||||
client
|
||||
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
|
||||
.await
|
||||
{
|
||||
log::error!("Failed to sync metadata: {e}");
|
||||
}
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// Stores an unwrapped event in local database with reference to original
|
||||
|
||||
@@ -9,10 +9,10 @@ use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, img, list, px, red, rems, white, Action, AnyElement, App, AppContext, ClipboardItem,
|
||||
Context, Element, Empty, Entity, EventEmitter, Flatten, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ListAlignment, ListState, MouseButton, ObjectFit,
|
||||
ParentElement, PathPromptOptions, Render, RetainAllImageCache, SharedString,
|
||||
StatefulInteractiveElement, Styled, StyledImage, Subscription, Window,
|
||||
Context, Element, Entity, EventEmitter, Flatten, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, ListAlignment, ListState, MouseButton, ObjectFit, ParentElement,
|
||||
PathPromptOptions, Render, RetainAllImageCache, SharedString, StatefulInteractiveElement,
|
||||
Styled, StyledImage, Subscription, Window,
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use i18n::t;
|
||||
@@ -140,15 +140,7 @@ impl Chat {
|
||||
|
||||
// Initialize list state
|
||||
// [item_count] always equal to 1 at the beginning
|
||||
let list_state = ListState::new(1, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.entity().downgrade();
|
||||
move |ix, window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
})
|
||||
.unwrap_or(Empty.into_any())
|
||||
}
|
||||
});
|
||||
let list_state = ListState::new(1, ListAlignment::Bottom, px(1024.));
|
||||
|
||||
Self {
|
||||
id: room.read(cx).id.to_string().into(),
|
||||
@@ -720,7 +712,7 @@ impl Chat {
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.title(SharedString::new(t!("chat.logs_title"))).child(
|
||||
div()
|
||||
.w_full()
|
||||
.pb_4()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
@@ -872,10 +864,19 @@ impl Focusable for Chat {
|
||||
|
||||
impl Render for Chat {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let entity = cx.entity();
|
||||
|
||||
v_flex()
|
||||
.image_cache(self.image_cache.clone())
|
||||
.size_full()
|
||||
.child(list(self.list_state.clone()).flex_1())
|
||||
.child(
|
||||
list(self.list_state.clone(), move |ix, window, cx| {
|
||||
entity.update(cx, |this, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
})
|
||||
})
|
||||
.flex_1(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use global::constants::{GIFT_WRAP_SUB_ID, NIP17_RELAYS};
|
||||
use global::constants::NIP17_RELAYS;
|
||||
use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
@@ -10,8 +10,6 @@ use gpui::{
|
||||
TextAlign, UniformList, Window,
|
||||
};
|
||||
use i18n::{shared_t, t};
|
||||
use identity::Identity;
|
||||
use itertools::Itertools;
|
||||
use nostr_sdk::prelude::*;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use theme::ActiveTheme;
|
||||
@@ -189,15 +187,12 @@ impl MessagingRelays {
|
||||
|
||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let signer = client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let tags: Vec<Tag> = relays
|
||||
.iter()
|
||||
.map(|relay| Tag::relay(relay.clone()))
|
||||
.collect();
|
||||
|
||||
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(
|
||||
relays
|
||||
.iter()
|
||||
.map(|relay| Tag::relay(relay.clone()))
|
||||
.collect_vec(),
|
||||
);
|
||||
let builder = EventBuilder::new(Kind::InboxRelays, "").tags(tags);
|
||||
|
||||
// Set messaging relays
|
||||
client.send_event_builder(builder).await?;
|
||||
@@ -208,15 +203,6 @@ impl MessagingRelays {
|
||||
_ = client.connect_relay(&relay).await;
|
||||
}
|
||||
|
||||
let id = SubscriptionId::new(GIFT_WRAP_SUB_ID);
|
||||
let new_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
|
||||
// Close old subscriptions
|
||||
client.unsubscribe(&id).await;
|
||||
|
||||
// Subscribe to new messages
|
||||
client.subscribe_with_id(id, new_messages, None).await?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
@@ -224,10 +210,6 @@ impl MessagingRelays {
|
||||
match task.await {
|
||||
Ok(_) => {
|
||||
cx.update(|window, cx| {
|
||||
Identity::global(cx).update(cx, |this, cx| {
|
||||
this.verify_dm_relays(window, cx);
|
||||
});
|
||||
// Close the current modal
|
||||
window.close_modal(cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
@@ -15,6 +15,7 @@ use ui::actions::OpenProfile;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::context_menu::ContextMenuExt;
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::skeleton::Skeleton;
|
||||
use ui::{h_flex, ContextModal, StyledExt};
|
||||
|
||||
use crate::views::screening;
|
||||
@@ -23,60 +24,109 @@ use crate::views::screening;
|
||||
pub struct RoomListItem {
|
||||
ix: usize,
|
||||
base: Div,
|
||||
room_id: u64,
|
||||
public_key: PublicKey,
|
||||
name: SharedString,
|
||||
avatar: SharedString,
|
||||
created_at: SharedString,
|
||||
kind: RoomKind,
|
||||
room_id: Option<u64>,
|
||||
public_key: Option<PublicKey>,
|
||||
name: Option<SharedString>,
|
||||
avatar: Option<SharedString>,
|
||||
created_at: Option<SharedString>,
|
||||
kind: Option<RoomKind>,
|
||||
#[allow(clippy::type_complexity)]
|
||||
handler: Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>,
|
||||
handler: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
|
||||
}
|
||||
|
||||
impl RoomListItem {
|
||||
pub fn new(
|
||||
ix: usize,
|
||||
room_id: u64,
|
||||
public_key: PublicKey,
|
||||
name: SharedString,
|
||||
avatar: SharedString,
|
||||
created_at: SharedString,
|
||||
kind: RoomKind,
|
||||
) -> Self {
|
||||
pub fn new(ix: usize) -> Self {
|
||||
Self {
|
||||
ix,
|
||||
public_key,
|
||||
room_id,
|
||||
name,
|
||||
avatar,
|
||||
created_at,
|
||||
kind,
|
||||
base: h_flex().h_9().w_full().px_1p5(),
|
||||
handler: Rc::new(|_, _, _| {}),
|
||||
base: h_flex().h_9().w_full().px_1p5().gap_2(),
|
||||
room_id: None,
|
||||
public_key: None,
|
||||
name: None,
|
||||
avatar: None,
|
||||
created_at: None,
|
||||
kind: None,
|
||||
handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn room_id(mut self, room_id: u64) -> Self {
|
||||
self.room_id = Some(room_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn public_key(mut self, public_key: PublicKey) -> Self {
|
||||
self.public_key = Some(public_key);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn name(mut self, name: SharedString) -> Self {
|
||||
self.name = Some(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn avatar(mut self, avatar: SharedString) -> Self {
|
||||
self.avatar = Some(avatar);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn created_at(mut self, created_at: SharedString) -> Self {
|
||||
self.created_at = Some(created_at);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn kind(mut self, kind: RoomKind) -> Self {
|
||||
self.kind = Some(kind);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.handler = Rc::new(handler);
|
||||
self.handler = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for RoomListItem {
|
||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let public_key = self.public_key;
|
||||
let room_id = self.room_id;
|
||||
let kind = self.kind;
|
||||
let handler = self.handler.clone();
|
||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||
let require_screening = AppSettings::get_screening(cx);
|
||||
|
||||
let (
|
||||
Some(public_key),
|
||||
Some(room_id),
|
||||
Some(name),
|
||||
Some(avatar),
|
||||
Some(created_at),
|
||||
Some(kind),
|
||||
Some(handler),
|
||||
) = (
|
||||
self.public_key,
|
||||
self.room_id,
|
||||
self.name,
|
||||
self.avatar,
|
||||
self.created_at,
|
||||
self.kind,
|
||||
self.handler,
|
||||
)
|
||||
else {
|
||||
return self
|
||||
.base
|
||||
.id(self.ix)
|
||||
.child(Skeleton::new().flex_shrink_0().size_6().rounded_full())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.child(Skeleton::new().w_32().h_2p5().rounded_sm())
|
||||
.child(Skeleton::new().w_6().h_2p5().rounded_sm()),
|
||||
);
|
||||
};
|
||||
|
||||
self.base
|
||||
.id(self.ix)
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.rounded(cx.theme().radius)
|
||||
.when(!hide_avatar, |this| {
|
||||
@@ -86,7 +136,7 @@ impl RenderOnce for RoomListItem {
|
||||
.size_6()
|
||||
.rounded_full()
|
||||
.overflow_hidden()
|
||||
.child(Avatar::new(self.avatar).size(rems(1.5))),
|
||||
.child(Avatar::new(avatar).size(rems(1.5))),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
@@ -102,14 +152,14 @@ impl RenderOnce for RoomListItem {
|
||||
.text_ellipsis()
|
||||
.truncate()
|
||||
.font_medium()
|
||||
.child(self.name),
|
||||
.child(name),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_shrink_0()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.child(self.created_at),
|
||||
.child(created_at),
|
||||
),
|
||||
)
|
||||
.context_menu(move |this, _window, _cx| {
|
||||
|
||||
@@ -9,9 +9,9 @@ use global::constants::{BOOTSTRAP_RELAYS, SEARCH_RELAYS};
|
||||
use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString,
|
||||
Styled, Subscription, Task, Window,
|
||||
div, uniform_list, AnyElement, App, AppContext, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString, Styled,
|
||||
Subscription, Task, Window,
|
||||
};
|
||||
use gpui_tokio::Tokio;
|
||||
use i18n::t;
|
||||
@@ -26,16 +26,15 @@ use smallvec::{smallvec, SmallVec};
|
||||
use theme::ActiveTheme;
|
||||
use ui::button::{Button, ButtonRounded, ButtonVariants};
|
||||
use ui::dock_area::panel::{Panel, PanelEvent};
|
||||
use ui::indicator::Indicator;
|
||||
use ui::input::{InputEvent, InputState, TextInput};
|
||||
use ui::popup_menu::PopupMenu;
|
||||
use ui::skeleton::Skeleton;
|
||||
use ui::{v_flex, ContextModal, IconName, Selectable, Sizable, StyledExt};
|
||||
|
||||
mod list_item;
|
||||
|
||||
const FIND_DELAY: u64 = 600;
|
||||
const FIND_LIMIT: usize = 10;
|
||||
const TOTAL_SKELETONS: usize = 3;
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Sidebar> {
|
||||
Sidebar::new(window, cx)
|
||||
@@ -547,60 +546,6 @@ impl Sidebar {
|
||||
});
|
||||
}
|
||||
|
||||
fn open_loading_modal(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let title = SharedString::new(t!("sidebar.loading_modal_title"));
|
||||
let text_1 = SharedString::new(t!("sidebar.loading_modal_body_1"));
|
||||
let text_2 = SharedString::new(t!("sidebar.loading_modal_body_2"));
|
||||
let desc = SharedString::new(t!("sidebar.loading_modal_description"));
|
||||
|
||||
window.open_modal(cx, move |this, _window, cx| {
|
||||
this.show_close(true)
|
||||
.keyboard(true)
|
||||
.title(title.clone())
|
||||
.child(
|
||||
v_flex()
|
||||
.pb_4()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_2()
|
||||
.text_sm()
|
||||
.child(text_1.clone())
|
||||
.child(text_2.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(desc.clone()),
|
||||
),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn skeletons(&self, total: i32) -> impl IntoIterator<Item = impl IntoElement> {
|
||||
(0..total).map(|_| {
|
||||
div()
|
||||
.h_9()
|
||||
.w_full()
|
||||
.px_1p5()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(Skeleton::new().flex_shrink_0().size_6().rounded_full())
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.child(Skeleton::new().w_32().h_2p5().rounded_sm())
|
||||
.child(Skeleton::new().w_6().h_2p5().rounded_sm()),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn list_items(
|
||||
&self,
|
||||
rooms: &[Entity<Room>],
|
||||
@@ -621,17 +566,17 @@ impl Sidebar {
|
||||
});
|
||||
|
||||
items.push(
|
||||
RoomListItem::new(
|
||||
ix,
|
||||
room_id,
|
||||
this.members[0],
|
||||
this.display_name(cx),
|
||||
this.display_image(proxy, cx),
|
||||
this.ago(),
|
||||
this.kind,
|
||||
)
|
||||
.on_click(handler),
|
||||
RoomListItem::new(ix)
|
||||
.room_id(room_id)
|
||||
.name(this.display_name(cx))
|
||||
.avatar(this.display_image(proxy, cx))
|
||||
.created_at(this.ago())
|
||||
.public_key(this.members[0])
|
||||
.kind(this.kind)
|
||||
.on_click(handler),
|
||||
)
|
||||
} else {
|
||||
items.push(RoomListItem::new(ix));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,6 +613,7 @@ impl Focusable for Sidebar {
|
||||
impl Render for Sidebar {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let registry = Registry::read_global(cx);
|
||||
let loading = registry.loading;
|
||||
|
||||
// Get rooms from either search results or the chat registry
|
||||
let rooms = if let Some(results) = self.local_result.read(cx).as_ref() {
|
||||
@@ -683,6 +629,15 @@ impl Render for Sidebar {
|
||||
}
|
||||
};
|
||||
|
||||
// Get total rooms count
|
||||
let mut total_rooms = rooms.len();
|
||||
|
||||
// If loading in progress
|
||||
// Add 3 skeletons to the room list
|
||||
if loading {
|
||||
total_rooms += TOTAL_SKELETONS;
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.image_cache(self.image_cache.clone())
|
||||
.size_full()
|
||||
@@ -722,86 +677,55 @@ impl Render for Sidebar {
|
||||
.overflow_y_hidden()
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.px_1()
|
||||
.w_full()
|
||||
.h_9()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.h_flex()
|
||||
.gap_2()
|
||||
.flex_none()
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("all")
|
||||
.label(t!("sidebar.all_button"))
|
||||
.tooltip(t!("sidebar.all_conversations_tooltip"))
|
||||
.when_some(
|
||||
self.indicator.read(cx).as_ref(),
|
||||
|this, kind| {
|
||||
this.when(kind == &RoomKind::Ongoing, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.size_1()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().cursor),
|
||||
)
|
||||
})
|
||||
},
|
||||
Button::new("all")
|
||||
.label(t!("sidebar.all_button"))
|
||||
.tooltip(t!("sidebar.all_conversations_tooltip"))
|
||||
.when_some(self.indicator.read(cx).as_ref(), |this, kind| {
|
||||
this.when(kind == &RoomKind::Ongoing, |this| {
|
||||
this.child(
|
||||
div().size_1().rounded_full().bg(cx.theme().cursor),
|
||||
)
|
||||
.small()
|
||||
.bold()
|
||||
.secondary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.selected(self.filter(&RoomKind::Ongoing, cx))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.set_filter(RoomKind::Ongoing, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("requests")
|
||||
.label(t!("sidebar.requests_button"))
|
||||
.tooltip(t!("sidebar.requests_tooltip"))
|
||||
.when_some(
|
||||
self.indicator.read(cx).as_ref(),
|
||||
|this, kind| {
|
||||
this.when(kind != &RoomKind::Ongoing, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.size_1()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().cursor),
|
||||
)
|
||||
})
|
||||
},
|
||||
})
|
||||
})
|
||||
.small()
|
||||
.bold()
|
||||
.secondary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.selected(self.filter(&RoomKind::Ongoing, cx))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.set_filter(RoomKind::Ongoing, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("requests")
|
||||
.label(t!("sidebar.requests_button"))
|
||||
.tooltip(t!("sidebar.requests_tooltip"))
|
||||
.when_some(self.indicator.read(cx).as_ref(), |this, kind| {
|
||||
this.when(kind != &RoomKind::Ongoing, |this| {
|
||||
this.child(
|
||||
div().size_1().rounded_full().bg(cx.theme().cursor),
|
||||
)
|
||||
.small()
|
||||
.bold()
|
||||
.secondary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.selected(!self.filter(&RoomKind::Ongoing, cx))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.set_filter(RoomKind::default(), cx);
|
||||
})),
|
||||
),
|
||||
})
|
||||
})
|
||||
.small()
|
||||
.bold()
|
||||
.secondary()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.selected(!self.filter(&RoomKind::Ongoing, cx))
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.set_filter(RoomKind::default(), cx);
|
||||
})),
|
||||
),
|
||||
)
|
||||
.when(registry.loading, |this| {
|
||||
this.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.children(self.skeletons(1)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
uniform_list(
|
||||
"rooms",
|
||||
rooms.len(),
|
||||
total_rooms,
|
||||
cx.processor(move |this, range, _window, cx| {
|
||||
this.list_items(&rooms, range, cx)
|
||||
}),
|
||||
@@ -809,59 +733,5 @@ impl Render for Sidebar {
|
||||
.h_full(),
|
||||
),
|
||||
)
|
||||
.when(registry.loading, |this| {
|
||||
let title = SharedString::new(t!("sidebar.retrieving_messages"));
|
||||
let desc = SharedString::new(t!("sidebar.retrieving_messages_description"));
|
||||
|
||||
this.child(
|
||||
div().absolute().bottom_3().px_3().w_full().child(
|
||||
div()
|
||||
.p_1()
|
||||
.w_full()
|
||||
.rounded_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.bg(cx.theme().panel_background)
|
||||
.shadow_sm()
|
||||
// Loading
|
||||
.child(div().flex_shrink_0().pl_1().child(Indicator::new().small()))
|
||||
// Title
|
||||
.child(
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_center()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.font_semibold()
|
||||
.line_height(relative(1.2))
|
||||
.child(title.clone()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(desc.clone()),
|
||||
),
|
||||
)
|
||||
// Info button
|
||||
.child(
|
||||
Button::new("help")
|
||||
.icon(IconName::Info)
|
||||
.tooltip(t!("sidebar.why_seeing_this_tooltip"))
|
||||
.small()
|
||||
.ghost()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.flex_shrink_0()
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.open_loading_modal(window, cx)
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,9 @@ pub const ACCOUNT_D: &str = "coop:account";
|
||||
pub const SETTINGS_D: &str = "coop:settings";
|
||||
|
||||
/// Bootstrap Relays.
|
||||
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
||||
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
|
||||
"wss://relay.damus.io",
|
||||
"wss://relay.primal.net",
|
||||
"wss://nostr.Wine",
|
||||
"wss://user.kindpag.es",
|
||||
"wss://purplepag.es",
|
||||
];
|
||||
@@ -36,14 +35,11 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
||||
/// Default timeout (in seconds) for Nostr Connect
|
||||
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
||||
|
||||
/// Unique ID for all gift wraps subscription.
|
||||
pub const GIFT_WRAP_SUB_ID: &str = "listen_for_giftwraps";
|
||||
|
||||
/// Total metadata requests will be grouped.
|
||||
pub const METADATA_BATCH_LIMIT: usize = 100;
|
||||
|
||||
/// Maximum timeout for grouping metadata requests.
|
||||
pub const METADATA_BATCH_TIMEOUT: u64 = 400;
|
||||
/// Maximum timeout for grouping metadata requests. (milliseconds)
|
||||
pub const METADATA_BATCH_TIMEOUT: u64 = 300;
|
||||
|
||||
/// Maximum timeout for waiting for finish (seconds)
|
||||
pub const WAIT_FOR_FINISH: u64 = 60;
|
||||
@@ -52,7 +48,7 @@ pub const WAIT_FOR_FINISH: u64 = 60;
|
||||
pub const DEFAULT_MODAL_WIDTH: f32 = 420.;
|
||||
|
||||
/// Default width of the sidebar.
|
||||
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 280.;
|
||||
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.;
|
||||
|
||||
/// Image Resize Service
|
||||
pub const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl";
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
|
||||
use nostr_connect::prelude::*;
|
||||
use nostr_sdk::prelude::*;
|
||||
use paths::nostr_file;
|
||||
use smol::lock::RwLock;
|
||||
|
||||
use crate::constants::GIFT_WRAP_SUB_ID;
|
||||
use crate::paths::support_dir;
|
||||
|
||||
pub mod constants;
|
||||
@@ -26,15 +27,15 @@ pub enum NostrSignal {
|
||||
/// Partially finished processing all gift wrap events
|
||||
PartialFinish,
|
||||
|
||||
/// Receives EOSE response from relay pool
|
||||
Eose(SubscriptionId),
|
||||
/// DM relays have been found
|
||||
DmRelaysFound,
|
||||
|
||||
/// Notice from Relay Pool
|
||||
Notice(String),
|
||||
}
|
||||
|
||||
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
|
||||
static GIFT_WRAP_ID: OnceLock<SubscriptionId> = OnceLock::new();
|
||||
static PROCESSED_EVENTS: OnceLock<RwLock<BTreeSet<EventId>>> = OnceLock::new();
|
||||
static CURRENT_TIMESTAMP: OnceLock<Timestamp> = OnceLock::new();
|
||||
static FIRST_RUN: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
@@ -50,22 +51,20 @@ pub fn nostr_client() -> &'static Client {
|
||||
let lmdb = NostrLMDB::open(nostr_file()).expect("Database is NOT initialized");
|
||||
|
||||
let opts = ClientOptions::new()
|
||||
// Coop isn't social client,
|
||||
// but it needs this option because it needs user's NIP65 Relays to fetch NIP17 Relays.
|
||||
.gossip(true)
|
||||
// TODO: Coop should handle authentication by itself
|
||||
.automatic_authentication(true)
|
||||
// Sleep after idle for 5 seconds
|
||||
.verify_subscriptions(false)
|
||||
// Sleep after idle for 30 seconds
|
||||
.sleep_when_idle(SleepWhenIdle::Enabled {
|
||||
timeout: Duration::from_secs(10),
|
||||
timeout: Duration::from_secs(30),
|
||||
});
|
||||
|
||||
ClientBuilder::default().database(lmdb).opts(opts).build()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn gift_wrap_sub_id() -> &'static SubscriptionId {
|
||||
GIFT_WRAP_ID.get_or_init(|| SubscriptionId::new(GIFT_WRAP_SUB_ID))
|
||||
pub fn processed_events() -> &'static RwLock<BTreeSet<EventId>> {
|
||||
PROCESSED_EVENTS.get_or_init(|| RwLock::new(BTreeSet::new()))
|
||||
}
|
||||
|
||||
pub fn starting_time() -> &'static Timestamp {
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Error};
|
||||
use client_keys::ClientKeys;
|
||||
use common::handle_auth::CoopAuthUrlHandler;
|
||||
use global::constants::{ACCOUNT_D, NIP17_RELAYS, NIP65_RELAYS, NOSTR_CONNECT_TIMEOUT};
|
||||
use global::{gift_wrap_sub_id, nostr_client};
|
||||
use global::nostr_client;
|
||||
use gpui::prelude::FluentBuilder;
|
||||
use gpui::{
|
||||
div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled,
|
||||
@@ -29,7 +29,7 @@ impl Global for GlobalIdentity {}
|
||||
pub struct Identity {
|
||||
public_key: Option<PublicKey>,
|
||||
logging_in: bool,
|
||||
relay_ready: Option<bool>,
|
||||
has_dm_relays: Option<bool>,
|
||||
need_backup: Option<Keys>,
|
||||
need_onboarding: bool,
|
||||
#[allow(dead_code)]
|
||||
@@ -73,8 +73,8 @@ impl Identity {
|
||||
|
||||
Self {
|
||||
public_key: None,
|
||||
relay_ready: None,
|
||||
need_backup: None,
|
||||
has_dm_relays: None,
|
||||
need_onboarding: false,
|
||||
logging_in: false,
|
||||
subscriptions,
|
||||
@@ -127,8 +127,10 @@ impl Identity {
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.identifier(ACCOUNT_D);
|
||||
|
||||
// Unset signer
|
||||
// Reset the nostr client
|
||||
client.unset_signer().await;
|
||||
client.unsubscribe_all().await;
|
||||
|
||||
// Delete account
|
||||
client.database().delete(filter).await?;
|
||||
|
||||
@@ -256,7 +258,7 @@ impl Identity {
|
||||
this.set_public_key(None, window, cx);
|
||||
})
|
||||
.ok();
|
||||
// Close modal
|
||||
// true to close the modal
|
||||
true
|
||||
})
|
||||
.on_ok(move |_, window, cx| {
|
||||
@@ -270,7 +272,7 @@ impl Identity {
|
||||
this.verify_keys(enc, password, weak_error, window, cx);
|
||||
})
|
||||
.ok();
|
||||
|
||||
// false to keep the modal open
|
||||
false
|
||||
})
|
||||
.child(
|
||||
@@ -320,32 +322,40 @@ impl Identity {
|
||||
}
|
||||
|
||||
// Decrypt the password in the background to prevent blocking the main thread
|
||||
let task: Task<Option<SecretKey>> =
|
||||
cx.background_spawn(async move { enc.decrypt(&password).ok() });
|
||||
let task: Task<Result<SecretKey, Error>> = cx.background_spawn(async move {
|
||||
let secret = enc.decrypt(&password)?;
|
||||
Ok(secret)
|
||||
});
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
if let Some(secret) = task.await {
|
||||
cx.update(|window, cx| {
|
||||
window.close_modal(cx);
|
||||
// Update user's signer with decrypted secret key
|
||||
this.update(cx, |this, cx| {
|
||||
this.set_signer(Keys::new(secret), window, cx);
|
||||
match task.await {
|
||||
Ok(secret) => {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
// Update user's signer with decrypted secret key
|
||||
this.set_signer(Keys::new(secret), window, cx);
|
||||
// Close the current modal
|
||||
window.close_modal(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.ok();
|
||||
} else {
|
||||
_ = error.update(cx, |this, cx| {
|
||||
*this = Some("Invalid password".into());
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error
|
||||
.update(cx, |this, cx| {
|
||||
*this = Some(e.to_string().into());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Sets a new signer for the client and updates user identity
|
||||
pub fn set_signer<S>(&self, signer: S, window: &mut Window, cx: &mut Context<Self>)
|
||||
pub fn set_signer<S>(&mut self, signer: S, window: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
S: NostrSigner + 'static,
|
||||
{
|
||||
@@ -357,7 +367,7 @@ impl Identity {
|
||||
client.set_signer(signer).await;
|
||||
|
||||
// Subscribe for user metadata
|
||||
Self::subscribe(client, public_key).await?;
|
||||
get_nip65_relays(public_key).await?;
|
||||
|
||||
Ok(public_key)
|
||||
});
|
||||
@@ -422,11 +432,12 @@ impl Identity {
|
||||
|
||||
// Set user's NIP65 relays
|
||||
client.send_event_builder(relay_list).await?;
|
||||
|
||||
// Set user's NIP17 relays
|
||||
client.send_event_builder(dm_relay).await?;
|
||||
|
||||
// Subscribe for user metadata
|
||||
Self::subscribe(client, public_key).await?;
|
||||
// Get user's NIP65 relays
|
||||
get_nip65_relays(public_key).await?;
|
||||
|
||||
Ok(public_key)
|
||||
});
|
||||
@@ -547,60 +558,15 @@ impl Identity {
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn verify_dm_relays(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(public_key) = self.public_key() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let task: Task<bool> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::InboxRelays)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
let Ok(events) = client.database().query(filter).await else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(event) = events.first() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let relays: Vec<RelayUrl> = event
|
||||
.tags
|
||||
.filter(TagKind::Relay)
|
||||
.filter_map(|tag| RelayUrl::parse(tag.content()?).ok())
|
||||
.collect();
|
||||
|
||||
!relays.is_empty()
|
||||
});
|
||||
|
||||
cx.spawn_in(window, async move |this, cx| {
|
||||
let result = task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.relay_ready = Some(result);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
/// Sets the public key of the identity
|
||||
pub(crate) fn set_public_key(
|
||||
&mut self,
|
||||
public_key: Option<PublicKey>,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.public_key = public_key;
|
||||
cx.notify();
|
||||
// Run verify user's dm relays task
|
||||
cx.defer_in(window, |this, window, cx| {
|
||||
this.verify_dm_relays(window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the current identity's public key
|
||||
@@ -613,8 +579,9 @@ impl Identity {
|
||||
self.public_key.is_some()
|
||||
}
|
||||
|
||||
pub fn relay_ready(&self) -> Option<bool> {
|
||||
self.relay_ready
|
||||
/// Returns true if the identity has DM Relays
|
||||
pub fn has_dm_relays(&self) -> Option<bool> {
|
||||
self.has_dm_relays
|
||||
}
|
||||
|
||||
/// Returns true if the identity is currently logging in
|
||||
@@ -622,45 +589,29 @@ impl Identity {
|
||||
self.logging_in
|
||||
}
|
||||
|
||||
/// Sets the DM Relays status of the identity
|
||||
pub fn set_has_dm_relays(&mut self, cx: &mut Context<Self>) {
|
||||
self.has_dm_relays = Some(true);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Sets the logging in status of the identity
|
||||
pub(crate) fn set_logging_in(&mut self, status: bool, cx: &mut Context<Self>) {
|
||||
self.logging_in = status;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
|
||||
client
|
||||
.subscribe_with_id(
|
||||
gift_wrap_sub_id().to_owned(),
|
||||
Filter::new().kind(Kind::GiftWrap).pubkey(public_key),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
client
|
||||
.subscribe(
|
||||
Filter::new()
|
||||
.author(public_key)
|
||||
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::RelayList])
|
||||
.since(Timestamp::now()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
client
|
||||
.subscribe(
|
||||
Filter::new()
|
||||
.kinds(vec![Kind::Metadata, Kind::ContactList, Kind::RelayList])
|
||||
.author(public_key)
|
||||
.limit(10),
|
||||
Some(opts),
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::info!("Getting all user's metadata and messages...");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_nip65_relays(public_key: PublicKey) -> Result<(), Error> {
|
||||
let client = nostr_client();
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let sub_id = SubscriptionId::new("nip65-relays");
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::RelayList)
|
||||
.author(public_key)
|
||||
.limit(1);
|
||||
|
||||
client.subscribe_with_id(sub_id, filter, Some(opts)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -101,6 +101,12 @@ impl Registry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, cx: &mut Context<Self>) {
|
||||
self.rooms = vec![];
|
||||
self.loading = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub(crate) fn set_persons_from_task(
|
||||
&mut self,
|
||||
task: Task<Result<Vec<Profile>, Error>>,
|
||||
|
||||
@@ -118,14 +118,14 @@ impl Render for TitleBar {
|
||||
.w_full()
|
||||
.when(cx.theme().platform_kind.is_mac(), |this| {
|
||||
this.on_click(|event, window, _| {
|
||||
if event.up.click_count == 2 {
|
||||
if event.click_count() == 2 {
|
||||
window.titlebar_double_click();
|
||||
}
|
||||
})
|
||||
})
|
||||
.when(cx.theme().platform_kind.is_linux(), |this| {
|
||||
this.on_click(|event, window, _| {
|
||||
if event.up.click_count == 2 {
|
||||
if event.click_count() == 2 {
|
||||
window.zoom_window();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ pub trait InteractiveElementExt: InteractiveElement {
|
||||
Self: Sized,
|
||||
{
|
||||
self.interactivity().on_click(move |event, window, cx| {
|
||||
if event.up.click_count == 2 {
|
||||
if event.click_count() == 2 {
|
||||
listener(event, window, cx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -335,17 +335,9 @@ sidebar:
|
||||
en: "Incoming new conversations"
|
||||
trusted_contacts_tooltip:
|
||||
en: "Only show rooms from trusted contacts"
|
||||
retrieving_messages:
|
||||
en: "Retrieving messages"
|
||||
retrieving_messages_description:
|
||||
en: "This may take some time"
|
||||
why_seeing_this_tooltip:
|
||||
en: "Why you're seeing this"
|
||||
loading_modal_title:
|
||||
en: "Retrieving Your Messages"
|
||||
loading_modal_body_1:
|
||||
en: "Coop is downloading all your messages from the messaging relays. Depending on your total number of messages, this process may take up to 15 minutes if you're using Nostr Connect."
|
||||
loading_modal_body_2:
|
||||
en: "Please be patient - you only need to do this full download once. Next time, Coop will only download new messages."
|
||||
loading_modal_description:
|
||||
en: "You still can use the app normally while messages are processing in the background"
|
||||
|
||||
loading:
|
||||
label:
|
||||
en: "Downloading messages"
|
||||
tooltip:
|
||||
en: "This may take a while. Please be patient."
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
arch=$(arch)
|
||||
fd_version=23.08
|
||||
fd_version=24.08
|
||||
flatpak install -y --user org.freedesktop.Platform/${arch}/${fd_version}
|
||||
flatpak install -y --user org.freedesktop.Sdk/${arch}/${fd_version}
|
||||
flatpak install -y --user org.freedesktop.Sdk.Extension.rust-stable/${arch}/${fd_version}
|
||||
|
||||
Reference in New Issue
Block a user