8802779ebcb7d4726342c28944c06e17b4a38e82 / frogue / src / views / index.rs
Raw
use maud::{Markup, PreEscaped, Render, html};
use tracing::instrument;
use super::Page;
use super::components::Pagination;
use crate::activitypub::feed::FeedEntry;
pub const FEED_PAGE_SIZE: usize = 20;
/// The body of the index page: when a user is logged in and federation is
/// enabled, show their home feed. Otherwise show an empty page.
pub(super) struct Index {
pub entries: Vec<FeedEntry>,
pub public_url: String,
pub viewer_username: Option<String>,
pub page: u64,
}
impl Render for Index {
fn render(&self) -> Markup {
if self.entries.is_empty() {
// Empty feed - show a message or empty page
if self.viewer_username.is_some() {
html! {
p { "Your feed is empty. Follow some remote users or post a note!" }
}
} else {
html! {}
}
} else {
html! {
div class="feed" {
@for entry in &self.entries {
(render_feed_entry(entry, &self.public_url, &self.viewer_username))
}
}
(Pagination {
current_page: self.page,
has_next: self.entries.len() == FEED_PAGE_SIZE,
base_url: "/".to_string(),
})
}
}
}
}
/// Render a single feed entry.
fn render_feed_entry(
entry: &FeedEntry,
public_url: &str,
viewer_username: &Option<String>,
) -> Markup {
let author_handle = entry.author_handle();
let content = entry.content();
let permalink = match entry {
FeedEntry::Local {
uuid,
author_username,
..
} => format!("{public_url}/~{author_username}/notes/{uuid}"),
FeedEntry::Remote { object_url, .. } => object_url.clone(),
};
let timestamp = entry.timestamp();
let time_str = timestamp.format("%Y-%m-%d %H:%M UTC").to_string();
let (reacted, boosted, remote_note_id) = match entry {
FeedEntry::Local { reacted, .. } => (*reacted, false, None),
FeedEntry::Remote {
remote_note_id,
reacted,
boosted,
..
} => (*reacted, *boosted, Some(*remote_note_id)),
};
html! {
article class="feed-entry" {
header class="feed-entry-header" {
strong class="feed-entry-author" { (author_handle) }
time datetime=(timestamp.to_rfc3339()) { (time_str) }
}
div class="feed-entry-content" {
(PreEscaped(content))
}
footer class="feed-entry-footer" {
a href=(permalink) { "permalink" }
@if viewer_username.is_some() {
@if let Some(note_id) = remote_note_id {
@if reacted {
form method="post" action=(format!("/remote-notes/{}/unreact", note_id)) {
button type="submit" class="react-button reacted" { "❤️ Unreact" }
}
} @else {
form method="post" action=(format!("/remote-notes/{}/react", note_id)) {
button type="submit" class="react-button" { "❤️ React" }
}
}
@if boosted {
form method="post" action=(format!("/remote-notes/{}/unannounce", note_id)) {
button type="submit" class="react-button reacted" { "🔁 Unboost" }
}
} @else {
form method="post" action=(format!("/remote-notes/{}/announce", note_id)) {
button type="submit" class="react-button" { "🔁 Boost" }
}
}
}
}
}
}
}
}
/// The index page, as seen by `viewer` - nobody, when it is `None`.
/// When logged in and federation is enabled, shows the home feed.
#[instrument(skip(entries))]
pub fn index(
viewer: Option<&str>,
entries: Vec<FeedEntry>,
public_url: &str,
page: u64,
) -> HttpResponse {
let viewer_username = viewer.map(str::to_string);
Page {
title: "Home - Frogue".to_string(),
viewer: viewer_username.clone(),
extra_head: None,
body: Index {
entries,
public_url: public_url.to_string(),
viewer_username,
page,
},
}
.into_response()
}
use actix_web::HttpResponse;