mirror of
https://gitlab.com/Anson-Projects/zine.git
synced 2025-06-16 05:26:40 +00:00
294 lines
9.4 KiB
Rust
294 lines
9.4 KiB
Rust
extern crate chrono;
|
|
extern crate feed_rs;
|
|
extern crate maud;
|
|
extern crate reqwest;
|
|
|
|
use chrono::DateTime;
|
|
use chrono::Utc;
|
|
use feed_rs::model::Entry;
|
|
use maud::{html, Markup};
|
|
use std::env;
|
|
|
|
use crate::utilities;
|
|
use crate::web_fetchers;
|
|
|
|
fn create_featured_card(entry: &Entry) -> Markup {
|
|
let title = entry
|
|
.title
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |t| t.content.clone());
|
|
|
|
let link = entry.links.first().unwrap();
|
|
let lang = link.clone().href_lang.unwrap_or("en".to_string());
|
|
|
|
if lang != "en" {
|
|
log::warn!("Non english! {} {}", lang, link.href);
|
|
}
|
|
|
|
let mut image_url = entry
|
|
.media
|
|
.first()
|
|
.and_then(|m| m.content.first())
|
|
.and_then(|c| c.url.as_ref().map(|u| u.to_string()))
|
|
.unwrap_or_default();
|
|
|
|
// Fallback to fetching social image if direct extraction didn't work
|
|
if image_url.is_empty() {
|
|
log::info!(
|
|
"Falling back to searching for a social image for {}",
|
|
link.href
|
|
);
|
|
image_url = web_fetchers::fetch_social_image(link.href.as_str()).unwrap_or_default();
|
|
}
|
|
|
|
let description = entry.content.as_ref().map_or_else(
|
|
|| {
|
|
entry
|
|
.summary
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |summary| summary.content.clone())
|
|
},
|
|
|content| {
|
|
content
|
|
.body
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |body| body.clone())
|
|
},
|
|
);
|
|
|
|
let cleaned_description = utilities::strip_html_tags(&description);
|
|
let truncated_description = utilities::truncate_description(&cleaned_description, 500);
|
|
|
|
let main_url = utilities::get_root_url(link.href.as_str());
|
|
|
|
html! {
|
|
article {
|
|
header class="grid" {
|
|
img src=(image_url) alt="Entry image";
|
|
hgroup class="featured-title" {
|
|
h2 { (title) }
|
|
a href=(format!("http://{}", main_url)) { (main_url) }
|
|
}
|
|
}
|
|
body {
|
|
p { (truncated_description) }
|
|
}
|
|
footer {
|
|
a class="grid" href=(link.href) style="--pico-text-decoration: none;" {
|
|
button class="outline primary" { "Read Post" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn create_post_card(entry: &Entry) -> Markup {
|
|
let title = entry
|
|
.title
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |t| t.content.clone());
|
|
|
|
let link = entry.links.first().unwrap();
|
|
let lang = link.clone().href_lang.unwrap_or("en".to_string());
|
|
|
|
if lang != "en" {
|
|
log::warn!("Non english! {} {}", lang, link.href);
|
|
}
|
|
|
|
let mut image_url = entry
|
|
.media
|
|
.first()
|
|
.and_then(|m| m.content.first())
|
|
.and_then(|c| c.url.as_ref().map(|u| u.to_string()))
|
|
.unwrap_or_default();
|
|
|
|
// Fallback to fetching social image if direct extraction didn't work
|
|
if image_url.is_empty() {
|
|
log::info!(
|
|
"Falling back to searching for a social image for {}",
|
|
link.href
|
|
);
|
|
image_url = web_fetchers::fetch_social_image(link.href.as_str()).unwrap_or_default();
|
|
}
|
|
if image_url.is_empty() {
|
|
log::warn!("No image could be gathered for {}", link.href);
|
|
}
|
|
|
|
let description = entry.content.as_ref().map_or_else(
|
|
|| {
|
|
entry
|
|
.summary
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |summary| summary.content.clone())
|
|
},
|
|
|content| {
|
|
content
|
|
.body
|
|
.as_ref()
|
|
.map_or_else(|| "".to_string(), |body| body.clone())
|
|
},
|
|
);
|
|
|
|
let cleaned_description = utilities::strip_html_tags(&description);
|
|
let truncated_description = utilities::truncate_description(&cleaned_description, 500);
|
|
|
|
let main_url = utilities::get_root_url(link.href.as_str());
|
|
|
|
html! {
|
|
article {
|
|
header {
|
|
hgroup {
|
|
h2 { (title) }
|
|
a href=(format!("http://{}", main_url)) { (main_url) }
|
|
}
|
|
}
|
|
body {
|
|
@if !image_url.is_empty() {
|
|
img src=(image_url) alt="Entry image";
|
|
p;
|
|
}
|
|
p { (truncated_description) }
|
|
}
|
|
footer {
|
|
a class="grid" href=(link.href) style="--pico-text-decoration: none;" {
|
|
button class="outline secondary" { "Read Post" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_footer() -> Markup {
|
|
let utc: DateTime<Utc> = Utc::now();
|
|
let formatted_utc = utc.format("%Y-%m-%d %H:%M:%S").to_string();
|
|
|
|
html! {
|
|
footer class="container" {
|
|
small {
|
|
p {
|
|
a href="https://ansonbiggs.com" { "Anson Biggs" }
|
|
" - "
|
|
a href="https://gitlab.com/MisterBiggs/zine" { "Source Code" }
|
|
" - "
|
|
"Page generated at: " em data-tooltip="8AM Mountain Time" { (formatted_utc) " UTC" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn generate_header() -> Markup {
|
|
html! {
|
|
header {
|
|
nav {
|
|
ul {
|
|
li { h1 { "Anson's Aggregated Feed" }}
|
|
}
|
|
ul {
|
|
li { button data-target="about" onclick="toggleModal(event)" { "About" } }
|
|
li {
|
|
details class="dropdown" {
|
|
summary role="button" class="outline secondary" { "Theme" }
|
|
ul {
|
|
li { a href="#" data-theme-switcher="auto" { "Auto" }}
|
|
li { a href="#" data-theme-switcher="light" { "Light" }}
|
|
li { a href="#" data-theme-switcher="dark" { "Dark" }}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn about_modal(entries: Vec<Entry>) -> Markup {
|
|
// Get link for each entry, which is a blog post then,
|
|
// convert it to a url to the main page of the blog
|
|
let mut links = entries
|
|
.iter()
|
|
.map(|entry| entry.links.first().unwrap().href.as_str())
|
|
.map(utilities::get_root_url)
|
|
.collect::<std::collections::HashSet<&str>>()
|
|
.into_iter()
|
|
.collect::<Vec<&str>>();
|
|
|
|
// Alphabetical to be fair to everytone :)
|
|
links.sort();
|
|
|
|
html! {
|
|
dialog id="about" {
|
|
article {
|
|
header {
|
|
a href="#" aria-label="Close" rel="prev" {}
|
|
p { strong { "About" }}
|
|
}
|
|
p {
|
|
"When looking for a RSS reader I came across "
|
|
a href="https://news.russellsaw.io/" {"news.russellsaw.io"}
|
|
" I thought the idea of building my own personalised newspaper was cool. \
|
|
So, I decided to build a clone using my own subscribed RSS feeds."
|
|
}
|
|
p {
|
|
"This page updates daily at 8:11ish AM Mountain Time. The following blogs are"
|
|
" featured on the page currently:"
|
|
}
|
|
ul {
|
|
@for link in links {
|
|
li {a href=(link) {(link)}}
|
|
}
|
|
}
|
|
p {
|
|
"For the full list of feeds that are followed see the raw list "
|
|
a href="https://gitlab.com/MisterBiggs/zine/-/blob/master/feeds.txt" { "here." }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn generate_index(mut entries: Vec<Entry>) -> Markup {
|
|
let running_in_gitlab = env::var("CI").map(|val| val == "true").unwrap_or(false);
|
|
|
|
let featured = entries.remove(0);
|
|
if running_in_gitlab {
|
|
log::info!("Building for deployment");
|
|
entries.truncate(30);
|
|
} else {
|
|
log::info!("Building for development");
|
|
entries.truncate(6);
|
|
}
|
|
|
|
html! {
|
|
(maud::DOCTYPE)
|
|
html {
|
|
head {
|
|
title { "Anson's Zine" }
|
|
meta charset="utf-8";
|
|
meta name="viewport" content="width=device-width, initial-scale=1";
|
|
meta name="description" content="Aggregate newspaper of RSS feeds for Anson"
|
|
link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📰</text></svg>";
|
|
link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.blue.min.css";
|
|
link rel="stylesheet" href="style.css";
|
|
}
|
|
body { main class="container" {
|
|
{(generate_header())}
|
|
(create_featured_card(&featured))
|
|
div class="grid" {
|
|
@for column_entries in utilities::group_by_nth(&entries, 3) {
|
|
div {
|
|
@for entry in column_entries {
|
|
{(create_post_card(&entry))}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
{(generate_footer())}
|
|
{(about_modal(entries))}
|
|
script src="modal.js" {}
|
|
script src="minimal-theme-switcher.js" {}
|
|
}}
|
|
}
|
|
}
|
|
}
|