//! blog-screen key-input handler (split out of tui/input.rs). use super::{ App, AppEvent, BlogCreateStep, ConfirmAction, DataPayload, KeyCode, KeyEvent, MnwApiClient, Screen, load_blog_posts, mpsc, }; pub(crate) async fn handle_blog_input( key: KeyEvent, app: &mut App, screen: &mut Screen, api: &MnwApiClient, tx: &mpsc::Sender, ) { // Cancel pending confirmation on any key other than the confirmation key if app.confirm_action.is_some() && !matches!(key.code, KeyCode::Char('d' | 'D')) { app.confirm_action = None; app.blog_status = None; } // Creating mode if let Some(step) = app.blog_create_step { // Ctrl+D in body step: advance to schedule if step == BlogCreateStep::Body && key.code == KeyCode::Char('d') && key .modifiers .contains(crossterm::event::KeyModifiers::CONTROL) { app.blog_create_body = app.edit_buffer.clone(); app.edit_buffer.clear(); app.blog_create_step = Some(BlogCreateStep::Schedule); app.blog_status = Some("Schedule (YYYY-MM-DDTHH:MM:SSZ or Enter for draft):".to_string()); return; } match key.code { KeyCode::Esc => { app.blog_create_step = None; app.blog_create_title.clear(); app.blog_create_body.clear(); app.edit_buffer.clear(); app.blog_status = None; } KeyCode::Enter => match step { BlogCreateStep::Title => { if !app.edit_buffer.is_empty() { app.blog_create_title = app.edit_buffer.clone(); app.edit_buffer.clear(); app.blog_create_step = Some(BlogCreateStep::Body); app.blog_status = Some( "Body (markdown, Enter for newline, Ctrl+D when done):".to_string(), ); } } BlogCreateStep::Body => { // Enter inserts a newline in body mode app.edit_buffer.push('\n'); return; } BlogCreateStep::Schedule => { let title = app.blog_create_title.clone(); let body = app.blog_create_body.clone(); let schedule_input = app.edit_buffer.trim().to_string(); let publish_at = if schedule_input.is_empty() { None } else { Some(schedule_input) }; if let Screen::Blog(_, project_id) = &*screen { let project_id = project_id.clone(); let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); app.blog_creating = true; let status_msg = if publish_at.is_some() { "Scheduling post..." } else { "Creating draft..." }; app.blog_status = Some(status_msg.to_string()); tokio::spawn(async move { match api .create_blog_post( &user_id, &project_id, &title, &body, false, publish_at.as_deref(), ) .await { Ok(_post) => { let _ = tx .send(AppEvent::DataLoaded(DataPayload::BlogCreated)) .await; } Err(e) => { let _ = tx .send(AppEvent::DataLoaded(DataPayload::GenericError { error: e.to_string(), })) .await; } } }); } app.blog_create_step = None; app.blog_create_title.clear(); app.blog_create_body.clear(); app.edit_buffer.clear(); } }, KeyCode::Backspace => { app.edit_buffer.pop(); } KeyCode::Char(c) => { app.edit_buffer.push(c); } _ => {} } return; } // Normal mode match key.code { KeyCode::Char('j') | KeyCode::Down => app.move_down(screen), KeyCode::Char('k') | KeyCode::Up => app.move_up(screen), KeyCode::Esc => { if let Screen::Blog(pidx, _) = screen { *screen = Screen::Project(*pidx); app.blog_posts.clear(); app.blog_status = None; app.selected_index = 0; } } KeyCode::Char('n' | 'N') => { app.blog_create_step = Some(BlogCreateStep::Title); app.edit_buffer.clear(); app.blog_status = Some("Title: _".to_string()); } KeyCode::Char('d' | 'D') if !app.blog_posts.is_empty() => { let idx = app.selected_index; if matches!(app.confirm_action, Some(ConfirmAction::DeleteBlogPost { post_idx }) if post_idx == idx) { // Confirmed — execute delete app.confirm_action = None; let post_id = app.blog_posts[idx].id.clone(); let post_title = app.blog_posts[idx].title.clone(); let user_id = app.user.user_id.clone(); tracing::info!( user_id = %user_id, post_id = %post_id, post_title = %post_title, "delete blog post confirmed" ); let api = api.clone(); let tx = tx.clone(); if let Screen::Blog(_, project_id) = &*screen { let project_id = project_id.clone(); app.blog_status = Some("Deleting...".to_string()); tokio::spawn(async move { match api.delete_blog_post(&user_id, &post_id).await { Ok(()) => { load_blog_posts(&api, &user_id, &project_id, &tx).await; } Err(e) => { let _ = tx .send(AppEvent::DataLoaded(DataPayload::GenericError { error: e.to_string(), })) .await; } } }); } } else { // First press — ask for confirmation app.confirm_action = Some(ConfirmAction::DeleteBlogPost { post_idx: idx }); app.blog_status = Some(format!( "Delete '{}'? Press d again to confirm", app.blog_posts[idx].title )); } } KeyCode::Char('r' | 'R') => { if let Screen::Blog(_, project_id) = &*screen { app.loading = true; let project_id = project_id.clone(); let api = api.clone(); let user_id = app.user.user_id.clone(); let tx = tx.clone(); tokio::spawn(async move { load_blog_posts(&api, &user_id, &project_id, &tx).await; }); } } _ => {} } }