consolidate shared helpers into utils crate
This commit is contained in:
Generated
+1
@@ -8134,6 +8134,7 @@ version = "0.2.0-alpha"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"nostr",
|
"nostr",
|
||||||
"serde",
|
"serde",
|
||||||
|
"utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ publish.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
nostr.workspace = true
|
nostr.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
utils = { path = "../utils" }
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ where
|
|||||||
.map(|root| {
|
.map(|root| {
|
||||||
let mut events = groups.remove(&root).unwrap_or_default();
|
let mut events = groups.remove(&root).unwrap_or_default();
|
||||||
let mut own_events = own_groups.remove(&root).unwrap_or_default();
|
let mut own_events = own_groups.remove(&root).unwrap_or_default();
|
||||||
sort_newest_first(&mut events);
|
utils::sort_newest_first(&mut events);
|
||||||
sort_newest_first(&mut own_events);
|
utils::sort_newest_first(&mut own_events);
|
||||||
|
|
||||||
let root_event = lookup(root);
|
let root_event = lookup(root);
|
||||||
|
|
||||||
@@ -218,15 +218,6 @@ where
|
|||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort thread events newest first, ties broken by id.
|
|
||||||
fn sort_newest_first(events: &mut [Event]) {
|
|
||||||
events.sort_by(|a, b| {
|
|
||||||
b.created_at
|
|
||||||
.cmp(&a.created_at)
|
|
||||||
.then_with(|| b.id.to_hex().cmp(&a.id.to_hex()))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read and archive state of the inbox, a high-water-mark model.
|
/// Read and archive state of the inbox, a high-water-mark model.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct InboxReadState {
|
pub struct InboxReadState {
|
||||||
|
|||||||
@@ -1418,7 +1418,7 @@ impl PushOutcome {
|
|||||||
self.failing()
|
self.failing()
|
||||||
.map(|server| {
|
.map(|server| {
|
||||||
let reason =
|
let reason =
|
||||||
flatten_whitespace(server.reason.as_deref().unwrap_or("unknown error"));
|
utils::flatten_whitespace(server.reason.as_deref().unwrap_or("unknown error"));
|
||||||
format!("{}: {reason}", server.relay)
|
format!("{}: {reason}", server.relay)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -1441,18 +1441,6 @@ impl PushOutcome {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_whitespace(text: &str) -> String {
|
|
||||||
const MAX_CHARS: usize = 200;
|
|
||||||
let flat: String = text.split_whitespace().collect::<Vec<_>>().join(" ");
|
|
||||||
if flat.chars().count() <= MAX_CHARS {
|
|
||||||
flat
|
|
||||||
} else {
|
|
||||||
let mut clipped: String = flat.chars().take(MAX_CHARS).collect();
|
|
||||||
clipped.push('…');
|
|
||||||
clipped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reasons a push attempt should be retried with a freshly staged state
|
/// Reasons a push attempt should be retried with a freshly staged state
|
||||||
/// event and a fresh git advertisement.
|
/// event and a fresh git advertisement.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use gpui::{App, AppContext, Context, Entity, Global, Subscription};
|
|||||||
use nostr::prelude::*;
|
use nostr::prelude::*;
|
||||||
use settings::{CheckoutRecord, SettingsStore};
|
use settings::{CheckoutRecord, SettingsStore};
|
||||||
use signed_core::{Announcement, RepoAddr};
|
use signed_core::{Announcement, RepoAddr};
|
||||||
|
use utils::same_repo_url;
|
||||||
|
|
||||||
use crate::backend::{Backend, BackendEvent};
|
use crate::backend::{Backend, BackendEvent};
|
||||||
use crate::git_store::repo_mirror_root;
|
use crate::git_store::repo_mirror_root;
|
||||||
@@ -511,23 +512,6 @@ impl CheckoutsStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_identity(url: &str) -> Option<(String, Option<u16>, String)> {
|
|
||||||
let parsed = Url::parse(url).ok()?;
|
|
||||||
let host = parsed.host_str()?.to_ascii_lowercase();
|
|
||||||
let mut path = parsed.path().trim_matches('/').to_owned();
|
|
||||||
if let Some(stripped) = path.strip_suffix(".git") {
|
|
||||||
path = stripped.to_owned();
|
|
||||||
}
|
|
||||||
Some((host, parsed.port(), path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn same_repo_url(a: &str, b: &str) -> bool {
|
|
||||||
match (url_identity(a), url_identity(b)) {
|
|
||||||
(Some(a), Some(b)) => a == b,
|
|
||||||
_ => a == b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_associations<'a>(
|
fn resolve_associations<'a>(
|
||||||
remembered: &[Remembered],
|
remembered: &[Remembered],
|
||||||
scanned: &[(PathBuf, Option<String>, Option<String>)],
|
scanned: &[(PathBuf, Option<String>, Option<String>)],
|
||||||
|
|||||||
@@ -392,12 +392,12 @@ impl RepoStore {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|e| !deletions.is_deleted(e));
|
.filter(|e| !deletions.is_deleted(e));
|
||||||
|
|
||||||
let announcement = latest(all_announcements)
|
let announcement = utils::latest(all_announcements)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Announcement::from_event);
|
.and_then(Announcement::from_event);
|
||||||
|
|
||||||
let all_states = states.into_iter().filter(|e| !deletions.is_deleted(e));
|
let all_states = states.into_iter().filter(|e| !deletions.is_deleted(e));
|
||||||
let state = latest(all_states).map(|state| parse_state(&state));
|
let state = utils::latest(all_states).map(|state| parse_state(&state));
|
||||||
|
|
||||||
let (mut issues, mut patches, mut pull_requests, mut statuses, mut comments) =
|
let (mut issues, mut patches, mut pull_requests, mut statuses, mut comments) =
|
||||||
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
|
(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new());
|
||||||
@@ -450,10 +450,10 @@ impl RepoStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort_newest_first(&mut issues);
|
utils::sort_newest_first(&mut issues);
|
||||||
sort_newest_first(&mut patches);
|
utils::sort_newest_first(&mut patches);
|
||||||
sort_newest_first(&mut pull_requests);
|
utils::sort_newest_first(&mut pull_requests);
|
||||||
sort_oldest_first(&mut comments);
|
utils::sort_oldest_first(&mut comments);
|
||||||
|
|
||||||
let maintainers = announcement
|
let maintainers = announcement
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -1447,13 +1447,6 @@ impl RepoStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn latest<I>(events: I) -> Option<Event>
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = Event>,
|
|
||||||
{
|
|
||||||
events.into_iter().max_by_key(|e| e.created_at)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn status_of(status_by_root: &HashMap<EventId, RepoStatus>, root: &Event) -> RepoStatus {
|
fn status_of(status_by_root: &HashMap<EventId, RepoStatus>, root: &Event) -> RepoStatus {
|
||||||
status_by_root
|
status_by_root
|
||||||
.get(&root.id)
|
.get(&root.id)
|
||||||
@@ -1492,14 +1485,6 @@ fn resolve_statuses(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort_newest_first(events: &mut [Event]) {
|
|
||||||
events.sort_by_key(|e| std::cmp::Reverse(e.created_at));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sort_oldest_first(events: &mut [Event]) {
|
|
||||||
events.sort_by_key(|e| e.created_at);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The proposed commit of a `git format-patch` output.
|
/// The proposed commit of a `git format-patch` output.
|
||||||
/// It is the `From <commit>` header on the first line.
|
/// It is the `From <commit>` header on the first line.
|
||||||
fn patch_current_commit(patch: &str) -> Option<&str> {
|
fn patch_current_commit(patch: &str) -> Option<&str> {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ mod title_bar;
|
|||||||
mod tree_row;
|
mod tree_row;
|
||||||
|
|
||||||
pub mod copy_row;
|
pub mod copy_row;
|
||||||
pub mod util;
|
|
||||||
|
|
||||||
pub use avatar::Avatar;
|
pub use avatar::Avatar;
|
||||||
pub use copy_row::{copy_row, menu_copy_row};
|
pub use copy_row::{copy_row, menu_copy_row};
|
||||||
@@ -25,4 +24,3 @@ pub use setting::{SelectOption, setting_block, setting_row};
|
|||||||
pub use status_badge::status_badge;
|
pub use status_badge::status_badge;
|
||||||
pub use title_bar::title_bar_drag_handlers;
|
pub use title_bar::title_bar_drag_handlers;
|
||||||
pub use tree_row::tree_row;
|
pub use tree_row::tree_row;
|
||||||
pub use util::middle_truncate;
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
/// `[head chars]...[tail chars]` middle truncation.
|
|
||||||
///
|
|
||||||
/// Values too short for the ellipsis to save space are left alone.
|
|
||||||
pub fn middle_truncate(value: &str, head: usize, tail: usize) -> String {
|
|
||||||
let len = value.chars().count();
|
|
||||||
if len <= head + tail + 3 {
|
|
||||||
return value.to_string();
|
|
||||||
}
|
|
||||||
let head: String = value.chars().take(head).collect();
|
|
||||||
let tail: String = value.chars().skip(len - tail).collect();
|
|
||||||
format!("{head}...{tail}")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn middle_truncates_long_values_only() {
|
|
||||||
assert_eq!(
|
|
||||||
middle_truncate(
|
|
||||||
"a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d",
|
|
||||||
10,
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
"a008def157...ad57a3564d"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
middle_truncate(
|
|
||||||
"30617:a008def15796fba9a0d6fab04e8fd57089285d9fd505da5a83fe8aad57a3564d:ngit",
|
|
||||||
10,
|
|
||||||
10
|
|
||||||
),
|
|
||||||
"30617:a008...3564d:ngit"
|
|
||||||
);
|
|
||||||
assert_eq!(middle_truncate("short", 10, 10), "short");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
mod pubkey;
|
mod pubkey;
|
||||||
|
mod repo_url;
|
||||||
|
mod text;
|
||||||
mod time;
|
mod time;
|
||||||
|
|
||||||
pub use pubkey::shorten_pubkey;
|
pub use pubkey::shorten_pubkey;
|
||||||
pub use time::{relative_time, relative_time_secs};
|
pub use repo_url::same_repo_url;
|
||||||
|
pub use text::{flatten_whitespace, middle_truncate};
|
||||||
|
pub use time::{latest, relative_time, relative_time_secs, sort_newest_first, sort_oldest_first};
|
||||||
|
|||||||
@@ -1,49 +1,14 @@
|
|||||||
use nostr::prelude::*;
|
use nostr::prelude::*;
|
||||||
|
|
||||||
/// Shorten a [`PublicKey`] to `npub1abc...wxyz` form.
|
use crate::text::middle_truncate;
|
||||||
|
|
||||||
|
const HEAD_CHARS: usize = 9;
|
||||||
|
const TAIL_CHARS: usize = 4;
|
||||||
|
|
||||||
pub fn shorten_pubkey(public_key: PublicKey) -> String {
|
pub fn shorten_pubkey(public_key: PublicKey) -> String {
|
||||||
let encoded = public_key
|
let encoded = public_key
|
||||||
.to_bech32()
|
.to_bech32()
|
||||||
.unwrap_or_else(|_| public_key.to_hex());
|
.unwrap_or_else(|_| public_key.to_hex());
|
||||||
|
|
||||||
truncate_middle(&encoded)
|
middle_truncate(&encoded, HEAD_CHARS, TAIL_CHARS)
|
||||||
}
|
|
||||||
|
|
||||||
fn truncate_middle(value: &str) -> String {
|
|
||||||
const HEAD_CHARS: usize = 9;
|
|
||||||
const TAIL_CHARS: usize = 4;
|
|
||||||
|
|
||||||
let length = value.chars().count();
|
|
||||||
if length <= HEAD_CHARS + TAIL_CHARS + 3 {
|
|
||||||
return value.to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
let head: String = value.chars().take(HEAD_CHARS).collect();
|
|
||||||
let tail: String = value.chars().skip(length - TAIL_CHARS).collect();
|
|
||||||
|
|
||||||
format!("{head}...{tail}")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
const PUBLIC_KEY_HEX: &str = "68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272";
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn shortens_a_valid_pubkey() {
|
|
||||||
let public_key = PublicKey::from_hex(PUBLIC_KEY_HEX).expect("valid pubkey");
|
|
||||||
let npub = public_key.to_bech32().expect("valid pubkey encodes");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
shorten_pubkey(public_key),
|
|
||||||
format!("{}...{}", &npub[..9], &npub[npub.len() - 4..])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn leaves_short_values_intact() {
|
|
||||||
assert_eq!(truncate_middle("npub1short"), "npub1short");
|
|
||||||
assert_eq!(truncate_middle("thirteenchars"), "thirteenchars");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use nostr::prelude::*;
|
||||||
|
|
||||||
|
/// Compare git URLs ignoring scheme, trailing `.git` and path slashes, so a
|
||||||
|
/// grasp announce URL matches its https origin. Unparseable values compare literally.
|
||||||
|
pub fn same_repo_url(a: &str, b: &str) -> bool {
|
||||||
|
match (url_identity(a), url_identity(b)) {
|
||||||
|
(Some(a), Some(b)) => a == b,
|
||||||
|
_ => a == b,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn url_identity(url: &str) -> Option<(String, Option<u16>, String)> {
|
||||||
|
let parsed = Url::parse(url).ok()?;
|
||||||
|
let host = parsed.host_str()?.to_ascii_lowercase();
|
||||||
|
let mut path = parsed.path().trim_matches('/').to_owned();
|
||||||
|
if let Some(stripped) = path.strip_suffix(".git") {
|
||||||
|
path = stripped.to_owned();
|
||||||
|
}
|
||||||
|
Some((host, parsed.port(), path))
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
pub fn middle_truncate(value: &str, head: usize, tail: usize) -> String {
|
||||||
|
let len = value.chars().count();
|
||||||
|
if len <= head + tail + 3 {
|
||||||
|
return value.to_string();
|
||||||
|
}
|
||||||
|
let head: String = value.chars().take(head).collect();
|
||||||
|
let tail: String = value.chars().skip(len - tail).collect();
|
||||||
|
format!("{head}...{tail}")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flatten_whitespace(text: &str) -> String {
|
||||||
|
const MAX_CHARS: usize = 200;
|
||||||
|
let flat = text.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||||
|
if flat.chars().count() <= MAX_CHARS {
|
||||||
|
flat
|
||||||
|
} else {
|
||||||
|
let mut clipped: String = flat.chars().take(MAX_CHARS).collect();
|
||||||
|
clipped.push('…');
|
||||||
|
clipped
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-20
@@ -1,6 +1,24 @@
|
|||||||
use nostr::prelude::*;
|
use nostr::prelude::*;
|
||||||
|
|
||||||
/// Format a timestamp as a short relative time, e.g. `3h ago`.
|
pub fn sort_newest_first(events: &mut [Event]) {
|
||||||
|
events.sort_by(|a, b| {
|
||||||
|
b.created_at
|
||||||
|
.cmp(&a.created_at)
|
||||||
|
.then_with(|| b.id.to_hex().cmp(&a.id.to_hex()))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort_oldest_first(events: &mut [Event]) {
|
||||||
|
events.sort_by_key(|e| e.created_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn latest<I>(events: I) -> Option<Event>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = Event>,
|
||||||
|
{
|
||||||
|
events.into_iter().max_by_key(|e| e.created_at)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn relative_time(timestamp: Timestamp) -> String {
|
pub fn relative_time(timestamp: Timestamp) -> String {
|
||||||
let now = Timestamp::now().as_secs();
|
let now = Timestamp::now().as_secs();
|
||||||
let secs = now.saturating_sub(timestamp.as_secs());
|
let secs = now.saturating_sub(timestamp.as_secs());
|
||||||
@@ -20,25 +38,6 @@ pub fn relative_time(timestamp: Timestamp) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format a unix timestamp in seconds as a short relative time, e.g. `3h ago`.
|
|
||||||
pub fn relative_time_secs(secs: i64) -> String {
|
pub fn relative_time_secs(secs: i64) -> String {
|
||||||
relative_time(Timestamp::from_secs(secs.max(0) as u64))
|
relative_time(Timestamp::from_secs(secs.max(0) as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn formats_relative_time() {
|
|
||||||
let now = Timestamp::now();
|
|
||||||
|
|
||||||
assert_eq!(relative_time(now), "just now");
|
|
||||||
assert_eq!(relative_time(now - 300), "5m ago");
|
|
||||||
assert_eq!(relative_time(now - 7_200), "2h ago");
|
|
||||||
assert_eq!(relative_time(now - 3 * 86_400), "3d ago");
|
|
||||||
assert_eq!(relative_time(now - 60 * 86_400), "2mo ago");
|
|
||||||
assert_eq!(relative_time(now - 800 * 86_400), "2y ago");
|
|
||||||
assert_eq!(relative_time(now + 600), "just now");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ use signed_state::{
|
|||||||
Backend, CheckoutsStore, RepoListStore, RepoStore, ensure_repo_mirror, repo_mirror_path,
|
Backend, CheckoutsStore, RepoListStore, RepoStore, ensure_repo_mirror, repo_mirror_path,
|
||||||
};
|
};
|
||||||
use signed_ui::{CountBadge, placeholder, ref_selector_trigger};
|
use signed_ui::{CountBadge, placeholder, ref_selector_trigger};
|
||||||
|
use utils::middle_truncate;
|
||||||
|
|
||||||
use crate::views::commit_diff::{COMMIT_ROW_HEIGHT, CommitDiffView, DiffPane, commit_row};
|
use crate::views::commit_diff::{COMMIT_ROW_HEIGHT, CommitDiffView, DiffPane, commit_row};
|
||||||
use crate::views::{repo_tab_avatar, tab_title};
|
use crate::views::{repo_tab_avatar, tab_title};
|
||||||
@@ -104,17 +105,8 @@ fn shorten_owner(owner: &PublicKey) -> String {
|
|||||||
hex.chars().take(10).collect()
|
hex.chars().take(10).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate a label for the fixed-width controls of the compare bar.
|
|
||||||
fn truncate_label(label: &str) -> SharedString {
|
fn truncate_label(label: &str) -> SharedString {
|
||||||
const MAX: usize = 18;
|
SharedString::from(middle_truncate(label, 18, 0))
|
||||||
let mut chars = label.chars();
|
|
||||||
let (prefix, rest) = (chars.by_ref().take(MAX).collect::<String>(), chars.next());
|
|
||||||
let label = if rest.is_some() {
|
|
||||||
format!("{}…", &prefix[..prefix.len().saturating_sub(1)])
|
|
||||||
} else {
|
|
||||||
prefix
|
|
||||||
};
|
|
||||||
SharedString::from(label)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn checkout_source_item(
|
fn checkout_source_item(
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use gpui_component::{ActiveTheme, StyledExt, WindowExt, h_flex, v_flex};
|
|||||||
use nostr::prelude::PublicKey;
|
use nostr::prelude::PublicKey;
|
||||||
use signed_core::Announcement;
|
use signed_core::Announcement;
|
||||||
use signed_state::ProfileStore;
|
use signed_state::ProfileStore;
|
||||||
use signed_ui::{Avatar, middle_truncate};
|
use signed_ui::Avatar;
|
||||||
|
use utils::middle_truncate;
|
||||||
|
|
||||||
pub(super) fn open_about_dialog(announcement: Announcement, window: &mut Window, cx: &mut App) {
|
pub(super) fn open_about_dialog(announcement: Announcement, window: &mut Window, cx: &mut App) {
|
||||||
window.open_dialog(cx, move |dialog, _window, cx| {
|
window.open_dialog(cx, move |dialog, _window, cx| {
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ use signed_state::{
|
|||||||
pr_proposes_checkout,
|
pr_proposes_checkout,
|
||||||
};
|
};
|
||||||
use signed_ui::{
|
use signed_ui::{
|
||||||
Avatar, CountBadge, DropdownButton, PixelAvatar, copy_row, menu_copy_row, middle_truncate,
|
Avatar, CountBadge, DropdownButton, PixelAvatar, copy_row, menu_copy_row, ref_selector_trigger,
|
||||||
ref_selector_trigger,
|
|
||||||
};
|
};
|
||||||
|
use utils::middle_truncate;
|
||||||
|
|
||||||
mod about;
|
mod about;
|
||||||
mod actions;
|
mod actions;
|
||||||
|
|||||||
Reference in New Issue
Block a user