diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..408d0bc --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,14 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +custom: 'paypal.me/notabena' diff --git a/src/api.rs b/src/api.rs index c55d935..816b060 100644 --- a/src/api.rs +++ b/src/api.rs @@ -65,5 +65,9 @@ pub fn get_notes(db_file: &PathBuf) -> Result> { notes.push(note?); } + // sort notes by date: newest first + notes.sort_by(|a, b| a.created.cmp(&b.created)); + notes.reverse(); + Ok(notes) } diff --git a/src/main.rs b/src/main.rs index 7e8f974..d311b84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,9 +12,7 @@ use crate::{ prompts::{multiselect::multiselect, select::select}, return_to_main::return_to_main, utilities::{ - cursor_to_origin::cursor_to_origin, - format_md::{inline, paragraph}, - truncate_note::truncate_note, + cursor_to_origin::cursor_to_origin, format_md::paragraph, truncate_note::truncate_note, }, }; use async_std::path::PathBuf; @@ -54,19 +52,19 @@ fn display_about() -> Result<(), Box> { let skin: MadSkin = MadSkin::default(); cursor_to_origin()?; - println!("{}", paragraph(&skin, &format!("# About Notabena"))); + println!("{}", paragraph(&skin, "# About Notabena")); println!( "{}", - inline( + paragraph( &skin, - "**Notabena** is a FOSS note-taking CLI tool, written in Rust.\n" + "**Notabena** is a FOSS note-taking CLI tool, written in Rust.\nDonations are always a great way to help us keeping the project alive. It can be done here: https://paypal.me/Notabena (ctrl+click to follow link)." ) ); println!( "version: v{}, licensed under: GPL v3", env!("CARGO_PKG_VERSION") ); - println!("COPYRIGHT (c) 2023-PRESENT NOTABENA ORGANISATION\nPROJECT LEADS @ThatFrogDev, @MrSerge01, GITHUB CONTRIBUTORS\n"); + println!("COPYRIGHT (c) 2023-PRESENT NOTABENA ORGANISATION\nPROJECT LEADS @ThatFrogDev, @MrSerge01, GITHUB CONTRIBUTORS\n\n(scroll up if you can't read everything)"); Ok(()) } diff --git a/src/note.rs b/src/note.rs index b049452..d53b378 100644 --- a/src/note.rs +++ b/src/note.rs @@ -1,7 +1,7 @@ use crate::{ api, multiselect, prompts::{confirm::confirm, input::input, select::select}, - return_to_main, truncate_note, + truncate_note, utilities::{cursor_to_origin::cursor_to_origin, display::display}, }; use async_std::path::PathBuf; @@ -18,18 +18,46 @@ pub struct Note { impl Note { pub fn create(db_file: &PathBuf) -> Result<(), Box> { + let sqlite = Connection::open(db_file)?; + + // fetch IDs from database, sort and find the first gap. if it does not exist, use the length of the array + 1 + let mut stmt = sqlite.prepare("SELECT id FROM saved_notes")?; + let ids: Result, _> = stmt.query_map(params![], |row| row.get(0))?.collect(); + let mut ids = ids?; + ids.sort_unstable(); + let id = ids + .clone() + .into_iter() + .enumerate() + .find(|(i, id)| i + 1 != *id) + .map_or_else(|| ids.len() + 1, |(i, _)| i + 1); + cursor_to_origin()?; println!( "If you're done inputting a field, you can press Enter twice to continue or save, or Alt/Option-Q to return to the main menu.\r" ); - let mut inputted_note = Note { - id: api::get_notes(db_file)?.len(), - name: input("Name:", "".to_string())?, + + let mut name: String; + loop { + name = input("Name:", "".to_string())?; + if name.len() > 64 { + cursor_to_origin()?; + println!( + "If you're done inputting a field, you can press Enter twice to continue or save, or Alt/Option-Q to return to the main menu.\n\n\ + error: The name is too long, it must be 64 characters or less.\r" + ); + } else { + break; + } + } + let inputted_note = Note { + id, + name, content: input("Content:", "".to_string())?, created: format!("{}", Local::now().format("%A %e %B, %H:%M")), }; - Connection::open(db_file)?.execute( + sqlite.execute( "INSERT INTO saved_notes (id, name, content, created) VALUES (?1, ?2, ?3, ?4);", params![ &inputted_note.id, @@ -55,10 +83,10 @@ impl Note { let mut options: Vec = Vec::new(); truncate_note(&mut options, db_file)?; let selection = select("Select the note that you want to view:", &options); - let mut selected_note = &saved_notes[selection]; + let selected_note = &saved_notes[selection]; cursor_to_origin()?; - display(&mut selected_note); + display(selected_note)?; Ok(()) } @@ -79,7 +107,7 @@ impl Note { let selection = select("Select the note that you want to edit:", &options); let selected_note = &saved_notes[selection]; let updated_note = Note { - id: selected_note.id.clone(), + id: selected_note.id, name: input("Name:", selected_note.name.clone())?, content: input("Content:", selected_note.content.clone())?, created: selected_note.created.clone(), @@ -120,16 +148,14 @@ impl Note { if selections.is_empty() { println!("You didn't select any notes."); Ok(()) + } else if confirm(prompt) { + api::delete_notes(selections, db_file)?; + cursor_to_origin()?; + println!("Notes deleted successfully."); + Ok(()) } else { - if confirm(prompt) { - api::delete_notes(selections, db_file)?; - cursor_to_origin()?; - println!("Notes deleted successfully."); - Ok(()) - } else { - cursor_to_origin()?; - Ok(()) - } + cursor_to_origin()?; + Ok(()) } } } diff --git a/src/utilities/cursor_to_origin.rs b/src/utilities/cursor_to_origin.rs index 2508638..3b7e74f 100644 --- a/src/utilities/cursor_to_origin.rs +++ b/src/utilities/cursor_to_origin.rs @@ -1,11 +1,13 @@ use std::process::Command; +#[cfg(target_os = "windows")] pub fn cursor_to_origin() -> Result<(), Box> { - if cfg!(target_os = "windows") { - Command::new("cmd").args(["/c", "cls"]).spawn()?.wait()?; - Ok(()) - } else { - Command::new("clear").spawn()?.wait()?; - Ok(()) - } + Command::new("cmd").args(["/c", "cls"]).spawn()?.wait()?; + Ok(()) +} + +#[cfg(not(target_os = "windows"))] +pub fn cursor_to_origin() -> Result<(), Box> { + Command::new("clear").spawn()?.wait()?; + Ok(()) }