Compare commits

..

No commits in common. "a7c6cf2f59381c4780d18dd0154b6b7371c350c6" and "29c104b96b8d20a31c5abd9612da0e3fdc3a0efe" have entirely different histories.

5 changed files with 91 additions and 99 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..]).first() { } else if let Some((key, _)) = users.find_user(&s[1..]) {
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

@ -22,7 +22,7 @@ use rustyline::error::ReadlineError;
use rustyline::DefaultEditor; use rustyline::DefaultEditor;
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::env; use std::env;
use std::env::args; use std::env::{args};
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader, Write}; use std::io::{BufRead, BufReader, Write};
@ -170,11 +170,11 @@ async fn main() -> Result<()> {
read_keys(&keys_entry, &mut rl)? read_keys(&keys_entry, &mut rl)?
}; };
info!("Your active public key: {}", keys.public_key()); info!("My active public key: {}", keys.public_key());
if args.peek().is_some_and(|arg| arg.trim_start_matches('-') == "export") { if args.peek().is_some_and(|arg| arg.trim_start_matches('-') == "export") {
let enc_pwd = read_password(&mut rl, "Please enter an encryption password for your secret key: ")?; let enc_pwd = read_password(&mut rl, "Please enter an encryption password for your secret key: ")?;
println!("Your encrypted key: {}", EncryptedSecretKey::new(keys.secret_key(), enc_pwd, 9, KeySecurity::Unknown)?.to_bech32()?); println!("Your encrypted key: {}", EncryptedSecretKey::new(keys.secret_key(), enc_pwd, 9, KeySecurity::Unknown)?.to_bech32()?);
if rl.readline("Do you want to erase your stored secret key (y/n)? ")? == "y" { if rl.readline("Do you want to erase your stored secret keys (y/n)? ")? == "y" {
keys_entry.delete_credential()?; keys_entry.delete_credential()?;
} }
// TODO optionally delete // TODO optionally delete
@ -469,7 +469,7 @@ async fn main() -> Result<()> {
if tasks.custom_time.is_none() { tasks.move_up(); } if tasks.custom_time.is_none() { tasks.move_up(); }
} }
Some('&') => Some('&') => {
match arg { match arg {
None => tasks.undo(), None => tasks.undo(),
Some(text) => { Some(text) => {
@ -506,8 +506,9 @@ async fn main() -> Result<()> {
} }
} }
} }
}
Some('@') => Some('@') => {
match arg { match arg {
None => { None => {
let today = Timestamp::now() - 80_000; let today = Timestamp::now() - 80_000;
@ -516,14 +517,12 @@ async fn main() -> Result<()> {
continue 'repl; continue 'repl;
} }
} }
Some("@") => {
tasks.reset_key_filter()
}
Some(arg) => { Some(arg) => {
let users = tasks.find_users(arg); if arg == "@" {
if !users.is_empty() { tasks.reset_key_filter()
info!("Showing tasks for {}", users.iter().map(|(k, v)| v).join(", ")); } else if let Some((key, name)) = tasks.find_user(arg) {
tasks.set_key_filter(users.iter().map(|(k, v)| *k).collect_vec()) info!("Showing {}'s tasks", name);
tasks.set_key_filter(key)
} else { } else {
if parse_hour(arg, 1) if parse_hour(arg, 1)
.or_else(|| parse_date(arg) .or_else(|| parse_date(arg)
@ -537,9 +536,10 @@ async fn main() -> Result<()> {
} }
} }
} }
} };
}
Some('*') => Some('*') => {
match arg { match arg {
None => match tasks.get_position() { None => match tasks.get_position() {
None => { None => {
@ -563,6 +563,7 @@ async fn main() -> Result<()> {
} }
} }
} }
}
Some('|') => Some('|') =>
match arg { match arg {
@ -587,12 +588,13 @@ async fn main() -> Result<()> {
} }
} }
Some('?') => Some('?') => {
match arg { match arg {
None => tasks.set_state_filter(StateFilter::Default), None => tasks.set_state_filter(StateFilter::Default),
Some("?") => tasks.set_state_filter(StateFilter::All), Some("?") => tasks.set_state_filter(StateFilter::All),
Some(arg) => tasks.set_state_filter(StateFilter::State(arg.to_string())), Some(arg) => tasks.set_state_filter(StateFilter::State(arg.to_string())),
} }
}
Some('!') => Some('!') =>
match tasks.get_position() { match tasks.get_position() {
@ -627,10 +629,11 @@ async fn main() -> Result<()> {
} }
} }
Some('#') => Some('#') => {
if !tasks.update_tags(arg_default.split_whitespace().map(Hashtag::from)) { if !tasks.update_tags(arg_default.split_whitespace().map(Hashtag::from)) {
continue; continue;
} }
}
Some('+') => Some('+') =>
match arg { match arg {
@ -662,7 +665,7 @@ async fn main() -> Result<()> {
} }
} }
println!("{}", tasks.times_tracked(max)); println!("{}", tasks.times_tracked(max));
} else if let Some((key, _)) = tasks.find_users(arg).first() { } else if let Some((key, _)) = tasks.find_user(arg) {
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 {
@ -676,7 +679,7 @@ async fn main() -> Result<()> {
continue 'repl; continue 'repl;
} }
Some(')') => Some(')') => {
match arg { match arg {
None => tasks.move_to(None), None => tasks.move_to(None),
Some(arg) => { Some(arg) => {
@ -688,6 +691,7 @@ async fn main() -> Result<()> {
continue 'repl; continue 'repl;
} }
} }
}
Some('.') => { Some('.') => {
let (remaining, dots) = trim_start_count(&command, '.'); let (remaining, dots) = trim_start_count(&command, '.');
@ -716,47 +720,46 @@ async fn main() -> Result<()> {
} }
} }
Some('/') => Some('/') => if arg.is_none() {
if arg.is_none() { tasks.move_to(None);
tasks.move_to(None); } else {
} else { let (remaining, dots) = trim_start_count(&command, '/');
let (remaining, dots) = trim_start_count(&command, '/'); let pos = tasks.up_by(dots - 1);
let pos = tasks.up_by(dots - 1);
if remaining.is_empty() { if remaining.is_empty() {
tasks.move_to(pos);
if dots > 1 {
info!("Moving up {} tasks", dots - 1)
}
} else if let Ok(depth) = remaining.parse::<usize>() {
if pos != tasks.get_position() {
tasks.move_to(pos); tasks.move_to(pos);
if dots > 1 { }
info!("Moving up {} tasks", dots - 1) tasks.set_search_depth(depth);
} } else {
} else if let Ok(depth) = remaining.parse::<usize>() { // TODO regex match
if pos != tasks.get_position() { let mut transform: Box<dyn Fn(&str) -> String> = Box::new(|s: &str| s.to_string());
tasks.move_to(pos); if !remaining.chars().any(|c| c.is_ascii_uppercase()) {
} // Smart-case - case-sensitive if any uppercase char is entered
tasks.set_search_depth(depth); transform = Box::new(|s| s.to_ascii_lowercase());
} else { }
// TODO regex match
let mut transform: Box<dyn Fn(&str) -> String> = Box::new(|s: &str| s.to_string());
if !remaining.chars().any(|c| c.is_ascii_uppercase()) {
// Smart-case - case-sensitive if any uppercase char is entered
transform = Box::new(|s| s.to_ascii_lowercase());
}
let filtered = let filtered =
tasks.get_filtered(pos, |t| { tasks.get_filtered(pos, |t| {
transform(&t.get_title()).contains(&remaining) || transform(&t.get_title()).contains(&remaining) ||
t.list_hashtags().any( t.list_hashtags().any(
|tag| tag.contains(&remaining)) |tag| tag.contains(&remaining))
}); });
if filtered.len() == 1 { if filtered.len() == 1 {
tasks.move_to(filtered.into_iter().next()); tasks.move_to(filtered.into_iter().next());
} else { } else {
tasks.move_to(pos); tasks.move_to(pos);
if !tasks.set_view(filtered) { if !tasks.set_view(filtered) {
continue 'repl; continue 'repl;
}
} }
} }
} }
}
_ => _ =>
if Regex::new("^wss?://").unwrap().is_match(command.trim()) { if Regex::new("^wss?://").unwrap().is_match(command.trim()) {

View file

@ -93,8 +93,7 @@ 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>,
keys: Vec<PublicKey>, pubkey: Option<PublicKey>,
own_keys: Vec<PublicKey>,
sender: EventSender, sender: EventSender,
overflow: VecDeque<Event>, overflow: VecDeque<Event>,
@ -193,8 +192,7 @@ impl TasksRelay {
tags_excluded: Default::default(), tags_excluded: Default::default(),
state: Default::default(), state: Default::default(),
priority: None, priority: None,
keys: vec![sender.pubkey()], pubkey: Some(sender.pubkey()),
own_keys: vec![sender.pubkey()],
search_depth: 4, search_depth: 4,
view_depth: 0, view_depth: 0,
@ -236,10 +234,6 @@ 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
} }
@ -284,7 +278,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.own_key()); // TODO self.own_keys let (label, times) = self.times_tracked_with(&self.sender.pubkey());
let times = times.collect_vec(); let times = times.collect_vec();
format!("{}\n{}", format!("{}\n{}",
if times.is_empty() { if times.is_empty() {
@ -358,8 +352,8 @@ impl TasksRelay {
let mut vec = Vec::with_capacity(set.len() / 2); let mut vec = Vec::with_capacity(set.len() / 2);
let mut iter = timestamps(set.values(), &ids).tuples(); let mut iter = timestamps(set.values(), &ids).tuples();
while let Some(((start, _), (end, _))) = iter.next() { while let Some(((start, _), (end, _))) = iter.next() {
// Filter out intervals <3 mins // Filter out intervals <2 mins
if start.as_u64() + 200 < end.as_u64() { if start.as_u64() + 120 < end.as_u64() {
vec.push(format!( vec.push(format!(
"{} - {} by {}", "{} - {} by {}",
format_timestamp_local(start), format_timestamp_local(start),
@ -444,10 +438,10 @@ impl TasksRelay {
} }
pub(crate) fn pubkey_str(&self) -> Option<String> { pub(crate) fn pubkey_str(&self) -> Option<String> {
match self.keys.first() { match self.pubkey {
None => Some("ALL".to_string()), None => Some("ALL".to_string()),
Some(key) => { Some(key) => {
if &self.keys != self.own_keys() { if key != self.sender.pubkey() {
Some(self.users.get_username(&key)) Some(self.users.get_username(&key))
} else { } else {
None None
@ -556,9 +550,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.keys.is_empty() || (!task.is_task() || self.pubkey.is_none_or(|p| p == task.get_owner() ||
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
}) && }) &&
@ -729,8 +722,8 @@ impl TasksRelay {
} }
} }
pub(super) fn find_users(&self, name: &str) -> Vec<(PublicKey, String)> { pub(super) fn find_user(&self, name: &str) -> Option<(PublicKey, String)> {
self.users.find_user_with_displayname(name).collect() self.users.find_user_with_displayname(name)
} }
// Movement and Selection // Movement and Selection
@ -756,18 +749,19 @@ impl TasksRelay {
} }
pub(crate) fn reset_key_filter(&mut self) { pub(crate) fn reset_key_filter(&mut self) {
if self.keys == self.own_keys { let own = self.sender.pubkey();
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.keys.clear() self.pubkey = None
} else { } else {
info!("Showing own tasks"); info!("Showing own tasks");
self.keys = self.own_keys().clone(); self.pubkey = Some(own)
} }
} }
pub(crate) fn set_key_filter(&mut self, key: Vec<PublicKey>) { pub(crate) fn set_key_filter(&mut self, key: PublicKey) {
self.keys = key self.pubkey = Some(key)
} }
pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool { pub(crate) fn set_filter_since(&mut self, time: Timestamp) -> bool {
@ -811,7 +805,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.keys = self.own_keys().clone(); self.pubkey = Some(self.sender.pubkey());
self.priority = None; self.priority = None;
self.view.clear(); self.view.clear();
self.tags.clear(); self.tags.clear();
@ -1136,7 +1130,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.keys.first().map(|p| Tag::public_key(*p)) self.pubkey.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,43 +11,40 @@ pub struct NostrUsers {
} }
impl NostrUsers { impl NostrUsers {
pub(crate) fn find_user_with_displayname(&self, term: &str) -> impl Iterator<Item=(PublicKey, String)> + '_ { pub(crate) fn find_user_with_displayname(&self, term: &str) -> Option<(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) -> Vec<(&PublicKey, &Metadata)> { pub(crate) fn find_user(&self, term: &str) -> Option<(&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() {
debug!("Tried to search user by empty term"); return None;
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).into_iter().collect(); return self.users.get_key_value(&key);
} }
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()
.filter(|(key, meta)| .find(|(k, v)|
// TODO regex word boundary // TODO regex word boundary
meta.name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) || v.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)) || v.display_name.as_ref().is_some_and(|n| n.to_ascii_lowercase().starts_with(term)) ||
(term.len() > 4 && key.to_string().starts_with(term))) (term.len() > 4 && k.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(|meta| meta.display_name.clone().or(meta.name.clone())) .and_then(|m| m.display_name.clone().or(m.name.clone()))
.map_or_else(|| pubkey.to_string(), |name| format!("{}@{:.6}", name, pubkey.to_string())) .unwrap_or_else(|| 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(|meta| meta.name.clone()) .and_then(|m| m.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.keys, vec![tasks.sender.pubkey()]); assert_eq!(tasks.pubkey, Some(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,14 +169,12 @@ 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.set_key_filter(vec![pubkey]); tasks.pubkey = Some(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);
// First sets to own key then to all tasks.pubkey = None;
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);