mirror of https://github.com/spikecodes/libreddit
Merge 4e863b32e2
into 3facaefb53
This commit is contained in:
commit
cd0a3883fe
|
@ -4,6 +4,7 @@
|
|||
|
||||
// Reference local files
|
||||
mod post;
|
||||
mod saved;
|
||||
mod search;
|
||||
mod settings;
|
||||
mod subreddit;
|
||||
|
@ -198,6 +199,11 @@ async fn main() {
|
|||
app.at("/user/:name/comments/:id/:title").get(|r| post::item(r).boxed());
|
||||
app.at("/user/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
||||
|
||||
// Saved posts
|
||||
app.at("/saved").get(|r| saved::get(r).boxed());
|
||||
app.at("/saved/:id/save").post(|r| saved::save(r).boxed());
|
||||
app.at("/saved/:id/unsave").post(|r| saved::unsave(r).boxed());
|
||||
|
||||
// Configure settings
|
||||
app.at("/settings").get(|r| settings::get(r).boxed()).post(|r| settings::set(r).boxed());
|
||||
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::esc;
|
|||
use crate::server::RequestExt;
|
||||
use crate::subreddit::{can_access_quarantine, quarantine};
|
||||
use crate::utils::{
|
||||
error, format_num, format_url, get_filters, param, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flags, Flair, FlairPart, Media, Post, Preferences,
|
||||
error, format_num, format_url, get_filters, get_saved_posts, param, rewrite_urls, setting, template, time, val, Author, Awards, Comment, Flags, Flair, FlairPart, Media, Post, Preferences,
|
||||
};
|
||||
use hyper::{Body, Request, Response};
|
||||
|
||||
|
@ -19,6 +19,7 @@ struct PostTemplate {
|
|||
post: Post,
|
||||
sort: String,
|
||||
prefs: Preferences,
|
||||
saved: Vec<String>,
|
||||
single_thread: bool,
|
||||
url: String,
|
||||
}
|
||||
|
@ -57,6 +58,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
// Parse the JSON into Post and Comment structs
|
||||
let post = parse_post(&response[0]).await;
|
||||
let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req));
|
||||
let saved = get_saved_posts(&req);
|
||||
let url = req.uri().to_string();
|
||||
|
||||
// Use the Post and Comment structs to generate a website to show users
|
||||
|
@ -65,6 +67,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
post,
|
||||
sort,
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
single_thread,
|
||||
url,
|
||||
})
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
// CRATES
|
||||
use crate::server::{ RequestExt, ResponseExt };
|
||||
use crate::utils::{get_saved_posts, redirect, template, Post, Preferences};
|
||||
use askama::Template;
|
||||
use cookie::Cookie;
|
||||
use hyper::{Body, Request, Response};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
// STRUCTS
|
||||
#[derive(Template)]
|
||||
#[template(path = "saved.html")]
|
||||
struct SavedTemplate {
|
||||
posts: Vec<Post>,
|
||||
prefs: Preferences,
|
||||
saved: Vec<String>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
// FUNCTIONS
|
||||
|
||||
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let saved = get_saved_posts(&req);
|
||||
let full_names: Vec<String> = saved.iter().map(|id| format!("t3_{}", id)).collect();
|
||||
let path = format!("/api/info.json?id={}", full_names.join(","));
|
||||
let posts;
|
||||
match Post::fetch(&path, false).await {
|
||||
Ok((post_results, _after)) => posts = post_results,
|
||||
Err(_) => posts = vec![],
|
||||
}
|
||||
// let posts = vec![];
|
||||
let url = req.uri().to_string();
|
||||
template(SavedTemplate{
|
||||
posts,
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn save(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// Get existing cookie
|
||||
let mut saved_posts = get_saved_posts(&req);
|
||||
|
||||
let query = req.uri().query().unwrap_or_default().as_bytes();
|
||||
let form = url::form_urlencoded::parse(query).collect::<HashMap<_, _>>();
|
||||
|
||||
let path = match form.get("redirect") {
|
||||
Some(value) => format!("{}", value.replace("%26", "&").replace("%23", "#")),
|
||||
None => "saved".to_string(),
|
||||
};
|
||||
|
||||
let mut response = redirect(path);
|
||||
|
||||
match req.param("id") {
|
||||
Some(id) => {
|
||||
saved_posts.push(id);
|
||||
response.insert_cookie(
|
||||
Cookie::build(String::from("saved_posts"), saved_posts.join("+"))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
||||
.finish(),
|
||||
);
|
||||
Ok(response)
|
||||
},
|
||||
None => Ok(response),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn unsave(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
// Get existing cookie
|
||||
let mut saved_posts = get_saved_posts(&req);
|
||||
|
||||
let query = req.uri().query().unwrap_or_default().as_bytes();
|
||||
let form = url::form_urlencoded::parse(query).collect::<HashMap<_, _>>();
|
||||
|
||||
let path = match form.get("redirect") {
|
||||
Some(value) => format!("{}", value.replace("%26", "&").replace("%23", "#")),
|
||||
None => "saved".to_string(),
|
||||
};
|
||||
|
||||
let mut response = redirect(path);
|
||||
|
||||
match req.param("id") {
|
||||
Some(id) => {
|
||||
if let Some(index) = saved_posts.iter().position(|el| el == &id) {
|
||||
saved_posts.remove(index);
|
||||
}
|
||||
response.insert_cookie(
|
||||
Cookie::build(String::from("saved_posts"), saved_posts.join("+"))
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
||||
.finish(),
|
||||
);
|
||||
Ok(response)
|
||||
},
|
||||
None => Ok(response),
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// CRATES
|
||||
use crate::utils::{catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, setting, template, val, Post, Preferences};
|
||||
use crate::utils::{catch_random, error, filter_posts, format_num, format_url, get_filters, get_saved_posts, param, redirect, setting, template, val, Post, Preferences};
|
||||
use crate::{
|
||||
client::json,
|
||||
subreddit::{can_access_quarantine, quarantine},
|
||||
|
@ -36,6 +36,7 @@ struct SearchTemplate {
|
|||
sub: String,
|
||||
params: SearchParams,
|
||||
prefs: Preferences,
|
||||
saved: Vec<String>,
|
||||
url: String,
|
||||
/// Whether the subreddit itself is filtered.
|
||||
is_filtered: bool,
|
||||
|
@ -69,6 +70,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
|
||||
let sort = param(&path, "sort").unwrap_or_else(|| "relevance".to_string());
|
||||
let filters = get_filters(&req);
|
||||
let saved = get_saved_posts(&req);
|
||||
|
||||
// If search is not restricted to this subreddit, show other subreddits in search results
|
||||
let subreddits = if param(&path, "restrict_sr").is_none() {
|
||||
|
@ -97,6 +99,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
typed,
|
||||
},
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
is_filtered: true,
|
||||
all_posts_filtered: false,
|
||||
|
@ -120,6 +123,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
typed,
|
||||
},
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
is_filtered: false,
|
||||
all_posts_filtered,
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||
|
||||
// CRATES
|
||||
use crate::server::ResponseExt;
|
||||
use crate::utils::{redirect, template, Preferences};
|
||||
use crate::utils::{get_saved_posts, redirect, template, Preferences};
|
||||
use askama::Template;
|
||||
use cookie::Cookie;
|
||||
use futures_lite::StreamExt;
|
||||
|
@ -14,6 +14,7 @@ use time::{Duration, OffsetDateTime};
|
|||
#[template(path = "settings.html")]
|
||||
struct SettingsTemplate {
|
||||
prefs: Preferences,
|
||||
saved_posts: Vec<String>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
|
@ -36,9 +37,11 @@ const PREFS: [&str; 10] = [
|
|||
|
||||
// Retrieve cookies from request "Cookie" header
|
||||
pub async fn get(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
let saved_posts = get_saved_posts(&req);
|
||||
let url = req.uri().to_string();
|
||||
template(SettingsTemplate {
|
||||
prefs: Preferences::new(req),
|
||||
saved_posts,
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
@ -109,7 +112,7 @@ fn set_cookies_method(req: Request<Body>, remove_cookies: bool) -> Response<Body
|
|||
|
||||
let mut response = redirect(path);
|
||||
|
||||
for name in [PREFS.to_vec(), vec!["subscriptions", "filters"]].concat() {
|
||||
for name in [PREFS.to_vec(), vec!["subscriptions", "filters", "saved_posts"]].concat() {
|
||||
match form.get(name) {
|
||||
Some(value) => response.insert_cookie(
|
||||
Cookie::build(name.to_owned(), value.clone())
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// CRATES
|
||||
use crate::esc;
|
||||
use crate::utils::{
|
||||
catch_random, error, filter_posts, format_num, format_url, get_filters, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||
catch_random, error, filter_posts, format_num, format_url, get_filters, get_saved_posts, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||
};
|
||||
use crate::{client::json, server::ResponseExt, RequestExt};
|
||||
use askama::Template;
|
||||
|
@ -18,6 +18,7 @@ struct SubredditTemplate {
|
|||
sort: (String, String),
|
||||
ends: (String, String),
|
||||
prefs: Preferences,
|
||||
saved: Vec<String>,
|
||||
url: String,
|
||||
redirect_url: String,
|
||||
/// Whether the subreddit itself is filtered.
|
||||
|
@ -99,6 +100,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
||||
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26");
|
||||
let filters = get_filters(&req);
|
||||
let saved = get_saved_posts(&req);
|
||||
|
||||
// If all requested subs are filtered, we don't need to fetch posts.
|
||||
if sub_name.split('+').all(|s| filters.contains(s)) {
|
||||
|
@ -108,6 +110,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
redirect_url,
|
||||
is_filtered: true,
|
||||
|
@ -124,6 +127,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
redirect_url,
|
||||
is_filtered: false,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
use crate::client::json;
|
||||
use crate::esc;
|
||||
use crate::server::RequestExt;
|
||||
use crate::utils::{error, filter_posts, format_url, get_filters, param, template, Post, Preferences, User};
|
||||
use crate::utils::{error, filter_posts, format_url, get_filters, get_saved_posts, param, template, Post, Preferences, User};
|
||||
use askama::Template;
|
||||
use hyper::{Body, Request, Response};
|
||||
use time::{OffsetDateTime, macros::format_description};
|
||||
|
@ -18,6 +18,7 @@ struct UserTemplate {
|
|||
/// "overview", "comments", or "submitted"
|
||||
listing: String,
|
||||
prefs: Preferences,
|
||||
saved: Vec<String>,
|
||||
url: String,
|
||||
redirect_url: String,
|
||||
/// Whether the user themself is filtered.
|
||||
|
@ -47,6 +48,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
let user = user(&username).await.unwrap_or_default();
|
||||
|
||||
let filters = get_filters(&req);
|
||||
let saved = get_saved_posts(&req);
|
||||
if filters.contains(&["u_", &username].concat()) {
|
||||
template(UserTemplate {
|
||||
user,
|
||||
|
@ -55,6 +57,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||
listing,
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
redirect_url,
|
||||
is_filtered: true,
|
||||
|
@ -73,6 +76,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
|||
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||
listing,
|
||||
prefs: Preferences::new(req),
|
||||
saved,
|
||||
url,
|
||||
redirect_url,
|
||||
is_filtered: false,
|
||||
|
|
|
@ -711,6 +711,13 @@ pub async fn error(req: Request<Body>, msg: String) -> Result<Response<Body>, St
|
|||
Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub fn get_saved_posts(req: &Request<Body>) -> Vec<String> {
|
||||
match req.cookie("saved_posts") {
|
||||
Some(cookie) => cookie.value().split('+').map(String::from).collect(),
|
||||
None => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::format_num;
|
||||
|
|
|
@ -548,10 +548,13 @@ button.submit {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
button.save { color: var(--text); }
|
||||
|
||||
select:hover { background: var(--foreground); }
|
||||
|
||||
input[type="submit"]:hover { color: var(--accent); }
|
||||
button.submit:hover > svg { stroke: var(--accent); }
|
||||
button.save:hover > svg { stroke: var(--accent); }
|
||||
|
||||
#timeframe {
|
||||
margin: 0 2px;
|
||||
|
@ -913,6 +916,14 @@ a.search_subreddit:hover {
|
|||
margin: 5px 20px 15px 12px;
|
||||
}
|
||||
|
||||
.post_footer__end {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.post_footer__end > p {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.post_comments {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
|
@ -117,7 +117,10 @@
|
|||
<li><a href="/{{ post.id }}">permalink</a></li>
|
||||
<li><a href="https://reddit.com/{{ post.id }}" rel="nofollow">reddit</a></li>
|
||||
</ul>
|
||||
<p>{{ post.upvote_ratio }}% Upvoted</p>
|
||||
<div class="post_footer__end">
|
||||
<p>{{ post.upvote_ratio }}% Upvoted</p>
|
||||
{% call utils::save_unsave(post, saved) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends "base.html" %}
|
||||
{% import "utils.html" as utils %}
|
||||
|
||||
{% block title %}
|
||||
Saved
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main>
|
||||
<div id="column_one">
|
||||
{% for post in posts %}
|
||||
{% if !(post.flags.nsfw && prefs.show_nsfw != "on") %}
|
||||
<hr class="sep" />
|
||||
{% call utils::post_in_list(post) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if prefs.use_hls == "on" %}
|
||||
<script src="/hls.min.js"></script>
|
||||
<script src="/playHLSVideo.js"></script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock body %}
|
|
@ -109,8 +109,8 @@
|
|||
{% endif %}
|
||||
|
||||
<div id="settings_note">
|
||||
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
||||
<p><b>Note:</b> settings, subscriptions and saved posts are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||
<p>You can restore your current settings, subscriptions and saved posts after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}&saved_posts={{ saved_posts.join("%2B") }}">this link</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
<a href="/">Home</a>
|
||||
<a href="/r/popular">Popular</a>
|
||||
<a href="/r/all">All</a>
|
||||
<a href="/saved">Saved</a>
|
||||
<p>REDDIT FEEDS</p>
|
||||
{% for sub in prefs.subscriptions %}
|
||||
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
|
||||
|
@ -142,6 +143,29 @@
|
|||
</div>
|
||||
<div class="post_footer">
|
||||
<a href="{{ post.permalink }}" class="post_comments" title="{{ post.comments.1 }} comments">{{ post.comments.0 }} comments</a>
|
||||
{% call save_unsave(post, saved) %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro save_unsave(post, saved) -%}
|
||||
{% if saved.contains(post.id) %}
|
||||
<form action="/saved/{{ post.id }}/unsave?redirect={{ url }}#{{ post.id }}" method="POST">
|
||||
<button class="save">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star-fill" viewBox="0 0 16 16">
|
||||
<title>Unsave</title>
|
||||
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/saved/{{ post.id }}/save?redirect={{ url }}#{{ post.id }}" method="POST">
|
||||
<button class="save">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16">
|
||||
<title>Save</title>
|
||||
<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.565.565 0 0 0-.163-.505L1.71 6.745l4.052-.576a.525.525 0 0 0 .393-.288L8 2.223l1.847 3.658a.525.525 0 0 0 .393.288l4.052.575-2.906 2.77a.565.565 0 0 0-.163.506l.694 3.957-3.686-1.894a.503.503 0 0 0-.461 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
|
Loading…
Reference in New Issue