feat: multi-key filtering

This commit is contained in:
xeruf 2025-01-29 23:03:42 +01:00
parent 29c104b96b
commit e82f3479fa
5 changed files with 52 additions and 39 deletions

View file

@ -108,8 +108,8 @@ pub(crate) fn extract_tags(input: &str, users: &NostrUsers) -> (String, Vec<Tag>
if let Ok(key) = PublicKey::parse(&s[1..]) { if let Ok(key) = PublicKey::parse(&s[1..]) {
tags.push(Tag::public_key(key)); tags.push(Tag::public_key(key));
return false; return false;
} else if let Some((key, _)) = users.find_user(&s[1..]) { } else if let Some((key, _)) = users.find_user(&s[1..]).first() {
tags.push(Tag::public_key(*key)); tags.push(Tag::public_key(**key));
return false; return false;
} }
} else if s.starts_with('*') { } else if s.starts_with('*') {

View file

@ -517,12 +517,14 @@ async fn main() -> Result<()> {
continue 'repl; continue 'repl;
} }
} }
Some(arg) => { Some("@") => {
if arg == "@" {
tasks.reset_key_filter() tasks.reset_key_filter()
} else if let Some((key, name)) = tasks.find_user(arg) { }
info!("Showing {}'s tasks", name); Some(arg) => {
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 { } else {
if parse_hour(arg, 1) if parse_hour(arg, 1)
.or_else(|| parse_date(arg) .or_else(|| parse_date(arg)
@ -665,7 +667,7 @@ async fn main() -> Result<()> {
} }
} }
println!("{}", tasks.times_tracked(max)); 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); let (label, mut times) = tasks.times_tracked_for(&key);
println!("{}\n{}", label.italic(), times.join("\n")); println!("{}\n{}", label.italic(), times.join("\n"));
} else { } else {

View file

@ -93,7 +93,8 @@ pub(crate) struct TasksRelay {
state: StateFilter, state: StateFilter,
/// Current priority for filtering and new tasks /// Current priority for filtering and new tasks
priority: Option<Prio>, priority: Option<Prio>,
pubkey: Option<PublicKey>, keys: Vec<PublicKey>,
own_keys: Vec<PublicKey>,
sender: EventSender, sender: EventSender,
overflow: VecDeque<Event>, overflow: VecDeque<Event>,
@ -192,7 +193,8 @@ impl TasksRelay {
tags_excluded: Default::default(), tags_excluded: Default::default(),
state: Default::default(), state: Default::default(),
priority: None, priority: None,
pubkey: Some(sender.pubkey()), keys: vec![sender.pubkey()],
own_keys: vec![sender.pubkey()],
search_depth: 4, search_depth: 4,
view_depth: 0, view_depth: 0,
@ -234,6 +236,10 @@ impl TasksRelay {
#[inline] #[inline]
pub(crate) fn len(&self) -> usize { self.tasks.len() } pub(crate) fn len(&self) -> usize { self.tasks.len() }
fn own_keys(&self) -> &Vec<PublicKey> { &self.own_keys }
fn own_key(&self) -> PublicKey { self.sender.pubkey() }
pub(crate) fn get_position(&self) -> Option<EventId> { pub(crate) fn get_position(&self) -> Option<EventId> {
self.get_position_at(now()).1 self.get_position_at(now()).1
} }
@ -278,7 +284,7 @@ impl TasksRelay {
/// Dynamic time tracking overview for current task or current user. /// Dynamic time tracking overview for current task or current user.
pub(crate) fn times_tracked(&self, limit: usize) -> String { 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(); let times = times.collect_vec();
format!("{}\n{}", format!("{}\n{}",
if times.is_empty() { if times.is_empty() {
@ -438,10 +444,10 @@ impl TasksRelay {
} }
pub(crate) fn pubkey_str(&self) -> Option<String> { pub(crate) fn pubkey_str(&self) -> Option<String> {
match self.pubkey { match self.keys.first() {
None => Some("ALL".to_string()), None => Some("ALL".to_string()),
Some(key) => { Some(key) => {
if key != self.sender.pubkey() { if &self.keys != self.own_keys() {
Some(self.users.get_username(&key)) Some(self.users.get_username(&key))
} else { } else {
None None
@ -550,7 +556,8 @@ impl TasksRelay {
fn filter(&self, task: &Task) -> bool { fn filter(&self, task: &Task) -> bool {
self.state.matches(task) && self.state.matches(task) &&
(!task.is_task() || self.pubkey.is_none_or(|p| p == task.get_owner() || (!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))))) && task.list_hashtags().any(|t| t.matches(&self.users.get_username(&p))))) &&
self.priority.is_none_or(|prio| { self.priority.is_none_or(|prio| {
task.priority().unwrap_or(DEFAULT_PRIO) >= 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)> { pub(super) fn find_users(&self, name: &str) -> Vec<(PublicKey, String)> {
self.users.find_user_with_displayname(name) self.users.find_user_with_displayname(name).collect()
} }
// Movement and Selection // Movement and Selection
@ -749,19 +756,18 @@ impl TasksRelay {
} }
pub(crate) fn reset_key_filter(&mut self) { pub(crate) fn reset_key_filter(&mut self) {
let own = self.sender.pubkey(); if self.keys == self.own_keys {
if self.pubkey.is_some_and(|k| k == own) {
self.view.clear(); self.view.clear();
info!("Showing everybody's tasks"); info!("Showing everybody's tasks");
self.pubkey = None self.keys.clear()
} else { } else {
info!("Showing own tasks"); info!("Showing own tasks");
self.pubkey = Some(own) self.keys = self.own_keys().clone();
} }
} }
pub(crate) fn set_key_filter(&mut self, key: PublicKey) { pub(crate) fn set_key_filter(&mut self, key: Vec<PublicKey>) {
self.pubkey = Some(key) self.keys = key
} }
pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool { pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool {
@ -805,7 +811,7 @@ impl TasksRelay {
pub(crate) fn clear_filters(&mut self) { pub(crate) fn clear_filters(&mut self) {
self.state = StateFilter::Default; self.state = StateFilter::Default;
self.pubkey = Some(self.sender.pubkey()); self.keys = self.own_keys().clone();
self.priority = None; self.priority = None;
self.view.clear(); self.view.clear();
self.tags.clear(); self.tags.clear();
@ -1130,7 +1136,7 @@ impl TasksRelay {
if tags.iter().any(|t| t.kind() == TagKind::p()) { if tags.iter().any(|t| t.kind() == TagKind::p()) {
None None
} else { } else {
self.pubkey.map(|p| Tag::public_key(p)) self.keys.first().map(|p| Tag::public_key(*p))
}; };
let prio = let prio =
if tags.iter().any(|t| t.kind().to_string() == PRIO) { if tags.iter().any(|t| t.kind().to_string() == PRIO) {

View file

@ -11,40 +11,43 @@ pub struct NostrUsers {
} }
impl 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<Item=(PublicKey, String)> + '_ {
self.find_user(term) self.find_user(term)
.into_iter()
.map(|(k, _)| (*k, self.get_displayname(k))) .map(|(k, _)| (*k, self.get_displayname(k)))
} }
// Find username or key starting with the given term. // 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 lowered = term.trim().to_ascii_lowercase();
let term = lowered.as_str(); let term = lowered.as_str();
if term.is_empty() { if term.is_empty() {
return None; debug!("Tried to search user by empty term");
return vec![];
} }
if let Ok(key) = PublicKey::from_str(term) { 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() self.users.iter()
.sorted_unstable_by_key(|(k, v)| self.get_user_time(k)) .sorted_unstable_by_key(|(k, v)| self.get_user_time(k))
.rev() .rev()
.find(|(k, v)| .filter(|(key, meta)|
// TODO regex word boundary // TODO regex word boundary
v.name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || meta.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)) || meta.display_name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) ||
(term.len() > 4 && k.to_string().starts_with(term))) (term.len() > 4 && key.to_string().starts_with(term)))
.collect()
} }
pub(crate) fn get_displayname(&self, pubkey: &PublicKey) -> String { pub(crate) fn get_displayname(&self, pubkey: &PublicKey) -> String {
self.users.get(pubkey) self.users.get(pubkey)
.and_then(|m| m.display_name.clone().or(m.name.clone())) .and_then(|meta| meta.display_name.clone().or(meta.name.clone()))
.unwrap_or_else(|| pubkey.to_string()) .map_or_else(|| pubkey.to_string(), |name| format!("{}@{:.6}", name, pubkey.to_string()))
} }
pub(crate) fn get_username(&self, pubkey: &PublicKey) -> String { pub(crate) fn get_username(&self, pubkey: &PublicKey) -> String {
self.users.get(pubkey) self.users.get(pubkey)
.and_then(|m| m.name.clone()) .and_then(|meta| meta.name.clone())
.unwrap_or_else(|| format!("{:.6}", pubkey.to_string())) .unwrap_or_else(|| format!("{:.6}", pubkey.to_string()))
} }

View file

@ -156,7 +156,7 @@ fn test_context() {
//env_logger::init(); //env_logger::init();
// ASSIGNEE // 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.make_task("hoi").unwrap();
let hoi = tasks.get_by_id(&hoi).unwrap(); let hoi = tasks.get_by_id(&hoi).unwrap();
assert_eq!(hoi.get_owner(), tasks.sender.pubkey()); assert_eq!(hoi.get_owner(), tasks.sender.pubkey());
@ -169,12 +169,14 @@ fn test_context() {
let test1 = tasks.get_by_id(&test1id).unwrap(); let test1 = tasks.get_by_id(&test1id).unwrap();
assert_eq!(test1.get_owner(), pubkey); assert_eq!(test1.get_owner(), pubkey);
tasks.pubkey = Some(pubkey); tasks.set_key_filter(vec![pubkey]);
let test2id = tasks.make_task("test2").unwrap(); let test2id = tasks.make_task("test2").unwrap();
let test2 = tasks.get_by_id(&test2id).unwrap(); let test2 = tasks.get_by_id(&test2id).unwrap();
assert_eq!(test2.get_owner(), pubkey); 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.make_task("all").unwrap();
let all = tasks.get_by_id(&all).unwrap(); let all = tasks.get_by_id(&all).unwrap();
assert_eq!(all.get_assignee(), None); assert_eq!(all.get_assignee(), None);