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..]) {
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('*') {

View file

@ -517,12 +517,14 @@ async fn main() -> Result<()> {
continue 'repl;
}
}
Some(arg) => {
if arg == "@" {
Some("@") => {
tasks.reset_key_filter()
} else if let Some((key, name)) = tasks.find_user(arg) {
info!("Showing {}'s tasks", name);
tasks.set_key_filter(key)
}
Some(arg) => {
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 {

View file

@ -93,7 +93,8 @@ pub(crate) struct TasksRelay {
state: StateFilter,
/// Current priority for filtering and new tasks
priority: Option<Prio>,
pubkey: Option<PublicKey>,
keys: Vec<PublicKey>,
own_keys: Vec<PublicKey>,
sender: EventSender,
overflow: VecDeque<Event>,
@ -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<PublicKey> { &self.own_keys }
fn own_key(&self) -> PublicKey { self.sender.pubkey() }
pub(crate) fn get_position(&self) -> Option<EventId> {
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<String> {
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,7 +556,8 @@ 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.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<PublicKey>) {
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) {

View file

@ -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<Item=(PublicKey, String)> + '_ {
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()))
}

View file

@ -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);