diff --git a/README.md b/README.md index b1dd77d..93737a9 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,46 @@ as you work. The currently active task is automatically time-tracked. To stop time-tracking completely, simply move to the root of all tasks. +Time-tracking by default recursively summarizes + +### Priorities + +Task priorities can be set as any natural number, +with higher numbers denoting higher priorities. +The syntax here allows for very convenient incremental usage: +By default, using priorities between 1 and 9 is recommended, +with an exemplary interpretation like this: + +* 1 Ideas / "Someday" +* 2 Later +* 3 Soon +* 4 Relevant +* 5 Important +* 9 DO NOW + +Internally, when giving a single digit, a 0 is appended, +so that the default priorities increment in steps of 10. +So in case you need more than 10 priorities, +instead of stacking them on top, +you can granularly add them in between. +For example, `12` is in between `1` and `2` +which are equivalent to `10` and `20`, +not above `9` but above `09`! + +By default, only tasks with priority `35` and upward are shown +so you can focus on what matters, +but you can temporarily override that using `**PRIO`. + +### Quick Access + +Paper-based lists are often popular because you can quickly put down a bunch of items. +Mostr offers three useful workflows depending on the use-case: +If you want to TBC... + +- temporary task with subtasks (especially handy for progression) +- Filter by recently created +- Pin to bookmarks +- high priority ## Reference diff --git a/src/kinds.rs b/src/kinds.rs index c0a03a6..3e0ee5d 100644 --- a/src/kinds.rs +++ b/src/kinds.rs @@ -1,10 +1,11 @@ +use crate::task::{State, MARKER_PARENT}; +use crate::tasks::HIGH_PRIO; use itertools::Itertools; use log::info; use nostr_sdk::TagStandard::Hashtag; -use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagStandard}; -use std::collections::HashSet; - -use crate::task::{State, MARKER_PARENT}; +use nostr_sdk::{Alphabet, EventBuilder, EventId, Kind, Tag, TagKind, TagStandard}; +use std::borrow::Cow; +use std::iter::once; pub const TASK_KIND: Kind = Kind::GitIssue; pub const PROCEDURE_KIND_ID: u16 = 1639; @@ -25,6 +26,8 @@ pub const PROP_KINDS: [Kind; 6] = [ PROCEDURE_KIND, ]; +pub const PRIO: &str = "priority"; + // TODO: use formatting - bold / heading / italics - and generate from code /// Helper for available properties. pub const PROPERTY_COLUMNS: &str = @@ -95,16 +98,31 @@ pub(crate) fn extract_hashtags(input: &str) -> impl Iterator + '_ { /// as well as various embedded tags. /// /// Expects sanitized input. -pub(crate) fn extract_tags(input: &str) -> (&str, Vec) { - match input.split_once(" # ") { - None => (input, extract_hashtags(input).collect_vec()), - Some((name, tags)) => { - let tags = extract_hashtags(name) - .chain(tags.split_ascii_whitespace().map(to_hashtag)) - .collect(); - (name, tags) +pub(crate) fn extract_tags(input: &str) -> (String, Vec) { + let words = input.split_ascii_whitespace(); + let mut prio = None; + let result = words.filter(|s| { + if s.starts_with('*') { + if s.len() == 1 { + prio = Some(HIGH_PRIO); + return false + } + return match s[1..].parse::() { + Ok(num) => { + prio = Some(num * (if s.len() > 2 { 1 } else { 10 })); + false + }, + _ => true, + } } - } + true + }).collect_vec(); + let mut split = result.split(|e| { e == &"#" }); + let main = split.next().unwrap().join(" "); + let tags = extract_hashtags(&main) + .chain(split.flatten().map(|s| to_hashtag(&s))) + .chain(prio.map(|p| to_prio_tag(&p.to_string()))).collect(); + (main, tags) } fn to_hashtag(tag: &str) -> Tag { @@ -137,9 +155,14 @@ pub(crate) fn is_hashtag(tag: &Tag) -> bool { .is_some_and(|letter| letter.character == Alphabet::T) } +pub(crate) fn to_prio_tag(value: &str) -> Tag { + Tag::custom(TagKind::Custom(Cow::from(PRIO)), [if value.len() < 2 { format!("{value}0") } else { value.to_string() }]) +} #[test] fn test_extract_tags() { - assert_eq!(extract_tags("Hello from #mars with #greetings # yeah done-it"), - ("Hello from #mars with #greetings", ["mars", "greetings", "yeah", "done-it"].into_iter().map(to_hashtag).collect())) + assert_eq!(extract_tags("Hello from #mars with #greetings *4 # yeah done-it"), + ("Hello from #mars with #greetings".to_string(), + ["mars", "greetings", "yeah", "done-it"].into_iter().map(to_hashtag) + .chain(once(Tag::custom(TagKind::Custom(Cow::from(PRIO)), [40.to_string()]))).collect())) } \ No newline at end of file diff --git a/src/tasks.rs b/src/tasks.rs index d8e38c1..75b26c9 100644 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -19,6 +19,10 @@ use regex::bytes::Regex; use tokio::sync::mpsc::Sender; use TagStandard::Hashtag; +const DEFAULT_PRIO: u16 = 25; +pub const HIGH_PRIO: u16 = 85; + +/// Amount of seconds to treat as "now" const MAX_OFFSET: u64 = 9; fn now() -> Timestamp { Timestamp::now() + MAX_OFFSET @@ -856,7 +860,7 @@ impl TasksRelay { pub(crate) fn make_task_with(&mut self, input: &str, tags: impl IntoIterator, set_state: bool) -> EventId { let (input, input_tags) = extract_tags(input.trim()); let id = self.submit( - build_task(input, input_tags, None) + build_task(&input, input_tags, None) .add_tags(self.tags.iter().cloned()) .add_tags(tags) ); @@ -1060,7 +1064,7 @@ impl TasksRelay { } let (input, tags) = extract_tags(note.trim()); self.submit( - build_task(input, tags, Some(("activity", Kind::TextNote))) + build_task(&input, tags, Some(("activity", Kind::TextNote))) .add_tags(self.parent_tag()) .add_tags(self.tags.iter().cloned()) )