Skip to content

Commit

Permalink
Merge pull request #5 from mark-pitblado/develop
Browse files Browse the repository at this point in the history
Add task description through task entry box with {}
mark-pitblado authored Nov 6, 2024
2 parents 671f14b + 86db899 commit 8ee19ca
Showing 5 changed files with 118 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ api_key = "<your-key-here>"
- Add tasks
- Title
- Priority, via ![1-5] (vikunja quick add magic syntax)
- Description, via {} in the add task flow

## Roadmap

5 changes: 5 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ pub async fn create_new_task(
instance_url: &str,
api_key: &str,
task_title: &str,
description: Option<&str>,
priority: Option<u8>,
) -> Result<(), Box<dyn Error>> {
let client = Client::new();
@@ -58,6 +59,10 @@ pub async fn create_new_task(
"title": task_title
});

if let Some(desc) = description {
task_data["description"] = json!(desc);
}

if let Some(priority_value) = priority {
task_data["priority"] = json!(priority_value);
}
3 changes: 1 addition & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -151,14 +151,13 @@ impl App {
match key.code {
KeyCode::Enter => {
if !self.new_task_title.trim().is_empty() {
// Use the parser to extract the task title, priority, and label titles
let parsed_task = parse_task_input(&self.new_task_title);

// Create the new task with the parsed title, priority, and labels
if let Err(err) = create_new_task(
instance_url,
api_key,
&parsed_task.title,
parsed_task.description.as_deref(),
parsed_task.priority,
)
.await
77 changes: 75 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
@@ -3,14 +3,18 @@ use regex::Regex;
#[derive(Debug, PartialEq)]
pub struct ParsedTask {
pub title: String,
pub description: Option<String>,
pub priority: Option<u8>,
}

pub fn parse_task_input(input: &str) -> ParsedTask {
let priority_re = Regex::new(r"!(\d+)\s*").unwrap();
let description_re = Regex::new(r"\{([^}]*)\}").unwrap();

let mut priority = None;
let mut description = None;

// Priority
for caps in priority_re.captures_iter(input) {
if let Some(priority_match) = caps.get(1) {
if let Ok(p) = priority_match.as_str().parse::<u8>() {
@@ -21,27 +25,90 @@ pub fn parse_task_input(input: &str) -> ParsedTask {
}
}

let title = priority_re.replace_all(input, "");
// Description
let mut title = input.to_string();
if let Some(caps) = description_re.captures(&title) {
if let Some(desc_match) = caps.get(1) {
description = Some(desc_match.as_str().trim().to_string());
}
}

title = description_re.replace_all(&title, "").to_string();

title = priority_re.replace_all(&title, "").to_string();

let title = Regex::new(r"\s+")
.unwrap()
.replace_all(&title, " ")
.trim()
.to_string();

ParsedTask { title, priority }
ParsedTask {
title,
priority,
description,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_with_description() {
let input = "Implement feature X {This is the description of the task}";
let expected = ParsedTask {
title: "Implement feature X".to_string(),
description: Some("This is the description of the task".to_string()),
priority: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
}

#[test]
fn test_parse_with_description_and_priority() {
let input = "Fix bug in module !2 {Critical issue that needs immediate attention}";
let expected = ParsedTask {
title: "Fix bug in module".to_string(),
description: Some("Critical issue that needs immediate attention".to_string()),
priority: Some(2),
};
let result = parse_task_input(input);
assert_eq!(result, expected);
}

#[test]
fn test_parse_with_description_and_priority_in_any_order() {
let input = "{Detailed description here} Update documentation !3";
let expected = ParsedTask {
title: "Update documentation".to_string(),
description: Some("Detailed description here".to_string()),
priority: Some(3),
};
let result = parse_task_input(input);
assert_eq!(result, expected);
}

#[test]
fn test_parse_with_multiple_descriptions() {
let input = "Task title {First description} {Second description}";
let expected = ParsedTask {
title: "Task title".to_string(),
description: Some("First description".to_string()),
priority: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
}

#[test]
fn test_parse_with_priority_in_middle() {
let input = "Update !4 software documentation";
let expected = ParsedTask {
title: "Update software documentation".to_string(),
priority: Some(4),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -53,6 +120,7 @@ mod tests {
let expected = ParsedTask {
title: "Fix bugs in the code".to_string(),
priority: Some(2),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -64,6 +132,7 @@ mod tests {
let expected = ParsedTask {
title: "Write tests for the parser".to_string(),
priority: Some(3),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -75,6 +144,7 @@ mod tests {
let expected = ParsedTask {
title: "Deploy to production".to_string(),
priority: Some(5),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -86,6 +156,7 @@ mod tests {
let expected = ParsedTask {
title: "Prepare presentation slides".to_string(),
priority: Some(2),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -97,6 +168,7 @@ mod tests {
let expected = ParsedTask {
title: "Organize team building event".to_string(),
priority: Some(1),
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
@@ -108,6 +180,7 @@ mod tests {
let expected = ParsedTask {
title: "Check logs immediately".to_string(),
priority: None,
description: None,
};
let result = parse_task_input(input);
assert_eq!(result, expected);
47 changes: 36 additions & 11 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -12,14 +12,14 @@ use ratatui::{
use std::io;
use std::time::Duration;

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
fn centered_rect_absolute(width: u16, height: u16, r: Rect) -> Rect {
let popup_layout = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Length((r.height.saturating_sub(height)) / 2),
Constraint::Length(height),
Constraint::Length((r.height.saturating_sub(height) + 1) / 2),
]
.as_ref(),
)
@@ -29,9 +29,9 @@ fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Length((r.width.saturating_sub(width)) / 2),
Constraint::Length(width),
Constraint::Length((r.width.saturating_sub(width) + 1) / 2),
]
.as_ref(),
)
@@ -243,16 +243,32 @@ pub async fn run_app<B: Backend>(
}
}
InputMode::Editing => {
let popup_area = centered_rect(60, 10, body_chunk);
let popup_width_percentage = 60;
let popup_width = (size.width * popup_width_percentage / 100).saturating_sub(2);

let lines_required = calculate_wrapped_lines(&app.new_task_title, popup_width);

let min_required_height = 1;

let required_height = std::cmp::max(lines_required as u16, min_required_height);

let popup_height = required_height + 2;

let max_popup_height = size.height - 2;
let popup_height = std::cmp::min(popup_height, max_popup_height);

let popup_area =
centered_rect_absolute(popup_width + 2, popup_height, body_chunk);

let popup_block = Block::default()
.title("Enter New Task Title (Press Enter to Submit)")
.title("Enter New Task (Press Enter to Submit)")
.borders(Borders::ALL)
.style(Style::default().fg(Color::Green));

let input = Paragraph::new(Text::from(app.new_task_title.as_str()))
let input = Paragraph::new(app.new_task_title.as_str())
.style(Style::default().fg(Color::White))
.block(popup_block);
.block(popup_block)
.wrap(Wrap { trim: false });

f.render_widget(Clear, popup_area);
f.render_widget(input, popup_area);
@@ -279,3 +295,12 @@ pub async fn run_app<B: Backend>(
}
}
}

fn calculate_wrapped_lines(text: &str, max_width: u16) -> usize {
let mut line_count = 0;
for line in text.lines() {
let line_width = line.chars().count() as u16;
line_count += ((line_width + max_width - 1) / max_width) as usize;
}
line_count
}

0 comments on commit 8ee19ca

Please sign in to comment.