From e82f3479fa82d8d35cc8fe2918bab8dc82c763c4 Mon Sep 17 00:00:00 2001 From: xeruf <27jf@pm.me> Date: Wed, 29 Jan 2025 23:03:42 +0100 Subject: [PATCH] feat: multi-key filtering --- src/kinds.rs | 4 ++-- src/main.rs | 14 ++++++++------ src/tasks.rs | 40 +++++++++++++++++++++++----------------- src/tasks/nostr_users.rs | 25 ++++++++++++++----------- src/tasks/tests.rs | 8 +++++--- 5 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/kinds.rs b/src/kinds.rs index be705de..fddcda7 100644 --- a/src/kinds.rs +++ b/src/kinds.rs @@ -108,8 +108,8 @@ pub(crate) fn extract_tags(input: &str, users: &NostrUsers) -> (String, Vec if let Ok(key) = PublicKey::parse(&s[1..]) { tags.push(Tag::public_key(key)); return false; - } else if let Some((key, _)) = users.find_user(&s[1..]) { - tags.push(Tag::public_key(*key)); + } else if let Some((key, _)) = users.find_user(&s[1..]).first() { + tags.push(Tag::public_key(**key)); return false; } } else if s.starts_with('*') { diff --git a/src/main.rs b/src/main.rs index 03fe8d9..669147f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -517,12 +517,14 @@ async fn main() -> Result<()> { continue 'repl; } } + Some("@") => { + tasks.reset_key_filter() + } Some(arg) => { - if arg == "@" { - tasks.reset_key_filter() - } else if let Some((key, name)) = tasks.find_user(arg) { - info!("Showing {}'s tasks", name); - tasks.set_key_filter(key) + let users = tasks.find_users(arg); + if !users.is_empty() { + info!("Showing tasks for {}", users.iter().map(|(k, v)| v).join(", ")); + tasks.set_key_filter(users.iter().map(|(k, v)| *k).collect_vec()) } else { if parse_hour(arg, 1) .or_else(|| parse_date(arg) @@ -665,7 +667,7 @@ async fn main() -> Result<()> { } } println!("{}", tasks.times_tracked(max)); - } else if let Some((key, _)) = tasks.find_user(arg) { + } else if let Some((key, _)) = tasks.find_users(arg).first() { let (label, mut times) = tasks.times_tracked_for(&key); println!("{}\n{}", label.italic(), times.join("\n")); } else { diff --git a/src/tasks.rs b/src/tasks.rs index 62a31e5..5b4968e 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -93,7 +93,8 @@ pub(crate) struct TasksRelay { state: StateFilter, /// Current priority for filtering and new tasks priority: Option, - pubkey: Option, + keys: Vec, + own_keys: Vec, sender: EventSender, overflow: VecDeque, @@ -192,7 +193,8 @@ impl TasksRelay { tags_excluded: Default::default(), state: Default::default(), priority: None, - pubkey: Some(sender.pubkey()), + keys: vec![sender.pubkey()], + own_keys: vec![sender.pubkey()], search_depth: 4, view_depth: 0, @@ -234,6 +236,10 @@ impl TasksRelay { #[inline] pub(crate) fn len(&self) -> usize { self.tasks.len() } + fn own_keys(&self) -> &Vec { &self.own_keys } + + fn own_key(&self) -> PublicKey { self.sender.pubkey() } + pub(crate) fn get_position(&self) -> Option { self.get_position_at(now()).1 } @@ -278,7 +284,7 @@ impl TasksRelay { /// Dynamic time tracking overview for current task or current user. pub(crate) fn times_tracked(&self, limit: usize) -> String { - let (label, times) = self.times_tracked_with(&self.sender.pubkey()); + let (label, times) = self.times_tracked_with(&self.own_key()); // TODO self.own_keys let times = times.collect_vec(); format!("{}\n{}", if times.is_empty() { @@ -438,10 +444,10 @@ impl TasksRelay { } pub(crate) fn pubkey_str(&self) -> Option { - match self.pubkey { + match self.keys.first() { None => Some("ALL".to_string()), Some(key) => { - if key != self.sender.pubkey() { + if &self.keys != self.own_keys() { Some(self.users.get_username(&key)) } else { None @@ -550,8 +556,9 @@ impl TasksRelay { fn filter(&self, task: &Task) -> bool { self.state.matches(task) && - (!task.is_task() || self.pubkey.is_none_or(|p| p == task.get_owner() || - task.list_hashtags().any(|t| t.matches(&self.users.get_username(&p))))) && + (!task.is_task() || self.keys.is_empty() || + self.keys.iter().any(|p| p == &task.get_owner() || + task.list_hashtags().any(|t| t.matches(&self.users.get_username(&p))))) && self.priority.is_none_or(|prio| { task.priority().unwrap_or(DEFAULT_PRIO) >= prio }) && @@ -722,8 +729,8 @@ impl TasksRelay { } } - pub(super) fn find_user(&self, name: &str) -> Option<(PublicKey, String)> { - self.users.find_user_with_displayname(name) + pub(super) fn find_users(&self, name: &str) -> Vec<(PublicKey, String)> { + self.users.find_user_with_displayname(name).collect() } // Movement and Selection @@ -749,19 +756,18 @@ impl TasksRelay { } pub(crate) fn reset_key_filter(&mut self) { - let own = self.sender.pubkey(); - if self.pubkey.is_some_and(|k| k == own) { + if self.keys == self.own_keys { self.view.clear(); info!("Showing everybody's tasks"); - self.pubkey = None + self.keys.clear() } else { info!("Showing own tasks"); - self.pubkey = Some(own) + self.keys = self.own_keys().clone(); } } - pub(crate) fn set_key_filter(&mut self, key: PublicKey) { - self.pubkey = Some(key) + pub(crate) fn set_key_filter(&mut self, key: Vec) { + self.keys = key } pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool { @@ -805,7 +811,7 @@ impl TasksRelay { pub(crate) fn clear_filters(&mut self) { self.state = StateFilter::Default; - self.pubkey = Some(self.sender.pubkey()); + self.keys = self.own_keys().clone(); self.priority = None; self.view.clear(); self.tags.clear(); @@ -1130,7 +1136,7 @@ impl TasksRelay { if tags.iter().any(|t| t.kind() == TagKind::p()) { None } else { - self.pubkey.map(|p| Tag::public_key(p)) + self.keys.first().map(|p| Tag::public_key(*p)) }; let prio = if tags.iter().any(|t| t.kind().to_string() == PRIO) { diff --git a/src/tasks/nostr_users.rs b/src/tasks/nostr_users.rs index faddeb2..ed5e175 100644 --- a/src/tasks/nostr_users.rs +++ b/src/tasks/nostr_users.rs @@ -11,40 +11,43 @@ pub struct NostrUsers { } impl NostrUsers { - pub(crate) fn find_user_with_displayname(&self, term: &str) -> Option<(PublicKey, String)> { + pub(crate) fn find_user_with_displayname(&self, term: &str) -> impl Iterator + '_ { self.find_user(term) + .into_iter() .map(|(k, _)| (*k, self.get_displayname(k))) } // Find username or key starting with the given term. - pub(crate) fn find_user(&self, term: &str) -> Option<(&PublicKey, &Metadata)> { + pub(crate) fn find_user(&self, term: &str) -> Vec<(&PublicKey, &Metadata)> { let lowered = term.trim().to_ascii_lowercase(); let term = lowered.as_str(); if term.is_empty() { - return None; + debug!("Tried to search user by empty term"); + return vec![]; } if let Ok(key) = PublicKey::from_str(term) { - return self.users.get_key_value(&key); + return self.users.get_key_value(&key).into_iter().collect(); } self.users.iter() .sorted_unstable_by_key(|(k, v)| self.get_user_time(k)) .rev() - .find(|(k, v)| + .filter(|(key, meta)| // TODO regex word boundary - v.name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || - v.display_name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || - (term.len() > 4 && k.to_string().starts_with(term))) + meta.name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || + meta.display_name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || + (term.len() > 4 && key.to_string().starts_with(term))) + .collect() } pub(crate) fn get_displayname(&self, pubkey: &PublicKey) -> String { self.users.get(pubkey) - .and_then(|m| m.display_name.clone().or(m.name.clone())) - .unwrap_or_else(|| pubkey.to_string()) + .and_then(|meta| meta.display_name.clone().or(meta.name.clone())) + .map_or_else(|| pubkey.to_string(), |name| format!("{}@{:.6}", name, pubkey.to_string())) } pub(crate) fn get_username(&self, pubkey: &PublicKey) -> String { self.users.get(pubkey) - .and_then(|m| m.name.clone()) + .and_then(|meta| meta.name.clone()) .unwrap_or_else(|| format!("{:.6}", pubkey.to_string())) } diff --git a/src/tasks/tests.rs b/src/tasks/tests.rs index 1bbaa7d..5e46ad2 100644 --- a/src/tasks/tests.rs +++ b/src/tasks/tests.rs @@ -156,7 +156,7 @@ fn test_context() { //env_logger::init(); // ASSIGNEE - assert_eq!(tasks.pubkey, Some(tasks.sender.pubkey())); + assert_eq!(tasks.keys, vec![tasks.sender.pubkey()]); let hoi = tasks.make_task("hoi").unwrap(); let hoi = tasks.get_by_id(&hoi).unwrap(); assert_eq!(hoi.get_owner(), tasks.sender.pubkey()); @@ -169,12 +169,14 @@ fn test_context() { let test1 = tasks.get_by_id(&test1id).unwrap(); assert_eq!(test1.get_owner(), pubkey); - tasks.pubkey = Some(pubkey); + tasks.set_key_filter(vec![pubkey]); let test2id = tasks.make_task("test2").unwrap(); let test2 = tasks.get_by_id(&test2id).unwrap(); assert_eq!(test2.get_owner(), pubkey); - tasks.pubkey = None; + // First sets to own key then to all + tasks.reset_key_filter(); + tasks.reset_key_filter(); let all = tasks.make_task("all").unwrap(); let all = tasks.get_by_id(&all).unwrap(); assert_eq!(all.get_assignee(), None);