mirror of https://github.com/spikecodes/libreddit
Add ability to save posts
This commit is contained in:
parent
3facaefb53
commit
08e48bbfed
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
// Reference local files
|
// Reference local files
|
||||||
mod post;
|
mod post;
|
||||||
|
mod saved;
|
||||||
mod search;
|
mod search;
|
||||||
mod settings;
|
mod settings;
|
||||||
mod subreddit;
|
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").get(|r| post::item(r).boxed());
|
||||||
app.at("/user/:name/comments/:id/:title/:comment_id").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
|
// Configure settings
|
||||||
app.at("/settings").get(|r| settings::get(r).boxed()).post(|r| settings::set(r).boxed());
|
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());
|
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::esc;
|
||||||
use crate::server::RequestExt;
|
use crate::server::RequestExt;
|
||||||
use crate::subreddit::{can_access_quarantine, quarantine};
|
use crate::subreddit::{can_access_quarantine, quarantine};
|
||||||
use crate::utils::{
|
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};
|
use hyper::{Body, Request, Response};
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ struct PostTemplate {
|
||||||
post: Post,
|
post: Post,
|
||||||
sort: String,
|
sort: String,
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
|
saved: Vec<String>,
|
||||||
single_thread: bool,
|
single_thread: bool,
|
||||||
url: String,
|
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
|
// Parse the JSON into Post and Comment structs
|
||||||
let post = parse_post(&response[0]).await;
|
let post = parse_post(&response[0]).await;
|
||||||
let comments = parse_comments(&response[1], &post.permalink, &post.author.name, highlighted_comment, &get_filters(&req));
|
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();
|
let url = req.uri().to_string();
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -65,6 +67,7 @@ pub async fn item(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
post,
|
post,
|
||||||
sort,
|
sort,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
single_thread,
|
single_thread,
|
||||||
url,
|
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
|
// 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::{
|
use crate::{
|
||||||
client::json,
|
client::json,
|
||||||
subreddit::{can_access_quarantine, quarantine},
|
subreddit::{can_access_quarantine, quarantine},
|
||||||
|
@ -36,6 +36,7 @@ struct SearchTemplate {
|
||||||
sub: String,
|
sub: String,
|
||||||
params: SearchParams,
|
params: SearchParams,
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
|
saved: Vec<String>,
|
||||||
url: String,
|
url: String,
|
||||||
/// Whether the subreddit itself is filtered.
|
/// Whether the subreddit itself is filtered.
|
||||||
is_filtered: bool,
|
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 sort = param(&path, "sort").unwrap_or_else(|| "relevance".to_string());
|
||||||
let filters = get_filters(&req);
|
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
|
// If search is not restricted to this subreddit, show other subreddits in search results
|
||||||
let subreddits = if param(&path, "restrict_sr").is_none() {
|
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,
|
typed,
|
||||||
},
|
},
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
|
@ -120,6 +123,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
typed,
|
typed,
|
||||||
},
|
},
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// CRATES
|
// CRATES
|
||||||
use crate::esc;
|
use crate::esc;
|
||||||
use crate::utils::{
|
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 crate::{client::json, server::ResponseExt, RequestExt};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
@ -18,6 +18,7 @@ struct SubredditTemplate {
|
||||||
sort: (String, String),
|
sort: (String, String),
|
||||||
ends: (String, String),
|
ends: (String, String),
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
|
saved: Vec<String>,
|
||||||
url: String,
|
url: String,
|
||||||
redirect_url: String,
|
redirect_url: String,
|
||||||
/// Whether the subreddit itself is filtered.
|
/// 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 url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
||||||
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26");
|
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26");
|
||||||
let filters = get_filters(&req);
|
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 all requested subs are filtered, we don't need to fetch posts.
|
||||||
if sub_name.split('+').all(|s| filters.contains(s)) {
|
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()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: true,
|
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()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
use crate::client::json;
|
use crate::client::json;
|
||||||
use crate::esc;
|
use crate::esc;
|
||||||
use crate::server::RequestExt;
|
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 askama::Template;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
use time::{OffsetDateTime, macros::format_description};
|
use time::{OffsetDateTime, macros::format_description};
|
||||||
|
@ -18,6 +18,7 @@ struct UserTemplate {
|
||||||
/// "overview", "comments", or "submitted"
|
/// "overview", "comments", or "submitted"
|
||||||
listing: String,
|
listing: String,
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
|
saved: Vec<String>,
|
||||||
url: String,
|
url: String,
|
||||||
redirect_url: String,
|
redirect_url: String,
|
||||||
/// Whether the user themself is filtered.
|
/// 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 user = user(&username).await.unwrap_or_default();
|
||||||
|
|
||||||
let filters = get_filters(&req);
|
let filters = get_filters(&req);
|
||||||
|
let saved = get_saved_posts(&req);
|
||||||
if filters.contains(&["u_", &username].concat()) {
|
if filters.contains(&["u_", &username].concat()) {
|
||||||
template(UserTemplate {
|
template(UserTemplate {
|
||||||
user,
|
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()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
listing,
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: true,
|
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),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
listing,
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
|
saved,
|
||||||
url,
|
url,
|
||||||
redirect_url,
|
redirect_url,
|
||||||
is_filtered: false,
|
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())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::format_num;
|
use super::format_num;
|
||||||
|
|
|
@ -548,10 +548,13 @@ button.submit {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.save { color: var(--text); }
|
||||||
|
|
||||||
select:hover { background: var(--foreground); }
|
select:hover { background: var(--foreground); }
|
||||||
|
|
||||||
input[type="submit"]:hover { color: var(--accent); }
|
input[type="submit"]:hover { color: var(--accent); }
|
||||||
button.submit:hover > svg { stroke: var(--accent); }
|
button.submit:hover > svg { stroke: var(--accent); }
|
||||||
|
button.save:hover > svg { stroke: var(--accent); }
|
||||||
|
|
||||||
#timeframe {
|
#timeframe {
|
||||||
margin: 0 2px;
|
margin: 0 2px;
|
||||||
|
|
|
@ -117,6 +117,7 @@
|
||||||
<li><a href="/{{ post.id }}">permalink</a></li>
|
<li><a href="/{{ post.id }}">permalink</a></li>
|
||||||
<li><a href="https://reddit.com/{{ post.id }}" rel="nofollow">reddit</a></li>
|
<li><a href="https://reddit.com/{{ post.id }}" rel="nofollow">reddit</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% call utils::save_unsave(post, saved) %}
|
||||||
<p>{{ post.upvote_ratio }}% Upvoted</p>
|
<p>{{ post.upvote_ratio }}% Upvoted</p>
|
||||||
</div>
|
</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 %}
|
|
@ -46,6 +46,7 @@
|
||||||
<a href="/">Home</a>
|
<a href="/">Home</a>
|
||||||
<a href="/r/popular">Popular</a>
|
<a href="/r/popular">Popular</a>
|
||||||
<a href="/r/all">All</a>
|
<a href="/r/all">All</a>
|
||||||
|
<a href="/saved">Saved</a>
|
||||||
<p>REDDIT FEEDS</p>
|
<p>REDDIT FEEDS</p>
|
||||||
{% for sub in prefs.subscriptions %}
|
{% for sub in prefs.subscriptions %}
|
||||||
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
|
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
|
||||||
|
@ -142,6 +143,29 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="post_footer">
|
<div class="post_footer">
|
||||||
<a href="{{ post.permalink }}" class="post_comments" title="{{ post.comments.1 }} comments">{{ post.comments.0 }} comments</a>
|
<a href="{{ post.permalink }}" class="post_comments" title="{{ post.comments.1 }} comments">{{ post.comments.0 }} comments</a>
|
||||||
|
{% call save_unsave(post, saved) %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro save_unsave(post, saved) -%}
|
||||||
|
{% if saved.contains(post.id) %}
|
||||||
|
<form action="/saved/{{ post.id }}/unsave?redirect={{ url }}" 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 }}" 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