Likes and reposts
Ctrl-T switches the post type (note, like, repost) among the types the server lists in q=config. Sends like-of / repost-of with an optional comment; editing a like or repost keeps its target locked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::app::{App, Discard, Focus, Mode, Posts, Screen, Status, Targets};
|
||||
use crate::micropub::Post;
|
||||
use crate::micropub::{Kind, Post};
|
||||
|
||||
const ACCENT: Color = Color::Cyan;
|
||||
const DIM: Color = Color::DarkGray;
|
||||
@@ -42,7 +42,7 @@ pub fn draw(f: &mut Frame, app: &mut App) {
|
||||
let place = match (app.screen, &app.editing) {
|
||||
(Screen::Posts, _) => "Posts".to_string(),
|
||||
(Screen::Compose, Some(p)) => format!("Editing {}", p.url),
|
||||
(Screen::Compose, None) => "New note".to_string(),
|
||||
(Screen::Compose, None) => format!("New {}", app.kind.label().to_lowercase()),
|
||||
};
|
||||
f.render_widget(
|
||||
Line::from(vec![
|
||||
@@ -94,13 +94,25 @@ fn draw_compose(f: &mut Frame, app: &mut App, area: Rect) -> &'static str {
|
||||
app.title
|
||||
.set_block(field_block("Title", app.focus == Focus::Title));
|
||||
f.render_widget(&app.title, title);
|
||||
app.reply
|
||||
.set_block(field_block("In reply to", app.focus == Focus::Reply));
|
||||
f.render_widget(&app.reply, reply);
|
||||
let link_label = match (app.kind, app.link_locked()) {
|
||||
(Kind::Note, _) => "In reply to",
|
||||
(Kind::Like, false) => "Like of",
|
||||
(Kind::Repost, false) => "Repost of",
|
||||
(Kind::Like, true) => "Like of · can't be changed",
|
||||
(Kind::Repost, true) => "Repost of · can't be changed",
|
||||
};
|
||||
app.link
|
||||
.set_block(field_block(link_label, app.focus == Focus::Link));
|
||||
f.render_widget(&app.link, reply);
|
||||
let chars: usize = app.content.lines().iter().map(|l| l.chars().count()).sum();
|
||||
let name = if app.kind == Kind::Note {
|
||||
"Note"
|
||||
} else {
|
||||
"Comment (optional)"
|
||||
};
|
||||
let label = match &app.editing {
|
||||
Some(p) if p.draft => format!("Note · draft · {chars} chars"),
|
||||
_ => format!("Note · {chars} chars"),
|
||||
Some(p) if p.draft => format!("{name} · draft · {chars} chars"),
|
||||
_ => format!("{name} · {chars} chars"),
|
||||
};
|
||||
app.content
|
||||
.set_block(field_block(&label, app.focus == Focus::Content));
|
||||
@@ -119,7 +131,7 @@ fn draw_compose(f: &mut Frame, app: &mut App, area: Rect) -> &'static str {
|
||||
" Tab next field · Ctrl-S save · Ctrl-N new note · Ctrl-P posts · Esc back to posts"
|
||||
}
|
||||
(_, false) => {
|
||||
" Tab next field · Ctrl-S publish · Ctrl-P posts · Ctrl-R refresh · Ctrl-O open last · Esc quit"
|
||||
" Tab next · Ctrl-T note/like/repost · Ctrl-S publish · Ctrl-P posts · Ctrl-O open last · Esc quit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,15 +198,15 @@ fn short_date(iso: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// The post's title, or the start of its first line.
|
||||
/// The post's title, or the start of its first line (for a bare like or repost, its URL).
|
||||
fn headline(p: &Post) -> String {
|
||||
p.title.clone().unwrap_or_else(|| {
|
||||
p.content
|
||||
.lines()
|
||||
.find(|l| !l.trim().is_empty())
|
||||
.unwrap_or("(empty)")
|
||||
.trim()
|
||||
.to_string()
|
||||
.map(|l| l.trim().to_string())
|
||||
.or_else(|| p.target().map(str::to_string))
|
||||
.unwrap_or_else(|| "(empty)".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,8 +221,13 @@ fn post_item(p: &Post) -> ListItem<'static> {
|
||||
Style::default().fg(Color::Yellow).bold(),
|
||||
));
|
||||
}
|
||||
if p.in_reply_to.is_some() {
|
||||
spans.push(Span::styled("↩ ", Style::default().fg(ACCENT)));
|
||||
match p.kind() {
|
||||
Kind::Like => spans.push(Span::styled("♥ ", Style::default().fg(Color::Magenta))),
|
||||
Kind::Repost => spans.push(Span::styled("🔁 ", Style::default().fg(Color::Green))),
|
||||
Kind::Note if p.in_reply_to.is_some() => {
|
||||
spans.push(Span::styled("↩ ", Style::default().fg(ACCENT)))
|
||||
}
|
||||
Kind::Note => {}
|
||||
}
|
||||
let head = headline(p);
|
||||
spans.push(if p.title.is_some() {
|
||||
@@ -279,6 +296,18 @@ fn draw_posts(f: &mut Frame, app: &App, area: Rect) -> &'static str {
|
||||
if let Some(t) = &p.title {
|
||||
lines.push(Line::styled(t.clone(), Style::default().bold()));
|
||||
}
|
||||
if let Some(u) = &p.like_of {
|
||||
lines.push(Line::styled(
|
||||
format!("♥ Like of {u}"),
|
||||
Style::default().fg(Color::Magenta),
|
||||
));
|
||||
}
|
||||
if let Some(u) = &p.repost_of {
|
||||
lines.push(Line::styled(
|
||||
format!("🔁 Repost of {u}"),
|
||||
Style::default().fg(Color::Green),
|
||||
));
|
||||
}
|
||||
if let Some(r) = &p.in_reply_to {
|
||||
lines.push(Line::styled(
|
||||
format!("In reply to {r}"),
|
||||
@@ -314,18 +343,23 @@ fn draw_popup(f: &mut Frame, app: &App) {
|
||||
match &app.mode {
|
||||
Mode::Normal => {}
|
||||
Mode::ConfirmPublish => {
|
||||
let verb = match (editing, app.kind) {
|
||||
(true, _) => "Save changes to ",
|
||||
(false, Kind::Note) => "Post to ",
|
||||
(false, Kind::Like) => "Like, on ",
|
||||
(false, Kind::Repost) => "Repost, on ",
|
||||
};
|
||||
let mut lines = vec![Line::from(vec![
|
||||
Span::raw(if editing {
|
||||
"Save changes to "
|
||||
} else {
|
||||
"Post to "
|
||||
}),
|
||||
Span::raw(verb),
|
||||
Span::styled(app.me.clone(), Style::default().bold()),
|
||||
])];
|
||||
lines.push(Line::from(vec![
|
||||
Span::raw("Crosspost: "),
|
||||
Span::styled(app.syndication_summary(), Style::default().fg(ACCENT)),
|
||||
]));
|
||||
if app.kind == Kind::Like && !editing {
|
||||
lines.push(hint("Likes of ordinary web pages aren't crossposted."));
|
||||
}
|
||||
lines.push(Line::raw(""));
|
||||
let keys = match (editing, app.default_draft()) {
|
||||
(false, _) => "y / Enter publish · d save as draft · Esc cancel",
|
||||
@@ -452,5 +486,10 @@ mod tests {
|
||||
assert_eq!(headline(&p), "First line");
|
||||
p.title = Some("Title".into());
|
||||
assert_eq!(headline(&p), "Title");
|
||||
let like = Post {
|
||||
like_of: Some("https://x.example/".into()),
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(headline(&like), "https://x.example/");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user