Skip to content

Commit 0f26a5c

Browse files
committed
feat(export): added markdown export functionality
1 parent 85664b4 commit 0f26a5c

File tree

9 files changed

+93
-5
lines changed

9 files changed

+93
-5
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ chrono = "0.4.35"
2121
tokio = "1"
2222
done_core = { git = "https://github.com/edfloreshz/done" }
2323
tracing = "0.1.40"
24+
cli-clipboard = "0.4.0"
2425

2526
[dependencies.libcosmic]
2627
git = "https://github.com/pop-os/libcosmic.git"

i18n/en/cosmic_tasks.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ icon-select-body = Choose an icon for the list
4141
# Date Dialog
4242
select-date = Select a date
4343
44+
# Export Dialog
45+
export = Export
46+
4447
# Dialogs
4548
cancel = Cancel
4649
ok = Ok
50+
copy = Copy
4751
confirm = Confirm
4852
save = Save
4953
list-name = List name

res/icons/bundled/share-symbolic.svg

+2
Loading

src/app.rs

+40-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::collections::{HashMap, VecDeque};
33
use std::{env, process};
44

55
use chrono::{Local, NaiveDate};
6+
use cli_clipboard::{ClipboardContext, ClipboardProvider};
67
use cosmic::app::{message, Core, Message as CosmicMessage};
78
use cosmic::iced::alignment::{Horizontal, Vertical};
89
use cosmic::iced::keyboard::{Key, Modifiers};
@@ -18,6 +19,7 @@ use cosmic::{
1819
Command, Element,
1920
};
2021
use done_core::models::list::List;
22+
use done_core::models::task::Task;
2123
use done_core::service::Service;
2224

2325
use crate::app::config::{AppTheme, CONFIG_VERSION};
@@ -31,6 +33,7 @@ pub mod icon_cache;
3133
mod key_bind;
3234
pub mod localize;
3335
pub mod menu;
36+
pub mod markdown;
3437

3538
pub struct App {
3639
core: Core,
@@ -67,10 +70,12 @@ pub enum Message {
6770
OpenRenameListDialog,
6871
OpenDeleteListDialog,
6972
OpenIconDialog,
73+
OpenCalendarDialog,
74+
OpenExportDialog(String),
7075
AddList(List),
7176
DeleteList,
72-
OpenCalendarDialog,
7377
Focus(widget::Id),
78+
Export(Vec<Task>),
7479
}
7580

7681
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -97,6 +102,7 @@ pub enum DialogPage {
97102
Rename { to: String },
98103
Delete,
99104
Calendar(NaiveDate),
105+
Export(String),
100106
}
101107

102108
#[derive(Clone, Debug)]
@@ -746,6 +752,22 @@ impl Application for App {
746752
);
747753
dialog
748754
}
755+
DialogPage::Export(contents) => {
756+
let dialog = widget::dialog(fl!("export"))
757+
.control(
758+
widget::container(scrollable(widget::text(contents)).width(Length::Fill))
759+
.height(Length::Fixed(200.0)).width(Length::Fill),
760+
)
761+
.primary_action(
762+
widget::button::suggested(fl!("copy"))
763+
.on_press_maybe(Some(Message::DialogComplete)),
764+
)
765+
.secondary_action(
766+
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
767+
);
768+
769+
dialog
770+
}
749771
};
750772

751773
Some(dialog.into())
@@ -931,6 +953,9 @@ impl Application for App {
931953
});
932954
commands.push(command);
933955
}
956+
content::Command::Export(tasks) => {
957+
commands.push(self.update(Message::Export(tasks)));
958+
}
934959
}
935960
}
936961
}
@@ -1034,6 +1059,12 @@ impl Application for App {
10341059
}
10351060
self.nav_model.remove(self.nav_model.active());
10361061
}
1062+
Message::Export(tasks) => {
1063+
if let Some(list) = self.nav_model.data::<List>(self.nav_model.active()) {
1064+
let exported_markdown = todo::export_list(list.clone(), tasks);
1065+
commands.push(self.update(Message::OpenExportDialog(exported_markdown)));
1066+
}
1067+
}
10371068
Message::OpenNewListDialog => {
10381069
self.dialog_pages.push_back(DialogPage::New(String::new()));
10391070
return widget::text_input::focus(self.dialog_text_input.clone());
@@ -1068,6 +1099,10 @@ impl Application for App {
10681099
self.dialog_pages
10691100
.push_back(DialogPage::Calendar(Local::now().date_naive()));
10701101
}
1102+
Message::OpenExportDialog(content) => {
1103+
self.dialog_pages
1104+
.push_back(DialogPage::Export(content));
1105+
}
10711106
Message::DialogCancel => {
10721107
self.dialog_pages.pop_front();
10731108
}
@@ -1116,6 +1151,10 @@ impl Application for App {
11161151
DialogPage::Calendar(date) => {
11171152
self.details.update(details::Message::SetDueDate(date));
11181153
}
1154+
DialogPage::Export(content) => {
1155+
let mut clipboard = ClipboardContext::new().unwrap();
1156+
clipboard.set_contents(content).unwrap();
1157+
}
11191158
}
11201159
}
11211160
}

src/app/icon_cache.rs

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl IconCache {
4444
bundle!("replace-symbolic", 18);
4545
bundle!("replace-all-symbolic", 18);
4646
bundle!("window-close-symbolic", 18);
47+
bundle!("share-symbolic", 18);
4748

4849
bundle!("paper-plane-symbolic", 18);
4950
bundle!("task-past-due-symbolic", 18);

src/app/markdown.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use done_core::models::list::List;
2+
use done_core::models::status::Status;
3+
use done_core::models::task::Task;
4+
5+
pub trait Markdown {
6+
fn markdown(&self) -> String;
7+
}
8+
9+
impl Markdown for List {
10+
fn markdown(&self) -> String {
11+
format!("# {}\n", self.name)
12+
}
13+
}
14+
15+
impl Markdown for Task {
16+
fn markdown(&self) -> String {
17+
let mut task = format!("- [{}] {}\n", if self.status == Status::Completed { "x" } else { " " }, self.title);
18+
let sub_tasks = self.sub_tasks.iter().fold(String::new(), |acc, sub_task| {
19+
format!("{} - [{}] {}\n", acc, if sub_task.status == Status::Completed { "x" } else { " " }, sub_task.title)
20+
});
21+
task.push_str(&sub_tasks);
22+
task
23+
}
24+
}

src/content.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub enum Message {
2828
Input(String),
2929
AddTask,
3030
UpdateTask(Task),
31+
Export(Vec<Task>),
3132
}
3233

3334
pub enum Command {
@@ -36,6 +37,7 @@ pub enum Command {
3637
UpdateTask(Task),
3738
Delete(String),
3839
CreateTask(Task),
40+
Export(Vec<Task>),
3941
}
4042

4143
impl Content {
@@ -48,17 +50,23 @@ impl Content {
4850
}
4951

5052
fn list_header<'a>(&'a self, list: &'a List) -> Element<'a, Message> {
51-
let cosmic_theme::Spacing { space_s, .. } = theme::active().cosmic().spacing;
53+
let cosmic_theme::Spacing { space_none, space_xxs, space_s, .. } = theme::active().cosmic().spacing;
54+
let export_button = widget::button(config::get_icon("share-symbolic", 18))
55+
.style(theme::Button::Suggested)
56+
.padding(space_xxs)
57+
.on_press(Message::Export(self.tasks.clone()));
5258

53-
widget::row::with_capacity(2)
59+
widget::row::with_capacity(3)
5460
.align_items(Alignment::Center)
5561
.spacing(space_s)
62+
.padding([space_none, space_xxs])
5663
.push_maybe(
5764
list.icon
5865
.as_deref()
5966
.map(|icon| widget::icon::from_name(icon).size(24).icon()),
6067
)
61-
.push(widget::text::title3(&list.name))
68+
.push(widget::text::title3(&list.name).width(Length::Fill))
69+
.push(export_button)
6270
.into()
6371
}
6472

@@ -212,6 +220,9 @@ impl Content {
212220
commands.push(Command::UpdateTask(task.clone()));
213221
}
214222
}
223+
Message::Export(tasks) => {
224+
commands.push(Command::Export(tasks));
225+
}
215226
}
216227
commands
217228
}

src/details.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::ops::IndexMut;
22

33
use crate::app::config;
4-
use crate::app::config::get_icon;
54
use chrono::{NaiveDate, TimeZone, Utc};
65
use cosmic::iced::{Alignment, Length};
76
use cosmic::iced_widget::row;

src/todo.rs

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use done_core::models::list::List;
22
use done_core::models::task::Task;
33
use done_core::service::Service;
4+
use crate::app::markdown::Markdown;
45
use std::error::Error;
56

67
pub async fn update_list(list: List) -> Result<(), Box<dyn Error>> {
@@ -45,3 +46,9 @@ pub async fn delete_task(list_id: String, task_id: String) -> Result<(), Box<dyn
4546
let mut service = Service::Computer.get_service();
4647
Ok(service.delete_task(list_id, task_id).await?)
4748
}
49+
50+
pub fn export_list(list: List, tasks: Vec<Task>) -> String {
51+
let markdown = list.markdown();
52+
let tasks_markdown: String = tasks.iter().map(|task| task.markdown()).collect();
53+
format!("{}\n{}", markdown, tasks_markdown)
54+
}

0 commit comments

Comments
 (0)