get authors also from feed data + some css
This commit is contained in:
parent
433afd364f
commit
603b4f2a29
69
planet.css
69
planet.css
@ -1,5 +1,5 @@
|
|||||||
p, h1, h2, h3, h4, h5, h6, small {
|
p, h1, h2, h3, h4, h5, h6, small {
|
||||||
max-width: 48em;
|
max-width: 48em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||||
@ -9,34 +9,71 @@ h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
|||||||
|
|
||||||
ul, ol {
|
ul, ol {
|
||||||
/* account for the 1em -webkit-margin-start for the list icon */
|
/* account for the 1em -webkit-margin-start for the list icon */
|
||||||
max-width: 45em;
|
max-width: 45em;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul,ol,dl, p {
|
ul,ol,dl, p {
|
||||||
line-height: 1.4;
|
margin-top: 0.3em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
ul, ol {
|
||||||
margin-top: 1em;
|
padding-inline-start: 1.5em;
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bodydiv {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 80em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maincontainer aside img {
|
||||||
|
max-width: 10em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maincontainer main blockquote {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
box-shadow: 10px 0px 0px 0px #C4C4C4;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote, pre code {
|
||||||
|
padding: 0.5ex 0;
|
||||||
|
display: block;
|
||||||
|
background-color: #EEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maincontainer main * {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maincontainer main pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry_meta {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 1024px) {
|
||||||
#maincontainer {
|
#maincontainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 80em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#maincontainer main {
|
#maincontainer main {
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
}
|
flex: 5;
|
||||||
|
|
||||||
#maincontainer main * {
|
|
||||||
max-width: 50em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#maincontainer aside {
|
#maincontainer aside {
|
||||||
margin-left: 5em;
|
margin-left: 5em;
|
||||||
max-width: 25em;
|
max-width: 15em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#maincontainer aside img {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
article > h2.entry_header {
|
article > h2.entry_header {
|
||||||
@ -44,7 +81,6 @@ article > h2.entry_header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.entry_meta {
|
.entry_meta {
|
||||||
border: 1px thin;
|
|
||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
background-color: LightBlue;
|
background-color: LightBlue;
|
||||||
}
|
}
|
||||||
@ -53,7 +89,8 @@ hr.entry_sep {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
hr.entry_sep::before {
|
hr.entry_sep::before {
|
||||||
content: '* * *';
|
content: '* * *';
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use feed_rs::model::Entry;
|
|||||||
use feed_rs::model::Feed;
|
use feed_rs::model::Feed;
|
||||||
use ron::ser::{to_string_pretty, PrettyConfig};
|
use ron::ser::{to_string_pretty, PrettyConfig};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::convert::AsRef;
|
use std::convert::AsRef;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
@ -13,9 +14,6 @@ use ureq::http::Response;
|
|||||||
use ureq::Body;
|
use ureq::Body;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// How many feed entries should be included in the planet
|
|
||||||
const ENTRIES_LEN: usize = 10;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Default)]
|
#[derive(Deserialize, Serialize, Default)]
|
||||||
pub struct FetchData {
|
pub struct FetchData {
|
||||||
pub etag: String,
|
pub etag: String,
|
||||||
@ -135,8 +133,12 @@ impl FeedStore {
|
|||||||
Ok(Some(parser.parse(BufReader::new(file))?))
|
Ok(Some(parser.parse(BufReader::new(file))?))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect(&self, feed_configs: &Vec<super::FeedConfig>) -> (Vec<Feed>, Vec<Entry>) {
|
pub fn collect(
|
||||||
let mut feeds = Vec::new();
|
&self,
|
||||||
|
feed_configs: &Vec<super::FeedConfig>,
|
||||||
|
max_entries: usize,
|
||||||
|
) -> (HashMap<String, Feed>, Vec<Entry>) {
|
||||||
|
let mut feeds = HashMap::new();
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
for feed_config in feed_configs {
|
for feed_config in feed_configs {
|
||||||
@ -154,21 +156,24 @@ impl FeedStore {
|
|||||||
Ok(None) => continue,
|
Ok(None) => continue,
|
||||||
Ok(Some(f)) => f,
|
Ok(Some(f)) => f,
|
||||||
};
|
};
|
||||||
|
for entry in &mut feed.entries {
|
||||||
|
entry.source = Some(feed_config.url.clone());
|
||||||
|
}
|
||||||
|
|
||||||
entries.append(&mut std::mem::take(&mut feed.entries));
|
entries.append(&mut std::mem::take(&mut feed.entries));
|
||||||
feeds.push(feed);
|
feeds.insert(feed_config.url.clone(), feed);
|
||||||
// optimization to reduce memory usage
|
// optimization to reduce memory usage
|
||||||
if entries.len() > 4 * ENTRIES_LEN {
|
if entries.len() > 4 * max_entries {
|
||||||
entries = trim_entries(entries);
|
entries = trim_entries(entries, max_entries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(feeds, trim_entries(entries))
|
(feeds, trim_entries(entries, max_entries))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trim_entries(mut entries: Vec<Entry>) -> Vec<Entry> {
|
fn trim_entries(mut entries: Vec<Entry>, max_entries: usize) -> Vec<Entry> {
|
||||||
entries.sort_by_key(|e| std::cmp::Reverse(e.updated.or(e.published).unwrap_or_default()));
|
entries.sort_by_key(|e| std::cmp::Reverse(e.updated.or(e.published).unwrap_or_default()));
|
||||||
entries.truncate(ENTRIES_LEN);
|
entries.truncate(max_entries);
|
||||||
entries
|
entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,8 @@ struct Config {
|
|||||||
out_dir: String,
|
out_dir: String,
|
||||||
/// templates folder
|
/// templates folder
|
||||||
templates_dir: String,
|
templates_dir: String,
|
||||||
|
/// How many feed entries should be included in the planet
|
||||||
|
max_entries: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_checked_pathbuf(dir: &str) -> PathBuf {
|
pub fn to_checked_pathbuf(dir: &str) -> PathBuf {
|
||||||
|
@ -2,21 +2,25 @@ use crate::feed_store::FeedStore;
|
|||||||
use crate::to_checked_pathbuf;
|
use crate::to_checked_pathbuf;
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use feed_rs::model::Feed;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use tera::Tera;
|
use tera::{from_value, Tera};
|
||||||
|
|
||||||
pub fn build(config: &Config, feed_store: &FeedStore) -> Result<()> {
|
pub fn build(config: &Config, feed_store: &FeedStore) -> Result<()> {
|
||||||
let tera = create_tera(&config.templates_dir)?;
|
let mut tera = create_tera(&config.templates_dir)?;
|
||||||
let out_dir = to_checked_pathbuf(&config.out_dir);
|
let out_dir = to_checked_pathbuf(&config.out_dir);
|
||||||
|
|
||||||
let mut context = tera::Context::new();
|
let mut context = tera::Context::new();
|
||||||
let (feeds, entries) = feed_store.collect(&config.feeds);
|
let (feeds, entries): (HashMap<String, Feed>, _) =
|
||||||
|
feed_store.collect(&config.feeds, config.max_entries);
|
||||||
context.insert("feeds", &feeds);
|
context.insert("feeds", &feeds);
|
||||||
context.insert("entries", &entries);
|
context.insert("entries", &entries);
|
||||||
context.insert("PKG_AUTHORS", env!("CARGO_PKG_AUTHORS"));
|
context.insert("PKG_AUTHORS", env!("CARGO_PKG_AUTHORS"));
|
||||||
context.insert("PKG_HOMEPAGE", env!("CARGO_PKG_HOMEPAGE"));
|
context.insert("PKG_HOMEPAGE", env!("CARGO_PKG_HOMEPAGE"));
|
||||||
context.insert("PKG_NAME", env!("CARGO_PKG_NAME"));
|
context.insert("PKG_NAME", env!("CARGO_PKG_NAME"));
|
||||||
context.insert("PKG_VERSION", env!("CARGO_PKG_VERSION"));
|
context.insert("PKG_VERSION", env!("CARGO_PKG_VERSION"));
|
||||||
|
tera.register_function("get_author", GetAuthorFunction { feeds });
|
||||||
|
|
||||||
for name in tera.get_template_names() {
|
for name in tera.get_template_names() {
|
||||||
debug!("Processing template {name}");
|
debug!("Processing template {name}");
|
||||||
@ -33,3 +37,54 @@ fn create_tera(templates_dir: &str) -> Result<Tera> {
|
|||||||
tera.autoescape_on(vec![]);
|
tera.autoescape_on(vec![]);
|
||||||
Ok(tera)
|
Ok(tera)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GetAuthorFunction {
|
||||||
|
feeds: HashMap<String, Feed>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl tera::Function for GetAuthorFunction {
|
||||||
|
fn call(&self, args: &HashMap<String, tera::Value>) -> Result<tera::Value, tera::Error> {
|
||||||
|
let entry_val: tera::Map<_, _> = match args.get("entry") {
|
||||||
|
None => {
|
||||||
|
return Err(tera::Error::msg(
|
||||||
|
"No argument of name 'entry' given to function.",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
Some(val) => from_value(val.clone())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let feed_url: String = from_value(entry_val.get("source").unwrap().clone())?;
|
||||||
|
let authors_val: Vec<tera::Map<_, _>> =
|
||||||
|
from_value(entry_val.get("authors").unwrap().clone())?;
|
||||||
|
|
||||||
|
let mut authors: Vec<String> = Vec::new();
|
||||||
|
for author_val in authors_val {
|
||||||
|
let name: String = from_value(author_val.get("name").unwrap().clone())?;
|
||||||
|
if is_valid_name(&name) {
|
||||||
|
authors.push(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if authors.is_empty() {
|
||||||
|
authors.append(&mut self.find_authors_from_feed(&feed_url));
|
||||||
|
}
|
||||||
|
Ok(tera::Value::String(authors.join(", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetAuthorFunction {
|
||||||
|
fn find_authors_from_feed(&self, feed_url: &str) -> Vec<String> {
|
||||||
|
let feed = self.feeds.get(feed_url).unwrap();
|
||||||
|
|
||||||
|
feed.authors
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.name)
|
||||||
|
.filter(is_valid_name)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_name(n: &String) -> bool {
|
||||||
|
!n.is_empty() && n != "unknown" && n != "author"
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{% set dateformat = "%d.%m.%Y %H:%M" -%}
|
{% set dateformat = "%d.%m.%Y %H:%M" -%}
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Planet TVL</title>
|
<title>Planet TVL</title>
|
||||||
@ -10,6 +9,7 @@
|
|||||||
<link rel="alternate" type="application/xml+atom" title="Planet Haskell Atom Feed" href="atom.xml">
|
<link rel="alternate" type="application/xml+atom" title="Planet Haskell Atom Feed" href="atom.xml">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="bodydiv">
|
||||||
<header>
|
<header>
|
||||||
<h1>Planet TVL</h1>
|
<h1>Planet TVL</h1>
|
||||||
</header>
|
</header>
|
||||||
@ -42,20 +42,20 @@
|
|||||||
<span>{{ entry.published | date(format=dateformat) }}</span>
|
<span>{{ entry.published | date(format=dateformat) }}</span>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
</date>
|
</date>
|
||||||
{% if entry.authors -%}
|
{% set author = get_author(entry=entry) -%}
|
||||||
— <span class="entry_author">{{ entry.authors.0.name | striptags }}</span>
|
{% if author -%}
|
||||||
|
— <span class="entry_author">{{ author | striptags }}</span>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if entry.summary -%}
|
|
||||||
<div class="entry_summary">
|
|
||||||
{{ entry.summary.content }}
|
|
||||||
</div>
|
|
||||||
{% endif -%}
|
|
||||||
{% if entry.content -%}
|
{% if entry.content -%}
|
||||||
<div class="entry_content">
|
<div class="entry_content">
|
||||||
{{ entry.content.body }}
|
{{ entry.content.body }}
|
||||||
</div>
|
</div>
|
||||||
|
{% elif entry.summary -%}
|
||||||
|
<div class="entry_summary">
|
||||||
|
{{ entry.summary.content }}
|
||||||
|
</div>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
</article>
|
</article>
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
@ -65,7 +65,7 @@
|
|||||||
<img src="logo.svg">
|
<img src="logo.svg">
|
||||||
<p>Last updated: {{now()|date(format="%Y-%m-%d %H:%M")}}</p>
|
<p>Last updated: {{now()|date(format="%Y-%m-%d %H:%M")}}</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for feed in feeds %}
|
{% for feed_url, feed in feeds %}
|
||||||
<li>
|
<li>
|
||||||
<a {% if feed.links.0 %}href="{{feed.links.0.href}}"{% endif -%}>
|
<a {% if feed.links.0 %}href="{{feed.links.0.href}}"{% endif -%}>
|
||||||
{% if feed.title -%}
|
{% if feed.title -%}
|
||||||
@ -74,10 +74,12 @@
|
|||||||
{{ feed.authors.0.name }}
|
{{ feed.authors.0.name }}
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
</a>
|
</a>
|
||||||
|
(<a href="{{feed_url}}">feed</a>)
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user