Compare commits

..

5 Commits

3 changed files with 48 additions and 32 deletions

View File

@ -3,16 +3,18 @@ use log::info;
use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard}; use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard};
use nostr_sdk::TagStandard::Hashtag; use nostr_sdk::TagStandard::Hashtag;
use crate::task::State; use crate::task::{MARKER_PARENT, State};
pub const METADATA_KIND: u16 = 0; pub const METADATA_KIND: u16 = 0;
pub const NOTE_KIND: u16 = 1; pub const NOTE_KIND: u16 = 1;
pub const TASK_KIND: u16 = 1621; pub const TASK_KIND: u16 = 1621;
pub const TRACKING_KIND: u16 = 1650; pub const TRACKING_KIND: u16 = 1650;
pub const KINDS: [u16; 9] = [ pub const KINDS: [u16; 3] = [
METADATA_KIND, METADATA_KIND,
NOTE_KIND, NOTE_KIND,
TASK_KIND, TASK_KIND,
];
pub const PROP_KINDS: [u16; 6] = [
TRACKING_KIND, TRACKING_KIND,
State::Open as u16, State::Open as u16,
State::Done as u16, State::Done as u16,
@ -30,7 +32,7 @@ Immutable:
- `parentid` - unique task id of the parent, if any - `parentid` - unique task id of the parent, if any
- `name` - initial name of the task - `name` - initial name of the task
- `created` - task creation timestamp - `created` - task creation timestamp
- `author` - name of the task creator - `author` - name or abbreviated key of the task creator
Task: Task:
- `status` - pure task status - `status` - pure task status
- `hashtags` - list of hashtags set for the task - `hashtags` - list of hashtags set for the task
@ -98,13 +100,16 @@ fn format_tag(tag: &Tag) -> String {
match tag.as_standardized() { match tag.as_standardized() {
Some(TagStandard::Event { Some(TagStandard::Event {
event_id, event_id,
marker,
.. ..
}) => format!("Parent: {}", event_id.to_string()[..8].to_string()), }) => format!("{}: {:.8}", marker.as_ref().map(|m| m.to_string()).unwrap_or(MARKER_PARENT.to_string()), event_id),
Some(TagStandard::PublicKey { Some(TagStandard::PublicKey {
public_key, public_key,
alias,
.. ..
}) => format!("Key: {}", public_key.to_string()[..8].to_string()), }) => format!("Key{}: {:.8}", public_key.to_string(), alias.as_ref().map(|s| format!(" {s}")).unwrap_or_default()),
Some(TagStandard::Hashtag(content)) => format!("#{content}"), Some(TagStandard::Hashtag(content)) =>
format!("#{content}"),
_ => tag.content().map_or_else( _ => tag.content().map_or_else(
|| format!("Kind {}", tag.kind()), || format!("Kind {}", tag.kind()),
|content| content.to_string(), |content| content.to_string(),

View File

@ -23,7 +23,7 @@ use regex::Regex;
use xdg::BaseDirectories; use xdg::BaseDirectories;
use crate::helpers::*; use crate::helpers::*;
use crate::kinds::{KINDS, PROPERTY_COLUMNS, TRACKING_KIND}; use crate::kinds::{KINDS, PROP_KINDS, PROPERTY_COLUMNS, TRACKING_KIND};
use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State}; use crate::task::{MARKER_DEPENDS, MARKER_PARENT, State};
use crate::tasks::{PropertyCollection, StateFilter, Tasks}; use crate::tasks::{PropertyCollection, StateFilter, Tasks};
@ -180,14 +180,19 @@ async fn main() {
}, },
} }
let sub_id = client.subscribe(vec![ let sub1 = client.subscribe(vec![
Filter::new().kinds(KINDS.into_iter().map(|k| Kind::from(k))) Filter::new().kinds(KINDS.into_iter().map(|k| Kind::from(k)))
], None).await; ], None).await;
info!("Subscribed with {:?}", sub_id); info!("Subscribed to tasks with {:?}", sub1);
let mut notifications = client.notifications(); let mut notifications = client.notifications();
client.connect().await; client.connect().await;
let sub2 = client.subscribe(vec![
Filter::new().kinds(PROP_KINDS.into_iter().map(|k| Kind::from(k)))
], None).await;
info!("Subscribed to updates with {:?}", sub2);
let (tx, rx) = mpsc::channel::<MostrMessage>(); let (tx, rx) = mpsc::channel::<MostrMessage>();
let tasks_for_url = |url: Option<Url>| Tasks::from(url, &tx, &keys); let tasks_for_url = |url: Option<Url>| Tasks::from(url, &tx, &keys);
let mut relays: HashMap<Url, Tasks> = let mut relays: HashMap<Url, Tasks> =
@ -374,11 +379,12 @@ async fn main() {
} }
Some('@') => { Some('@') => {
let author = arg.and_then(|a| PublicKey::from_str(a).ok()).unwrap_or_else(|| keys.public_key()); let key = arg.and_then(|a| PublicKey::from_str(a).ok()).unwrap_or_else(|| keys.public_key());
let author = tasks.get_author(&key);
info!("Filtering for events by {author}"); info!("Filtering for events by {author}");
tasks.set_filter( tasks.set_filter(
tasks.filtered_tasks(tasks.get_position_ref()) tasks.filtered_tasks(tasks.get_position_ref())
.filter(|t| t.event.pubkey == author) .filter(|t| t.event.pubkey == key)
.map(|t| t.event.id) .map(|t| t.event.id)
.collect() .collect()
) )
@ -467,10 +473,13 @@ async fn main() {
} }
Some(')') => { Some(')') => {
tasks.move_to(None); match arg {
if let Some(arg) = arg { None => tasks.move_to(None),
if !tasks.track_from(arg) { Some(arg) => {
continue; if parse_tracking_stamp(arg).map(|stamp| tasks.track_at(stamp, None)).is_none() {
// So the error message is not covered up
continue
}
} }
} }
} }

View File

@ -119,19 +119,21 @@ impl Tasks {
tasks: Default::default(), tasks: Default::default(),
history: Default::default(), history: Default::default(),
users: Default::default(), users: Default::default(),
properties: vec![ properties: [
"state".into(), "author",
"rtime".into(), "state",
"hashtags".into(), "rtime",
"rpath".into(), "hashtags",
"desc".into(), "rpath",
], "desc",
sorting: VecDeque::from([ ].into_iter().map(|s| s.to_string()).collect(),
"state".into(), sorting: [
"hashtags".into(), "author",
"rtime".into(), "state",
"name".into(), "hashtags",
]), "rtime",
"name",
].into_iter().map(|s| s.to_string()).collect(),
view: Default::default(), view: Default::default(),
tags: Default::default(), tags: Default::default(),
tags_excluded: Default::default(), tags_excluded: Default::default(),
@ -212,11 +214,11 @@ impl Tasks {
} else { } else {
format_stamp(end, "%H:%M") format_stamp(end, "%H:%M")
}, },
key)) self.get_author(key)))
} }
iter.into_buffer() iter.into_buffer()
.for_each(|(stamp, _)| .for_each(|(stamp, _)|
vec.push(format!("{} started by {}", local_datetimestamp(stamp), key))); vec.push(format!("{} started by {}", local_datetimestamp(stamp), self.get_author(key))));
vec vec
}).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people }).sorted_unstable(); // TODO sorting depends on timestamp format - needed to interleave different people
(format!("Times Tracked on {:?}", self.get_task_title(&id)), Box::from(history)) (format!("Times Tracked on {:?}", self.get_task_title(&id)), Box::from(history))
@ -502,7 +504,7 @@ impl Tasks {
pub(crate) fn get_author(&self, pubkey: &PublicKey) -> String { pub(crate) fn get_author(&self, pubkey: &PublicKey) -> String {
self.users.get(pubkey) self.users.get(pubkey)
.and_then(|m| m.name.clone()) .and_then(|m| m.name.clone())
.unwrap_or_else(|| pubkey.to_string()) .unwrap_or_else(|| format!("{:.6}", pubkey.to_string()))
} }
// Movement and Selection // Movement and Selection