Merge pull request #78 from mcrossman/subscriptions
Subscribing to subreddits (favorites)
This commit is contained in:
commit
79027c4c75
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -480,9 +480,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.5.0"
|
version = "3.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59"
|
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
@ -531,9 +531,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chunked_transfer"
|
name = "chunked_transfer"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca"
|
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_fn"
|
name = "const_fn"
|
||||||
@ -628,9 +628,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
|
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
@ -988,9 +988,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.83"
|
version = "0.2.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0c4e9c72ee9d69b767adebc5f4788462a3b45624acd919475c92597bcaf4f"
|
checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libreddit"
|
name = "libreddit"
|
||||||
|
21
src/main.rs
21
src/main.rs
@ -54,7 +54,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.wrap_fn(move |req, srv| {
|
.wrap_fn(move |req, srv| {
|
||||||
let secure = req.connection_info().scheme() == "https";
|
let secure = req.connection_info().scheme() == "https";
|
||||||
let https_url = format!("https://{}{}", req.connection_info().host(), req.uri().to_string());
|
let https_url = format!("https://{}{}", req.connection_info().host(), req.uri().to_string());
|
||||||
srv.call(req).map(move |res: Result<ServiceResponse, _>|
|
srv.call(req).map(move |res: Result<ServiceResponse, _>| {
|
||||||
if force_https && !secure {
|
if force_https && !secure {
|
||||||
Ok(ServiceResponse::new(
|
Ok(ServiceResponse::new(
|
||||||
res.unwrap().request().to_owned(),
|
res.unwrap().request().to_owned(),
|
||||||
@ -63,16 +63,21 @@ async fn main() -> std::io::Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
// Append trailing slash and remove double slashes
|
// Append trailing slash and remove double slashes
|
||||||
.wrap(middleware::NormalizePath::default())
|
.wrap(middleware::NormalizePath::default())
|
||||||
// Apply default headers for security
|
// Apply default headers for security
|
||||||
.wrap(middleware::DefaultHeaders::new()
|
.wrap(
|
||||||
.header("Referrer-Policy", "no-referrer")
|
middleware::DefaultHeaders::new()
|
||||||
.header("X-Content-Type-Options", "nosniff")
|
.header("Referrer-Policy", "no-referrer")
|
||||||
.header("X-Frame-Options", "DENY")
|
.header("X-Content-Type-Options", "nosniff")
|
||||||
.header("Content-Security-Policy", "default-src 'none'; media-src 'self'; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none';"))
|
.header("X-Frame-Options", "DENY")
|
||||||
|
.header(
|
||||||
|
"Content-Security-Policy",
|
||||||
|
"default-src 'none'; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none';",
|
||||||
|
),
|
||||||
|
)
|
||||||
// Default service in case no routes match
|
// Default service in case no routes match
|
||||||
.default_service(web::get().to(|| utils::error("Nothing here".to_string())))
|
.default_service(web::get().to(|| utils::error("Nothing here".to_string())))
|
||||||
// Read static files
|
// Read static files
|
||||||
@ -99,6 +104,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
// See posts and info about subreddit
|
// See posts and info about subreddit
|
||||||
.route("/", web::get().to(subreddit::page))
|
.route("/", web::get().to(subreddit::page))
|
||||||
.route("/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page))
|
.route("/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page))
|
||||||
|
// Handle subscribe/unsubscribe
|
||||||
|
.route("/{action:subscribe|unsubscribe}/", web::post().to(subreddit::subscriptions))
|
||||||
// View post on subreddit
|
// View post on subreddit
|
||||||
.service(
|
.service(
|
||||||
web::scope("/comments/{id}/{title}")
|
web::scope("/comments/{id}/{title}")
|
||||||
|
32
src/proxy.rs
32
src/proxy.rs
@ -24,26 +24,24 @@ pub async fn handler(web::Path(b64): web::Path<String>) -> Result<HttpResponse>
|
|||||||
let decoded = decode(b64).map(|bytes| String::from_utf8(bytes).unwrap_or_default());
|
let decoded = decode(b64).map(|bytes| String::from_utf8(bytes).unwrap_or_default());
|
||||||
|
|
||||||
match decoded {
|
match decoded {
|
||||||
Ok(media) => {
|
Ok(media) => match Url::parse(media.as_str()) {
|
||||||
match Url::parse(media.as_str()) {
|
Ok(url) => {
|
||||||
Ok(url) => {
|
let domain = url.domain().unwrap_or_default();
|
||||||
let domain = url.domain().unwrap_or_default();
|
|
||||||
|
|
||||||
if domains.contains(&domain) {
|
if domains.contains(&domain) {
|
||||||
Client::default().get(media.replace("&", "&")).send().await.map_err(Error::from).map(|res| {
|
Client::default().get(media.replace("&", "&")).send().await.map_err(Error::from).map(|res| {
|
||||||
HttpResponse::build(res.status())
|
HttpResponse::build(res.status())
|
||||||
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
||||||
.header("Content-Length", res.headers().get("Content-Length").unwrap().to_owned())
|
.header("Content-Length", res.headers().get("Content-Length").unwrap().to_owned())
|
||||||
.header("Content-Type", res.headers().get("Content-Type").unwrap().to_owned())
|
.header("Content-Type", res.headers().get("Content-Type").unwrap().to_owned())
|
||||||
.streaming(res)
|
.streaming(res)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(error::ErrorForbidden("Resource must be from Reddit"))
|
Err(error::ErrorForbidden("Resource must be from Reddit"))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => Err(error::ErrorBadRequest("Can't parse base64 into URL")),
|
|
||||||
}
|
}
|
||||||
}
|
_ => Err(error::ErrorBadRequest("Can't parse base64 into URL")),
|
||||||
|
},
|
||||||
_ => Err(error::ErrorBadRequest("Can't decode base64")),
|
_ => Err(error::ErrorBadRequest("Can't decode base64")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
// CRATES
|
// CRATES
|
||||||
use crate::utils::*;
|
use crate::utils::*;
|
||||||
use actix_web::{HttpRequest, HttpResponse, Result};
|
use actix_web::{cookie::Cookie, HttpRequest, HttpResponse, Result};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
// STRUCTS
|
// STRUCTS
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@ -25,23 +26,43 @@ struct WikiTemplate {
|
|||||||
|
|
||||||
// SERVICES
|
// SERVICES
|
||||||
pub async fn page(req: HttpRequest) -> HttpResponse {
|
pub async fn page(req: HttpRequest) -> HttpResponse {
|
||||||
let path = format!("{}.json?{}", req.path(), req.query_string());
|
let subscribed = cookie(&req, "subscriptions");
|
||||||
let default = cookie(&req, "front_page");
|
let front_page = cookie(&req, "front_page");
|
||||||
let sub_name = req
|
let sort = req.match_info().get("sort").unwrap_or("hot").to_string();
|
||||||
|
|
||||||
|
let sub = req
|
||||||
.match_info()
|
.match_info()
|
||||||
.get("sub")
|
.get("sub")
|
||||||
.unwrap_or(if default.is_empty() { "popular" } else { default.as_str() })
|
.map(String::from)
|
||||||
.to_string();
|
.unwrap_or(if front_page == "default" || front_page.is_empty() {
|
||||||
let sort = req.match_info().get("sort").unwrap_or("hot").to_string();
|
if subscribed.is_empty() {
|
||||||
|
"popular".to_string()
|
||||||
|
} else {
|
||||||
|
subscribed.to_owned()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
front_page.to_owned()
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = format!("/r/{}.json?{}", sub, req.query_string());
|
||||||
|
|
||||||
match fetch_posts(&path, String::new()).await {
|
match fetch_posts(&path, String::new()).await {
|
||||||
Ok((posts, after)) => {
|
Ok((posts, after)) => {
|
||||||
// If you can get subreddit posts, also request subreddit metadata
|
// If you can get subreddit posts, also request subreddit metadata
|
||||||
let sub = if !sub_name.contains('+') && sub_name != "popular" && sub_name != "all" {
|
let sub = if !sub.contains('+') && sub != subscribed && sub != "popular" && sub != "all" {
|
||||||
subreddit(&sub_name).await.unwrap_or_default()
|
// Regular subreddit
|
||||||
} else if sub_name.contains('+') {
|
subreddit(&sub).await.unwrap_or_default()
|
||||||
|
} else if sub == subscribed {
|
||||||
|
// Subscription feed
|
||||||
|
if req.path().starts_with("/r/") {
|
||||||
|
subreddit(&sub).await.unwrap_or_default()
|
||||||
|
} else {
|
||||||
|
Subreddit::default()
|
||||||
|
}
|
||||||
|
} else if sub.contains('+') {
|
||||||
|
// Multireddit
|
||||||
Subreddit {
|
Subreddit {
|
||||||
name: sub_name,
|
name: sub,
|
||||||
..Subreddit::default()
|
..Subreddit::default()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -63,6 +84,50 @@ pub async fn page(req: HttpRequest) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sub or unsub by setting subscription cookie using response "Set-Cookie" header
|
||||||
|
pub async fn subscriptions(req: HttpRequest) -> HttpResponse {
|
||||||
|
let mut res = HttpResponse::Found();
|
||||||
|
|
||||||
|
let sub = req.match_info().get("sub").unwrap_or_default().to_string();
|
||||||
|
let action = req.match_info().get("action").unwrap_or_default().to_string();
|
||||||
|
let mut sub_list = prefs(req.to_owned()).subs;
|
||||||
|
|
||||||
|
// Modify sub list based on action
|
||||||
|
if action == "subscribe" && !sub_list.contains(&sub) {
|
||||||
|
sub_list.push(sub.to_owned());
|
||||||
|
sub_list.sort();
|
||||||
|
} else if action == "unsubscribe" {
|
||||||
|
sub_list.retain(|s| s != &sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cookie if empty, else set
|
||||||
|
if sub_list.is_empty() {
|
||||||
|
res.del_cookie(&Cookie::build("subscriptions", "").path("/").finish());
|
||||||
|
} else {
|
||||||
|
res.cookie(
|
||||||
|
Cookie::build("subscriptions", sub_list.join("+"))
|
||||||
|
.path("/")
|
||||||
|
.http_only(true)
|
||||||
|
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
|
||||||
|
.finish(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to subreddit
|
||||||
|
// check for redirect parameter if unsubscribing from outside sidebar
|
||||||
|
let redirect_path = param(&req.uri().to_string(), "redirect");
|
||||||
|
let path = if !redirect_path.is_empty() && redirect_path.starts_with('/') {
|
||||||
|
redirect_path
|
||||||
|
} else {
|
||||||
|
format!("/r/{}", sub)
|
||||||
|
};
|
||||||
|
|
||||||
|
res
|
||||||
|
.content_type("text/html")
|
||||||
|
.set_header("Location", path.to_owned())
|
||||||
|
.body(format!("Redirecting to <a href=\"{0}\">{0}</a>...", path))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn wiki(req: HttpRequest) -> HttpResponse {
|
pub async fn wiki(req: HttpRequest) -> HttpResponse {
|
||||||
let sub = req.match_info().get("sub").unwrap_or("reddit.com").to_string();
|
let sub = req.match_info().get("sub").unwrap_or("reddit.com").to_string();
|
||||||
let page = req.match_info().get("page").unwrap_or("index").to_string();
|
let page = req.match_info().get("page").unwrap_or("index").to_string();
|
||||||
|
@ -129,6 +129,7 @@ pub struct Preferences {
|
|||||||
pub wide: String,
|
pub wide: String,
|
||||||
pub hide_nsfw: String,
|
pub hide_nsfw: String,
|
||||||
pub comment_sort: String,
|
pub comment_sort: String,
|
||||||
|
pub subs: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -144,6 +145,7 @@ pub fn prefs(req: HttpRequest) -> Preferences {
|
|||||||
wide: cookie(&req, "wide"),
|
wide: cookie(&req, "wide"),
|
||||||
hide_nsfw: cookie(&req, "hide_nsfw"),
|
hide_nsfw: cookie(&req, "hide_nsfw"),
|
||||||
comment_sort: cookie(&req, "comment_sort"),
|
comment_sort: cookie(&req, "comment_sort"),
|
||||||
|
subs: cookie(&req, "subscriptions").split('+').map(String::from).filter(|s| s != "").collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
150
static/style.css
150
static/style.css
@ -68,11 +68,12 @@ pre, form, fieldset, table, th, td, select, input {
|
|||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
padding-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "logo searchbox code";
|
grid-template-areas: "logo searchbox links";
|
||||||
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -83,7 +84,7 @@ nav {
|
|||||||
|
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
|
||||||
z-index: 1;
|
z-index: 2;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: 5px 15px;
|
padding: 5px 15px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
@ -94,12 +95,23 @@ nav {
|
|||||||
nav * { color: var(--text); }
|
nav * { color: var(--text); }
|
||||||
nav #reddit, #code { color: var(--accent); }
|
nav #reddit, #code { color: var(--accent); }
|
||||||
nav #logo { grid-area: logo; }
|
nav #logo { grid-area: logo; }
|
||||||
nav #code { grid-area: code; }
|
|
||||||
nav #version { opacity: 50%; }
|
nav #links {
|
||||||
|
grid-area: links;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav #version {
|
||||||
|
opacity: 50%;
|
||||||
|
vertical-align: -2px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav #libreddit {
|
||||||
|
vertical-align: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
#settings_link {
|
#settings_link {
|
||||||
font-size: 18px;
|
|
||||||
margin-left: 10px;
|
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +120,7 @@ main {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
margin: 60px auto 20px auto
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wide main {
|
.wide main {
|
||||||
@ -232,6 +244,71 @@ aside {
|
|||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Subscriptions */
|
||||||
|
|
||||||
|
#sub_subscription {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribe, .unsubscribe {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscribe {
|
||||||
|
color: var(--foreground);
|
||||||
|
background-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsubscribe {
|
||||||
|
color: var(--text);
|
||||||
|
background-color: var(--highlighted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subscribed subreddit list */
|
||||||
|
|
||||||
|
#subscriptions {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: var(--panel-border);
|
||||||
|
background-color: var(--outside);
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#subscriptions > summary {
|
||||||
|
padding: 8px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub_list {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
min-width: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
background: var(--outside);
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub_list > a {
|
||||||
|
padding: 10px 20px;
|
||||||
|
transition: 0.2s background;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub_list > .selected {
|
||||||
|
background-color: var(--accent);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sub_list > a:not(.selected):hover {
|
||||||
|
background-color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
/* Wiki Pages */
|
/* Wiki Pages */
|
||||||
|
|
||||||
#wiki {
|
#wiki {
|
||||||
@ -452,10 +529,6 @@ a.search_subreddit:hover {
|
|||||||
|
|
||||||
.post:not(:last-child) { margin-bottom: 10px; }
|
.post:not(:last-child) { margin-bottom: 10px; }
|
||||||
|
|
||||||
.post.highlighted {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post:hover {
|
.post:hover {
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
}
|
}
|
||||||
@ -789,7 +862,7 @@ a.search_subreddit:hover {
|
|||||||
|
|
||||||
/* Settings */
|
/* Settings */
|
||||||
|
|
||||||
#settings {
|
#settings, #settings > form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -802,7 +875,7 @@ a.search_subreddit:hover {
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
#prefs {
|
.prefs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -812,7 +885,7 @@ a.search_subreddit:hover {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#prefs > div {
|
.prefs > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -820,17 +893,21 @@ a.search_subreddit:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#prefs > div:not(:last-of-type) {
|
.prefs > div:not(:last-of-type) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#prefs select {
|
.prefs select {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aside.prefs {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#save {
|
#save {
|
||||||
background: var(--highlighted);
|
background: var(--highlighted);
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
@ -843,6 +920,27 @@ input[type="submit"] {
|
|||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#settings_subs {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings_subs > li {
|
||||||
|
display: flex;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
#settings_subs > li:last-of-type { margin-bottom: 0; }
|
||||||
|
|
||||||
|
#settings_subs > li > span {
|
||||||
|
padding: 10px 0;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings_subs .unsubscribe {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Markdown */
|
/* Markdown */
|
||||||
|
|
||||||
.md > *:not(:first-child) {
|
.md > *:not(:first-child) {
|
||||||
@ -916,6 +1014,8 @@ td, th {
|
|||||||
/* Mobile */
|
/* Mobile */
|
||||||
|
|
||||||
@media screen and (max-width: 480px) {
|
@media screen and (max-width: 480px) {
|
||||||
|
#version { display: none; }
|
||||||
|
|
||||||
.post {
|
.post {
|
||||||
grid-template: "post_header post_header post_thumbnail" auto
|
grid-template: "post_header post_header post_thumbnail" auto
|
||||||
"post_title post_title post_thumbnail" 1fr
|
"post_title post_title post_thumbnail" 1fr
|
||||||
@ -954,25 +1054,37 @@ td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 800px) {
|
@media screen and (max-width: 800px) {
|
||||||
|
body { padding-top: 120px }
|
||||||
|
|
||||||
main {
|
main {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 100px 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
grid-template-areas: 'logo code' 'searchbox searchbox';
|
grid-template-areas: 'logo links' 'searchbox searchbox';
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav #links { margin-left: auto; }
|
||||||
|
|
||||||
|
#subscriptions { position: unset; }
|
||||||
|
|
||||||
|
#sub_list {
|
||||||
|
left: 10px;
|
||||||
|
right: 10px;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
aside, #subreddit, #user {
|
aside, #subreddit, #user {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user, #sidebar { margin: 20px 0; }
|
#user, #sidebar { margin: 20px 0; }
|
||||||
#logo { margin: 5px auto; }
|
#logo, #links { margin-bottom: 5px; }
|
||||||
#searchbox { width: calc(100vw - 35px); }
|
#searchbox { width: calc(100vw - 35px); }
|
||||||
}
|
}
|
||||||
|
@ -16,15 +16,18 @@
|
|||||||
{% if prefs.theme != "system" %} {{ prefs.theme }}{% endif %}">
|
{% if prefs.theme != "system" %} {{ prefs.theme }}{% endif %}">
|
||||||
<!-- NAVIGATION BAR -->
|
<!-- NAVIGATION BAR -->
|
||||||
<nav>
|
<nav>
|
||||||
<p id="logo">
|
<div id="logo">
|
||||||
<a id="libreddit" href="/">
|
<a id="libreddit" href="/">
|
||||||
<span id="lib">lib</span><span id="reddit">reddit.</span>
|
<span id="lib">lib</span><span id="reddit">reddit.</span>
|
||||||
</a>
|
</a>
|
||||||
<span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span>
|
<span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span>
|
||||||
<a id="settings_link" href="/settings">settings</a>
|
{% block subscriptions %}{% endblock %}
|
||||||
</p>
|
</div>
|
||||||
{% block search %}{% endblock %}
|
{% block search %}{% endblock %}
|
||||||
<a id="code" href="https://github.com/spikecodes/libreddit">code</a>
|
<div id="links">
|
||||||
|
<a id="settings_link" href="/settings">settings</a>
|
||||||
|
<a id="code" href="https://github.com/spikecodes/libreddit">code</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
<meta name="author" content="u/{{ post.author.name }}">
|
<meta name="author" content="u/{{ post.author.name }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list(post.community.as_str()) %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<!-- OPEN COMMENT MACRO -->
|
<!-- OPEN COMMENT MACRO -->
|
||||||
{% macro comment(item) -%}
|
{% macro comment(item) -%}
|
||||||
<div id="{{ item.id }}" class="comment">
|
<div id="{{ item.id }}" class="comment">
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
{% block title %}Libreddit: search results - {{ params.q }}{% endblock %}
|
{% block title %}Libreddit: search results - {{ params.q }}{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list("") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form id="search_sort">
|
<form id="search_sort">
|
||||||
|
@ -8,45 +8,63 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form id="settings" action="/settings" method="POST">
|
<div id="settings">
|
||||||
<div id="prefs">
|
<form action="/settings" method="POST">
|
||||||
<p>Appearance</p>
|
<div class="prefs">
|
||||||
<div id="theme">
|
<p>Appearance</p>
|
||||||
<label for="theme">Theme:</label>
|
<div id="theme">
|
||||||
<select name="theme">
|
<label for="theme">Theme:</label>
|
||||||
{% call utils::options(prefs.theme, ["system", "light", "dark"], "system") %}
|
<select name="theme">
|
||||||
</select>
|
{% call utils::options(prefs.theme, ["system", "light", "dark"], "system") %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p>Interface</p>
|
||||||
|
<div id="front_page">
|
||||||
|
<label for="front_page">Front page:</label>
|
||||||
|
<select name="front_page">
|
||||||
|
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="layout">
|
||||||
|
<label for="layout">Layout:</label>
|
||||||
|
<select name="layout">
|
||||||
|
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="wide">
|
||||||
|
<label for="wide">Wide UI:</label>
|
||||||
|
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
|
<p>Content</p>
|
||||||
|
<div id="comment_sort">
|
||||||
|
<label for="comment_sort">Default comment sort:</label>
|
||||||
|
<select name="comment_sort">
|
||||||
|
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="hide_nsfw">
|
||||||
|
<label for="hide_nsfw">Hide NSFW posts:</label>
|
||||||
|
<input type="checkbox" name="hide_nsfw" {% if prefs.hide_nsfw == "on" %}checked{% endif %}>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>Interface</p>
|
<p id="settings_note"><b>Note:</b> settings are saved in browser cookies. Clearing your cookie data will reset them.</p>
|
||||||
<div id="front_page">
|
<input id="save" type="submit" value="Save">
|
||||||
<label for="front_page">Front page:</label>
|
</form>
|
||||||
<select name="front_page">
|
{% if prefs.subs.len() > 0 %}
|
||||||
{% call utils::options(prefs.front_page, ["popular", "all"], "popular") %}
|
<aside class="prefs">
|
||||||
</select>
|
<p>Subscribed Subreddits</p>
|
||||||
</div>
|
<ul id="settings_subs">
|
||||||
<div id="layout">
|
{% for sub in prefs.subs %}
|
||||||
<label for="layout">Layout:</label>
|
<li>
|
||||||
<select name="layout">
|
<span>{{ sub }}</span>
|
||||||
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
|
<form action="/r/{{ sub }}/unsubscribe/?redirect=/settings" method="POST">
|
||||||
</select>
|
<button class="unsubscribe">Unsubscribe</button>
|
||||||
</div>
|
</form>
|
||||||
<div id="wide">
|
</li>
|
||||||
<label for="wide">Wide UI:</label>
|
{% endfor %}
|
||||||
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
|
</ul>
|
||||||
</div>
|
</aside>
|
||||||
<p>Content</p>
|
{% endif %}
|
||||||
<div id="comment_sort">
|
</div>
|
||||||
<label for="comment_sort">Default comment sort:</label>
|
|
||||||
<select name="comment_sort">
|
|
||||||
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="hide_nsfw">
|
|
||||||
<label for="hide_nsfw">Hide NSFW posts:</label>
|
|
||||||
<input type="checkbox" name="hide_nsfw" {% if prefs.hide_nsfw == "on" %}checked{% endif %}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p id="settings_note"><b>Note:</b> settings are saved in browser cookies. Clearing your cookie data will reset them.</p>
|
|
||||||
<input id="save" type="submit" value="Save">
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -11,6 +11,10 @@
|
|||||||
{% call utils::search(["/r/", sub.name.as_str()].concat(), "") %}
|
{% call utils::search(["/r/", sub.name.as_str()].concat(), "") %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list(sub.name.as_str(), "wide") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main>
|
<main>
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
@ -121,6 +125,17 @@
|
|||||||
<div>{{ sub.members }}</div>
|
<div>{{ sub.members }}</div>
|
||||||
<div>{{ sub.active }}</div>
|
<div>{{ sub.active }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="sub_subscription">
|
||||||
|
{% if prefs.subs.contains(sub.name) %}
|
||||||
|
<form action="/r/{{ sub.name }}/unsubscribe" method="POST">
|
||||||
|
<button class="unsubscribe">Unsubscribe</button>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form action="/r/{{ sub.name }}/subscribe" method="POST">
|
||||||
|
<button class="subscribe">Subscribe</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<details class="panel" id="sidebar">
|
<details class="panel" id="sidebar">
|
||||||
|
@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %}
|
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list("") %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main>
|
<main>
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
|
@ -39,3 +39,16 @@
|
|||||||
{% else if flair_part.flair_part_type == "text" %}<span>{{ flair_part.value }}</span>{% endif %}
|
{% else if flair_part.flair_part_type == "text" %}<span>{{ flair_part.value }}</span>{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro sub_list(current) -%}
|
||||||
|
{% if prefs.subs.len() > 0 %}
|
||||||
|
<details id="subscriptions">
|
||||||
|
<summary>Subscriptions</summary>
|
||||||
|
<div id="sub_list">
|
||||||
|
{% for sub in prefs.subs %}
|
||||||
|
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
{%- endmacro %}
|
||||||
|
@ -10,6 +10,10 @@
|
|||||||
{% call utils::search(["/r/", sub.as_str()].concat(), "") %}
|
{% call utils::search(["/r/", sub.as_str()].concat(), "") %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subscriptions %}
|
||||||
|
{% call utils::sub_list(sub.as_str()) %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<main>
|
<main>
|
||||||
<div class="panel" id="column_one">
|
<div class="panel" id="column_one">
|
||||||
|
Loading…
Reference in New Issue
Block a user