Skip to content

feat(export): added markdown export functionality #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ chrono = "0.4.35"
tokio = "1"
done_core = { git = "https://github.com/edfloreshz/done" }
tracing = "0.1.40"
cli-clipboard = "0.4.0"

[dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic.git"
Expand Down
4 changes: 4 additions & 0 deletions i18n/en/cosmic_tasks.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ icon-select-body = Choose an icon for the list
# Date Dialog
select-date = Select a date

# Export Dialog
export = Export

# Dialogs
cancel = Cancel
ok = Ok
copy = Copy
confirm = Confirm
save = Save
list-name = List name
Expand Down
2 changes: 2 additions & 0 deletions res/icons/bundled/share-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 40 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::collections::{HashMap, VecDeque};
use std::{env, process};

use chrono::{Local, NaiveDate};
use cli_clipboard::{ClipboardContext, ClipboardProvider};
use cosmic::app::{message, Core, Message as CosmicMessage};
use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::keyboard::{Key, Modifiers};
Expand All @@ -18,6 +19,7 @@ use cosmic::{
Command, Element,
};
use done_core::models::list::List;
use done_core::models::task::Task;
use done_core::service::Service;

use crate::app::config::{AppTheme, CONFIG_VERSION};
Expand All @@ -31,6 +33,7 @@ pub mod icon_cache;
mod key_bind;
pub mod localize;
pub mod menu;
pub mod markdown;

pub struct App {
core: Core,
Expand Down Expand Up @@ -67,10 +70,12 @@ pub enum Message {
OpenRenameListDialog,
OpenDeleteListDialog,
OpenIconDialog,
OpenCalendarDialog,
OpenExportDialog(String),
AddList(List),
DeleteList,
OpenCalendarDialog,
Focus(widget::Id),
Export(Vec<Task>),
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -97,6 +102,7 @@ pub enum DialogPage {
Rename { to: String },
Delete,
Calendar(NaiveDate),
Export(String),
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -746,6 +752,22 @@ impl Application for App {
);
dialog
}
DialogPage::Export(contents) => {
let dialog = widget::dialog(fl!("export"))
.control(
widget::container(scrollable(widget::text(contents)).width(Length::Fill))
.height(Length::Fixed(200.0)).width(Length::Fill),
)
.primary_action(
widget::button::suggested(fl!("copy"))
.on_press_maybe(Some(Message::DialogComplete)),
)
.secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
);

dialog
}
};

Some(dialog.into())
Expand Down Expand Up @@ -931,6 +953,9 @@ impl Application for App {
});
commands.push(command);
}
content::Command::Export(tasks) => {
commands.push(self.update(Message::Export(tasks)));
}
}
}
}
Expand Down Expand Up @@ -1034,6 +1059,12 @@ impl Application for App {
}
self.nav_model.remove(self.nav_model.active());
}
Message::Export(tasks) => {
if let Some(list) = self.nav_model.data::<List>(self.nav_model.active()) {
let exported_markdown = todo::export_list(list.clone(), tasks);
commands.push(self.update(Message::OpenExportDialog(exported_markdown)));
}
}
Message::OpenNewListDialog => {
self.dialog_pages.push_back(DialogPage::New(String::new()));
return widget::text_input::focus(self.dialog_text_input.clone());
Expand Down Expand Up @@ -1068,6 +1099,10 @@ impl Application for App {
self.dialog_pages
.push_back(DialogPage::Calendar(Local::now().date_naive()));
}
Message::OpenExportDialog(content) => {
self.dialog_pages
.push_back(DialogPage::Export(content));
}
Message::DialogCancel => {
self.dialog_pages.pop_front();
}
Expand Down Expand Up @@ -1116,6 +1151,10 @@ impl Application for App {
DialogPage::Calendar(date) => {
self.details.update(details::Message::SetDueDate(date));
}
DialogPage::Export(content) => {
let mut clipboard = ClipboardContext::new().unwrap();
clipboard.set_contents(content).unwrap();
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/app/icon_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl IconCache {
bundle!("replace-symbolic", 18);
bundle!("replace-all-symbolic", 18);
bundle!("window-close-symbolic", 18);
bundle!("share-symbolic", 18);

bundle!("paper-plane-symbolic", 18);
bundle!("task-past-due-symbolic", 18);
Expand Down
24 changes: 24 additions & 0 deletions src/app/markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use done_core::models::list::List;
use done_core::models::status::Status;
use done_core::models::task::Task;

pub trait Markdown {
fn markdown(&self) -> String;
}

impl Markdown for List {
fn markdown(&self) -> String {
format!("# {}\n", self.name)
}
}

impl Markdown for Task {
fn markdown(&self) -> String {
let mut task = format!("- [{}] {}\n", if self.status == Status::Completed { "x" } else { " " }, self.title);
let sub_tasks = self.sub_tasks.iter().fold(String::new(), |acc, sub_task| {
format!("{} - [{}] {}\n", acc, if sub_task.status == Status::Completed { "x" } else { " " }, sub_task.title)
});
task.push_str(&sub_tasks);
task
}
}
17 changes: 14 additions & 3 deletions src/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub enum Message {
Input(String),
AddTask,
UpdateTask(Task),
Export(Vec<Task>),
}

pub enum Command {
Expand All @@ -36,6 +37,7 @@ pub enum Command {
UpdateTask(Task),
Delete(String),
CreateTask(Task),
Export(Vec<Task>),
}

impl Content {
Expand All @@ -48,17 +50,23 @@ impl Content {
}

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

widget::row::with_capacity(2)
widget::row::with_capacity(3)
.align_items(Alignment::Center)
.spacing(space_s)
.padding([space_none, space_xxs])
.push_maybe(
list.icon
.as_deref()
.map(|icon| widget::icon::from_name(icon).size(24).icon()),
)
.push(widget::text::title3(&list.name))
.push(widget::text::title3(&list.name).width(Length::Fill))
.push(export_button)
.into()
}

Expand Down Expand Up @@ -212,6 +220,9 @@ impl Content {
commands.push(Command::UpdateTask(task.clone()));
}
}
Message::Export(tasks) => {
commands.push(Command::Export(tasks));
}
}
commands
}
Expand Down
1 change: 0 additions & 1 deletion src/details.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::ops::IndexMut;

use crate::app::config;
use crate::app::config::get_icon;
use chrono::{NaiveDate, TimeZone, Utc};
use cosmic::iced::{Alignment, Length};
use cosmic::iced_widget::row;
Expand Down
7 changes: 7 additions & 0 deletions src/todo.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use done_core::models::list::List;
use done_core::models::task::Task;
use done_core::service::Service;
use crate::app::markdown::Markdown;
use std::error::Error;

pub async fn update_list(list: List) -> Result<(), Box<dyn Error>> {
Expand Down Expand Up @@ -45,3 +46,9 @@ pub async fn delete_task(list_id: String, task_id: String) -> Result<(), Box<dyn
let mut service = Service::Computer.get_service();
Ok(service.delete_task(list_id, task_id).await?)
}

pub fn export_list(list: List, tasks: Vec<Task>) -> String {
let markdown = list.markdown();
let tasks_markdown: String = tasks.iter().map(|task| task.markdown()).collect();
format!("{}\n{}", markdown, tasks_markdown)
}