Initial braindump app implementation

Create a complete braindump note-taking application with Dioxus 0.7 featuring:
- Note capture with title, content, and tags
- Full CRUD operations (create, read, update, delete)
- Search functionality for notes
- Tag-based filtering
- Note pinning for quick access
- Modern dark theme with purple accents
- Responsive sidebar layout
- Clean card-based note list
- Full-text editor with auto-save hint

Implemented with:
- Dioxus 0.7.1 fullstack for reactive UI and server functions
- Workspace pattern with shared API crate
- In-memory storage using LazyLock
- Server functions for note management

All core features working and ready for testing.
This commit is contained in:
2026-02-04 02:08:08 +01:00
commit bbed451f3e
46 changed files with 8139 additions and 0 deletions

115
packages/api/src/lib.rs Normal file
View File

@@ -0,0 +1,115 @@
//! This crate contains all shared fullstack server functions.
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::{Mutex, LazyLock};
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Note {
pub id: String,
pub title: String,
pub content: String,
pub tags: Vec<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub is_pinned: bool,
}
impl Note {
pub fn new(title: String, content: String, tags: Vec<String>) -> Self {
let now = Utc::now();
Self {
id: uuid::Uuid::new_v4().to_string(),
title,
content,
tags,
created_at: now,
updated_at: now,
is_pinned: false,
}
}
}
type NotesStore = Mutex<Vec<Note>>;
static NOTES: LazyLock<NotesStore> = LazyLock::new(|| {
Mutex::new(vec![
Note::new(
"Welcome to Braindump".to_string(),
"# Welcome\n\nThis is your braindump app. Quickly capture your thoughts, ideas, and notes here.\n\n## Features\n- Quick note capture\n- Markdown support\n- Tag and categorize\n- Search and filter\n- Pin important notes".to_string(),
vec!["welcome".to_string(), "guide".to_string()],
),
Note::new(
"Project Ideas".to_string(),
"- Build a personal dashboard\n- Create a habit tracker\n- Develop a recipe manager\n- Make a fitness app\n- Design a reading list organizer".to_string(),
vec!["ideas".to_string(), "projects".to_string()],
),
Note::new(
"Meeting Notes".to_string(),
"## Team Sync - Daily\n\nAttendees: Alice, Bob, Charlie\n\nTopics:\n1. Sprint progress review\n2. Blocker discussion\n3. Planning for next week\n\nAction items:\n- [ ] Review PR #123\n- [ ] Update documentation\n- [ ] Schedule follow-up meeting".to_string(),
vec!["work".to_string(), "meetings".to_string()],
),
])
});
#[post("/api/notes")]
pub async fn create_note(title: String, content: String, tags: Vec<String>) -> Result<String, ServerFnError> {
let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
let note = Note::new(title, content, tags);
notes.push(note.clone());
Ok(note.id)
}
#[get("/api/notes")]
pub async fn list_notes() -> Result<Vec<Note>, ServerFnError> {
let notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
Ok(notes.clone())
}
#[get("/api/notes/:id")]
pub async fn get_note(id: String) -> Result<Note, ServerFnError> {
let notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
notes
.iter()
.find(|n| n.id == id)
.cloned()
.ok_or_else(|| ServerFnError::new("Note not found".to_string()))
}
#[post("/api/notes/:id")]
pub async fn update_note(id: String, title: String, content: String, tags: Vec<String>, is_pinned: bool) -> Result<(), ServerFnError> {
let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
if let Some(note) = notes.iter_mut().find(|n| n.id == id) {
note.title = title;
note.content = content;
note.tags = tags;
note.is_pinned = is_pinned;
note.updated_at = Utc::now();
Ok(())
} else {
Err(ServerFnError::new("Note not found".to_string()))
}
}
#[post("/api/notes/:id/delete")]
pub async fn delete_note(id: String) -> Result<(), ServerFnError> {
let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
if notes.iter().any(|n| n.id == id) {
notes.retain(|n| n.id != id);
Ok(())
} else {
Err(ServerFnError::new("Note not found".to_string()))
}
}
#[post("/api/notes/:id/pin")]
pub async fn toggle_pin_note(id: String) -> Result<(), ServerFnError> {
let mut notes = NOTES.lock().map_err(|e| ServerFnError::new(e.to_string()))?;
if let Some(note) = notes.iter_mut().find(|n| n.id == id) {
note.is_pinned = !note.is_pinned;
note.updated_at = Utc::now();
Ok(())
} else {
Err(ServerFnError::new("Note not found".to_string()))
}
}