Rewrite + Searching
This commit is contained in:
parent
c7282520cd
commit
a6dc7ee043
21
Cargo.lock
generated
21
Cargo.lock
generated
@ -272,9 +272,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.14.0"
|
version = "0.14.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423"
|
checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gimli",
|
"gimli",
|
||||||
]
|
]
|
||||||
@ -755,11 +755,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.15"
|
version = "0.1.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
|
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
@ -1006,7 +1006,7 @@ checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libreddit"
|
name = "libreddit"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"askama",
|
"askama",
|
||||||
@ -1016,6 +1016,7 @@ dependencies = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1624,9 +1625,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.5.1"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75"
|
checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@ -1647,9 +1648,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.13"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf906c8b8fc3f6ecd1046e01da1d8ddec83e48c8b08b84dcc02b585a6bedf5a8"
|
checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"version_check 0.9.2",
|
"version_check 0.9.2",
|
||||||
]
|
]
|
||||||
|
@ -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.2.3"
|
version = "0.2.4"
|
||||||
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"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
async-recursion = "0.3.1"
|
async-recursion = "0.3.1"
|
||||||
|
url = "2.2.0"
|
@ -18,7 +18,6 @@ Like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the
|
|||||||
- [About](#about)
|
- [About](#about)
|
||||||
- [Elsewhere](#elsewhere)
|
- [Elsewhere](#elsewhere)
|
||||||
- [Info](#info)
|
- [Info](#info)
|
||||||
- [In Progress](#in-progress)
|
|
||||||
- [Teddit Comparison](#how-does-it-compare-to-teddit)
|
- [Teddit Comparison](#how-does-it-compare-to-teddit)
|
||||||
- [Comparison](#comparison)
|
- [Comparison](#comparison)
|
||||||
- [Speed](#speed)
|
- [Speed](#speed)
|
||||||
@ -66,9 +65,6 @@ Libreddit hopes to provide an easier way to browse Reddit, without the ads, trac
|
|||||||
|
|
||||||
Libreddit currently implements most of Reddit's functionalities but still lacks a few features that are being worked on below.
|
Libreddit currently implements most of Reddit's functionalities but still lacks a few features that are being worked on below.
|
||||||
|
|
||||||
### In Progress
|
|
||||||
- Searching
|
|
||||||
|
|
||||||
### How does it compare to Teddit?
|
### How does it compare to Teddit?
|
||||||
|
|
||||||
Teddit is another awesome open source project designed to provide an alternative frontend to Reddit. There is no connection between the two and you're welcome to use whichever one you favor. Competition fosters innovation and Teddit's release has motivated me to build Libreddit into an even more polished product.
|
Teddit is another awesome open source project designed to provide an alternative frontend to Reddit. There is no connection between the two and you're welcome to use whichever one you favor. Competition fosters innovation and Teddit's release has motivated me to build Libreddit into an even more polished product.
|
||||||
@ -76,7 +72,6 @@ Teddit is another awesome open source project designed to provide an alternative
|
|||||||
If you are looking to compare, the biggest differences I have noticed are:
|
If you are looking to compare, the biggest differences I have noticed are:
|
||||||
- Libreddit is themed around Reddit's redesign whereas Teddit appears to stick much closer to Reddit's old design. This may suit some users better as design is always subjective.
|
- Libreddit is themed around Reddit's redesign whereas Teddit appears to stick much closer to Reddit's old design. This may suit some users better as design is always subjective.
|
||||||
- Libreddit is written in [Rust](https://www.rust-lang.org) for speed and memory safety. It uses [Actix Web](https://actix.rs), which was [benchmarked as the fastest web server for single queries](https://www.techempower.com/benchmarks/#hw=ph&test=db).
|
- Libreddit is written in [Rust](https://www.rust-lang.org) for speed and memory safety. It uses [Actix Web](https://actix.rs), which was [benchmarked as the fastest web server for single queries](https://www.techempower.com/benchmarks/#hw=ph&test=db).
|
||||||
- Unlike Teddit (at the time of writing this), Libreddit does not require a Reddit API key to host.
|
|
||||||
|
|
||||||
## Comparison
|
## Comparison
|
||||||
|
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -2,9 +2,9 @@
|
|||||||
use actix_web::{get, middleware::NormalizePath, web, App, HttpResponse, HttpServer};
|
use actix_web::{get, middleware::NormalizePath, web, App, HttpResponse, HttpServer};
|
||||||
|
|
||||||
// Reference local files
|
// Reference local files
|
||||||
mod popular;
|
|
||||||
mod post;
|
mod post;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
|
mod search;
|
||||||
mod subreddit;
|
mod subreddit;
|
||||||
mod user;
|
mod user;
|
||||||
mod utils;
|
mod utils;
|
||||||
@ -42,6 +42,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
HttpServer::new(|| {
|
HttpServer::new(|| {
|
||||||
App::new()
|
App::new()
|
||||||
|
// .default_service(web::get().to(subreddit::page))
|
||||||
// TRAILING SLASH MIDDLEWARE
|
// TRAILING SLASH MIDDLEWARE
|
||||||
.wrap(NormalizePath::default())
|
.wrap(NormalizePath::default())
|
||||||
// GENERAL SERVICES
|
// GENERAL SERVICES
|
||||||
@ -50,17 +51,22 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.route("/robots.txt/", web::get().to(robots))
|
.route("/robots.txt/", web::get().to(robots))
|
||||||
// PROXY SERVICE
|
// PROXY SERVICE
|
||||||
.route("/proxy/{url:.*}/", web::get().to(proxy::handler))
|
.route("/proxy/{url:.*}/", web::get().to(proxy::handler))
|
||||||
|
// SEARCH SERVICES
|
||||||
|
.route("/search/", web::get().to(search::page))
|
||||||
|
.route("r/{sub}/search/", web::get().to(search::page))
|
||||||
// USER SERVICES
|
// USER SERVICES
|
||||||
.route("/u/{username}/", web::get().to(user::page))
|
.route("/u/{username}/", web::get().to(user::profile))
|
||||||
.route("/user/{username}/", web::get().to(user::page))
|
.route("/user/{username}/", web::get().to(user::profile))
|
||||||
// SUBREDDIT SERVICES
|
// SUBREDDIT SERVICES
|
||||||
.route("/r/{sub}/", web::get().to(subreddit::page))
|
.route("/r/{sub}/", web::get().to(subreddit::page))
|
||||||
|
.route("/r/{sub}/{sort}/", web::get().to(subreddit::page))
|
||||||
// POPULAR SERVICES
|
// POPULAR SERVICES
|
||||||
.route("/", web::get().to(popular::page))
|
.route("/", web::get().to(subreddit::page))
|
||||||
|
.route("/{sort:best|hot|new|top|rising}/", web::get().to(subreddit::page))
|
||||||
// POST SERVICES
|
// POST SERVICES
|
||||||
.route("/{id:.{5,6}}/", web::get().to(post::short))
|
.route("/{id:.{5,6}}/", web::get().to(post::item))
|
||||||
.route("/r/{sub}/comments/{id}/{title}/", web::get().to(post::page))
|
.route("/r/{sub}/comments/{id}/{title}/", web::get().to(post::item))
|
||||||
.route("/r/{sub}/comments/{id}/{title}/{comment_id}/", web::get().to(post::comment))
|
.route("/r/{sub}/comments/{id}/{title}/{comment_id}/", web::get().to(post::item))
|
||||||
})
|
})
|
||||||
.bind(address.clone())
|
.bind(address.clone())
|
||||||
.expect(format!("Cannot bind to the address: {}", address).as_str())
|
.expect(format!("Cannot bind to the address: {}", address).as_str())
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
// CRATES
|
|
||||||
use crate::utils::{fetch_posts, ErrorTemplate, Params, Post};
|
|
||||||
use actix_web::{http::StatusCode, web, HttpResponse, Result};
|
|
||||||
use askama::Template;
|
|
||||||
|
|
||||||
// STRUCTS
|
|
||||||
#[derive(Template)]
|
|
||||||
#[template(path = "popular.html", escape = "none")]
|
|
||||||
struct PopularTemplate {
|
|
||||||
posts: Vec<Post>,
|
|
||||||
sort: (String, String),
|
|
||||||
ends: (String, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
// RENDER
|
|
||||||
async fn render(sort: Option<String>, t: Option<String>, ends: (Option<String>, Option<String>)) -> Result<HttpResponse> {
|
|
||||||
let sorting = sort.unwrap_or("hot".to_string());
|
|
||||||
let before = ends.1.clone().unwrap_or(String::new()); // If there is an after, there must be a before
|
|
||||||
|
|
||||||
let timeframe = match &t { Some(val) => format!("&t={}", val), None => String::new() };
|
|
||||||
|
|
||||||
// Build the Reddit JSON API url
|
|
||||||
let url = match ends.0 {
|
|
||||||
Some(val) => format!("r/popular/{}.json?before={}&count=25{}", sorting, val, timeframe),
|
|
||||||
None => match ends.1 {
|
|
||||||
Some(val) => format!("r/popular/{}.json?after={}&count=25{}", sorting, val, timeframe),
|
|
||||||
None => format!("r/popular/{}.json?{}", sorting, timeframe),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let items_result = fetch_posts(url, String::new()).await;
|
|
||||||
|
|
||||||
if items_result.is_err() {
|
|
||||||
let s = ErrorTemplate {
|
|
||||||
message: items_result.err().unwrap().to_string(),
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap();
|
|
||||||
Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s))
|
|
||||||
} else {
|
|
||||||
let items = items_result.unwrap();
|
|
||||||
|
|
||||||
let s = PopularTemplate {
|
|
||||||
posts: items.0,
|
|
||||||
sort: (sorting, t.unwrap_or(String::new())),
|
|
||||||
ends: (before, items.1),
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap();
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SERVICES
|
|
||||||
pub async fn page(params: web::Query<Params>) -> Result<HttpResponse> {
|
|
||||||
render(params.sort.clone(), params.t.clone(), (params.before.clone(), params.after.clone())).await
|
|
||||||
}
|
|
54
src/post.rs
54
src/post.rs
@ -1,6 +1,6 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::{format_num, format_url, request, val, Comment, ErrorTemplate, Flair, Flags, Params, Post};
|
use crate::utils::{format_num, format_url, param, request, val, Comment, ErrorTemplate, Flags, Flair, Post};
|
||||||
use actix_web::{http::StatusCode, web, HttpResponse, Result};
|
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, Result};
|
||||||
|
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
|
|
||||||
@ -16,22 +16,17 @@ struct PostTemplate {
|
|||||||
sort: String,
|
sort: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(id: String, sort: Option<String>, comment_id: Option<String>) -> Result<HttpResponse> {
|
pub async fn item(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
let path = format!("{}.json?{}&raw_json=1", req.path(), req.query_string());
|
||||||
|
let sort = param(&path, "sort").await;
|
||||||
|
let id = req.match_info().get("id").unwrap_or("").to_string();
|
||||||
|
|
||||||
// Log the post ID being fetched in debug mode
|
// Log the post ID being fetched in debug mode
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
dbg!(&id);
|
dbg!(&id);
|
||||||
|
|
||||||
// Handling sort paramater
|
|
||||||
let sorting: String = sort.unwrap_or("confidence".to_string());
|
|
||||||
|
|
||||||
// Build the Reddit JSON API url
|
|
||||||
let url: String = match comment_id {
|
|
||||||
None => format!("{}.json?sort={}&raw_json=1", id, sorting),
|
|
||||||
Some(val) => format!("{}.json?sort={}&comment={}&raw_json=1", id, sorting, val),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send a request to the url, receive JSON in response
|
// Send a request to the url, receive JSON in response
|
||||||
let req = request(url).await;
|
let req = request(path.clone()).await;
|
||||||
|
|
||||||
// If the Reddit API returns an error, exit and send error page to user
|
// If the Reddit API returns an error, exit and send error page to user
|
||||||
if req.is_err() {
|
if req.is_err() {
|
||||||
@ -41,37 +36,18 @@ async fn render(id: String, sort: Option<String>, comment_id: Option<String>) ->
|
|||||||
.render()
|
.render()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s));
|
return Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s));
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// Otherwise, grab the JSON output from the request
|
// Otherwise, grab the JSON output from the request
|
||||||
let res = req.unwrap();
|
let res = req.unwrap();
|
||||||
|
|
||||||
// Parse the JSON into Post and Comment structs
|
// Parse the JSON into Post and Comment structs
|
||||||
let post = parse_post(res[0].clone()).await;
|
let post = parse_post(res[0].clone()).await.unwrap();
|
||||||
let comments = parse_comments(res[1].clone()).await;
|
let comments = parse_comments(res[1].clone()).await.unwrap();
|
||||||
|
|
||||||
// Use the Post and Comment structs to generate a website to show users
|
// Use the Post and Comment structs to generate a website to show users
|
||||||
let s = PostTemplate {
|
let s = PostTemplate { comments, post, sort }.render().unwrap();
|
||||||
comments: comments.unwrap(),
|
|
||||||
post: post.unwrap(),
|
|
||||||
sort: sorting,
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap();
|
|
||||||
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SERVICES
|
|
||||||
pub async fn short(web::Path(id): web::Path<String>, params: web::Query<Params>) -> Result<HttpResponse> {
|
|
||||||
render(id, params.sort.clone(), None).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn comment(web::Path((_sub, id, _title, comment_id)): web::Path<(String, String, String, String)>, params: web::Query<Params>) -> Result<HttpResponse> {
|
|
||||||
render(id, params.sort.clone(), Some(comment_id)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn page(web::Path((_sub, id)): web::Path<(String, String)>, params: web::Query<Params>) -> Result<HttpResponse> {
|
|
||||||
render(id, params.sort.clone(), None).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTILITIES
|
// UTILITIES
|
||||||
@ -111,7 +87,7 @@ async fn parse_post(json: serde_json::Value) -> Result<Post, &'static str> {
|
|||||||
let post = Post {
|
let post = Post {
|
||||||
title: val(post_data, "title").await,
|
title: val(post_data, "title").await,
|
||||||
community: val(post_data, "subreddit").await,
|
community: val(post_data, "subreddit").await,
|
||||||
body: val(post_data,"selftext_html").await,
|
body: val(post_data, "selftext_html").await,
|
||||||
author: val(post_data, "author").await,
|
author: val(post_data, "author").await,
|
||||||
author_flair: Flair(
|
author_flair: Flair(
|
||||||
val(post_data, "author_flair_text").await,
|
val(post_data, "author_flair_text").await,
|
||||||
@ -132,7 +108,7 @@ async fn parse_post(json: serde_json::Value) -> Result<Post, &'static str> {
|
|||||||
),
|
),
|
||||||
flags: Flags {
|
flags: Flags {
|
||||||
nsfw: post_data["data"]["over_18"].as_bool().unwrap_or(false),
|
nsfw: post_data["data"]["over_18"].as_bool().unwrap_or(false),
|
||||||
stickied: post_data["data"]["stickied"].as_bool().unwrap_or(false)
|
stickied: post_data["data"]["stickied"].as_bool().unwrap_or(false),
|
||||||
},
|
},
|
||||||
media: media.1,
|
media: media.1,
|
||||||
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(),
|
||||||
|
52
src/search.rs
Normal file
52
src/search.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// CRATES
|
||||||
|
use crate::utils::{fetch_posts, param, ErrorTemplate, Post};
|
||||||
|
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, Result};
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
// STRUCTS
|
||||||
|
#[derive(Template)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[template(path = "search.html", escape = "none")]
|
||||||
|
struct SearchTemplate {
|
||||||
|
posts: Vec<Post>,
|
||||||
|
query: String,
|
||||||
|
sub: String,
|
||||||
|
sort: (String, String),
|
||||||
|
ends: (String, String),
|
||||||
|
}
|
||||||
|
|
||||||
|
// SERVICES
|
||||||
|
pub async fn page(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
let path = format!("{}.json?{}", req.path(), req.query_string());
|
||||||
|
let q = param(&path, "q").await;
|
||||||
|
let sort = if param(&path, "sort").await.is_empty() {
|
||||||
|
"relevance".to_string()
|
||||||
|
} else {
|
||||||
|
param(&path, "sort").await
|
||||||
|
};
|
||||||
|
let sub = req.match_info().get("sub").unwrap_or("").to_string();
|
||||||
|
|
||||||
|
let posts = fetch_posts(path.clone(), String::new()).await;
|
||||||
|
|
||||||
|
if posts.is_err() {
|
||||||
|
let s = ErrorTemplate {
|
||||||
|
message: posts.err().unwrap().to_string(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s))
|
||||||
|
} else {
|
||||||
|
let items = posts.unwrap();
|
||||||
|
|
||||||
|
let s = SearchTemplate {
|
||||||
|
posts: items.0,
|
||||||
|
query: q,
|
||||||
|
sub: sub,
|
||||||
|
sort: (sort, param(&path, "t").await),
|
||||||
|
ends: (param(&path, "after").await, items.1),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap();
|
||||||
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::{fetch_posts, format_num, format_url, request, val, ErrorTemplate, Params, Post, Subreddit};
|
use crate::utils::{fetch_posts, format_num, format_url, param, request, val, ErrorTemplate, Post, Subreddit};
|
||||||
use actix_web::{http::StatusCode, web, HttpResponse, Result};
|
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, Result};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
@ -15,49 +15,35 @@ struct SubredditTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
#[allow(dead_code)]
|
// web::Path(sub): web::Path<String>, params: web::Query<Params>
|
||||||
pub async fn page(web::Path(sub): web::Path<String>, params: web::Query<Params>) -> Result<HttpResponse> {
|
pub async fn page(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
render(sub, params.sort.clone(), params.t.clone(), (params.before.clone(), params.after.clone())).await
|
let path = format!("{}.json?{}", req.path(), req.query_string());
|
||||||
}
|
let sub = req.match_info().get("sub").unwrap_or("popular").to_string();
|
||||||
|
let sort = req.match_info().get("sort").unwrap_or("hot").to_string();
|
||||||
|
|
||||||
pub async fn render(sub_name: String, sort: Option<String>, t: Option<String>, ends: (Option<String>, Option<String>)) -> Result<HttpResponse> {
|
let sub_result = if !&sub.contains("+") && sub != "popular" {
|
||||||
let sorting = sort.unwrap_or("hot".to_string());
|
subreddit(&sub).await
|
||||||
let before = ends.1.clone().unwrap_or(String::new()); // If there is an after, there must be a before
|
|
||||||
|
|
||||||
let timeframe = match &t { Some(val) => format!("&t={}", val), None => String::new() };
|
|
||||||
|
|
||||||
// Build the Reddit JSON API url
|
|
||||||
let url = match ends.0 {
|
|
||||||
Some(val) => format!("r/{}/{}.json?before={}&count=25{}", sub_name, sorting, val, timeframe),
|
|
||||||
None => match ends.1 {
|
|
||||||
Some(val) => format!("r/{}/{}.json?after={}&count=25{}", sub_name, sorting, val, timeframe),
|
|
||||||
None => format!("r/{}/{}.json?{}", sub_name, sorting, timeframe),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let sub_result = if !&sub_name.contains("+") {
|
|
||||||
subreddit(&sub_name).await
|
|
||||||
} else {
|
} else {
|
||||||
Ok(Subreddit::default())
|
Ok(Subreddit::default())
|
||||||
};
|
};
|
||||||
let items_result = fetch_posts(url, String::new()).await;
|
let posts = fetch_posts(path.clone(), String::new()).await;
|
||||||
|
|
||||||
if sub_result.is_err() || items_result.is_err() {
|
if posts.is_err() {
|
||||||
let s = ErrorTemplate {
|
let s = ErrorTemplate {
|
||||||
message: sub_result.err().unwrap().to_string(),
|
message: posts.err().unwrap().to_string(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s))
|
Ok(HttpResponse::Ok().status(StatusCode::NOT_FOUND).content_type("text/html").body(s))
|
||||||
} else {
|
} else {
|
||||||
let sub = sub_result.unwrap();
|
let sub = sub_result.unwrap_or(Subreddit::default());
|
||||||
let items = items_result.unwrap();
|
let items = posts.unwrap();
|
||||||
|
|
||||||
let s = SubredditTemplate {
|
let s = SubredditTemplate {
|
||||||
sub: sub,
|
sub: sub,
|
||||||
posts: items.0,
|
posts: items.0,
|
||||||
sort: (sorting, t.unwrap_or(String::new())),
|
sort: (sort, param(&path, "t").await),
|
||||||
ends: (before, items.1),
|
ends: (param(&path, "after").await, items.1),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
38
src/user.rs
38
src/user.rs
@ -1,6 +1,6 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::{fetch_posts, format_url, nested_val, request, ErrorTemplate, Params, Post, User};
|
use crate::utils::{fetch_posts, format_url, nested_val, param, request, ErrorTemplate, Post, User};
|
||||||
use actix_web::{http::StatusCode, web, HttpResponse, Result};
|
use actix_web::{http::StatusCode, HttpRequest, HttpResponse, Result};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
|
|
||||||
@ -14,25 +14,19 @@ struct UserTemplate {
|
|||||||
ends: (String, String),
|
ends: (String, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn render(username: String, sort: Option<String>, t: Option<String>, ends: (Option<String>, Option<String>)) -> Result<HttpResponse> {
|
pub async fn profile(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
let sorting = sort.unwrap_or("new".to_string());
|
// Build the Reddit JSON API path
|
||||||
|
let path = format!("{}.json?{}&raw_json=1", req.path(), req.query_string());
|
||||||
|
|
||||||
let before = ends.1.clone().unwrap_or(String::new()); // If there is an after, there must be a before
|
// Retrieve other variables from Libreddit request
|
||||||
|
let sort = param(&path, "sort").await;
|
||||||
let timeframe = match &t { Some(val) => format!("&t={}", val), None => String::new() };
|
let username = req.match_info().get("username").unwrap_or("").to_string();
|
||||||
|
|
||||||
// Build the Reddit JSON API url
|
|
||||||
let url = match ends.0 {
|
|
||||||
Some(val) => format!("user/{}/.json?sort={}&before={}&count=25&raw_json=1{}", username, sorting, val, timeframe),
|
|
||||||
None => match ends.1 {
|
|
||||||
Some(val) => format!("user/{}/.json?sort={}&after={}&count=25&raw_json=1{}", username, sorting, val, timeframe),
|
|
||||||
None => format!("user/{}/.json?sort={}&raw_json=1{}", username, sorting, timeframe),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Request user profile data and user posts/comments from Reddit
|
||||||
let user = user(&username).await;
|
let user = user(&username).await;
|
||||||
let posts = fetch_posts(url, "Comment".to_string()).await;
|
let posts = fetch_posts(path.clone(), "Comment".to_string()).await;
|
||||||
|
|
||||||
|
// If there is an error show error page
|
||||||
if user.is_err() || posts.is_err() {
|
if user.is_err() || posts.is_err() {
|
||||||
let s = ErrorTemplate {
|
let s = ErrorTemplate {
|
||||||
message: user.err().unwrap().to_string(),
|
message: user.err().unwrap().to_string(),
|
||||||
@ -46,8 +40,8 @@ async fn render(username: String, sort: Option<String>, t: Option<String>, ends:
|
|||||||
let s = UserTemplate {
|
let s = UserTemplate {
|
||||||
user: user.unwrap(),
|
user: user.unwrap(),
|
||||||
posts: posts_unwrapped.0,
|
posts: posts_unwrapped.0,
|
||||||
sort: (sorting, t.unwrap_or(String::new())),
|
sort: (sort, param(&path, "t").await),
|
||||||
ends: (before, posts_unwrapped.1)
|
ends: (param(&path, "after").await, posts_unwrapped.1),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -56,9 +50,9 @@ async fn render(username: String, sort: Option<String>, t: Option<String>, ends:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
pub async fn page(web::Path(username): web::Path<String>, params: web::Query<Params>) -> Result<HttpResponse> {
|
// pub async fn page(web::Path(username): web::Path<String>, params: web::Query<Params>) -> Result<HttpResponse> {
|
||||||
render(username, params.sort.clone(), params.t.clone(), (params.before.clone(), params.after.clone())).await
|
// render(username, params.sort.clone(), params.t.clone(), (params.before.clone(), params.after.clone())).await
|
||||||
}
|
// }
|
||||||
|
|
||||||
// USER
|
// USER
|
||||||
async fn user(name: &String) -> Result<User, &'static str> {
|
async fn user(name: &String) -> Result<User, &'static str> {
|
||||||
|
23
src/utils.rs
23
src/utils.rs
@ -3,6 +3,7 @@
|
|||||||
//
|
//
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use serde_json::{from_str, Value};
|
use serde_json::{from_str, Value};
|
||||||
|
use url::Url;
|
||||||
// use surf::{client, get, middleware::Redirect};
|
// use surf::{client, get, middleware::Redirect};
|
||||||
|
|
||||||
#[cfg(feature = "proxy")]
|
#[cfg(feature = "proxy")]
|
||||||
@ -16,7 +17,7 @@ pub struct Flair(pub String, pub String, pub String);
|
|||||||
// Post flags with nsfw and stickied
|
// Post flags with nsfw and stickied
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
pub nsfw: bool,
|
pub nsfw: bool,
|
||||||
pub stickied: bool
|
pub stickied: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post containing content, metadata and media
|
// Post containing content, metadata and media
|
||||||
@ -72,6 +73,7 @@ pub struct Subreddit {
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct Params {
|
pub struct Params {
|
||||||
pub t: Option<String>,
|
pub t: Option<String>,
|
||||||
|
pub q: Option<String>,
|
||||||
pub sort: Option<String>,
|
pub sort: Option<String>,
|
||||||
pub after: Option<String>,
|
pub after: Option<String>,
|
||||||
pub before: Option<String>,
|
pub before: Option<String>,
|
||||||
@ -88,6 +90,13 @@ pub struct ErrorTemplate {
|
|||||||
// FORMATTING
|
// FORMATTING
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// Grab a query param from a url
|
||||||
|
pub async fn param(path: &String, value: &str) -> String {
|
||||||
|
let url = Url::parse(format!("https://reddit.com/{}", path).as_str()).unwrap();
|
||||||
|
let pairs: std::collections::HashMap<_, _> = url.query_pairs().into_owned().collect();
|
||||||
|
pairs.get(value).unwrap_or(&String::new()).to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
// Direct urls to proxy if proxy is enabled
|
// Direct urls to proxy if proxy is enabled
|
||||||
pub async fn format_url(url: String) -> String {
|
pub async fn format_url(url: String) -> String {
|
||||||
if url.is_empty() {
|
if url.is_empty() {
|
||||||
@ -127,9 +136,9 @@ pub async fn nested_val(j: &serde_json::Value, n: &str, k: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch posts of a user or subreddit
|
// Fetch posts of a user or subreddit
|
||||||
pub async fn fetch_posts(url: String, fallback_title: String) -> Result<(Vec<Post>, String), &'static str> {
|
pub async fn fetch_posts(path: String, fallback_title: String) -> Result<(Vec<Post>, String), &'static str> {
|
||||||
// Send a request to the url, receive JSON in response
|
// Send a request to the url, receive JSON in response
|
||||||
let req = request(url.clone()).await;
|
let req = request(path.clone()).await;
|
||||||
|
|
||||||
// If the Reddit API returns an error, exit this function
|
// If the Reddit API returns an error, exit this function
|
||||||
if req.is_err() {
|
if req.is_err() {
|
||||||
@ -178,14 +187,14 @@ pub async fn fetch_posts(url: String, fallback_title: String) -> Result<(Vec<Pos
|
|||||||
),
|
),
|
||||||
flags: Flags {
|
flags: Flags {
|
||||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or(false),
|
nsfw: post["data"]["over_18"].as_bool().unwrap_or(false),
|
||||||
stickied: post["data"]["stickied"].as_bool().unwrap_or(false)
|
stickied: post["data"]["stickied"].as_bool().unwrap_or(false),
|
||||||
},
|
},
|
||||||
url: val(post, "permalink").await,
|
url: val(post, "permalink").await,
|
||||||
time: Utc.timestamp(unix_time, 0).format("%b %e '%y").to_string(),
|
time: Utc.timestamp(unix_time, 0).format("%b %e '%y").to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg!(url);
|
dbg!(path);
|
||||||
|
|
||||||
Ok((posts, res["data"]["after"].as_str().unwrap_or("").to_string()))
|
Ok((posts, res["data"]["after"].as_str().unwrap_or("").to_string()))
|
||||||
}
|
}
|
||||||
@ -195,8 +204,8 @@ pub async fn fetch_posts(url: String, fallback_title: String) -> Result<(Vec<Pos
|
|||||||
//
|
//
|
||||||
|
|
||||||
// Make a request to a Reddit API and parse the JSON response
|
// Make a request to a Reddit API and parse the JSON response
|
||||||
pub async fn request(mut url: String) -> Result<serde_json::Value, &'static str> {
|
pub async fn request(path: String) -> Result<serde_json::Value, &'static str> {
|
||||||
url = format!("https://www.reddit.com/{}", url);
|
let url = format!("https://www.reddit.com/{}", path);
|
||||||
|
|
||||||
// --- actix-web::client ---
|
// --- actix-web::client ---
|
||||||
// let client = actix_web::client::Client::default();
|
// let client = actix_web::client::Client::default();
|
||||||
|
118
static/style.css
118
static/style.css
@ -10,6 +10,11 @@
|
|||||||
--black-contrast: 0 1px 3px rgba(0,0,0,0.5);
|
--black-contrast: 0 1px 3px rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
color: var(--background);
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
transition: 0.2s all;
|
transition: 0.2s all;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -25,10 +30,12 @@ body {
|
|||||||
nav {
|
nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
background: var(--outside);
|
background: var(--outside);
|
||||||
padding: 15px;
|
padding: 5px 15px;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav #lib, nav #github, nav #version { color: white; }
|
nav #lib, nav #github, nav #version { color: white; }
|
||||||
@ -47,6 +54,10 @@ footer {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer > a {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
@ -76,7 +87,6 @@ aside {
|
|||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* User & Subreddit */
|
/* User & Subreddit */
|
||||||
|
|
||||||
#user, #subreddit, #sidebar {
|
#user, #subreddit, #sidebar {
|
||||||
@ -130,53 +140,112 @@ aside {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sorting */
|
/* Sorting and Search */
|
||||||
|
|
||||||
#sort, #timeframe {
|
select {
|
||||||
background: var(--outside);
|
background: var(--outside);
|
||||||
box-shadow: var(--black-contrast);
|
}
|
||||||
|
|
||||||
|
select, #search {
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
margin-bottom: 20px;
|
|
||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
border-radius: 5px 0 0 5px;
|
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
border-radius: 5px 0px 0px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#searchbox {
|
||||||
|
display: flex;
|
||||||
|
box-shadow: var(--black-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
#searchbox > *, #sort_submit {
|
||||||
|
background: var(--highlighted);
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search {
|
||||||
|
border-right: 2px var(--outside) solid;
|
||||||
|
min-width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inside {
|
||||||
|
display: flex;
|
||||||
|
font-size: 15px;
|
||||||
|
align-items: center;
|
||||||
|
border-right: 2px var(--outside) solid;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restrict_sr { margin-right: 5px; }
|
||||||
|
|
||||||
|
input[type="submit"] {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0px 5px 5px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
select:hover { background: var(--foreground); }
|
||||||
|
input[type="submit"]:hover { color: var(--accent); }
|
||||||
|
|
||||||
#timeframe {
|
#timeframe {
|
||||||
border-radius: 0;
|
border-radius: 5px 0px 0px 5px;
|
||||||
border-left: 4px solid var(--highlighted);
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_submit {
|
#search_sort {
|
||||||
background: var(--highlighted);
|
background: var(--highlighted);
|
||||||
border: 0;
|
border-radius: 5px;
|
||||||
font-size: 15px;
|
overflow: hidden;
|
||||||
height: 40px;
|
|
||||||
border-radius: 0 5px 5px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort:hover, #timeframe:hover { background: var(--foreground); }
|
#search_sort > #search {
|
||||||
#sort_submit:hover { color: var(--accent); }
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
#sort > div, footer > a {
|
#search_sort > :not(:first-child), #search_sort > #sort_options {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border-right: 0;
|
||||||
|
border-left: 2px solid var(--background);
|
||||||
|
box-shadow: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort_options {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort, #search_sort {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort_options, footer > a {
|
||||||
|
border-radius: 5px;
|
||||||
box-shadow: var(--black-contrast);
|
box-shadow: var(--black-contrast);
|
||||||
background: var(--outside);
|
background: var(--outside);
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort_options > a, footer > a {
|
||||||
color: lightgrey;
|
color: lightgrey;
|
||||||
border-radius: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort > div.selected {
|
#sort_options > a.selected {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort > div:hover {
|
#sort_options > a:not(.selected):hover {
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +312,7 @@ aside {
|
|||||||
|
|
||||||
.post_title {
|
.post_title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post_right {
|
.post_right {
|
||||||
@ -283,7 +353,6 @@ aside {
|
|||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: black;
|
color: black;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
margin-right: 5px;
|
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -494,9 +563,8 @@ td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
main {
|
main { flex-direction: column-reverse; }
|
||||||
flex-direction: column-reverse;
|
nav { flex-direction: column; }
|
||||||
}
|
|
||||||
|
|
||||||
aside {
|
aside {
|
||||||
margin: 20px 0 0 0;
|
margin: 20px 0 0 0;
|
||||||
|
@ -13,12 +13,11 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body style="visibility: hidden;">
|
<body style="visibility: hidden;">
|
||||||
{% block navbar %}
|
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/"><span id="lib">lib</span>reddit. <span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span></a>
|
<a href="/"><span id="lib">lib</span>reddit. <span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span></a>
|
||||||
|
{% block search %}{% endblock %}
|
||||||
<a id="github" href="https://github.com/spikecodes/libreddit">GITHUB</a>
|
<a id="github" href="https://github.com/spikecodes/libreddit">GITHUB</a>
|
||||||
</nav>
|
</nav>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main>
|
<main>
|
||||||
|
@ -1,5 +1,13 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
{% block title %}{{ post.title }} - r/{{ post.community }}{% endblock %}
|
{% block title %}{{ post.title }} - r/{{ post.community }}{% endblock %}
|
||||||
|
|
||||||
|
{% block search %}
|
||||||
|
{% call utils::search(["/r/", post.community.as_str()].concat(), "") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block root %}/r/{{ post.community }}{% endblock %}{% block location %}r/{{ post.community }}{% endblock %}
|
||||||
{% block head %}
|
{% block head %}
|
||||||
{% call super() %}
|
{% call super() %}
|
||||||
<meta name="author" content="u/{{ post.author }}">
|
<meta name="author" content="u/{{ post.author }}">
|
||||||
@ -56,13 +64,9 @@
|
|||||||
<div class="post_body">{{ post.body }}</div>
|
<div class="post_body">{{ post.body }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form>
|
<form id="sort">
|
||||||
<select id="sort" name="sort">
|
<select name="sort">
|
||||||
<option value="confidence" {% if sort == "confidence" %}selected{% endif %}>Best</option>
|
{% call utils::options(sort, ["confidence", "top", "new", "controversial", "old"], "") %}
|
||||||
<option value="top" {% if sort == "top" %}selected{% endif %}>Top</option>
|
|
||||||
<option value="new" {% if sort == "new" %}selected{% endif %}>New</option>
|
|
||||||
<option value="controversial" {% if sort == "controversial" %}selected{% endif %}>Controversial</option>
|
|
||||||
<option value="old" {% if sort == "old" %}selected{% endif %}>Old</option>
|
|
||||||
</select><input id="sort_submit" type="submit" value="→">
|
</select><input id="sort_submit" type="submit" value="→">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form>
|
<form id="search_sort">
|
||||||
<select id="sort" name="sort">
|
<input id="search" type="text" name="q" placeholder="Search" value="{{ query }}">
|
||||||
<option value="best" {% if sort.0 == "best" %}selected{% endif %}>Best</option>
|
{% if sub != "" %}
|
||||||
<option value="hot" {% if sort.0 == "hot" %}selected{% endif %}>Hot</option>
|
<div id="inside">
|
||||||
<option value="new" {% if sort.0 == "new" %}selected{% endif %}>New</option>
|
<input type="checkbox" name="restrict_sr" id="restrict_sr" checked="checked" data-com.bitwarden.browser.user-edited="yes">
|
||||||
<option value="top" {% if sort.0 == "top" %}selected{% endif %}>Top</option>
|
<label for="restrict_sr">in r/{{ sub }}</label>
|
||||||
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
</div>
|
||||||
<option value="hour" {% if sort.1 == "hour" %}selected{% endif %}>Hour</option>
|
{% endif %}
|
||||||
<option value="day" {% if sort.1 == "day" || sort.1 == "" %}selected{% endif %}>Day</option>
|
<select id="sort_options" name="sort">
|
||||||
<option value="week" {% if sort.1 == "week" %}selected{% endif %}>Week</option>
|
{% call utils::options(sort.0, ["relevance", "hot", "top", "new", "comments"], "") %}
|
||||||
<option value="month" {% if sort.1 == "month" %}selected{% endif %}>Month</option>
|
</select>{% if sort.0 != "new" %}<select id="timeframe" name="t">
|
||||||
<option value="year" {% if sort.1 == "year" %}selected{% endif %}>Year</option>
|
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
|
||||||
<option value="all" {% if sort.1 == "all" %}selected{% endif %}>All</option>
|
|
||||||
</select>{% endif %}<input id="sort_submit" type="submit" value="→">
|
</select>{% endif %}<input id="sort_submit" type="submit" value="→">
|
||||||
</form>
|
</form>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
|
{% if post.title != "Comment" %}
|
||||||
<div class="post">
|
<div class="post">
|
||||||
<div class="post_left">
|
<div class="post_left">
|
||||||
<p class="post_score">{{ post.score }}</p>
|
<p class="post_score">{{ post.score }}</p>
|
||||||
@ -40,6 +42,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<img class="post_thumbnail" src="{{ post.media }}">
|
<img class="post_thumbnail" src="{{ post.media }}">
|
||||||
</div><br>
|
</div><br>
|
||||||
|
{% else %}
|
||||||
|
<div class="comment">
|
||||||
|
<div class="comment_left">
|
||||||
|
<p class="comment_score">{{ post.score }}</p>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<details class="comment_right" open>
|
||||||
|
<summary class="comment_data">
|
||||||
|
<a class="comment_link" href="{{ post.url }}">COMMENT</a>
|
||||||
|
<span class="datetime">{{ post.time }}</span>
|
||||||
|
</summary>
|
||||||
|
<p class="comment_body">{{ post.body }}</p>
|
||||||
|
</details>
|
||||||
|
</div><br>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<footer>
|
<footer>
|
@ -1,25 +1,27 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
{% if sub.name != "" %}
|
|
||||||
{% block title %}r/{{ sub.name }}: {{ sub.description }}{% endblock %}
|
{% block title %}r/{{ sub.name }}: {{ sub.description }}{% endblock %}
|
||||||
{% endif %}
|
|
||||||
|
{% block search %}
|
||||||
|
{% call utils::search(["/r/", sub.name.as_str()].concat(), "") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main style="max-width: 1000px;">
|
<main>
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form>
|
<form id="sort">
|
||||||
<select id="sort" name="sort">
|
<div id="sort_options">
|
||||||
<option value="hot" {% if sort.0 == "hot" %}selected{% endif %}>Hot</option>
|
{% if sub.name.is_empty() %}
|
||||||
<option value="new" {% if sort.0 == "new" %}selected{% endif %}>New</option>
|
{% call utils::sort("", ["hot", "new", "top", "rising"], sort.0) %}
|
||||||
<option value="top" {% if sort.0 == "top" %}selected{% endif %}>Top</option>
|
{% else %}
|
||||||
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
{% call utils::sort(["/r/", sub.name.as_str()].concat(), ["hot", "new", "top", "rising"], sort.0) %}
|
||||||
<option value="hour" {% if sort.1 == "hour" %}selected{% endif %}>Hour</option>
|
{% endif %}
|
||||||
<option value="day" {% if sort.1 == "day" || sort.1 == "" %}selected{% endif %}>Day</option>
|
</div>
|
||||||
<option value="week" {% if sort.1 == "week" %}selected{% endif %}>Week</option>
|
{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
||||||
<option value="month" {% if sort.1 == "month" %}selected{% endif %}>Month</option>
|
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "day") %}
|
||||||
<option value="year" {% if sort.1 == "year" %}selected{% endif %}>Year</option>
|
<input id="sort_submit" type="submit" value="→">
|
||||||
<option value="all" {% if sort.1 == "all" %}selected{% endif %}>All</option>
|
</select>{% endif %}
|
||||||
</select>{% endif %}<input id="sort_submit" type="submit" value="→">
|
|
||||||
</form>
|
</form>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<div class="post {% if post.flags.stickied %}stickied{% endif %}">
|
<div class="post {% if post.flags.stickied %}stickied{% endif %}">
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% import "utils.html" as utils %}
|
||||||
|
|
||||||
|
{% block search %}
|
||||||
|
{% call utils::search("".to_owned(), "", "") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}Libreddit: u/{{ user.name }}{% endblock %}
|
{% block title %}Libreddit: u/{{ user.name }}{% endblock %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main style="max-width: 1000px;">
|
<main style="max-width: 1000px;">
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form>
|
<form id="sort">
|
||||||
<select id="sort" name="sort">
|
<select name="sort">
|
||||||
<option value="hot" {% if sort.0 == "hot" %}selected{% endif %}>Hot</option>
|
{% call utils::options(sort.0, ["hot", "new", "top"], "") %}
|
||||||
<option value="new" {% if sort.0 == "new" %}selected{% endif %}>New</option>
|
|
||||||
<option value="top" {% if sort.0 == "top" %}selected{% endif %}>Top</option>
|
|
||||||
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
||||||
<option value="hour" {% if sort.1 == "hour" %}selected{% endif %}>Hour</option>
|
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
|
||||||
<option value="day" {% if sort.1 == "day" %}selected{% endif %}>Day</option>
|
|
||||||
<option value="week" {% if sort.1 == "week" %}selected{% endif %}>Week</option>
|
|
||||||
<option value="month" {% if sort.1 == "month" %}selected{% endif %}>Month</option>
|
|
||||||
<option value="year" {% if sort.1 == "year" %}selected{% endif %}>Year</option>
|
|
||||||
<option value="all" {% if sort.1 == "all" || sort.1 == "" %}selected{% endif %}>All</option>
|
|
||||||
</select>{% endif %}<input id="sort_submit" type="submit" value="→">
|
</select>{% endif %}<input id="sort_submit" type="submit" value="→">
|
||||||
</form>
|
</form>
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
|
28
templates/utils.html
Normal file
28
templates/utils.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% macro options(current, values, default) -%}
|
||||||
|
{% for value in values %}
|
||||||
|
<option value="{{ value }}" {% if current == value || (current == "" && value == default) %}selected{% endif %}>
|
||||||
|
{{ format!("{}{}", value.get(0..1).unwrap().to_uppercase(), value.get(1..).unwrap()) }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro sort(root, methods, selected) -%}
|
||||||
|
{% for method in methods %}
|
||||||
|
<a {% if method == selected %}class="selected"{% endif %} href="{{ root }}/{{ method }}">
|
||||||
|
{{ format!("{}{}", method.get(0..1).unwrap().to_uppercase(), method.get(1..).unwrap()) }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro search(root, search) -%}
|
||||||
|
<form action="{% if root != "/r/" %}{{ root }}{% endif %}/search/" id="searchbox">
|
||||||
|
<input id="search" type="text" name="q" placeholder="Search" value="{{ search }}">
|
||||||
|
{% if root != "/r/" %}
|
||||||
|
<div id="inside">
|
||||||
|
<input type="checkbox" name="restrict_sr" id="restrict_sr" checked="checked" data-com.bitwarden.browser.user-edited="yes">
|
||||||
|
<label for="restrict_sr">in {{ root }}</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<input type="submit" value="→">
|
||||||
|
</form>
|
||||||
|
{%- endmacro %}
|
Loading…
Reference in New Issue
Block a user