//! Blog post management screen — list, create, delete posts. use ratatui::Frame; use ratatui::layout::{Constraint, Layout}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Paragraph, Row}; use super::App; use super::widgets; pub fn render(frame: &mut Frame, app: &App) { let area = frame.area(); let project_title = app .blog_project_title .as_deref() .unwrap_or("Blog"); let title = Line::from(vec![ Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" -- "), Span::styled(project_title, Style::default().add_modifier(Modifier::BOLD)), Span::raw(" -- Blog "), ]); let block = Block::default() .title(title) .borders(Borders::ALL) .border_style(Style::default().fg(Color::Gray)); let inner = block.inner(area); frame.render_widget(block, area); let chunks = Layout::vertical([ Constraint::Length(1), // spacer Constraint::Length(1), // section header Constraint::Min(3), // post list Constraint::Length(1), // status line Constraint::Length(1), // keybindings ]) .split(inner); // Section header let count = app.blog_posts.len(); let header = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled("Posts", Style::default().add_modifier(Modifier::BOLD)), if count == 0 { Span::raw("") } else { Span::raw(format!(" ({})", count)) }, ])); frame.render_widget(header, chunks[1]); // Post list if app.loading { let loading = Paragraph::new(" Loading..."); frame.render_widget(loading, chunks[2]); } else if app.blog_posts.is_empty() { let empty = Paragraph::new(" No blog posts. Press [n] to create one."); frame.render_widget(empty, chunks[2]); } else { render_post_table(frame, app, chunks[2]); } // Status line if let Some(ref status) = app.blog_status { let style = if status.starts_with("Error") { Style::default().fg(Color::Red) } else { Style::default().fg(Color::Green) }; let status_line = Paragraph::new(Line::from(vec![ Span::raw(" "), Span::styled(status.as_str(), style), ])); frame.render_widget(status_line, chunks[3]); } // Keybindings let mut key_spans = vec![ Span::raw(" "), Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Nav "), Span::styled("[n]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" New "), ]; if !app.blog_posts.is_empty() { key_spans.extend([ Span::styled("[d]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Delete "), ]); } key_spans.extend([ Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Refresh "), Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" Back"), ]); let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray)); frame.render_widget(keys, chunks[4]); } fn render_post_table(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) { let rows: Vec = app .blog_posts .iter() .enumerate() .map(|(i, post)| { let status = if post.is_published { "published".to_string() } else if let Some(ref pa) = post.publish_at { let scheduled = pa.get(..16).unwrap_or(pa); format!("sched {}", scheduled) } else { "draft".to_string() }; let date = post.created_at.get(..10).unwrap_or(&post.created_at); Row::new(vec![ format!(" {}", post.title), post.slug.clone(), status, date.to_string(), ]) .style(widgets::selected_style(i, Some(app.selected_index))) }) .collect(); let widths = [ Constraint::Min(20), Constraint::Length(20), Constraint::Length(22), Constraint::Length(12), ]; widgets::render_table(frame, area, &[" Title", "Slug", "Status", "Created"], &widths, rows); }