feat: add polls

This commit is contained in:
Ondřej Pešek 2023-03-23 13:21:09 +01:00
parent 5dc3279ac3
commit c1c867a5ff
3 changed files with 135 additions and 0 deletions

View file

@ -96,6 +96,62 @@ pub struct Author {
pub distinguished: String,
}
pub struct Poll {
pub poll_options: Vec<PollOption>,
pub voting_end_timestamp: (String, String),
pub total_vote_count: u64,
}
impl Poll {
pub fn parse(poll_data: &Value) -> Option<Self> {
if poll_data.as_object().is_none() { return None };
let total_vote_count = poll_data["total_vote_count"].as_u64()?;
// voting_end_timestamp is in the format of milliseconds
let voting_end_timestamp = time(poll_data["voting_end_timestamp"].as_f64()? / 1000.0);
let poll_options = PollOption::parse(&poll_data["options"]);
Some(Self {
poll_options,
total_vote_count,
voting_end_timestamp
})
}
pub fn most_votes(&self) -> u64 {
self.poll_options.iter().map(|o| o.vote_count).max().unwrap_or(0)
}
}
pub struct PollOption {
pub id: u64,
pub text: String,
pub vote_count: u64
}
impl PollOption {
pub fn parse(options: &Value) -> Vec<Self> {
options
.as_array()
.unwrap_or(&Vec::new())
.iter()
.map(|option| {
// For each poll option
let id = option["id"].as_u64().unwrap_or_default();
let text = option["text"].as_str().unwrap_or_default().to_owned();
let vote_count = option["vote_count"].as_u64().unwrap_or_default();
// Construct PollOption items
Self {
id,
text,
vote_count
}
})
.collect::<Vec<Self>>()
}
}
// Post flags with nsfw and stickied
pub struct Flags {
pub nsfw: bool,
@ -233,6 +289,7 @@ pub struct Post {
pub body: String,
pub author: Author,
pub permalink: String,
pub poll: Option<Poll>,
pub score: (String, String),
pub upvote_ratio: i64,
pub post_type: String,
@ -342,6 +399,7 @@ impl Post {
stickied: data["stickied"].as_bool().unwrap_or_default() || data["pinned"].as_bool().unwrap_or_default(),
},
permalink: val(post, "permalink"),
poll: Poll::parse(&data["poll_data"]),
rel_time,
created,
num_duplicates: post["data"]["num_duplicates"].as_u64().unwrap_or(0),
@ -600,6 +658,8 @@ pub async fn parse_post(post: &serde_json::Value) -> Post {
let permalink = val(post, "permalink");
let poll = Poll::parse(&post["data"]["poll_data"]);
let body = if val(post, "removed_by_category") == "moderator" {
format!(
"<div class=\"md\"><p>[removed] — <a href=\"https://www.unddit.com{}\">view removed post</a></p></div>",
@ -630,6 +690,7 @@ pub async fn parse_post(post: &serde_json::Value) -> Post {
distinguished: val(post, "distinguished"),
},
permalink,
poll,
score: format_num(score),
upvote_ratio: ratio as i64,
post_type,

View file

@ -752,6 +752,7 @@ a.search_subreddit:hover {
"post_score post_title post_thumbnail" 1fr
"post_score post_media post_thumbnail" auto
"post_score post_body post_thumbnail" auto
"post_score post_poll post_thumbnail" auto
"post_score post_notification post_thumbnail" auto
"post_score post_footer post_thumbnail" auto
/ minmax(40px, auto) minmax(0, 1fr) fit-content(min(20%, 152px));
@ -952,6 +953,43 @@ a.search_subreddit:hover {
overflow-wrap: anywhere;
}
.post_poll {
grid-area: post_poll;
padding: 5px 15px 5px 12px;
}
.poll_option {
position: relative;
margin-right: 15px;
margin-top: 14px;
z-index: 0;
display: flex;
align-items: center;
}
.poll_chart {
padding: 14px 0;
background-color: var(--accent);
opacity: 0.2;
border-radius: 5px;
z-index: -1;
position: absolute;
}
.poll_option span {
margin-left: 8px;
color: var(--text);
}
.poll_option span:nth-of-type(1) {
min-width: 10%;
font-weight: bold;
}
.most_voted {
opacity: 0.45;
}
/* Used only for text post preview */
.post_preview {
-webkit-mask-image: linear-gradient(180deg,#000 60%,transparent);;
@ -1563,6 +1601,7 @@ td, th {
"post_title post_title post_thumbnail" 1fr
"post_media post_media post_thumbnail" auto
"post_body post_body post_thumbnail" auto
"post_poll post_poll post_thumbnail" auto
"post_notification post_notification post_thumbnail" auto
"post_score post_footer post_thumbnail" auto
/ auto 1fr fit-content(min(20%, 152px));
@ -1573,6 +1612,10 @@ td, th {
padding: 0;
}
.post_poll {
padding: 5px 15px 10px 12px;
}
.compact .post_score { padding: 0; }
.post_score::before { content: "↑" }

View file

@ -148,6 +148,9 @@
<!-- POST BODY -->
<div class="post_body">{{ post.body|safe }}</div>
<div class="post_score" title="{{ post.score.1 }}">{{ post.score.0 }}<span class="label"> Upvotes</span></div>
{% call poll(post) %}
<div class="post_footer">
<ul id="post_links">
<li class="desktop_item"><a href="{{ post.permalink }}">permalink</a></li>
@ -272,6 +275,9 @@
<div class="post_body post_preview">
{{ post.body|safe }}
</div>
{% call poll(post) %}
<div class="post_footer">
<a href="{{ post.permalink }}" class="post_comments" title="{{ post.comments.1 }} {% if post.comments.1 == "1" %}comment{% else %}comments{% endif %}">{{ post.comments.0 }} {% if post.comments.1 == "1" %}comment{% else %}comments{% endif %}</a>
</div>
@ -299,3 +305,28 @@
</div>
</div>
{%- endmacro %}
{% macro poll(post) -%}
{% match post.poll %}
{% when Some with (poll) %}
{% let widest = poll.most_votes() %}
<div class="post_poll">
<span>{{ poll.total_vote_count }} votes</span>
<span>{{ poll.voting_end_timestamp.0 }}</span>
{% for option in poll.poll_options %}
<div class="poll_option">
{# Posts without vote_count (all open polls) will show up as having 0 votes.
This is an issue with Reddit API, it doesn't work on Old Reddit either. #}
{% if option.vote_count == widest %}
<div class="poll_chart most_voted" style="width: 100%"></div>
{% else %}
<div class="poll_chart" style="width: {{ (option.vote_count * 100) / widest }}%"></div>
{% endif %}
<span>{{ option.vote_count }}</span>
<span>{{ option.text }}</span>
</div>
{% endfor %}
</div>
{% when None %}
{% endmatch %}
{%- endmacro %}