Skip to main content

max / makenotwork

4.4 KB · 144 lines History Blame Raw
1 //! Blog post management screen — list, create, delete posts.
2
3 use ratatui::Frame;
4 use ratatui::layout::{Constraint, Layout};
5 use ratatui::style::{Color, Modifier, Style};
6 use ratatui::text::{Line, Span};
7 use ratatui::widgets::{Block, Borders, Paragraph, Row};
8
9 use super::App;
10 use super::widgets;
11
12 pub fn render(frame: &mut Frame, app: &App) {
13 let area = frame.area();
14
15 let project_title = app
16 .blog_project_title
17 .as_deref()
18 .unwrap_or("Blog");
19
20 let title = Line::from(vec![
21 Span::styled(" Makenot.work ", Style::default().add_modifier(Modifier::BOLD)),
22 Span::raw(" -- "),
23 Span::styled(project_title, Style::default().add_modifier(Modifier::BOLD)),
24 Span::raw(" -- Blog "),
25 ]);
26
27 let block = Block::default()
28 .title(title)
29 .borders(Borders::ALL)
30 .border_style(Style::default().fg(Color::Gray));
31
32 let inner = block.inner(area);
33 frame.render_widget(block, area);
34
35 let chunks = Layout::vertical([
36 Constraint::Length(1), // spacer
37 Constraint::Length(1), // section header
38 Constraint::Min(3), // post list
39 Constraint::Length(1), // status line
40 Constraint::Length(1), // keybindings
41 ])
42 .split(inner);
43
44 // Section header
45 let count = app.blog_posts.len();
46 let header = Paragraph::new(Line::from(vec![
47 Span::raw(" "),
48 Span::styled("Posts", Style::default().add_modifier(Modifier::BOLD)),
49 if count == 0 {
50 Span::raw("")
51 } else {
52 Span::raw(format!(" ({})", count))
53 },
54 ]));
55 frame.render_widget(header, chunks[1]);
56
57 // Post list
58 if app.loading {
59 let loading = Paragraph::new(" Loading...");
60 frame.render_widget(loading, chunks[2]);
61 } else if app.blog_posts.is_empty() {
62 let empty = Paragraph::new(" No blog posts. Press [n] to create one.");
63 frame.render_widget(empty, chunks[2]);
64 } else {
65 render_post_table(frame, app, chunks[2]);
66 }
67
68 // Status line
69 if let Some(ref status) = app.blog_status {
70 let style = if status.starts_with("Error") {
71 Style::default().fg(Color::Red)
72 } else {
73 Style::default().fg(Color::Green)
74 };
75 let status_line = Paragraph::new(Line::from(vec![
76 Span::raw(" "),
77 Span::styled(status.as_str(), style),
78 ]));
79 frame.render_widget(status_line, chunks[3]);
80 }
81
82 // Keybindings
83 let mut key_spans = vec![
84 Span::raw(" "),
85 Span::styled("[j/k]", Style::default().add_modifier(Modifier::BOLD)),
86 Span::raw(" Nav "),
87 Span::styled("[n]", Style::default().add_modifier(Modifier::BOLD)),
88 Span::raw(" New "),
89 ];
90
91 if !app.blog_posts.is_empty() {
92 key_spans.extend([
93 Span::styled("[d]", Style::default().add_modifier(Modifier::BOLD)),
94 Span::raw(" Delete "),
95 ]);
96 }
97
98 key_spans.extend([
99 Span::styled("[r]", Style::default().add_modifier(Modifier::BOLD)),
100 Span::raw(" Refresh "),
101 Span::styled("[Esc]", Style::default().add_modifier(Modifier::BOLD)),
102 Span::raw(" Back"),
103 ]);
104
105 let keys = Paragraph::new(Line::from(key_spans)).style(Style::default().fg(Color::DarkGray));
106 frame.render_widget(keys, chunks[4]);
107 }
108
109 fn render_post_table(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) {
110 let rows: Vec<Row> = app
111 .blog_posts
112 .iter()
113 .enumerate()
114 .map(|(i, post)| {
115 let status = if post.is_published {
116 "published".to_string()
117 } else if let Some(ref pa) = post.publish_at {
118 let scheduled = pa.get(..16).unwrap_or(pa);
119 format!("sched {}", scheduled)
120 } else {
121 "draft".to_string()
122 };
123 let date = post.created_at.get(..10).unwrap_or(&post.created_at);
124
125 Row::new(vec![
126 format!(" {}", post.title),
127 post.slug.clone(),
128 status,
129 date.to_string(),
130 ])
131 .style(widgets::selected_style(i, Some(app.selected_index)))
132 })
133 .collect();
134
135 let widths = [
136 Constraint::Min(20),
137 Constraint::Length(20),
138 Constraint::Length(22),
139 Constraint::Length(12),
140 ];
141
142 widgets::render_table(frame, area, &[" Title", "Slug", "Status", "Created"], &widths, rows);
143 }
144