Basic Nested & Collapsible Comments

This commit is contained in:
spikecodes 2020-12-19 19:54:46 -08:00
parent 19dc7de3c5
commit 7b8f694c8c
8 changed files with 124 additions and 69 deletions

23
Cargo.lock generated
View file

@ -338,6 +338,17 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "async-recursion"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5444eec77a9ec2bfe4524139e09195862e981400c4358d3b760cae634e4c4ee"
dependencies = [
"proc-macro2 1.0.24",
"quote 1.0.7",
"syn 1.0.54",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.42" version = "0.1.42"
@ -915,9 +926,9 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown", "hashbrown",
@ -1004,10 +1015,11 @@ checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
[[package]] [[package]]
name = "libreddit" name = "libreddit"
version = "0.1.11" version = "0.2.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"askama", "askama",
"async-recursion",
"base64 0.13.0", "base64 0.13.0",
"chrono", "chrono",
"pulldown-cmark", "pulldown-cmark",
@ -1640,13 +1652,12 @@ checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.17" version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" checksum = "97e0e9fd577458a4f61fb91fcb559ea2afecc54c934119421f9f5d3d5b1a1057"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall",
"winapi 0.3.9", "winapi 0.3.9",
] ]

View file

@ -3,7 +3,7 @@ name = "libreddit"
description = " Alternative private front-end to Reddit" description = " Alternative private front-end to Reddit"
license = "AGPL-3.0" license = "AGPL-3.0"
repository = "https://github.com/spikecodes/libreddit" repository = "https://github.com/spikecodes/libreddit"
version = "0.1.11" version = "0.2.0"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"] authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2018" edition = "2018"
@ -20,3 +20,4 @@ serde = "1.0.117"
serde_json = "1.0" serde_json = "1.0"
pulldown-cmark = "0.8.0" pulldown-cmark = "0.8.0"
chrono = "0.4.19" chrono = "0.4.19"
async-recursion = "0.3.1"

View file

@ -1,5 +1,5 @@
// Import Crates // Import Crates
use actix_web::{web, get, App, HttpResponse, HttpServer, middleware::NormalizePath}; use actix_web::{get, middleware::NormalizePath, web, App, HttpResponse, HttpServer};
// Reference local files // Reference local files
mod popular; mod popular;

View file

@ -2,6 +2,8 @@
use crate::utils::{format_num, format_url, request, val, Comment, ErrorTemplate, Flair, Params, Post}; use crate::utils::{format_num, format_url, request, val, Comment, ErrorTemplate, Flair, Params, Post};
use actix_web::{http::StatusCode, web, HttpResponse, Result}; use actix_web::{http::StatusCode, web, HttpResponse, Result};
use async_recursion::async_recursion;
use askama::Template; use askama::Template;
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use pulldown_cmark::{html, Options, Parser}; use pulldown_cmark::{html, Options, Parser};
@ -133,25 +135,35 @@ async fn parse_post(json: serde_json::Value) -> Result<Post, &'static str> {
} }
// COMMENTS // COMMENTS
#[async_recursion]
async fn parse_comments(json: serde_json::Value) -> Result<Vec<Comment>, &'static str> { async fn parse_comments(json: serde_json::Value) -> Result<Vec<Comment>, &'static str> {
// Separate the comment JSON into a Vector of comments
let comment_data = json["data"]["children"].as_array().unwrap(); let comment_data = json["data"]["children"].as_array().unwrap();
let mut comments: Vec<Comment> = Vec::new(); let mut comments: Vec<Comment> = Vec::new();
// For each comment, retrieve the values to build a Comment object
for comment in comment_data.iter() { for comment in comment_data.iter() {
let unix_time: i64 = comment["data"]["created_utc"].as_f64().unwrap_or(0.0).round() as i64; let unix_time: i64 = comment["data"]["created_utc"].as_f64().unwrap_or(0.0).round() as i64;
if unix_time == 0 {
continue;
}
let score = comment["data"]["score"].as_i64().unwrap_or(0); let score = comment["data"]["score"].as_i64().unwrap_or(0);
let body = markdown_to_html(comment["data"]["body"].as_str().unwrap_or("")).await; let body = markdown_to_html(comment["data"]["body"].as_str().unwrap_or("")).await;
// if comment["data"]["replies"].is_object() { let replies: Vec<Comment> = if comment["data"]["replies"].is_object() {
// let replies = parse_comments(comment["data"]["replies"].clone()).await.unwrap(); parse_comments(comment["data"]["replies"].clone()).await.unwrap_or(Vec::new())
// } } else {
Vec::new()
};
comments.push(Comment { comments.push(Comment {
body: body, body: body,
author: val(comment, "author").await, author: val(comment, "author").await,
score: format_num(score), score: format_num(score),
time: Utc.timestamp(unix_time, 0).format("%b %e %Y %H:%M UTC").to_string(), time: Utc.timestamp(unix_time, 0).format("%b %e %Y %H:%M UTC").to_string(),
replies: replies,
}); });
} }

View file

@ -37,6 +37,7 @@ pub struct Comment {
pub author: String, pub author: String,
pub score: String, pub score: String,
pub time: String, pub time: String,
pub replies: Vec<Comment>,
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -1,5 +1,14 @@
/* General */ /* General */
:root {
--background: #0F0F0F;
--foreground: #222;
--outside: #1F1F1F;
--post: #161616;
--highlighted: #333;
--black-contrast: 0 1px 3px rgba(0,0,0,0.5);
}
* { * {
transition: 0.2s all; transition: 0.2s all;
margin: 0px; margin: 0px;
@ -8,15 +17,15 @@
font-weight: normal; font-weight: normal;
} }
html { body {
background: black; background: var(--background);
} }
header { header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
color: aqua; color: aqua;
background: #151515; background: var(--outside);
padding: 15px; padding: 15px;
font-weight: bold; font-weight: bold;
font-size: 20px; font-size: 20px;
@ -53,10 +62,6 @@ a:not(.post_right):hover {
text-decoration: underline; text-decoration: underline;
} }
span {
color: aqua;
}
#about { #about {
background: #151515; background: #151515;
} }
@ -128,7 +133,7 @@ span {
} }
#sort > div, footer > a { #sort > div, footer > a {
background: #151515; background: var(--outside);
color: lightgrey; color: lightgrey;
border-radius: 5px; border-radius: 5px;
margin-right: 5px; margin-right: 5px;
@ -143,32 +148,24 @@ span {
} }
#sort > div:hover { #sort > div:hover {
background: #222; background: var(--foreground);
} }
/* Post */ /* Post */
.post { .post {
border-radius: 5px; border-radius: 5px;
background: #151515; background: var(--post);
box-shadow: var(--black-contrast);
display: flex; display: flex;
} }
.post.highlighted {
border: 2px solid #555;
background: #222;
}
.post.highlighted > .post_left {
background: #333;
}
.post:hover { .post:hover {
background: #222; background: var(--foreground);
} }
.post:hover > .post_left { .post:hover > .post_left {
background: #333; background: var(--highlighted);
} }
.post_left, .post_right { .post_left, .post_right {
@ -179,16 +176,12 @@ span {
.post_left { .post_left {
text-align: center; text-align: center;
background: #222; background: var(--foreground);
border-radius: 5px 0px 0px 5px; border-radius: 5px 0px 0px 5px;
min-width: 50px; min-width: 50px;
padding: 5px; padding: 5px;
} }
.datetime {
float: right;
}
.post_subreddit { .post_subreddit {
font-weight: bold; font-weight: bold;
} }
@ -271,13 +264,14 @@ small {
/* Comment */ /* Comment */
.comment { .comment {
margin-top: 1em;
border-radius: 5px; border-radius: 5px;
display: flex; display: flex;
border: 2px solid #222; /* border: 2px solid var(--foreground); */
} }
.comment:hover { .comment:hover {
background: #111; background: var(--post);
} }
.comment_left, .comment_right { .comment_left, .comment_right {
@ -289,17 +283,21 @@ small {
text-align: center; text-align: center;
min-width: 50px; min-width: 50px;
padding: 5px; padding: 5px;
align-items: flex-end; align-items: center;
} }
.comment_title { .comment_title {
font-size: 20px; font-size: 20px;
} }
.comment_author {
opacity: 0.9;
}
.comment_upvote { .comment_upvote {
margin-top: 0.5em; margin-top: 0.5em;
border-radius: 5px 5px 0px 0px; border-radius: 5px 5px 0px 0px;
background: #222; background: var(--foreground);
width: 40px; width: 40px;
padding: 10px 0px 0px 0px; padding: 10px 0px 0px 0px;
} }
@ -310,27 +308,23 @@ small {
.comment_score { .comment_score {
color: aqua; color: aqua;
background: #222; background: var(--foreground);
width: 40px; min-width: 40px;
padding: 5px 0px 10px 0px; border-radius: 5px;
border-radius: 0px 0px 5px 5px; padding: 10px 0px;
} }
.comment_right { .comment_right {
word-wrap: anywhere; word-wrap: anywhere;
padding: 10px 25px 10px 10px; padding: 10px 25px 10px 5px;
flex-grow: 1; flex-grow: 1;
flex-shrink: 1; flex-shrink: 1;
} }
.comment_right > * { .comment_data > * {
margin: 5px; margin: 5px;
} }
.comment_right > p {
opacity: 0.75;
}
.comment_image { .comment_image {
max-width: 500px; max-width: 500px;
align-self: center; align-self: center;
@ -355,9 +349,28 @@ small {
color: aqua; color: aqua;
} }
::marker {
color: aqua;
}
.reply {
margin-top: 0;
margin-left: 2em;
}
.datetime {
opacity: 0.75;
}
.line {
width: 2px;
height: 100%;
background: var(--foreground);
}
.post.comment { .post.comment {
background: #000; background: #000;
border: 2px solid #222; border: 2px solid var(--foreground);
} }
.post.comment > .post_left { .post.comment > .post_left {
@ -367,12 +380,12 @@ small {
/* Tables */ /* Tables */
table { table {
border: 3px #333 solid; border: 3px var(--highlighted) solid;
border-spacing: 0rem; border-spacing: 0rem;
} }
td, th { td, th {
border: 1px #333 solid; border: 1px var(--highlighted) solid;
padding: 0.5em; padding: 0.5em;
} }

View file

@ -4,6 +4,22 @@
{% call super() %} {% call super() %}
<meta name="author" content="u/{{ post.author }}"> <meta name="author" content="u/{{ post.author }}">
{% endblock %} {% endblock %}
{% macro comment(item) -%}
<div class="comment">
<div class="comment_left">
<h3 class="comment_score">{{ item.score }}</h3>
<div class="line"></div>
</div>
<details class="comment_right" open>
<summary class="comment_data">
<a class="comment_author" href="/u/{{ item.author }}">u/{{ item.author }}</a><span class="datetime">{{ item.time }}</span>
</summary>
<h4 class="comment_body">{{ item.body }}</h4>
{%- endmacro %}
{% block content %} {% block content %}
<div class="post highlighted"> <div class="post highlighted">
<div class="post_left"> <div class="post_left">
@ -40,19 +56,21 @@
<div id="sort_controversial"><a href="?sort=controversial">Controversial</a></div> <div id="sort_controversial"><a href="?sort=controversial">Controversial</a></div>
<div id="sort_old"><a href="?sort=old">Old</a></div> <div id="sort_old"><a href="?sort=old">Old</a></div>
</div> </div>
{% for comment in comments %}
<div class="comment"> {% for c in comments -%}
<div class="comment_left"> <div class="thread">
<div class="comment_upvote"></div> {% call comment(c) %}
<h3 class="comment_score">{{ comment.score }}</h3> <div class="replies">
{% for reply in c.replies %}
{% call comment(reply) %}
<div class="replies">
{% for response in reply.replies %}
{% call comment(response) %}</details></div>
{% endfor %}
</div></details></div>
{% endfor %}
</div></details></div>
</div> </div>
<div class="comment_right"> {%- endfor %}
<h4>
Posted by <a class="comment_author" href="/u/{{ comment.author }}">u/{{ comment.author }}</a>
<span class="datetime">{{ comment.time }}</span>
</h4>
<h4 class="comment_body">{{ comment.body }}</h4>
</div>
</div><br>
{% endfor %}
{% endblock %} {% endblock %}

View file

@ -46,7 +46,6 @@
{% else %} {% else %}
<div class="comment"> <div class="comment">
<div class="comment_left"> <div class="comment_left">
<div class="comment_upvote"></div>
<h3 class="comment_score">{{ post.score }}</h3> <h3 class="comment_score">{{ post.score }}</h3>
</div> </div>
<div class="comment_right"> <div class="comment_right">