delete pages/spaces
This commit is contained in:
136
src/main.rs
136
src/main.rs
@@ -16,8 +16,8 @@ use tui_textarea::TextArea;
|
||||
use tracing::info;
|
||||
use tracing_subscriber::fmt;
|
||||
|
||||
use api::{create_page, do_login, fetch_page_content, fetch_pages, fetch_spaces, save_page, search_pages};
|
||||
use app::{App, AppMsg, AppState, EditorFocus, EditorStatus, EditorView, LoginField, NewPageDialog, Panel, SearchView};
|
||||
use api::{create_page, create_space, delete_page, delete_space, do_login, fetch_page_content, fetch_pages, fetch_spaces, save_page, search_pages};
|
||||
use app::{App, AppMsg, AppState, DeleteConfirmDialog, DeleteTarget, EditorFocus, EditorStatus, EditorView, LoginField, NewPageDialog, NewSpaceDialog, Panel, SearchView};
|
||||
use ui::{draw_editor, draw_login, draw_main, draw_search};
|
||||
|
||||
// ─── Entry point ──────────────────────────────────────────────────────────────
|
||||
@@ -168,6 +168,48 @@ async fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::R
|
||||
d.error = Some(e);
|
||||
}
|
||||
}
|
||||
AppMsg::SpaceDeleted(space_id) => {
|
||||
info!("space deleted: {space_id}");
|
||||
app.main.delete_dialog = None;
|
||||
app.main.spaces.retain(|s| s.id != space_id);
|
||||
app.main.selected_space = 0;
|
||||
app.main.pages = vec![];
|
||||
if let Some(space) = app.main.spaces.first() {
|
||||
app.main.loading_pages = true;
|
||||
spawn_fetch_pages_msg(&app.base_url, &app.token, &space.id, &tx);
|
||||
}
|
||||
}
|
||||
AppMsg::PageDeleted(page_id) => {
|
||||
info!("page deleted: {page_id}");
|
||||
app.main.delete_dialog = None;
|
||||
app.main.pages.retain(|p| p.id != page_id);
|
||||
if app.main.selected_page >= app.main.pages.len() {
|
||||
app.main.selected_page = app.main.pages.len().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
AppMsg::DeleteError(e) => {
|
||||
info!("delete error: {e}");
|
||||
if let Some(d) = app.main.delete_dialog.as_mut() {
|
||||
d.deleting = false;
|
||||
d.error = Some(e);
|
||||
}
|
||||
}
|
||||
AppMsg::SpaceCreated(space) => {
|
||||
info!("space created: id={} name={:?}", space.id, space.name);
|
||||
app.main.new_space_dialog = None;
|
||||
// Add to list and select it
|
||||
app.main.spaces.push(space);
|
||||
app.main.selected_space = app.main.spaces.len() - 1;
|
||||
app.main.pages = vec![];
|
||||
app.main.loading_pages = false;
|
||||
}
|
||||
AppMsg::CreateSpaceError(e) => {
|
||||
info!("create space error: {e}");
|
||||
if let Some(d) = app.main.new_space_dialog.as_mut() {
|
||||
d.creating = false;
|
||||
d.error = Some(e);
|
||||
}
|
||||
}
|
||||
AppMsg::SearchResults(results) => {
|
||||
info!("search results: {} items", results.len());
|
||||
app.search.loading = false;
|
||||
@@ -280,6 +322,75 @@ async fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::R
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── Delete confirm dialog intercepts all keys when open ──
|
||||
if let Some(dialog) = app.main.delete_dialog.as_mut() {
|
||||
if dialog.deleting { continue; }
|
||||
match key.code {
|
||||
KeyCode::Esc => { app.main.delete_dialog = None; }
|
||||
KeyCode::Char('d') => {
|
||||
dialog.deleting = true;
|
||||
dialog.error = None;
|
||||
let base_url = app.base_url.clone();
|
||||
let token = app.token.clone();
|
||||
let target = dialog.target.clone();
|
||||
let tx2 = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let msg = match &target {
|
||||
DeleteTarget::Space { id, .. } => {
|
||||
match delete_space(&base_url, &token, id).await {
|
||||
Ok(()) => AppMsg::SpaceDeleted(id.clone()),
|
||||
Err(e) => AppMsg::DeleteError(e),
|
||||
}
|
||||
}
|
||||
DeleteTarget::Page { id, .. } => {
|
||||
match delete_page(&base_url, &token, id).await {
|
||||
Ok(()) => AppMsg::PageDeleted(id.clone()),
|
||||
Err(e) => AppMsg::DeleteError(e),
|
||||
}
|
||||
}
|
||||
};
|
||||
let _ = tx2.send(msg);
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── New-space dialog intercepts all keys when open ──
|
||||
if let Some(dialog) = app.main.new_space_dialog.as_mut() {
|
||||
if dialog.creating { continue; }
|
||||
match key.code {
|
||||
KeyCode::Esc => { app.main.new_space_dialog = None; }
|
||||
KeyCode::Backspace => { dialog.name.pop(); }
|
||||
KeyCode::Enter => {
|
||||
if dialog.name.trim().is_empty() {
|
||||
dialog.error = Some("Name cannot be empty.".into());
|
||||
} else {
|
||||
dialog.creating = true;
|
||||
dialog.error = None;
|
||||
let base_url = app.base_url.clone();
|
||||
let token = app.token.clone();
|
||||
let name = dialog.name.trim().to_string();
|
||||
let tx2 = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let msg = match create_space(&base_url, &token, &name).await {
|
||||
Ok(space) => AppMsg::SpaceCreated(space),
|
||||
Err(e) => AppMsg::CreateSpaceError(e),
|
||||
};
|
||||
let _ = tx2.send(msg);
|
||||
});
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
dialog.error = None;
|
||||
dialog.name.push(c);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// ── New-page dialog intercepts all keys when open ──
|
||||
if let Some(dialog) = app.main.new_page_dialog.as_mut() {
|
||||
if dialog.creating { continue; }
|
||||
@@ -355,6 +466,27 @@ async fn run_app(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::R
|
||||
}
|
||||
}
|
||||
},
|
||||
// d → delete focused item
|
||||
KeyCode::Char('d') => {
|
||||
let target = match app.main.focus {
|
||||
Panel::Spaces => app.main.spaces
|
||||
.get(app.main.selected_space)
|
||||
.map(|s| DeleteTarget::Space { id: s.id.clone(), name: s.name.clone() }),
|
||||
Panel::Pages => app.main.pages
|
||||
.get(app.main.selected_page)
|
||||
.map(|p| DeleteTarget::Page {
|
||||
id: p.id.clone(),
|
||||
title: p.title.clone().unwrap_or_else(|| "Untitled".into()),
|
||||
}),
|
||||
};
|
||||
if let Some(target) = target {
|
||||
app.main.delete_dialog = Some(DeleteConfirmDialog::new(target));
|
||||
}
|
||||
}
|
||||
// Ctrl+N → new space
|
||||
KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
app.main.new_space_dialog = Some(NewSpaceDialog::new());
|
||||
}
|
||||
// n → new page in current space
|
||||
KeyCode::Char('n') => {
|
||||
if !app.main.spaces.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user