Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
584ab34df6 | ||
|
|
0514c1d982 | ||
|
|
2f834a0bcc | ||
|
|
49cd5cb9a0 |
Generated
+480
-648
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -4,7 +4,7 @@ members = ["crates/*", "desktop", "web"]
|
|||||||
default-members = ["desktop"]
|
default-members = ["desktop"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "1.0.0-beta5"
|
version = "1.0.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
[workspace]
|
|
||||||
resolver = "2"
|
|
||||||
members = ["crates/*", "desktop", "web"]
|
|
||||||
default-members = ["desktop"]
|
|
||||||
|
|
||||||
[workspace.package]
|
|
||||||
version = "1.0.0-beta5"
|
|
||||||
edition = "2024"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
# GPUI
|
|
||||||
gpui = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "x11", "wayland"] }
|
|
||||||
gpui_linux = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_windows = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_macos = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
gpui_tokio = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
reqwest_client = { git = "https://github.com/zed-industries/zed" }
|
|
||||||
|
|
||||||
# Nostr
|
|
||||||
nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-blossom = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr-sdk = { git = "https://github.com/rust-nostr/nostr" }
|
|
||||||
nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44" ] }
|
|
||||||
|
|
||||||
# Others
|
|
||||||
anyhow = "1.0.44"
|
|
||||||
chrono = { version = "0.4.38", features = ["wasmbind"] }
|
|
||||||
futures = "0.3"
|
|
||||||
itertools = "0.13.0"
|
|
||||||
log = "0.4"
|
|
||||||
oneshot = "0.1.10"
|
|
||||||
flume = { version = "0.11.1", default-features = false, features = ["async", "select"] }
|
|
||||||
rust-embed = { version = "8.5", features = ["include-exclude"] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
serde_json = "1.0"
|
|
||||||
schemars = "1"
|
|
||||||
smallvec = "1.14.0"
|
|
||||||
smol = "2"
|
|
||||||
webbrowser = "1.0.4"
|
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
|
||||||
errno = { version = "0.3.14", default-features = false }
|
|
||||||
instant = "0.1"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
# Use stacker's psm version which may have better WASM support
|
|
||||||
psm = { git = "https://github.com/rust-lang/stacker", branch = "master" }
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
strip = true
|
|
||||||
opt-level = "z"
|
|
||||||
lto = true
|
|
||||||
codegen-units = 1
|
|
||||||
panic = "abort"
|
|
||||||
|
|
||||||
[profile.profiling]
|
|
||||||
inherits = "release"
|
|
||||||
debug = true
|
|
||||||
@@ -5,6 +5,7 @@ use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version};
|
|||||||
use instant::{Duration, Instant};
|
use instant::{Duration, Instant};
|
||||||
|
|
||||||
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
|
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
|
||||||
|
const COOP_BUNDLE_TYPE: &str = "COOP_BUNDLE_TYPE";
|
||||||
|
|
||||||
fn get_github_repo_owner() -> String {
|
fn get_github_repo_owner() -> String {
|
||||||
std::env::var("COOP_GITHUB_REPO_OWNER").unwrap_or_else(|_| "reyakov".to_string())
|
std::env::var("COOP_GITHUB_REPO_OWNER").unwrap_or_else(|_| "reyakov".to_string())
|
||||||
@@ -14,18 +15,27 @@ fn get_github_repo_name() -> String {
|
|||||||
std::env::var("COOP_GITHUB_REPO_NAME").unwrap_or_else(|_| "coop".to_string())
|
std::env::var("COOP_GITHUB_REPO_NAME").unwrap_or_else(|_| "coop".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_flatpak_installation() -> bool {
|
/// Whether updates are managed by an external distribution channel
|
||||||
std::env::var("FLATPAK_ID").is_ok() || std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
/// (Flatpak/Snap), in which case the in-app updater must not run.
|
||||||
|
fn uses_managed_updates() -> bool {
|
||||||
|
// The Flatpak runtime exports `FLATPAK_ID` inside the sandbox.
|
||||||
|
std::env::var("FLATPAK_ID").is_ok()
|
||||||
|
// Allow opting out of in-app updates via an explicit environment variable.
|
||||||
|
|| std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||||
|
// The Snap package sets `COOP_BUNDLE_TYPE=snap` (see snapcraft.yaml.in).
|
||||||
|
|| std::env::var(COOP_BUNDLE_TYPE).is_ok_and(|value| value == "snap")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the auto-update system.
|
/// Initialize the auto-update system.
|
||||||
///
|
///
|
||||||
/// Skips initialization when running as a Flatpak (updates are handled by the
|
/// Skips initialization when updates are handled by an external distribution
|
||||||
/// Flatpak distribution channel). Otherwise creates the global [`AutoUpdater`]
|
/// channel (Flatpak/Snap). Otherwise creates the global [`AutoUpdater`]
|
||||||
/// entity and schedules a check for updates after a 2-minute delay.
|
/// entity and schedules a check for updates after a 2-minute delay.
|
||||||
pub fn init(window: &mut Window, cx: &mut App) {
|
pub fn init(window: &mut Window, cx: &mut App) {
|
||||||
if is_flatpak_installation() {
|
if uses_managed_updates() {
|
||||||
log::info!("Skipping auto-update initialization: App is installed via Flatpak");
|
log::info!(
|
||||||
|
"Skipping auto-update initialization: App is installed via a managed distribution channel (Flatpak/Snap)"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +67,27 @@ pub struct AutoUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AutoUpdater {
|
impl AutoUpdater {
|
||||||
|
/// Whether auto-update is available for this installation.
|
||||||
|
///
|
||||||
|
/// Returns `false` on managed distribution channels (Flatpak/Snap), where
|
||||||
|
/// updates are handled by the channel and no global updater is created.
|
||||||
|
pub fn is_available(cx: &App) -> bool {
|
||||||
|
cx.try_global::<GlobalAutoUpdater>().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the global auto updater instance, if one was initialized.
|
||||||
|
pub fn try_global(cx: &App) -> Option<Entity<Self>> {
|
||||||
|
cx.try_global::<GlobalAutoUpdater>()
|
||||||
|
.map(|global| global.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve the global auto updater instance.
|
/// Retrieve the global auto updater instance.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when auto-update is not available for this installation. Prefer
|
||||||
|
/// [`AutoUpdater::try_global`] when the installation type is not known at
|
||||||
|
/// compile time (e.g. Flatpak/Snap).
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
cx.global::<GlobalAutoUpdater>().0.clone()
|
cx.global::<GlobalAutoUpdater>().0.clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ publish = false
|
|||||||
atomic-destructor = "0.2"
|
atomic-destructor = "0.2"
|
||||||
event-listener = "5"
|
event-listener = "5"
|
||||||
nostr.workspace = true
|
nostr.workspace = true
|
||||||
opaquerr = "0.1"
|
opaquerr = { version = "0.1", features = ["alloc"] }
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
|
|||||||
+19
-6
@@ -365,7 +365,8 @@ impl ChatRegistry {
|
|||||||
.query(filter)
|
.query(filter)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.first_owned()
|
.into_iter()
|
||||||
|
.next()
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
@@ -397,7 +398,8 @@ impl ChatRegistry {
|
|||||||
.database()
|
.database()
|
||||||
.query(filter)
|
.query(filter)
|
||||||
.await?
|
.await?
|
||||||
.first_owned()
|
.into_iter()
|
||||||
|
.next()
|
||||||
.ok_or(anyhow::anyhow!("No inbox relays found"))?;
|
.ok_or(anyhow::anyhow!("No inbox relays found"))?;
|
||||||
|
|
||||||
let relays: Vec<RelayUrl> = nip17::extract_relay_list(&event).collect();
|
let relays: Vec<RelayUrl> = nip17::extract_relay_list(&event).collect();
|
||||||
@@ -656,15 +658,26 @@ impl ChatRegistry {
|
|||||||
|
|
||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let public_key = signer.get_public_key_async().await?;
|
let public_key = signer.get_public_key_async().await?;
|
||||||
let contacts = client
|
|
||||||
|
// Query the latest contact list (previously `NostrDatabaseExt::contacts_public_keys`)
|
||||||
|
let filter = Filter::new()
|
||||||
|
.author(public_key)
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let contacts: HashSet<PublicKey> = client
|
||||||
.database()
|
.database()
|
||||||
.contacts_public_keys(public_key)
|
.query(filter)
|
||||||
.await
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|event| event.tags.public_keys().collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.custom_tag(SingleLetterTag::lowercase(Alphabet::K), "14");
|
.custom_tag(SingleLetterTag::LOWERCASE_K, "14");
|
||||||
|
|
||||||
let events = client.database().query(filter).await?;
|
let events = client.database().query(filter).await?;
|
||||||
let mut grouped: HashMap<u64, Vec<UnsignedEvent>> = HashMap::new();
|
let mut grouped: HashMap<u64, Vec<UnsignedEvent>> = HashMap::new();
|
||||||
@@ -827,7 +840,7 @@ async fn set_rumor(client: &Client, id: EventId, rumor: &UnsignedEvent) -> Resul
|
|||||||
async fn get_rumor(client: &Client, gift_wrap: EventId) -> Result<UnsignedEvent, Error> {
|
async fn get_rumor(client: &Client, gift_wrap: EventId) -> Result<UnsignedEvent, Error> {
|
||||||
let filter = Filter::new().identifier(gift_wrap).limit(1);
|
let filter = Filter::new().identifier(gift_wrap).limit(1);
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||||
UnsignedEvent::from_json(event.content).map_err(|e| anyhow!(e))
|
UnsignedEvent::from_json(event.content).map_err(|e| anyhow!(e))
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Event is not cached yet."))
|
Err(anyhow!("Event is not cached yet."))
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ impl Room {
|
|||||||
cx.background_spawn(async move {
|
cx.background_spawn(async move {
|
||||||
let filter = Filter::new()
|
let filter = Filter::new()
|
||||||
.kind(Kind::ApplicationSpecificData)
|
.kind(Kind::ApplicationSpecificData)
|
||||||
.custom_tag(SingleLetterTag::lowercase(Alphabet::R), room_id);
|
.custom_tag(SingleLetterTag::LOWERCASE_R, room_id);
|
||||||
|
|
||||||
let messages = client
|
let messages = client
|
||||||
.database()
|
.database()
|
||||||
@@ -461,13 +461,10 @@ impl Room {
|
|||||||
// Add all receiver tags (no intermediate allocation)
|
// Add all receiver tags (no intermediate allocation)
|
||||||
for public_key in self.members.iter().filter(|pk| *pk != &sender) {
|
for public_key in self.members.iter().filter(|pk| *pk != &sender) {
|
||||||
let member = persons.read(cx).get(public_key, cx);
|
let member = persons.read(cx).get(public_key, cx);
|
||||||
tags.push(
|
tags.push(Tag::from(Nip01Tag::PublicKey {
|
||||||
Nip01Tag::PublicKey {
|
public_key: member.public_key(),
|
||||||
public_key: member.public_key(),
|
relay_hint: member.messaging_relay_hint(),
|
||||||
relay_hint: member.messaging_relay_hint(),
|
}));
|
||||||
}
|
|
||||||
.to_tag(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a direct message rumor event
|
// Construct a direct message rumor event
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use std::path::PathBuf;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use instant::Duration;
|
|
||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Error, anyhow};
|
use anyhow::{Context as AnyhowContext, Error, anyhow};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Context, Entity, EventEmitter, Global, IntoElement, ParentElement,
|
App, AppContext, Context, Entity, EventEmitter, Global, IntoElement, ParentElement,
|
||||||
SharedString, Styled, Subscription, Task, Window, div, relative,
|
SharedString, Styled, Subscription, Task, Window, div, relative,
|
||||||
};
|
};
|
||||||
|
use instant::Duration;
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use settings::AppSettings;
|
use settings::AppSettings;
|
||||||
@@ -414,7 +414,7 @@ impl DeviceRegistry {
|
|||||||
.pubkey(app_pubkey)
|
.pubkey(app_pubkey)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
match client.database().query(filter).await?.first_owned() {
|
match client.database().query(filter).await?.into_iter().next() {
|
||||||
// Found an approval event
|
// Found an approval event
|
||||||
Some(event) => Ok(Some(event)),
|
Some(event) => Ok(Some(event)),
|
||||||
// No approval event found, construct a request event
|
// No approval event found, construct a request event
|
||||||
|
|||||||
@@ -84,8 +84,20 @@ impl Screening {
|
|||||||
|
|
||||||
let task: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<bool, Error>> = cx.background_spawn(async move {
|
||||||
// Check if user is in contact list
|
// Check if user is in contact list
|
||||||
let contacts = client.database().contacts_public_keys(current_user).await;
|
let filter = Filter::new()
|
||||||
let followed = contacts.unwrap_or_default().contains(&public_key);
|
.author(current_user)
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let followed = client
|
||||||
|
.database()
|
||||||
|
.query(filter)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|event| event.tags.public_keys().any(|k| k == public_key))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
Ok(followed)
|
Ok(followed)
|
||||||
});
|
});
|
||||||
@@ -228,11 +240,10 @@ impl Screening {
|
|||||||
let public_key = self.public_key;
|
let public_key = self.public_key;
|
||||||
|
|
||||||
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
let task: Task<Result<(), Error>> = cx.background_spawn(async move {
|
||||||
let tag = Nip56Tag::PublicKey {
|
let tag = Tag::from(Nip56Tag::PublicKey {
|
||||||
public_key,
|
public_key,
|
||||||
report: Report::Impersonation,
|
report: Report::Impersonation,
|
||||||
}
|
});
|
||||||
.to_tag();
|
|
||||||
|
|
||||||
let event = EventBuilder::new(Kind::Reporting, "")
|
let event = EventBuilder::new(Kind::Reporting, "")
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
|
|||||||
+27
-16
@@ -380,12 +380,15 @@ impl Workspace {
|
|||||||
self.import_encryption(window, cx);
|
self.import_encryption(window, cx);
|
||||||
}
|
}
|
||||||
Command::Update => {
|
Command::Update => {
|
||||||
let auto_updater = AutoUpdater::global(cx);
|
// No-op on managed distribution channels (Flatpak/Snap) where
|
||||||
auto_updater.update(cx, |this, cx| {
|
// the in-app updater is never initialized.
|
||||||
this.updater.update(cx, |updater, cx| {
|
if let Some(auto_updater) = AutoUpdater::try_global(cx) {
|
||||||
updater.check(cx);
|
auto_updater.update(cx, |this, cx| {
|
||||||
|
this.updater.update(cx, |updater, cx| {
|
||||||
|
updater.check(cx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,7 +562,7 @@ impl Workspace {
|
|||||||
.caret()
|
.caret()
|
||||||
.compact()
|
.compact()
|
||||||
.transparent()
|
.transparent()
|
||||||
.dropdown_menu(move |this, _window, _cx| {
|
.dropdown_menu(move |this, _window, cx| {
|
||||||
let avatar = avatar.clone();
|
let avatar = avatar.clone();
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
|
|
||||||
@@ -593,12 +596,15 @@ impl Workspace {
|
|||||||
IconName::Sun,
|
IconName::Sun,
|
||||||
Box::new(Command::ToggleTheme),
|
Box::new(Command::ToggleTheme),
|
||||||
)
|
)
|
||||||
.separator()
|
// Only offer in-app updates when auto-update is
|
||||||
.menu_with_icon(
|
// enabled (managed channels update themselves).
|
||||||
"Check for Updates",
|
.when(AutoUpdater::is_available(cx), |this| {
|
||||||
IconName::Device,
|
this.separator().menu_with_icon(
|
||||||
Box::new(Command::Update),
|
"Check for Updates",
|
||||||
)
|
IconName::Device,
|
||||||
|
Box::new(Command::Update),
|
||||||
|
)
|
||||||
|
})
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Settings",
|
"Settings",
|
||||||
IconName::Settings,
|
IconName::Settings,
|
||||||
@@ -610,7 +616,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let updater = AutoUpdater::global(cx);
|
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
@@ -622,13 +627,19 @@ impl Workspace {
|
|||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let profile = persons.read(cx).get(&public_key, cx);
|
let profile = persons.read(cx).get(&public_key, cx);
|
||||||
let announcement = profile.announcement();
|
let announcement = profile.announcement();
|
||||||
let updater_idle = updater.read(cx).idle(cx);
|
|
||||||
|
// Update status is only shown when auto-update is available. On
|
||||||
|
// managed distribution channels (Flatpak/Snap) no updater exists, so
|
||||||
|
// nothing is rendered.
|
||||||
|
let updater_status = AutoUpdater::try_global(cx).and_then(|updater| {
|
||||||
|
let updater = updater.read(cx);
|
||||||
|
(!updater.idle(cx)).then(|| updater.status(cx))
|
||||||
|
});
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when(!updater_idle, |this| {
|
.when_some(updater_status, |this, status| {
|
||||||
let status = updater.read(cx).status(cx);
|
|
||||||
this.child(div().text_xs().italic().child(status))
|
this.child(div().text_xs().italic().child(status))
|
||||||
})
|
})
|
||||||
.when(nip4e_enabled, |this| {
|
.when(nip4e_enabled, |this| {
|
||||||
|
|||||||
@@ -88,7 +88,20 @@ impl ContactListPanel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
||||||
let contact_list = client.database().contacts_public_keys(public_key).await?;
|
let filter = Filter::new()
|
||||||
|
.author(public_key)
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let contact_list: HashSet<PublicKey> = client
|
||||||
|
.database()
|
||||||
|
.query(filter)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|event| event.tags.public_keys().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(contact_list)
|
Ok(contact_list)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ impl MessagingRelayPanel {
|
|||||||
.author(public_key)
|
.author(public_key)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||||
Ok(nip17::extract_relay_list(&event).collect())
|
Ok(nip17::extract_relay_list(&event).collect())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Not found."))
|
Err(anyhow!("Not found."))
|
||||||
@@ -177,7 +177,7 @@ impl MessagingRelayPanel {
|
|||||||
let tags: Vec<Tag> = self
|
let tags: Vec<Tag> = self
|
||||||
.relays
|
.relays
|
||||||
.iter()
|
.iter()
|
||||||
.map(|relay| Nip17Tag::Relay(relay.to_owned()).to_tag())
|
.map(|relay| Tag::from(Nip17Tag::Relay(relay.to_owned())))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Set updating state
|
// Set updating state
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ impl RelayListPanel {
|
|||||||
.author(public_key)
|
.author(public_key)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
if let Some(event) = client.database().query(filter).await?.into_iter().next() {
|
||||||
Ok(nip65::extract_relay_list(&event).collect())
|
Ok(nip65::extract_relay_list(&event).collect())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("Not found."))
|
Err(anyhow!("Not found."))
|
||||||
|
|||||||
@@ -158,7 +158,20 @@ impl Sidebar {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
let task: Task<Result<HashSet<PublicKey>, Error>> = cx.background_spawn(async move {
|
||||||
let contacts = client.database().contacts_public_keys(public_key).await?;
|
let filter = Filter::new()
|
||||||
|
.author(public_key)
|
||||||
|
.kind(Kind::ContactList)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let contacts: HashSet<PublicKey> = client
|
||||||
|
.database()
|
||||||
|
.query(filter)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.map(|event| event.tags.public_keys().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(contacts)
|
Ok(contacts)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ product-name = "Coop"
|
|||||||
description = "Chat Freely, Stay Private on Nostr"
|
description = "Chat Freely, Stay Private on Nostr"
|
||||||
identifier = "su.reya.coop"
|
identifier = "su.reya.coop"
|
||||||
category = "SocialNetworking"
|
category = "SocialNetworking"
|
||||||
version = "1.0.0-beta5"
|
version = "1.0.1"
|
||||||
out-dir = "../dist"
|
out-dir = "../dist"
|
||||||
before-packaging-command = "cargo build --release"
|
before-packaging-command = "cargo build --release"
|
||||||
resources = ["Cargo.toml", "src"]
|
resources = ["Cargo.toml", "src"]
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "coop"
|
|
||||||
version.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
publish.workspace = true
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "coop"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[package.metadata.packager]
|
|
||||||
name = "Coop"
|
|
||||||
product-name = "Coop"
|
|
||||||
description = "Chat Freely, Stay Private on Nostr"
|
|
||||||
identifier = "su.reya.coop"
|
|
||||||
category = "SocialNetworking"
|
|
||||||
version = "1.0.0-beta5"
|
|
||||||
out-dir = "../dist"
|
|
||||||
before-packaging-command = "cargo build --release"
|
|
||||||
resources = ["Cargo.toml", "src"]
|
|
||||||
icons = [
|
|
||||||
"resources/32x32.png",
|
|
||||||
"resources/128x128.png",
|
|
||||||
"resources/128x128@2x.png",
|
|
||||||
"resources/icon.icns",
|
|
||||||
"resources/icon.ico",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
assets = { path = "../crates/assets" }
|
|
||||||
workspace = { path = "../crates/workspace" }
|
|
||||||
ui = { path = "../crates/ui" }
|
|
||||||
theme = { path = "../crates/theme" }
|
|
||||||
common = { path = "../crates/common" }
|
|
||||||
state = { path = "../crates/state" }
|
|
||||||
device = { path = "../crates/device" }
|
|
||||||
chat = { path = "../crates/chat" }
|
|
||||||
settings = { path = "../crates/settings" }
|
|
||||||
auto_update = { path = "../crates/auto_update" }
|
|
||||||
person = { path = "../crates/person" }
|
|
||||||
|
|
||||||
gpui.workspace = true
|
|
||||||
gpui_platform.workspace = true
|
|
||||||
gpui_linux.workspace = true
|
|
||||||
gpui_windows.workspace = true
|
|
||||||
gpui_macos.workspace = true
|
|
||||||
reqwest_client.workspace = true
|
|
||||||
|
|
||||||
log.workspace = true
|
|
||||||
tracing-subscriber.workspace = true
|
|
||||||
nostr-sdk.workspace = true
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# Snaps built by snapcraft without Snap Store credentials are unsigned and
|
||||||
|
# cannot be installed without bypassing signature checks. Use:
|
||||||
|
# sudo snap install --dangerous ./coop_<version>_<arch>.snap
|
||||||
|
# For signed installs (`snap install coop`), publish via the Snap Store.
|
||||||
name: coop
|
name: coop
|
||||||
title: Coop
|
title: Coop
|
||||||
base: core24
|
base: core24
|
||||||
|
|||||||
+4
-4
@@ -55,8 +55,8 @@ flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo
|
|||||||
|
|
||||||
Ensure you have:
|
Ensure you have:
|
||||||
- [ ] Committed all changes
|
- [ ] Committed all changes
|
||||||
- [ ] Tagged the release: `git tag -a v1.0.0-beta2 -m "Release v1.0.0-beta2"`
|
- [ ] Tagged the release: `git tag -a v1.0.0 -m "Release v1.0.0"`
|
||||||
- [ ] Pushed the tag: `git push origin v1.0.0-beta2`
|
- [ ] Pushed the tag: `git push origin v1.0.0`
|
||||||
- [ ] Run `./script/prepare-flathub.sh` to regenerate files
|
- [ ] Run `./script/prepare-flathub.sh` to regenerate files
|
||||||
|
|
||||||
### 2. Fork and Submit
|
### 2. Fork and Submit
|
||||||
@@ -101,8 +101,8 @@ git push origin su.reya.coop
|
|||||||
To release a new version:
|
To release a new version:
|
||||||
|
|
||||||
1. Update version in workspace `Cargo.toml`
|
1. Update version in workspace `Cargo.toml`
|
||||||
2. Tag the new release: `git tag -a v1.0.0-beta3 -m "Release v1.0.0-beta3"`
|
2. Tag the new release: `git tag -a v1.0.0 -m "Release v1.0.0"`
|
||||||
3. Push the tag: `git push origin v1.0.0-beta3`
|
3. Push the tag: `git push origin v1.0.0`
|
||||||
4. Run `./script/prepare-flathub.sh` to regenerate
|
4. Run `./script/prepare-flathub.sh` to regenerate
|
||||||
5. Clone the flathub repo: `git clone https://github.com/flathub/su.reya.coop.git`
|
5. Clone the flathub repo: `git clone https://github.com/flathub/su.reya.coop.git`
|
||||||
6. Update the manifest with new commit/tag and hashes
|
6. Update the manifest with new commit/tag and hashes
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ SNAP_NAME="coop_${1}_${ARCH_SUFFIX}.snap"
|
|||||||
snapcraft --destructive-mode --output "$SNAP_NAME"
|
snapcraft --destructive-mode --output "$SNAP_NAME"
|
||||||
|
|
||||||
echo "Created snap package: $SNAP_NAME"
|
echo "Created snap package: $SNAP_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "This snap is unsigned (built without Snap Store credentials)."
|
||||||
|
echo "To install it locally, use:"
|
||||||
|
echo " sudo snap install --dangerous ./$SNAP_NAME"
|
||||||
|
|||||||
+16
-10
@@ -29,23 +29,29 @@ fi
|
|||||||
# Function to update version in a Cargo.toml file
|
# Function to update version in a Cargo.toml file
|
||||||
update_version() {
|
update_version() {
|
||||||
local file="$1"
|
local file="$1"
|
||||||
local backup="${file}.bak"
|
local tmp="${file}.tmp"
|
||||||
|
|
||||||
# Backup the original file
|
# Portable in-place edit. `sed -i` behaves differently on GNU sed (Linux)
|
||||||
cp "$file" "$backup"
|
# and BSD sed (macOS): on macOS `sed -i -E` treats `-E` as the backup
|
||||||
|
# suffix instead of the extended-regex flag, leaving a stray
|
||||||
# More flexible regex that handles various version formats and whitespace
|
# `Cargo.toml-E` behind and never updating the file.
|
||||||
if sed -i -E "s/^[[:space:]]*version[[:space:]]*=[[:space:]]*\"[^\"]+\"/version = \"$NEW_VERSION\"/" "$file"; then
|
# Writing to a temp file and moving it over works on both implementations.
|
||||||
|
if sed -E "s/^[[:space:]]*version[[:space:]]*=[[:space:]]*\"[^\"]+\"/version = \"$NEW_VERSION\"/" "$file" > "$tmp" \
|
||||||
|
&& mv "$tmp" "$file"; then
|
||||||
echo "✓ Updated version to $NEW_VERSION in $file"
|
echo "✓ Updated version to $NEW_VERSION in $file"
|
||||||
else
|
else
|
||||||
echo "Error: Failed to update version in $file"
|
echo "Error: Failed to update version in $file"
|
||||||
# Restore original backup
|
# Remove any partial temp file; the original file is untouched
|
||||||
mv "$backup" "$file"
|
rm -f "$tmp"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove the backup file
|
# The substitution can silently match nothing (e.g. a `version.workspace` key),
|
||||||
rm -f "$backup"
|
# so verify the new version actually landed before moving on.
|
||||||
|
if ! grep -q "^[[:space:]]*version[[:space:]]*=[[:space:]]*\"$NEW_VERSION\"" "$file"; then
|
||||||
|
echo "Error: Version line not found/updated in $file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update both Cargo.toml files
|
# Update both Cargo.toml files
|
||||||
|
|||||||
+16
-1
@@ -15,11 +15,26 @@ if [ "$#" -ne 1 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Get system architecture (same mapping as script/bundle-snap)
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64) ARCH_SUFFIX="x86_64" ;;
|
||||||
|
aarch64) ARCH_SUFFIX="aarch64" ;;
|
||||||
|
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
snap_file="coop_${1}_${ARCH_SUFFIX}.snap"
|
||||||
|
if [ ! -f "$snap_file" ]; then
|
||||||
|
echo "Snap file not found: $snap_file"
|
||||||
|
echo "Build it first with: script/bundle-snap $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Rerun as root
|
# Rerun as root
|
||||||
[ "$UID" -eq 0 ] || exec sudo bash -e "$0" "$@"
|
[ "$UID" -eq 0 ] || exec sudo bash -e "$0" "$@"
|
||||||
|
|
||||||
snap remove coop || true
|
snap remove coop || true
|
||||||
mkdir -p snap
|
mkdir -p snap
|
||||||
rm -rf snap/unpacked
|
rm -rf snap/unpacked
|
||||||
unsquashfs -dest snap/unpacked "coop_$1_amd64.snap"
|
unsquashfs -dest snap/unpacked "$snap_file"
|
||||||
snap try --classic snap/unpacked
|
snap try --classic snap/unpacked
|
||||||
|
|||||||
Reference in New Issue
Block a user