From 08e48bbfedeae67186d2c07ae5677b642de55945 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Thu, 30 Dec 2021 16:05:56 -0700 Subject: [PATCH 1/4] Add ability to save posts --- src/main.rs | 6 +++ src/post.rs | 5 ++- src/saved.rs | 102 +++++++++++++++++++++++++++++++++++++++++++ src/search.rs | 6 ++- src/subreddit.rs | 6 ++- src/user.rs | 6 ++- src/utils.rs | 7 +++ static/style.css | 3 ++ templates/post.html | 1 + templates/saved.html | 23 ++++++++++ templates/utils.html | 24 ++++++++++ 11 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 src/saved.rs create mode 100644 templates/saved.html diff --git a/src/main.rs b/src/main.rs index 7efa558..f153e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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()); diff --git a/src/post.rs b/src/post.rs index 2ebc313..128fc57 100644 --- a/src/post.rs +++ b/src/post.rs @@ -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, single_thread: bool, url: String, } @@ -57,6 +58,7 @@ pub async fn item(req: Request) -> Result, 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) -> Result, String> { post, sort, prefs: Preferences::new(req), + saved, single_thread, url, }) diff --git a/src/saved.rs b/src/saved.rs new file mode 100644 index 0000000..0eb925e --- /dev/null +++ b/src/saved.rs @@ -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, + prefs: Preferences, + saved: Vec, + url: String, +} + +// FUNCTIONS + +pub async fn get(req: Request) -> Result, String> { + let saved = get_saved_posts(&req); + let full_names: Vec = 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) -> Result, 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::>(); + + 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) -> Result, 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::>(); + + 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), + } +} diff --git a/src/search.rs b/src/search.rs index 3547679..e8110fa 100644 --- a/src/search.rs +++ b/src/search.rs @@ -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, url: String, /// Whether the subreddit itself is filtered. is_filtered: bool, @@ -69,6 +70,7 @@ pub async fn find(req: Request) -> Result, 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) -> Result, 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) -> Result, String> { typed, }, prefs: Preferences::new(req), + saved, url, is_filtered: false, all_posts_filtered, diff --git a/src/subreddit.rs b/src/subreddit.rs index 141c011..c861e35 100644 --- a/src/subreddit.rs +++ b/src/subreddit.rs @@ -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, url: String, redirect_url: String, /// Whether the subreddit itself is filtered. @@ -99,6 +100,7 @@ pub async fn community(req: Request) -> Result, 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) -> Result, 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) -> Result, 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, diff --git a/src/user.rs b/src/user.rs index a5a11d0..8407f46 100644 --- a/src/user.rs +++ b/src/user.rs @@ -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, url: String, redirect_url: String, /// Whether the user themself is filtered. @@ -47,6 +48,7 @@ pub async fn profile(req: Request) -> Result, 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) -> Result, 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) -> Result, String> { ends: (param(&path, "after").unwrap_or_default(), after), listing, prefs: Preferences::new(req), + saved, url, redirect_url, is_filtered: false, diff --git a/src/utils.rs b/src/utils.rs index 56a7de7..8c56191 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -711,6 +711,13 @@ pub async fn error(req: Request, msg: String) -> Result, St Ok(Response::builder().status(404).header("content-type", "text/html").body(body.into()).unwrap_or_default()) } +pub fn get_saved_posts(req: &Request) -> Vec { + 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; diff --git a/static/style.css b/static/style.css index 90d849c..8a5e907 100644 --- a/static/style.css +++ b/static/style.css @@ -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; diff --git a/templates/post.html b/templates/post.html index add5881..2b36ab7 100644 --- a/templates/post.html +++ b/templates/post.html @@ -117,6 +117,7 @@
  • permalink
  • reddit
  • + {% call utils::save_unsave(post, saved) %}

    {{ post.upvote_ratio }}% Upvoted

    diff --git a/templates/saved.html b/templates/saved.html new file mode 100644 index 0000000..b953762 --- /dev/null +++ b/templates/saved.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} +{% import "utils.html" as utils %} + +{% block title %} + Saved +{% endblock %} + +{% block body %} +
    +
    + {% for post in posts %} + {% if !(post.flags.nsfw && prefs.show_nsfw != "on") %} +
    + {% call utils::post_in_list(post) %} + {% endif %} + {% endfor %} + {% if prefs.use_hls == "on" %} + + + {% endif %} +
    +
    +{% endblock body %} diff --git a/templates/utils.html b/templates/utils.html index a11583f..fd56a3b 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -46,6 +46,7 @@ Home Popular All + Saved

    REDDIT FEEDS

    {% for sub in prefs.subscriptions %} {{ sub }} @@ -142,6 +143,29 @@ {%- endmacro %} + +{% macro save_unsave(post, saved) -%} +{% if saved.contains(post.id) %} +
    + +
    +{% else %} +
    + +
    +{% endif %} +{%- endmacro %} From 6c551f0d334bb5411fe303141f88476c2d54130c Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Mon, 21 Mar 2022 16:34:15 -0700 Subject: [PATCH 2/4] Add saved posts to restore settings link --- src/saved.rs | 4 ++-- src/settings.rs | 7 +++++-- src/utils.rs | 2 +- templates/settings.html | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/saved.rs b/src/saved.rs index 0eb925e..3309bc2 100644 --- a/src/saved.rs +++ b/src/saved.rs @@ -57,7 +57,7 @@ pub async fn save(req: Request) -> Result, String> { Some(id) => { saved_posts.push(id); response.insert_cookie( - Cookie::build(String::from("saved_posts"), saved_posts.join(",")) + Cookie::build(String::from("saved_posts"), saved_posts.join("+")) .path("/") .http_only(true) .expires(OffsetDateTime::now_utc() + Duration::weeks(52)) @@ -89,7 +89,7 @@ pub async fn unsave(req: Request) -> Result, String> { saved_posts.remove(index); } response.insert_cookie( - Cookie::build(String::from("saved_posts"), saved_posts.join(",")) + Cookie::build(String::from("saved_posts"), saved_posts.join("+")) .path("/") .http_only(true) .expires(OffsetDateTime::now_utc() + Duration::weeks(52)) diff --git a/src/settings.rs b/src/settings.rs index 9cdd266..c6a16d3 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -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, url: String, } @@ -36,9 +37,11 @@ const PREFS: [&str; 10] = [ // Retrieve cookies from request "Cookie" header pub async fn get(req: Request) -> Result, 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, remove_cookies: bool) -> Response response.insert_cookie( Cookie::build(name.to_owned(), value.clone()) diff --git a/src/utils.rs b/src/utils.rs index 8c56191..e40399f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -713,7 +713,7 @@ pub async fn error(req: Request, msg: String) -> Result, St pub fn get_saved_posts(req: &Request) -> Vec { match req.cookie("saved_posts") { - Some(cookie) => cookie.value().split(',').map(String::from).collect(), + Some(cookie) => cookie.value().split('+').map(String::from).collect(), None => Vec::new(), } } diff --git a/templates/settings.html b/templates/settings.html index 8a70912..fc99ceb 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -109,8 +109,8 @@ {% endif %}
    -

    Note: settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.


    -

    You can restore your current settings and subscriptions after clearing your cookies using this link.

    +

    Note: settings, subscriptions and saved posts are saved in browser cookies. Clearing your cookies will reset them.


    +

    You can restore your current settings, subscriptions and saved posts after clearing your cookies using this link.

    From 50edb2b187c0f42ad924b7a008441e9f6b84ecbf Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Wed, 4 May 2022 15:23:50 -0700 Subject: [PATCH 3/4] Edit save button position on post page --- static/style.css | 8 ++++++++ templates/post.html | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/static/style.css b/static/style.css index 8a5e907..34886e3 100644 --- a/static/style.css +++ b/static/style.css @@ -916,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; } diff --git a/templates/post.html b/templates/post.html index 2b36ab7..88579e9 100644 --- a/templates/post.html +++ b/templates/post.html @@ -117,8 +117,10 @@
  • permalink
  • reddit
  • - {% call utils::save_unsave(post, saved) %} -

    {{ post.upvote_ratio }}% Upvoted

    + From 4e863b32e2c35d7a8764525e7dbacff89847df06 Mon Sep 17 00:00:00 2001 From: David Hamilton Date: Wed, 4 May 2022 15:48:41 -0700 Subject: [PATCH 4/4] Add post ID fragment to redirect url --- templates/utils.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/utils.html b/templates/utils.html index fd56a3b..9ad6aed 100644 --- a/templates/utils.html +++ b/templates/utils.html @@ -150,7 +150,7 @@ {% macro save_unsave(post, saved) -%} {% if saved.contains(post.id) %} -
    +
    {% else %} -
    +