Support displaying awards (#168)
* Initial implementation of award parsing * Posts: Implement awards as part of post * Posts: remove parse_awards dead code * Posts: initial implementation of displaying Awards at the post title * Posts: Proxy static award images * Client: i.redd.it should take path as argument not ID * Posts: Just like Reddit make award size 16px * Templates: limit the awards to 4 awards to increase performance * Comments: Make awards a property of comments and display them * Format and correct /img/:id * Update comment.html * [Optimization] Awards is not longer async * [Revert] Posts can now display more than 4 awards again * [Implementation] Awards not display on the frontpage * [Implementation] Display count on awards * Post: Start working on awards css * Awards: Move the image size to css * Awards: Start implementing tooltips * Refactor awards code and tweak CSS indentation * Unify Awards::new and Awards::parse * Use native tooltips and brighten awards background Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
This commit is contained in:
parent
3054b9f4a0
commit
bd413060c6
@ -1 +0,0 @@
|
|||||||
* @spikecodes
|
|
@ -180,7 +180,7 @@ async fn main() {
|
|||||||
// Proxy media through Libreddit
|
// Proxy media through Libreddit
|
||||||
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
|
app.at("/vid/:id/:size").get(|r| proxy(r, "https://v.redd.it/{id}/DASH_{size}").boxed());
|
||||||
app.at("/hls/:id/*path").get(|r| proxy(r, "https://v.redd.it/{id}/{path}").boxed());
|
app.at("/hls/:id/*path").get(|r| proxy(r, "https://v.redd.it/{id}/{path}").boxed());
|
||||||
app.at("/img/:id").get(|r| proxy(r, "https://i.redd.it/{id}").boxed());
|
app.at("/img/*path").get(|r| proxy(r, "https://i.redd.it/{path}").boxed());
|
||||||
app.at("/thumb/:point/:id").get(|r| proxy(r, "https://{point}.thumbs.redditmedia.com/{id}").boxed());
|
app.at("/thumb/:point/:id").get(|r| proxy(r, "https://{point}.thumbs.redditmedia.com/{id}").boxed());
|
||||||
app.at("/emoji/:id/:name").get(|r| proxy(r, "https://emoji.redditmedia.com/{id}/{name}").boxed());
|
app.at("/emoji/:id/:name").get(|r| proxy(r, "https://emoji.redditmedia.com/{id}/{name}").boxed());
|
||||||
app.at("/preview/:loc/:id").get(|r| proxy(r, "https://{loc}view.redd.it/{id}").boxed());
|
app.at("/preview/:loc/:id").get(|r| proxy(r, "https://{loc}view.redd.it/{id}").boxed());
|
||||||
|
11
src/post.rs
11
src/post.rs
@ -3,8 +3,9 @@ use crate::client::json;
|
|||||||
use crate::esc;
|
use crate::esc;
|
||||||
use crate::server::RequestExt;
|
use crate::server::RequestExt;
|
||||||
use crate::subreddit::{can_access_quarantine, quarantine};
|
use crate::subreddit::{can_access_quarantine, quarantine};
|
||||||
use crate::utils::{error, format_num, format_url, param, rewrite_urls, setting, template, time, val, Author, Comment, Flags, Flair, FlairPart, Media, Post, Preferences};
|
use crate::utils::{
|
||||||
|
error, format_num, format_url, param, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flags, Flair, FlairPart, Media, Post, Preferences,
|
||||||
|
};
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
@ -93,6 +94,8 @@ async fn parse_post(json: &serde_json::Value) -> Post {
|
|||||||
// Determine the type of media along with the media URL
|
// Determine the type of media along with the media URL
|
||||||
let (post_type, media, gallery) = Media::parse(&post["data"]).await;
|
let (post_type, media, gallery) = Media::parse(&post["data"]).await;
|
||||||
|
|
||||||
|
let awards: Awards = Awards::parse(&post["data"]["all_awardings"]);
|
||||||
|
|
||||||
// Build a post using data parsed from Reddit post API
|
// Build a post using data parsed from Reddit post API
|
||||||
Post {
|
Post {
|
||||||
id: val(post, "id"),
|
id: val(post, "id"),
|
||||||
@ -148,6 +151,7 @@ async fn parse_post(json: &serde_json::Value) -> Post {
|
|||||||
created,
|
created,
|
||||||
comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()),
|
comments: format_num(post["data"]["num_comments"].as_i64().unwrap_or_default()),
|
||||||
gallery,
|
gallery,
|
||||||
|
awards,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +182,8 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let awards: Awards = Awards::parse(&data["all_awardings"]);
|
||||||
|
|
||||||
let parent_kind_and_id = val(&comment, "parent_id");
|
let parent_kind_and_id = val(&comment, "parent_id");
|
||||||
let parent_info = parent_kind_and_id.split('_').collect::<Vec<&str>>();
|
let parent_info = parent_kind_and_id.split('_').collect::<Vec<&str>>();
|
||||||
|
|
||||||
@ -224,6 +230,7 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
|
|||||||
edited,
|
edited,
|
||||||
replies,
|
replies,
|
||||||
highlighted,
|
highlighted,
|
||||||
|
awards,
|
||||||
collapsed,
|
collapsed,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
60
src/utils.rs
60
src/utils.rs
@ -8,6 +8,7 @@ use hyper::{Body, Request, Response};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -213,6 +214,7 @@ pub struct Post {
|
|||||||
pub created: String,
|
pub created: String,
|
||||||
pub comments: (String, String),
|
pub comments: (String, String),
|
||||||
pub gallery: Vec<GalleryMedia>,
|
pub gallery: Vec<GalleryMedia>,
|
||||||
|
pub awards: Awards,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
@ -249,7 +251,8 @@ impl Post {
|
|||||||
let title = esc!(post, "title");
|
let title = esc!(post, "title");
|
||||||
|
|
||||||
// Determine the type of media along with the media URL
|
// Determine the type of media along with the media URL
|
||||||
let (post_type, media, gallery) = Media::parse(data).await;
|
let (post_type, media, gallery) = Media::parse(&data).await;
|
||||||
|
let awards = Awards::parse(&data["all_awardings"]);
|
||||||
|
|
||||||
// selftext_html is set for text posts when browsing.
|
// selftext_html is set for text posts when browsing.
|
||||||
let mut body = rewrite_urls(&val(post, "selftext_html"));
|
let mut body = rewrite_urls(&val(post, "selftext_html"));
|
||||||
@ -315,6 +318,7 @@ impl Post {
|
|||||||
created,
|
created,
|
||||||
comments: format_num(data["num_comments"].as_i64().unwrap_or_default()),
|
comments: format_num(data["num_comments"].as_i64().unwrap_or_default()),
|
||||||
gallery,
|
gallery,
|
||||||
|
awards,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,9 +344,63 @@ pub struct Comment {
|
|||||||
pub edited: (String, String),
|
pub edited: (String, String),
|
||||||
pub replies: Vec<Comment>,
|
pub replies: Vec<Comment>,
|
||||||
pub highlighted: bool,
|
pub highlighted: bool,
|
||||||
|
pub awards: Awards,
|
||||||
pub collapsed: bool,
|
pub collapsed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Award {
|
||||||
|
pub name: String,
|
||||||
|
pub icon_url: String,
|
||||||
|
pub description: String,
|
||||||
|
pub count: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Award {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{} {} {}", self.name, self.icon_url, self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Awards(pub Vec<Award>);
|
||||||
|
|
||||||
|
impl std::ops::Deref for Awards {
|
||||||
|
type Target = Vec<Award>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Awards {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
self.iter().fold(Ok(()), |result, award| result.and_then(|_| writeln!(f, "{}", award)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Reddit awards JSON to Awards struct
|
||||||
|
impl Awards {
|
||||||
|
pub fn parse(items: &Value) -> Self {
|
||||||
|
let parsed = items.as_array().unwrap_or(&Vec::new()).iter().fold(Vec::new(), |mut awards, item| {
|
||||||
|
let name = item["name"].as_str().unwrap_or_default().to_string();
|
||||||
|
let icon_url = format_url(&item["icon_url"].as_str().unwrap_or_default().to_string());
|
||||||
|
let description = item["description"].as_str().unwrap_or_default().to_string();
|
||||||
|
let count: i64 = i64::from_str(&item["count"].to_string()).unwrap_or(1);
|
||||||
|
|
||||||
|
awards.push(Award {
|
||||||
|
name,
|
||||||
|
icon_url,
|
||||||
|
description,
|
||||||
|
count,
|
||||||
|
});
|
||||||
|
|
||||||
|
awards
|
||||||
|
});
|
||||||
|
|
||||||
|
Self(parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "error.html", escape = "none")]
|
#[template(path = "error.html", escape = "none")]
|
||||||
pub struct ErrorTemplate {
|
pub struct ErrorTemplate {
|
||||||
|
@ -272,7 +272,7 @@ main {
|
|||||||
#column_one {
|
#column_one {
|
||||||
max-width: 750px;
|
max-width: 750px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
overflow: hidden;
|
overflow: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
@ -735,6 +735,7 @@ a.search_subreddit:hover {
|
|||||||
.post_header {
|
.post_header {
|
||||||
margin: 15px 20px 5px 12px;
|
margin: 15px 20px 5px 12px;
|
||||||
grid-area: post_header;
|
grid-area: post_header;
|
||||||
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post_subreddit {
|
.post_subreddit {
|
||||||
@ -774,6 +775,26 @@ a.search_subreddit:hover {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.awards {
|
||||||
|
background-color: var(--foreground);
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.awards .award {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.award {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.award > img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.author_flair:empty, .post_flair:empty {
|
.author_flair:empty, .post_flair:empty {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -1102,7 +1123,7 @@ summary.comment_data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.compact .post_header {
|
.compact .post_header {
|
||||||
margin: 15px 15px 2.5px 12px;
|
margin: 11px 15px 2.5px 12px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ post_link }}{{ id }}/?context=3" class="created" title="{{ created }}">{{ rel_time }}</a>
|
<a href="{{ post_link }}{{ id }}/?context=3" class="created" title="{{ created }}">{{ rel_time }}</a>
|
||||||
{% if edited.0 != "".to_string() %}<span class="edited" title="{{ edited.1 }}">edited {{ edited.0 }}</span>{% endif %}
|
{% if edited.0 != "".to_string() %}<span class="edited" title="{{ edited.1 }}">edited {{ edited.0 }}</span>{% endif %}
|
||||||
|
{% if !awards.is_empty() %}
|
||||||
|
<span class="dot">•</span>
|
||||||
|
{% for award in awards.clone() %}
|
||||||
|
<span class="award" title="{{ award.name }}">
|
||||||
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</summary>
|
</summary>
|
||||||
<div class="comment_body {% if highlighted %}highlighted{% endif %}">{{ body }}</div>
|
<div class="comment_body {% if highlighted %}highlighted{% endif %}">{{ body }}</div>
|
||||||
<blockquote class="replies">{% for c in replies -%}{{ c.render().unwrap() }}{%- endfor %}
|
<blockquote class="replies">{% for c in replies -%}{{ c.render().unwrap() }}{%- endfor %}
|
||||||
|
@ -43,6 +43,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="dot">•</span>
|
<span class="dot">•</span>
|
||||||
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
||||||
|
{% if !post.awards.is_empty() %}
|
||||||
|
<span class="dot">•</span>
|
||||||
|
<span class="awards">
|
||||||
|
{% for award in post.awards.clone() %}
|
||||||
|
<span class="award" title="{{ award.name }}">
|
||||||
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
|
{{ award.count }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<p class="post_title">
|
<p class="post_title">
|
||||||
<a href="{{ post.permalink }}">{{ post.title }}</a>
|
<a href="{{ post.permalink }}">{{ post.title }}</a>
|
||||||
|
@ -75,6 +75,13 @@
|
|||||||
<a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
<a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
|
||||||
<span class="dot">•</span>
|
<span class="dot">•</span>
|
||||||
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
|
||||||
|
{% if !post.awards.is_empty() %}
|
||||||
|
{% for award in post.awards.clone() %}
|
||||||
|
<span class="award" title="{{ award.name }}">
|
||||||
|
<img alt="{{ award.name }}" src="{{ award.icon_url }}" width="16" height="16"/>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<p class="post_title">
|
<p class="post_title">
|
||||||
{% if post.flair.flair_parts.len() > 0 %}
|
{% if post.flair.flair_parts.len() > 0 %}
|
||||||
|
Loading…
Reference in New Issue
Block a user