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::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) -> 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::>() .into_iter() .collect::>(); // 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) -> 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,📰"; 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" {} }} } } }