Basic Nested & Collapsible Comments
This commit is contained in:
parent
19dc7de3c5
commit
7b8f694c8c
8 changed files with 124 additions and 69 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
@ -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;
|
||||||
|
|
18
src/post.rs
18
src/post.rs
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 %}
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue