group message
This commit is contained in:
72
Cargo.lock
generated
72
Cargo.lock
generated
@@ -247,9 +247,9 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ar_archive_writer"
|
name = "ar_archive_writer"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b"
|
checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"object",
|
"object",
|
||||||
]
|
]
|
||||||
@@ -1369,7 +1369,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui_util",
|
"gpui_util",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1891,7 +1891,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2865,7 +2865,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -2947,7 +2947,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_linux"
|
name = "gpui_linux"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
@@ -2998,7 +2998,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macos"
|
name = "gpui_macos"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_macos",
|
"accesskit_macos",
|
||||||
@@ -3043,7 +3043,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -3054,7 +3054,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_platform"
|
name = "gpui_platform"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -3067,7 +3067,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_shared_string"
|
name = "gpui_shared_string"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3077,7 +3077,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_tokio"
|
name = "gpui_tokio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -3088,7 +3088,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_util"
|
name = "gpui_util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"log",
|
"log",
|
||||||
@@ -3097,7 +3097,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_web"
|
name = "gpui_web"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
@@ -3121,7 +3121,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_wgpu"
|
name = "gpui_wgpu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
@@ -3150,7 +3150,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_windows"
|
name = "gpui_windows"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_windows",
|
"accesskit_windows",
|
||||||
@@ -3439,7 +3439,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -3464,7 +3464,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
@@ -4322,7 +4322,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen",
|
"bindgen",
|
||||||
@@ -5225,7 +5225,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -6018,16 +6018,16 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -6048,9 +6048,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.10"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "relay_auth"
|
name = "relay_auth"
|
||||||
@@ -6118,7 +6118,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -6433,7 +6433,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "scheduler"
|
name = "scheduler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-task",
|
"async-task",
|
||||||
"backtrace",
|
"backtrace",
|
||||||
@@ -7077,7 +7077,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless 0.9.3",
|
"heapless 0.9.3",
|
||||||
"log",
|
"log",
|
||||||
@@ -8071,7 +8071,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#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs",
|
"async-fs",
|
||||||
@@ -8110,7 +8110,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -9875,18 +9875,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.50"
|
version = "0.8.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.50"
|
version = "0.8.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -9970,7 +9970,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "zlog"
|
name = "zlog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -9987,7 +9987,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing"
|
name = "ztracing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -9998,7 +9998,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ztracing_macro"
|
name = "ztracing_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed#e5052961af01e6810f961d6b217376edbe02b106"
|
source = "git+https://github.com/zed-industries/zed#d989c7c5cdd057de2375a55bdc109ff61409801c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
|
|||||||
@@ -5,6 +5,106 @@ use common::{EventExt, NostrParser, extract_and_remove_media_urls};
|
|||||||
use gpui::{SharedString, SharedUri};
|
use gpui::{SharedString, SharedUri};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
|
|
||||||
|
/// Rendered message.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Message {
|
||||||
|
pub id: EventId,
|
||||||
|
/// Author's public key
|
||||||
|
pub author: PublicKey,
|
||||||
|
/// The content/text of the message
|
||||||
|
pub content: String,
|
||||||
|
/// List of media URLs in the message
|
||||||
|
pub media: Vec<SharedUri>,
|
||||||
|
/// Message created time as unix timestamp
|
||||||
|
pub created_at: Timestamp,
|
||||||
|
/// List of mentioned public keys in the message
|
||||||
|
pub mentions: Vec<Mention>,
|
||||||
|
/// List of event of the message this message is a reply to
|
||||||
|
pub replies_to: Vec<EventId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Event> for Message {
|
||||||
|
fn from(val: &Event) -> Self {
|
||||||
|
let mentions = extract_mentions(&val.content);
|
||||||
|
let replies_to = extract_reply_ids(&val.tags);
|
||||||
|
let (media, string) = extract_and_remove_media_urls(&val.content);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: val.id,
|
||||||
|
author: val.pubkey,
|
||||||
|
content: string,
|
||||||
|
media,
|
||||||
|
created_at: val.created_at,
|
||||||
|
mentions,
|
||||||
|
replies_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&UnsignedEvent> for Message {
|
||||||
|
fn from(val: &UnsignedEvent) -> Self {
|
||||||
|
let mentions = extract_mentions(&val.content);
|
||||||
|
let replies_to = extract_reply_ids(&val.tags);
|
||||||
|
let (media, string) = extract_and_remove_media_urls(&val.content);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// Event ID must be known
|
||||||
|
id: val.id.unwrap(),
|
||||||
|
author: val.pubkey,
|
||||||
|
content: string,
|
||||||
|
media,
|
||||||
|
created_at: val.created_at,
|
||||||
|
mentions,
|
||||||
|
replies_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&NewMessage> for Message {
|
||||||
|
fn from(val: &NewMessage) -> Self {
|
||||||
|
let mentions = extract_mentions(&val.rumor.content);
|
||||||
|
let replies_to = extract_reply_ids(&val.rumor.tags);
|
||||||
|
let (media, string) = extract_and_remove_media_urls(&val.rumor.content);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
// Event ID must be known
|
||||||
|
id: val.rumor.id.unwrap(),
|
||||||
|
author: val.rumor.pubkey,
|
||||||
|
content: string,
|
||||||
|
media,
|
||||||
|
created_at: val.rumor.created_at,
|
||||||
|
mentions,
|
||||||
|
replies_to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Message {}
|
||||||
|
|
||||||
|
impl PartialEq for Message {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Message {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.created_at.cmp(&other.created_at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Message {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Message {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// New message.
|
/// New message.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct NewMessage {
|
pub struct NewMessage {
|
||||||
@@ -44,74 +144,6 @@ impl FailedMessage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message.
|
|
||||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
|
||||||
pub enum Message {
|
|
||||||
User(RenderedMessage),
|
|
||||||
Warning(String, Timestamp),
|
|
||||||
System(Timestamp),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
|
||||||
pub fn user<I>(user: I) -> Self
|
|
||||||
where
|
|
||||||
I: Into<RenderedMessage>,
|
|
||||||
{
|
|
||||||
Self::User(user.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn warning<I>(content: I) -> Self
|
|
||||||
where
|
|
||||||
I: Into<String>,
|
|
||||||
{
|
|
||||||
Self::Warning(content.into(), Timestamp::now())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn system() -> Self {
|
|
||||||
Self::System(Timestamp::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timestamp(&self) -> &Timestamp {
|
|
||||||
match self {
|
|
||||||
Message::User(msg) => &msg.created_at,
|
|
||||||
Message::Warning(_, ts) => ts,
|
|
||||||
Message::System(ts) => ts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&NewMessage> for Message {
|
|
||||||
fn from(val: &NewMessage) -> Self {
|
|
||||||
Self::User(val.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&UnsignedEvent> for Message {
|
|
||||||
fn from(val: &UnsignedEvent) -> Self {
|
|
||||||
Self::User(val.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for Message {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
// System always comes first
|
|
||||||
(Message::System(_), Message::System(_)) => self.timestamp().cmp(other.timestamp()),
|
|
||||||
(Message::System(_), _) => std::cmp::Ordering::Less,
|
|
||||||
(_, Message::System(_)) => std::cmp::Ordering::Greater,
|
|
||||||
|
|
||||||
// For non-system messages, compare by timestamp
|
|
||||||
_ => self.timestamp().cmp(other.timestamp()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for Message {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Mention {
|
pub struct Mention {
|
||||||
pub public_key: PublicKey,
|
pub public_key: PublicKey,
|
||||||
@@ -124,106 +156,6 @@ impl Mention {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rendered message.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RenderedMessage {
|
|
||||||
pub id: EventId,
|
|
||||||
/// Author's public key
|
|
||||||
pub author: PublicKey,
|
|
||||||
/// The content/text of the message
|
|
||||||
pub content: String,
|
|
||||||
/// List of media URLs in the message
|
|
||||||
pub media: Vec<SharedUri>,
|
|
||||||
/// Message created time as unix timestamp
|
|
||||||
pub created_at: Timestamp,
|
|
||||||
/// List of mentioned public keys in the message
|
|
||||||
pub mentions: Vec<Mention>,
|
|
||||||
/// List of event of the message this message is a reply to
|
|
||||||
pub replies_to: Vec<EventId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Event> for RenderedMessage {
|
|
||||||
fn from(val: &Event) -> Self {
|
|
||||||
let mentions = extract_mentions(&val.content);
|
|
||||||
let replies_to = extract_reply_ids(&val.tags);
|
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.content);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
id: val.id,
|
|
||||||
author: val.pubkey,
|
|
||||||
content: string,
|
|
||||||
media,
|
|
||||||
created_at: val.created_at,
|
|
||||||
mentions,
|
|
||||||
replies_to,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&UnsignedEvent> for RenderedMessage {
|
|
||||||
fn from(val: &UnsignedEvent) -> Self {
|
|
||||||
let mentions = extract_mentions(&val.content);
|
|
||||||
let replies_to = extract_reply_ids(&val.tags);
|
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.content);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Event ID must be known
|
|
||||||
id: val.id.unwrap(),
|
|
||||||
author: val.pubkey,
|
|
||||||
content: string,
|
|
||||||
media,
|
|
||||||
created_at: val.created_at,
|
|
||||||
mentions,
|
|
||||||
replies_to,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&NewMessage> for RenderedMessage {
|
|
||||||
fn from(val: &NewMessage) -> Self {
|
|
||||||
let mentions = extract_mentions(&val.rumor.content);
|
|
||||||
let replies_to = extract_reply_ids(&val.rumor.tags);
|
|
||||||
let (media, string) = extract_and_remove_media_urls(&val.rumor.content);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
// Event ID must be known
|
|
||||||
id: val.rumor.id.unwrap(),
|
|
||||||
author: val.rumor.pubkey,
|
|
||||||
content: string,
|
|
||||||
media,
|
|
||||||
created_at: val.rumor.created_at,
|
|
||||||
mentions,
|
|
||||||
replies_to,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for RenderedMessage {}
|
|
||||||
|
|
||||||
impl PartialEq for RenderedMessage {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id == other.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for RenderedMessage {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.created_at.cmp(&other.created_at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for RenderedMessage {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for RenderedMessage {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts all mentions (public keys) from a content string.
|
/// Extracts all mentions (public keys) from a content string.
|
||||||
fn extract_mentions(content: &str) -> Vec<Mention> {
|
fn extract_mentions(content: &str) -> Vec<Mention> {
|
||||||
let parser = NostrParser::new();
|
let parser = NostrParser::new();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
|
use chat::{ChatRegistry, Message, Room, RoomEvent, SendReport, SendStatus};
|
||||||
use common::{TimestampExt, coop_cache};
|
use common::{TimestampExt, coop_cache};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -38,9 +38,6 @@ use crate::text::RenderedText;
|
|||||||
mod actions;
|
mod actions;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
const ANNOUNCEMENT: &str =
|
|
||||||
"This conversation is private. Only members can see each other's messages.";
|
|
||||||
|
|
||||||
pub fn init(room: WeakEntity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
pub fn init(room: WeakEntity<Room>, window: &mut Window, cx: &mut App) -> Entity<ChatPanel> {
|
||||||
cx.new(|cx| ChatPanel::new(room, window, cx))
|
cx.new(|cx| ChatPanel::new(room, window, cx))
|
||||||
}
|
}
|
||||||
@@ -101,7 +98,7 @@ impl ChatPanel {
|
|||||||
let reports_by_id = cx.new(|_| BTreeMap::new());
|
let reports_by_id = cx.new(|_| BTreeMap::new());
|
||||||
|
|
||||||
// Define list of messages
|
// Define list of messages
|
||||||
let messages = BTreeSet::from([Message::system()]);
|
let messages = BTreeSet::default();
|
||||||
let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.));
|
let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.));
|
||||||
|
|
||||||
// Get room id and name
|
// Get room id and name
|
||||||
@@ -476,25 +473,13 @@ impl ChatPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a message by its ID
|
/// Get a message by its ID
|
||||||
fn message(&self, id: &EventId) -> Option<&RenderedMessage> {
|
fn message(&self, id: &EventId) -> Option<&Message> {
|
||||||
self.messages.iter().find_map(|msg| {
|
self.messages.iter().find(|msg| &msg.id == id)
|
||||||
if let Message::User(rendered) = msg
|
|
||||||
&& &rendered.id == id
|
|
||||||
{
|
|
||||||
return Some(rendered);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_to(&self, id: EventId) {
|
/// Scroll to a message by its ID
|
||||||
if let Some(ix) = self.messages.iter().position(|m| {
|
fn scroll_to(&self, id: &EventId) {
|
||||||
if let Message::User(msg) = m {
|
if let Some(ix) = self.messages.iter().position(|msg| &msg.id == id) {
|
||||||
msg.id == id
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
self.list_state.scroll_to_reveal_item(ix);
|
self.list_state.scroll_to_reveal_item(ix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -742,9 +727,11 @@ impl ChatPanel {
|
|||||||
cx.open_url(&content);
|
cx.open_url(&content);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_announcement(&self, ix: usize, cx: &Context<Self>) -> AnyElement {
|
fn render_announcement(&self, cx: &Context<Self>) -> AnyElement {
|
||||||
|
const MSG: &str =
|
||||||
|
"This conversation is private. Only members can see each other's messages.";
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.id(ix)
|
|
||||||
.h_40()
|
.h_40()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
@@ -761,7 +748,7 @@ impl ChatPanel {
|
|||||||
.size_12()
|
.size_12()
|
||||||
.text_color(cx.theme().ghost_element_active),
|
.text_color(cx.theme().ghost_element_active),
|
||||||
)
|
)
|
||||||
.child(SharedString::from(ANNOUNCEMENT))
|
.child(MSG)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +785,34 @@ impl ChatPanel {
|
|||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_group_start(&self, ix: usize) -> bool {
|
||||||
|
// 5 minutes
|
||||||
|
const GROUP_WINDOW: u64 = 300;
|
||||||
|
|
||||||
|
if ix == 0 {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut iter = self.messages.iter();
|
||||||
|
|
||||||
|
if let Some(previous) = iter.nth(ix - 1)
|
||||||
|
&& let Some(current) = iter.next()
|
||||||
|
{
|
||||||
|
if current.author != previous.author {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let gap = current
|
||||||
|
.created_at
|
||||||
|
.as_secs()
|
||||||
|
.saturating_sub(previous.created_at.as_secs());
|
||||||
|
|
||||||
|
return gap > GROUP_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn render_message(
|
fn render_message(
|
||||||
&mut self,
|
&mut self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
@@ -805,24 +820,17 @@ impl ChatPanel {
|
|||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
if let Some(message) = self.messages.iter().nth(ix) {
|
if let Some(message) = self.messages.iter().nth(ix) {
|
||||||
match message {
|
let persons = PersonRegistry::global(cx);
|
||||||
Message::User(rendered) => {
|
let show_author = self.is_group_start(ix);
|
||||||
let persons = PersonRegistry::global(cx);
|
let text = self
|
||||||
let text = self
|
.rendered_texts_by_id
|
||||||
.rendered_texts_by_id
|
.entry(message.id)
|
||||||
.entry(rendered.id)
|
.or_insert_with(|| {
|
||||||
.or_insert_with(|| {
|
RenderedText::new(&message.content, &message.mentions, &persons, cx)
|
||||||
RenderedText::new(&rendered.content, &rendered.mentions, &persons, cx)
|
})
|
||||||
})
|
.element(ix.into(), window, cx);
|
||||||
.element(ix.into(), window, cx);
|
|
||||||
|
|
||||||
self.render_text_message(ix, rendered, text, cx)
|
self.render_text_message(ix, message, text, show_author, cx)
|
||||||
}
|
|
||||||
Message::Warning(content, _timestamp) => {
|
|
||||||
self.render_warning(ix, SharedString::from(content), cx)
|
|
||||||
}
|
|
||||||
Message::System(_timestamp) => self.render_announcement(ix, cx),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.render_warning(ix, SharedString::from("Message not found"), cx)
|
self.render_warning(ix, SharedString::from("Message not found"), cx)
|
||||||
}
|
}
|
||||||
@@ -831,8 +839,9 @@ impl ChatPanel {
|
|||||||
fn render_text_message(
|
fn render_text_message(
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
message: &RenderedMessage,
|
message: &Message,
|
||||||
rendered_text: AnyElement,
|
rendered_text: AnyElement,
|
||||||
|
show_author: bool,
|
||||||
cx: &Context<Self>,
|
cx: &Context<Self>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
let id = message.id;
|
let id = message.id;
|
||||||
@@ -858,17 +867,21 @@ impl ChatPanel {
|
|||||||
.flex()
|
.flex()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.when(!hide_avatar, |this| {
|
.when(!hide_avatar, |this| {
|
||||||
this.child(
|
if show_author {
|
||||||
Avatar::new(author.avatar())
|
this.child(
|
||||||
.flex_shrink_0()
|
Avatar::new(author.avatar())
|
||||||
.relative()
|
.flex_shrink_0()
|
||||||
.dropdown_menu(move |this, _window, _cx| {
|
.relative()
|
||||||
this.menu("Public Key", Box::new(Command::Copy(pk)))
|
.dropdown_menu(move |this, _window, _cx| {
|
||||||
.menu("View Relays", Box::new(Command::Relays(pk)))
|
this.menu("Public Key", Box::new(Command::Copy(pk)))
|
||||||
.separator()
|
.menu("View Relays", Box::new(Command::Relays(pk)))
|
||||||
.menu("View on njump.me", Box::new(Command::Njump(pk)))
|
.separator()
|
||||||
}),
|
.menu("View on njump.me", Box::new(Command::Njump(pk)))
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.child(div().flex_shrink_0().w(px(32.)))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
@@ -876,22 +889,24 @@ impl ChatPanel {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.flex_initial()
|
.flex_initial()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(
|
.when(show_author, |this| {
|
||||||
h_flex()
|
this.child(
|
||||||
.gap_2()
|
h_flex()
|
||||||
.text_sm()
|
.gap_2()
|
||||||
.text_color(cx.theme().text_placeholder)
|
.text_sm()
|
||||||
.child(
|
.text_color(cx.theme().text_placeholder)
|
||||||
div()
|
.child(
|
||||||
.font_semibold()
|
div()
|
||||||
.text_color(cx.theme().text)
|
.font_semibold()
|
||||||
.child(author.name()),
|
.text_color(cx.theme().text)
|
||||||
)
|
.child(author.name()),
|
||||||
.child(message.created_at.to_human_time())
|
)
|
||||||
.when(has_reports, |this| {
|
.child(message.created_at.to_human_time())
|
||||||
this.child(self.render_sent_reports(&id, cx))
|
.when(has_reports, |this| {
|
||||||
}),
|
this.child(self.render_sent_reports(&id, cx))
|
||||||
)
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when(has_replies, |this| {
|
.when(has_replies, |this| {
|
||||||
this.children(self.render_message_replies(replies, cx))
|
this.children(self.render_message_replies(replies, cx))
|
||||||
})
|
})
|
||||||
@@ -1009,7 +1024,7 @@ impl ChatPanel {
|
|||||||
.on_click({
|
.on_click({
|
||||||
let id = *id;
|
let id = *id;
|
||||||
cx.listener(move |this, _event, _window, _cx| {
|
cx.listener(move |this, _event, _window, _cx| {
|
||||||
this.scroll_to(id);
|
this.scroll_to(&id);
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1500,15 +1515,28 @@ impl Render for ChatPanel {
|
|||||||
v_flex()
|
v_flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.relative()
|
.relative()
|
||||||
.child(
|
.map(|this| {
|
||||||
list(
|
if self.messages.is_empty() {
|
||||||
self.list_state.clone(),
|
this.child(
|
||||||
cx.processor(move |this, ix, window, cx| {
|
div()
|
||||||
this.render_message(ix, window, cx)
|
.size_full()
|
||||||
}),
|
.flex()
|
||||||
)
|
.items_center()
|
||||||
.size_full(),
|
.justify_end()
|
||||||
)
|
.child(self.render_announcement(cx)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.child(
|
||||||
|
list(
|
||||||
|
self.list_state.clone(),
|
||||||
|
cx.processor(move |this, ix, window, cx| {
|
||||||
|
this.render_message(ix, window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
.child(Scrollbar::vertical(&self.list_state)),
|
.child(Scrollbar::vertical(&self.list_state)),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
Reference in New Issue
Block a user