diff --git a/frontend/fakenoteprovider.js b/frontend/fakenoteprovider.js deleted file mode 100644 index 959ceb7..0000000 --- a/frontend/fakenoteprovider.js +++ /dev/null @@ -1,88 +0,0 @@ -import { NoteProvider } from "./noteprovider"; - -class Note { - name; - content; - tags; - - constructor(name, content, tags) { - this.name = name; - this.content = content || ""; - this.tags = tags || []; - } -} - -class FakeNoteProvider extends NoteProvider { - - #notes; - - constructor() { - super(); - this.#notes = new Map([ - ["Sample", new Note("Sample", "# Sample Note\n", ["Sample"])] - ]); - } - - #get_note(name) { - const note = this.#notes.get(name); - if (!note) { - throw new Error(`unknown note \"${name}\"`); - } - return note; - } - - async list() { - return [...this.#notes.keys()]; - } - - async read(name) { - const note = this.#get_note(name); - return note.content; - } - - async create() { - let name = "Untitled"; - let count = 0; - while (this.#notes.has(name)) { - count++; - name = `Untitled ${count}`; - } - - this.#notes.set(name, new Note(name)); - return name; - } - - async rename(old_name, new_name) { - const note = this.#get_note(old_name); - if (this.#notes.has(new_name)) { - throw new Error(`failed to rename to an existing note \"${new_name}\"`); - } - - this.#notes.delete(old_name); - note.name = new_name; - this.#notes.set(note.name, note); - } - - async write(name, content) { - const note = this.#get_note(name); - note.content = content; - } - - async remove(name) { - if (this.#notes.has(name)) { - this.#notes.delete(name); - } - } - - async read_tags(name) { - const note = this.#get_note(name); - return note.tags; - } - - async write_tags(name, tags) { - const note = this.#get_note(name); - note.tags = tags; - } -} - -export { FakeNoteProvider } \ No newline at end of file diff --git a/frontend/main.js b/frontend/main.js index ad29a55..5ded296 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -4,7 +4,7 @@ import "lineicons/web-font/lineicons.css" import { slider_attach } from "./slider.js" import { init_titlebar } from "./titlebar.js" import { init_settings } from "./settings.js" -import { FakeNoteProvider } from "./fakenoteprovider.js" +import { TauriNoteProvider } from "./taurinoteprovider.js" import { NoteList } from "./notelist.js" import { Editor } from "./editor.js" import { TagList } from "./taglist.js" @@ -30,7 +30,7 @@ document.querySelector("#toggle-mode").addEventListener("click", () => { }); -const noteProvider = new FakeNoteProvider(); +const noteProvider = new TauriNoteProvider(); const taglist_elemnt = document.querySelector("#taglist"); const taglist = new TagList(taglist_elemnt, noteProvider); diff --git a/frontend/taurinoteprovider.js b/frontend/taurinoteprovider.js new file mode 100644 index 0000000..8b63d8d --- /dev/null +++ b/frontend/taurinoteprovider.js @@ -0,0 +1,47 @@ +import { tauri } from "@tauri-apps/api"; +import { NoteProvider } from "./noteprovider"; + +class TauriNoteProvider extends NoteProvider { + async list() { + return await tauri.invoke("list"); + } + + async read(name) { + return await tauri.invoke("read", { name: name }); + } + + async create() { + return await tauri.invoke("create"); + } + + async rename(old_name, new_name) { + return await tauri.invoke("rename", { + oldName: old_name, + newName: new_name + }); + } + + async write(name, content) { + await tauri.invoke("write", { + name: name, + content: content + }); + } + + async remove(name) { + await tauri.invoke("remove", { name: name}); + } + + async read_tags(name) { + return await tauri.invoke("read_tags", { name: name}); + } + + async write_tags(name, tags) { + await tauri.invoke("write_tags", { + name: name, + tags: tags + }); + } +} + +export { TauriNoteProvider } \ No newline at end of file diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index bc27126..6348f86 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -3,7 +3,8 @@ use home::{home_dir}; use std::fs; use std::path::{Path, PathBuf}; -use core::fmt::Display; + +use crate::noteerror::{NoteError,NoteResult}; const DEFAULT_CONFIG_FILENAME: &str = ".noters.yaml"; @@ -32,23 +33,6 @@ fn get_home_dir() -> PathBuf { home_dir().unwrap_or(PathBuf::from(".")) } -#[derive(Debug)] -struct ConfigError { - message: String -} - -impl ConfigError { - fn new(message: &str) -> Self { - ConfigError { message: message.into() } - } -} - -impl From for ConfigError { - fn from(value: E) -> Self { - ConfigError { message: value.to_string() } - } -} - impl Config { /// Creates a new config with default values. @@ -83,7 +67,7 @@ impl Config { match Config::from_file(filename.as_path()) { Ok(config) => config, Err(error) => { - eprintln!("warning: failed to load config {:?}: {}", filename, error.message); + eprintln!("warning: failed to load config {:?}: {}", filename, error.to_string()); let config = Config::new(filename.as_path()); config.save(); config @@ -92,12 +76,12 @@ impl Config { } /// Try to load the config from a given path. - fn from_file(filename: &Path) -> Result { + fn from_file(filename: &Path) -> NoteResult { let text = fs::read_to_string(filename)?; let config_file = serde_yaml::from_str::(&text)?; if config_file.meta.version != 1 { - return Err(ConfigError::new("unknown file format (version mismatch)")); + return Err(NoteError::new("unknown file format (version mismatch)")); } Ok(Config { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1bc6387..6d20030 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,9 +3,11 @@ mod config; mod note; +mod noteerror; use config::{Config}; use note::{Note}; +use noteerror::{NoteResult}; fn main() { let config = Config::from_default_file(); @@ -14,7 +16,14 @@ fn main() { tauri::Builder::default() .manage(note) .invoke_handler(tauri::generate_handler![ - list + list, + read, + create, + rename, + write, + remove, + read_tags, + write_tags ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); @@ -24,7 +33,41 @@ fn main() { /// /// list all directories in the base directory that have a `README.md` file inside #[tauri::command] -async fn list(note: tauri::State<'_, Note>) -> std::result::Result, String> { - Ok(note.list()) +async fn list(note: tauri::State<'_, Note>) -> NoteResult> { + note.list() } +#[tauri::command] +async fn read(note: tauri::State<'_, Note>, name: &str) -> NoteResult { + note.read(name) +} + +#[tauri::command] +async fn create(note: tauri::State<'_, Note>) -> NoteResult { + note.create() +} + +#[tauri::command] +async fn rename(note: tauri::State<'_, Note>, old_name: &str, new_name: &str) -> NoteResult<()> { + note.rename(old_name, new_name) +} + +#[tauri::command] +async fn write(note: tauri::State<'_, Note>, name: &str, content: &str) -> NoteResult<()> { + note.write(name, content) +} + +#[tauri::command] +async fn remove(note: tauri::State<'_, Note>, name: &str) -> NoteResult<()> { + note.remove(name) +} + +#[tauri::command] +async fn read_tags(note: tauri::State<'_, Note>, name: &str) -> NoteResult> { + note.read_tags(name) +} + +#[tauri::command] +async fn write_tags(note: tauri::State<'_, Note>, name: &str, tags: Vec) -> NoteResult<()> { + note.write_tags(name, &tags) +} diff --git a/src-tauri/src/note.rs b/src-tauri/src/note.rs index dec5482..c168a4f 100644 --- a/src-tauri/src/note.rs +++ b/src-tauri/src/note.rs @@ -1,5 +1,6 @@ -use std::{fs::DirEntry, io::{Error, Result}, path::{Path, PathBuf}}; +use std::{fs, fs::DirEntry, path::{Path, PathBuf}}; use crate::config::{Config}; +use crate::noteerror::{NoteError, NoteResult}; pub struct Note { config: Config @@ -13,26 +14,106 @@ impl Note { } - /// list the names of the notes + /// Lists the names of the notes. /// /// list all directories in the base directory that have a `README.md` file inside - pub fn list(&self) -> Vec { - let base_path = self.config.get_base_path().join(".notes"); + pub fn list(&self) -> Result, NoteError> { + let base_path = self.config.get_base_path(); match get_note_names(base_path) { - Ok(note_names) => return note_names, + Ok(note_names) => return Ok(note_names), Err(e) => error_handling(e.to_string()) } - Vec::::new() + Ok(Vec::::new()) } + /// Reads the contents of the given note. + pub fn read(&self, name : &str) -> NoteResult { + let readme = Path::new(&self.get_note_path(name)?).join("README.md"); + Ok(fs::read_to_string(readme)?) + } + + /// Creates a new note and return it's name. + pub fn create(&self) -> NoteResult { + let mut name = String::from("Untitled"); + let mut path = self.get_note_path(&name)?; + let mut n = 0; + while path.as_path().exists() { + n += 1; + name = format!("Untitled {}", n); + path = self.get_note_path(&name)?; + } + + fs::create_dir(path.clone())?; + let readme = Path::new(&path).join("README.md"); + fs::write(readme, format!("# {}\n", name))?; + let tags = Path::new(&path).join("tags.txt"); + fs::write(tags, "")?; + + Ok(name) + } + + /// Renames a note. + pub fn rename(&self, old_name: &str, new_name: &str) -> NoteResult<()> { + let old_path = self.get_note_path(old_name)?; + let new_path = self.get_note_path(new_name)?; + Ok(fs::rename(old_path, new_path)?) + } + + /// Writes the content of the given notes. + pub fn write(&self, name: &str, content: &str) -> NoteResult<()> { + let readme = Path::new(&self.get_note_path(name)?).join("README.md"); + fs::write(readme, content.as_bytes())?; + Ok(()) + } + + /// Removes the given note. + pub fn remove(&self, name: &str) -> NoteResult<()> { + let path = self.get_note_path(name)?; + Ok(fs::remove_dir_all(path)?) + } + + /// Reads the tags associated with the given note. + pub fn read_tags(&self, name: &str) -> NoteResult> { + let tags = Path::new(&self.get_note_path(name)?).join("tags.txt"); + let content = fs::read_to_string(tags)?; + let tags: Vec = content + .lines() + .filter(|s| !s.is_empty()) + .map(String::from) + .collect(); + Ok (tags) + } + + /// Replaces the tags of tags of the given note. + pub fn write_tags(&self, name: &str, tags: &Vec) -> NoteResult<()> { + let content = tags.join("\n"); + let tags = Path::new(&self.get_note_path(name)?).join("tags.txt"); + fs::write(tags, content.as_bytes())?; + Ok(()) + } + + fn get_note_path(&self, name: &str) -> NoteResult { + check_name(name)?; + let mut path = self.config.get_base_path(); + path.push(name); + Ok(path) + } +} + +/// Verifies, that a note name is valid +fn check_name(name: &str) -> NoteResult<()> { + match name.chars().nth(0) { + None | Some('/') | Some('\\') => Err(NoteError::new("invalid note name")), + _ => Ok(()) + } } /// list all directories in `base_path` that have a `README.md` file inside -fn get_note_names(base_path: PathBuf) -> Result> { +fn get_note_names(base_path: PathBuf) -> NoteResult> { let mut note_names = Vec::::new(); let dir_entries = std::fs::read_dir(base_path)?; for dir_entry in dir_entries { - match get_note_name(dir_entry) { + match get_note_name(dir_entry?) { Ok(note_name) => note_names.push(note_name), Err(e) => error_handling(e.to_string()) } @@ -41,17 +122,12 @@ fn get_note_names(base_path: PathBuf) -> Result> { } /// check if the `dir_entry` contains a `README.md` file inside and returns the folder name if so -fn get_note_name(dir_entry: Result) -> Result { - let dir_entry = dir_entry?; +fn get_note_name(dir_entry: DirEntry) -> NoteResult { if dir_entry.file_type()?.is_dir() && Path::new(&dir_entry.path()).join("README.md").is_file() { return Ok(dir_entry.file_name().into_string().unwrap()); } - Err( - Error::new( - std::io::ErrorKind::NotFound, - format!("README.md not found for `{}`.", dir_entry.path().to_str().unwrap()) - ) - ) + + Err(NoteError::new(&format!("README.md not found for `{:?}`.", dir_entry.path()))) } fn error_handling(e: String) { diff --git a/src-tauri/src/noteerror.rs b/src-tauri/src/noteerror.rs new file mode 100644 index 0000000..0bdf6f9 --- /dev/null +++ b/src-tauri/src/noteerror.rs @@ -0,0 +1,25 @@ +use serde::{Serialize, Deserialize}; +use core::fmt::{Display}; + +#[derive(Serialize, Deserialize,Debug)] +pub struct NoteError { + message: String +} + +impl NoteError { + pub fn new(message: &str) -> Self { + NoteError { message: message.into() } + } + + pub fn to_string(&self) -> String { + self.message.clone() + } +} + +impl From for NoteError { + fn from(value: E) -> Self { + NoteError { message: value.to_string() } + } +} + +pub type NoteResult = Result; \ No newline at end of file