Compare commits
3 Commits
ed5e4d97d7
...
960a5210c6
Author | SHA1 | Date |
---|---|---|
xeruf | 960a5210c6 | |
xeruf | fd94de7149 | |
xeruf | ce7e015b02 |
11
README.md
11
README.md
|
@ -22,7 +22,9 @@ Recommendation: Flat hierarchy, using tags for filtering (TBI)
|
||||||
|
|
||||||
## Reference
|
## Reference
|
||||||
|
|
||||||
TASK add syntax: `NAME: TAG1 TAG2`
|
### Command Syntax
|
||||||
|
|
||||||
|
TASK add syntax: `NAME: TAG1 TAG2 ...`
|
||||||
|
|
||||||
- `TASK` - create task
|
- `TASK` - create task
|
||||||
- `.` - clear filters and reload
|
- `.` - clear filters and reload
|
||||||
|
@ -35,26 +37,29 @@ Dots can be repeated to move to parent tasks
|
||||||
- `>[TEXT]` - Complete active task and move to parent, with optional state description
|
- `>[TEXT]` - Complete active task and move to parent, with optional state description
|
||||||
- `<[TEXT]` - Close active task and move to parent, with optional state description
|
- `<[TEXT]` - Close active task and move to parent, with optional state description
|
||||||
- `-TEXT` - add text note (comment / description)
|
- `-TEXT` - add text note (comment / description)
|
||||||
|
- `#TAG` - filter by tag
|
||||||
|
|
||||||
State descriptions can be used for example for Kanban columns.
|
State descriptions can be used for example for Kanban columns.
|
||||||
|
|
||||||
### Columns
|
### Available Columns
|
||||||
|
|
||||||
- `id`
|
- `id`
|
||||||
- `parentid`
|
- `parentid`
|
||||||
- `name`
|
- `name`
|
||||||
- `state`
|
- `state`
|
||||||
|
- `tags`
|
||||||
- `desc` - accumulated notes of the task
|
- `desc` - accumulated notes of the task
|
||||||
- `path` - name including parent tasks
|
- `path` - name including parent tasks
|
||||||
- `rpath` - name including parent tasks up to active task
|
- `rpath` - name including parent tasks up to active task
|
||||||
- `time` - time tracked
|
- `time` - time tracked
|
||||||
- `ttime` - time tracked including subtasks
|
- `ttime` - time tracked including subtasks
|
||||||
- TBI: `progress` - how many subtasks are complete
|
- TBI: `progress` - how many subtasks are complete
|
||||||
|
- TBI: `progressp` - subtask completion in percent
|
||||||
|
|
||||||
For debugging: `props` - Task Property Events
|
For debugging: `props` - Task Property Events
|
||||||
|
|
||||||
## Plans
|
## Plans
|
||||||
|
|
||||||
- Expiry (no need to fetch potential years of history)
|
- Expiry (no need to fetch potential years of history)
|
||||||
- Web Interface, Messenger bots
|
- Web Interface, Messenger integrations
|
||||||
- TUI - Clear terminal?
|
- TUI - Clear terminal?
|
|
@ -201,6 +201,10 @@ async fn main() {
|
||||||
tasks.move_up()
|
tasks.move_up()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some('#') => {
|
||||||
|
tasks.add_tag(input[1..].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
Some('.') => {
|
Some('.') => {
|
||||||
let mut dots = 1;
|
let mut dots = 1;
|
||||||
let mut pos = tasks.get_position();
|
let mut pos = tasks.get_position();
|
||||||
|
|
11
src/task.rs
11
src/task.rs
|
@ -7,13 +7,16 @@ pub(crate) struct Task {
|
||||||
pub(crate) event: Event,
|
pub(crate) event: Event,
|
||||||
pub(crate) children: HashSet<EventId>,
|
pub(crate) children: HashSet<EventId>,
|
||||||
pub(crate) props: BTreeSet<Event>,
|
pub(crate) props: BTreeSet<Event>,
|
||||||
|
/// Cached sorted tags of the event
|
||||||
|
pub(crate) tags: Option<BTreeSet<Tag>>,
|
||||||
}
|
}
|
||||||
impl Task {
|
impl Task {
|
||||||
pub(crate) fn new(event: Event) -> Task {
|
pub(crate) fn new(event: Event) -> Task {
|
||||||
Task {
|
Task {
|
||||||
event,
|
|
||||||
children: Default::default(),
|
children: Default::default(),
|
||||||
props: Default::default(),
|
props: Default::default(),
|
||||||
|
tags: if event.tags.is_empty() { None } else { Some(event.tags.iter().cloned().collect()) },
|
||||||
|
event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +103,12 @@ impl Task {
|
||||||
"state" => self.state().map(|s| s.to_string()),
|
"state" => self.state().map(|s| s.to_string()),
|
||||||
"name" => Some(self.event.content.clone()),
|
"name" => Some(self.event.content.clone()),
|
||||||
"time" => Some(self.time_tracked().to_string()), // TODO: format properly
|
"time" => Some(self.time_tracked().to_string()), // TODO: format properly
|
||||||
|
"tags" => self.tags.as_ref().map(|tags| {
|
||||||
|
tags.iter()
|
||||||
|
.map(|t| format!("{}", t.content().unwrap()))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" ")
|
||||||
|
}),
|
||||||
"props" => Some(format!(
|
"props" => Some(format!(
|
||||||
"{:?}",
|
"{:?}",
|
||||||
self.props
|
self.props
|
||||||
|
|
47
src/tasks.rs
47
src/tasks.rs
|
@ -1,7 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
|
||||||
use nostr_sdk::{Event, EventBuilder, EventId, Keys, Kind, Tag};
|
use nostr_sdk::{Event, EventBuilder, EventId, Keys, Kind, Tag};
|
||||||
|
use nostr_sdk::Tag::Hashtag;
|
||||||
|
|
||||||
use crate::{EventSender, TASK_KIND};
|
use crate::{EventSender, TASK_KIND};
|
||||||
use crate::task::{State, Task};
|
use crate::task::{State, Task};
|
||||||
|
@ -17,8 +18,10 @@ pub(crate) struct Tasks {
|
||||||
/// Positive: Go down the respective level
|
/// Positive: Go down the respective level
|
||||||
pub(crate) depth: i8,
|
pub(crate) depth: i8,
|
||||||
|
|
||||||
/// The task currently selected.
|
/// Currently active task
|
||||||
position: Option<EventId>,
|
position: Option<EventId>,
|
||||||
|
/// Currently active tags
|
||||||
|
tags: BTreeSet<Tag>,
|
||||||
/// A filtered view of the current tasks
|
/// A filtered view of the current tasks
|
||||||
view: Vec<EventId>,
|
view: Vec<EventId>,
|
||||||
|
|
||||||
|
@ -32,6 +35,7 @@ impl Tasks {
|
||||||
properties: vec!["id".into(), "name".into(), "state".into(), "ttime".into()],
|
properties: vec!["id".into(), "name".into(), "state".into(), "ttime".into()],
|
||||||
position: None,
|
position: None,
|
||||||
view: Default::default(),
|
view: Default::default(),
|
||||||
|
tags: Default::default(),
|
||||||
depth: 1,
|
depth: 1,
|
||||||
sender,
|
sender,
|
||||||
}
|
}
|
||||||
|
@ -65,6 +69,12 @@ impl Tasks {
|
||||||
|
|
||||||
pub(crate) fn taskpath(&self, id: Option<EventId>) -> String {
|
pub(crate) fn taskpath(&self, id: Option<EventId>) -> String {
|
||||||
join_tasks(self.traverse_up_from(id))
|
join_tasks(self.traverse_up_from(id))
|
||||||
|
+ &self
|
||||||
|
.tags
|
||||||
|
.iter()
|
||||||
|
.map(|t| format!(" #{}", t.content().unwrap()))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn traverse_up_from(&self, id: Option<EventId>) -> ParentIterator {
|
pub(crate) fn traverse_up_from(&self, id: Option<EventId>) -> ParentIterator {
|
||||||
|
@ -132,7 +142,7 @@ impl Tasks {
|
||||||
if res.len() > 0 {
|
if res.len() > 0 {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
self.position.map_or_else(
|
let tasks = self.position.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
if self.depth > 8 {
|
if self.depth > 8 {
|
||||||
self.tasks.values().collect()
|
self.tasks.values().collect()
|
||||||
|
@ -155,7 +165,20 @@ impl Tasks {
|
||||||
.get(&p)
|
.get(&p)
|
||||||
.map_or(Vec::new(), |t| self.resolve_tasks(t.children.iter()))
|
.map_or(Vec::new(), |t| self.resolve_tasks(t.children.iter()))
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
if self.tags.is_empty() {
|
||||||
|
tasks
|
||||||
|
} else {
|
||||||
|
tasks
|
||||||
|
.into_iter()
|
||||||
|
.filter(|t| {
|
||||||
|
t.tags.as_ref().map_or(false, |tags| {
|
||||||
|
let mut iter = tags.iter();
|
||||||
|
self.tags.iter().all(|tag| iter.any(|t| t == tag))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn print_current_tasks(&self) {
|
pub(crate) fn print_current_tasks(&self) {
|
||||||
|
@ -187,6 +210,11 @@ impl Tasks {
|
||||||
self.view = view
|
self.view = view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_tag(&mut self, tag: String) {
|
||||||
|
self.view.clear();
|
||||||
|
self.tags.insert(Hashtag(tag));
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn move_up(&mut self) {
|
pub(crate) fn move_up(&mut self) {
|
||||||
self.move_to(
|
self.move_to(
|
||||||
self.position
|
self.position
|
||||||
|
@ -197,6 +225,7 @@ impl Tasks {
|
||||||
|
|
||||||
pub(crate) fn move_to(&mut self, id: Option<EventId>) {
|
pub(crate) fn move_to(&mut self, id: Option<EventId>) {
|
||||||
self.view.clear();
|
self.view.clear();
|
||||||
|
self.tags.clear();
|
||||||
if id == self.position {
|
if id == self.position {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -220,18 +249,12 @@ impl Tasks {
|
||||||
// Updates
|
// Updates
|
||||||
|
|
||||||
pub(crate) fn build_task(&self, input: &str) -> EventBuilder {
|
pub(crate) fn build_task(&self, input: &str) -> EventBuilder {
|
||||||
let mut tags: Vec<Tag> = Vec::new();
|
let mut tags: Vec<Tag> = self.tags.iter().cloned().collect();
|
||||||
self.position.inspect(|p| tags.push(Tag::event(*p)));
|
self.position.inspect(|p| tags.push(Tag::event(*p)));
|
||||||
return match input.split_once(": ") {
|
return match input.split_once(": ") {
|
||||||
None => EventBuilder::new(Kind::from(TASK_KIND), input, tags),
|
None => EventBuilder::new(Kind::from(TASK_KIND), input, tags),
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
tags.append(
|
tags.append(&mut s.1.split(" ").map(|t| Hashtag(t.to_string())).collect());
|
||||||
&mut s
|
|
||||||
.1
|
|
||||||
.split(" ")
|
|
||||||
.map(|t| Tag::Hashtag(t.to_string()))
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
EventBuilder::new(Kind::from(TASK_KIND), s.0, tags)
|
EventBuilder::new(Kind::from(TASK_KIND), s.0, tags)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue