Compare commits

...

17 Commits

Author SHA1 Message Date
c15f305be0 v0.25.3 2023-01-01 23:54:35 -07:00
222d216854 Merge pull request #673 from spenserblack/code-in-new-tab 2023-01-01 23:08:15 -05:00
6d8aaba8bb Merge pull request #676 from ellieeet123/master 2023-01-01 19:23:30 -05:00
6cf3748642 Fix for #675
/:id route now accepts 7 character post IDs.
2023-01-01 17:06:58 -06:00
9c938c6210 build: enable LTO, set codegen-unit to 1 and strip the binary (#467)
Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2023-01-01 14:33:31 -07:00
a49d399f72 Link to libreddit/libreddit and open in new tab
This sets the target of the "code" link to `_blank`, so that it will
open in a new tab in browsers. Because the GitHub page is a different
context from libreddit, and accessing the repository doesn't imply that
the user is finished browsing libreddit, this seemed reasonable. This
also changes the link from `spikecodes/libreddit` to
`libreddit/libreddit`.
2023-01-01 13:43:33 -05:00
9178b50b73 fix a11y and HTML issues on settings page (#662)
- connect labels with corresponding form controls
- use fieldsets to group form sections
- don't nest details/summary element into label
2023-01-01 01:56:09 -07:00
b5d04f1a50 v0.25.2 2022-12-31 21:34:15 -07:00
9e434e7db6 Search - add support for raw reddit links (#663)
* Search - add support for raw reddit links

If a search query starts with 'https://www.reddit.com/' or 'https://old.reddit.com/',
this prefix will be truncated and the query will be processed normally.
For example, a search query 'https://www.reddit.com/r/rust' will redirect to
r/rust.

* Search - support a wider variety of reddit links.

Add once cell dependency for static regex support (avoid compiling the
same regex multiple times).
All search queries are now matched against a regex (provided by @Daniel-Valentine)
that determines if it is a reddit link. If it is, the prefix specifying
the reddit instance will be truncated from the query that will then be
processed normally.
For example, the query 'https://www.reddit.com/r/rust' will be treated
the same way as the query 'r/rust'.
2022-12-31 20:57:42 -07:00
ab30b8bbec Bugfix: 'all posts are hidden because NSFW' when no posts where found (#666)
* Fix 'all_posts_hidden_nsfw' when there are no posts.

If a search query yielded no results and the user set nsfw posts to be
hidden, libreddit would show 'All posts are hidden because they are NSFW.
Enable "Show NSFW posts" in settings to view'. This is fixed by
verifying tnat posts.len > 0 before setting 'all_posts_hidden_nsfw' to
true.

* Add a message when no posts were found.

* Delete 2
2022-12-31 19:11:59 -07:00
37d1939dc0 Fix #658.
Dimensions for embedded video in post are explicitly set only when defined by Reddit.

c/o: NKIPSC <15067635+NKIPSC@users.noreply.github.com>
2022-12-13 21:15:28 -07:00
08a20b89a6 Merge branch 'cache-determine-compressor' 2022-12-10 18:35:38 -07:00
5d518cfc18 Cache result of server::determine_compressor. 2022-12-04 17:56:02 -07:00
7e752b3d81 Fix Docker credential secrets 2022-12-04 11:07:18 -08:00
87729d0daa Use new libreddit org for GitLab and Docker links 2022-12-04 11:05:19 -08:00
dc06ae3b29 Automatically-update Docker Repo description 2022-12-04 11:01:28 -08:00
225380b7d9 Fix workflow to push to new Libreddit Docker repo 2022-12-04 10:57:19 -08:00
18 changed files with 192 additions and 143 deletions

View File

@ -33,6 +33,6 @@ jobs:
file: ./Dockerfile.arm
platforms: linux/arm64
push: true
tags: spikecodes/libreddit:arm
tags: libreddit/libreddit:arm
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -36,6 +36,6 @@ jobs:
file: ./Dockerfile.armv7
platforms: linux/arm/v7
push: true
tags: spikecodes/libreddit:armv7
tags: libreddit/libreddit:armv7
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -26,6 +26,12 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
repository: libreddit/libreddit
- name: Build and push
uses: docker/build-push-action@v2
with:
@ -33,6 +39,6 @@ jobs:
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: spikecodes/libreddit:latest
tags: libreddit/libreddit:latest
cache-from: type=gha
cache-to: type=gha,mode=max

3
Cargo.lock generated
View File

@ -664,7 +664,7 @@ dependencies = [
[[package]]
name = "libreddit"
version = "0.25.0"
version = "0.25.3"
dependencies = [
"askama",
"async-recursion",
@ -677,6 +677,7 @@ dependencies = [
"hyper-rustls",
"libflate",
"lipsum",
"once_cell",
"percent-encoding",
"regex",
"route-recognizer",

View File

@ -3,7 +3,7 @@ name = "libreddit"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0"
repository = "https://github.com/spikecodes/libreddit"
version = "0.25.0"
version = "0.25.3"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2021"
@ -27,6 +27,12 @@ url = "2.3.1"
rust-embed = { version = "6.4.2", features = ["include-exclude"] }
libflate = "1.2.0"
brotli = { version = "3.3.4", features = ["std"] }
once_cell = "1.16.0"
[dev-dependencies]
lipsum = "0.8.2"
[profile.release]
codegen-units = 1
lto = true
strip = true

View File

@ -39,7 +39,7 @@ Both files are part of the [libreddit-instances](https://github.com/libreddit/li
# About
Find Libreddit on 💬 [Matrix](https://matrix.to/#/#libreddit:kde.org), 🐋 [Docker](https://hub.docker.com/r/spikecodes/libreddit), :octocat: [GitHub](https://github.com/libreddit/libreddit), and 🦊 [GitLab](https://gitlab.com/spikecodes/libreddit).
Find Libreddit on 💬 [Matrix](https://matrix.to/#/#libreddit:kde.org), 🐋 [Docker](https://hub.docker.com/r/libreddit/libreddit), :octocat: [GitHub](https://github.com/libreddit/libreddit), and 🦊 [GitLab](https://gitlab.com/libreddit/libreddit).
## Built with
@ -136,21 +136,21 @@ cargo install libreddit
## 2) Docker
Deploy the [Docker image](https://hub.docker.com/r/spikecodes/libreddit) of Libreddit:
Deploy the [Docker image](https://hub.docker.com/r/libreddit/libreddit) of Libreddit:
```
docker pull spikecodes/libreddit
docker run -d --name libreddit -p 8080:8080 spikecodes/libreddit
docker pull libreddit/libreddit
docker run -d --name libreddit -p 8080:8080 libreddit/libreddit
```
Deploy using a different port (in this case, port 80):
```
docker pull spikecodes/libreddit
docker run -d --name libreddit -p 80:8080 spikecodes/libreddit
docker pull libreddit/libreddit
docker run -d --name libreddit -p 80:8080 libreddit/libreddit
```
To deploy on `arm64` platforms, simply replace `spikecodes/libreddit` in the commands above with `spikecodes/libreddit:arm`.
To deploy on `arm64` platforms, simply replace `libreddit/libreddit` in the commands above with `libreddit/libreddit:arm`.
To deploy on `armv7` platforms, simply replace `spikecodes/libreddit` in the commands above with `spikecodes/libreddit:armv7`.
To deploy on `armv7` platforms, simply replace `libreddit/libreddit` in the commands above with `libreddit/libreddit:armv7`.
## 3) AUR

View File

@ -289,7 +289,7 @@ async fn main() {
Some("best" | "hot" | "new" | "top" | "rising" | "controversial") => subreddit::community(req).await,
// Short link for post
Some(id) if (5..7).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
Some(id) if (5..8).contains(&id.len()) => match canonical_path(format!("/{}", id)).await {
Ok(path_opt) => match path_opt {
Some(path) => Ok(redirect(path)),
None => error(req, "Post ID is invalid. It may point to a post on a community that has been banned.").await,

View File

@ -7,6 +7,8 @@ use crate::{
};
use askama::Template;
use hyper::{Body, Request, Response};
use once_cell::sync::Lazy;
use regex::Regex;
// STRUCTS
struct SearchParams {
@ -44,13 +46,18 @@ struct SearchTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
all_posts_hidden_nsfw: bool,
no_posts: bool,
}
// Regex matched against search queries to determine if they are reddit urls.
static REDDIT_URL_MATCH: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://([^\./]+\.)*reddit.com/").unwrap());
// SERVICES
pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
let nsfw_results = if setting(&req, "show_nsfw") == "on" { "&include_over_18=on" } else { "" };
let path = format!("{}.json?{}{}&raw_json=1", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
let query = param(&path, "q").unwrap_or_default();
let mut query = param(&path, "q").unwrap_or_default();
query = REDDIT_URL_MATCH.replace(&query, "").to_string();
if query.is_empty() {
return Ok(redirect("/".to_string()));
@ -103,12 +110,14 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
match Post::fetch(&path, quarantined).await {
Ok((mut posts, after)) => {
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
let no_posts = posts.is_empty();
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
template(SearchTemplate {
posts,
subreddits,
@ -127,6 +136,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
Err(msg) => {

View File

@ -243,7 +243,7 @@ impl Server {
match func.await {
Ok(mut res) => {
res.headers_mut().extend(def_headers);
let _ = compress_response(req_headers, &mut res).await;
let _ = compress_response(&req_headers, &mut res).await;
Ok(res)
}
@ -282,7 +282,7 @@ async fn new_boilerplate(
) -> Result<Response<Body>, String> {
match Response::builder().status(status).body(body) {
Ok(mut res) => {
let _ = compress_response(req_headers, &mut res).await;
let _ = compress_response(&req_headers, &mut res).await;
res.headers_mut().extend(default_headers.clone());
Ok(res)
@ -306,7 +306,8 @@ async fn new_boilerplate(
/// Accept-Encoding: gzip, compress, br
/// Accept-Encoding: br;q=1.0, gzip;q=0.8, *;q=0.1
/// ```
fn determine_compressor(accept_encoding: &str) -> Option<CompressionType> {
#[cached]
fn determine_compressor(accept_encoding: String) -> Option<CompressionType> {
if accept_encoding.is_empty() {
return None;
};
@ -473,7 +474,7 @@ fn determine_compressor(accept_encoding: &str) -> Option<CompressionType> {
///
/// This function logs errors to stderr, but only in debug mode. No information
/// is logged in release builds.
async fn compress_response(req_headers: HeaderMap<header::HeaderValue>, res: &mut Response<Body>) -> Result<(), String> {
async fn compress_response(req_headers: &HeaderMap<header::HeaderValue>, res: &mut Response<Body>) -> Result<(), String> {
// Check if the data is eligible for compression.
if let Some(hdr) = res.headers().get(header::CONTENT_TYPE) {
match from_utf8(hdr.as_bytes()) {
@ -503,30 +504,22 @@ async fn compress_response(req_headers: HeaderMap<header::HeaderValue>, res: &mu
return Ok(());
};
// Quick and dirty closure for extracting a header from the request and
// returning it as a &str.
let get_req_header = |k: header::HeaderName| -> Option<&str> {
match req_headers.get(k) {
Some(hdr) => match from_utf8(hdr.as_bytes()) {
Ok(val) => Some(val),
#[cfg(debug_assertions)]
Err(e) => {
dbg_msg!(e);
None
}
#[cfg(not(debug_assertions))]
Err(_) => None,
},
None => None,
}
};
// Check to see which compressor is requested, and if we can use it.
let accept_encoding: &str = match get_req_header(header::ACCEPT_ENCODING) {
Some(val) => val,
let accept_encoding: String = match req_headers.get(header::ACCEPT_ENCODING) {
None => return Ok(()), // Client requested no compression.
Some(hdr) => match String::from_utf8(hdr.as_bytes().into()) {
Ok(val) => val,
#[cfg(debug_assertions)]
Err(e) => {
dbg_msg!(e);
return Ok(());
}
#[cfg(not(debug_assertions))]
Err(_) => return Ok(()),
},
};
let compressor: CompressionType = match determine_compressor(accept_encoding) {
@ -636,18 +629,18 @@ mod tests {
#[test]
fn test_determine_compressor() {
// Single compressor given.
assert_eq!(determine_compressor("unsupported"), None);
assert_eq!(determine_compressor("gzip"), Some(CompressionType::Gzip));
assert_eq!(determine_compressor("*"), Some(DEFAULT_COMPRESSOR));
assert_eq!(determine_compressor("unsupported".to_string()), None);
assert_eq!(determine_compressor("gzip".to_string()), Some(CompressionType::Gzip));
assert_eq!(determine_compressor("*".to_string()), Some(DEFAULT_COMPRESSOR));
// Multiple compressors.
assert_eq!(determine_compressor("gzip, br"), Some(CompressionType::Brotli));
assert_eq!(determine_compressor("gzip;q=0.8, br;q=0.3"), Some(CompressionType::Gzip));
assert_eq!(determine_compressor("br, gzip"), Some(CompressionType::Brotli));
assert_eq!(determine_compressor("br;q=0.3, gzip;q=0.4"), Some(CompressionType::Gzip));
assert_eq!(determine_compressor("gzip, br".to_string()), Some(CompressionType::Brotli));
assert_eq!(determine_compressor("gzip;q=0.8, br;q=0.3".to_string()), Some(CompressionType::Gzip));
assert_eq!(determine_compressor("br, gzip".to_string()), Some(CompressionType::Brotli));
assert_eq!(determine_compressor("br;q=0.3, gzip;q=0.4".to_string()), Some(CompressionType::Gzip));
// Invalid q-values.
assert_eq!(determine_compressor("gzip;q=NAN"), None);
assert_eq!(determine_compressor("gzip;q=NAN".to_string()), None);
}
#[test]
@ -672,9 +665,9 @@ mod tests {
] {
// Determine what the expected encoding should be based on both the
// specific encodings we accept.
let expected_encoding: CompressionType = match determine_compressor(accept_encoding) {
let expected_encoding: CompressionType = match determine_compressor(accept_encoding.to_string()) {
Some(s) => s,
None => panic!("determine_compressor(accept_encoding) => None"),
None => panic!("determine_compressor(accept_encoding.to_string()) => None"),
};
// Build headers with our Accept-Encoding.
@ -691,8 +684,8 @@ mod tests {
.unwrap();
// Perform the compression.
if let Err(e) = block_on(compress_response(req_headers, &mut res)) {
panic!("compress_response(req_headers, &mut res) => Err(\"{}\")", e);
if let Err(e) = block_on(compress_response(&req_headers, &mut res)) {
panic!("compress_response(&req_headers, &mut res) => Err(\"{}\")", e);
};
// If the content was compressed, we expect the Content-Encoding
@ -739,9 +732,8 @@ mod tests {
};
let mut decompressed = Vec::<u8>::new();
match io::copy(&mut decoder, &mut decompressed) {
Ok(_) => {}
Err(e) => panic!("{}", e),
if let Err(e) = io::copy(&mut decoder, &mut decompressed) {
panic!("{}", e);
};
assert!(decompressed.eq(&expected_lorem_ipsum));

View File

@ -26,6 +26,7 @@ struct SubredditTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
all_posts_hidden_nsfw: bool,
no_posts: bool,
}
#[derive(Template)]
@ -114,12 +115,14 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
match Post::fetch(&path, quarantined).await {
Ok((mut posts, after)) => {
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
let no_posts = posts.is_empty();
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
template(SubredditTemplate {
sub,
posts,
@ -131,6 +134,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
Err(msg) => match msg.as_str() {

View File

@ -26,6 +26,7 @@ struct UserTemplate {
all_posts_filtered: bool,
/// Whether all posts were hidden because they are NSFW (and user has disabled show NSFW)
all_posts_hidden_nsfw: bool,
no_posts: bool,
}
// FUNCTIONS
@ -61,13 +62,15 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: true,
all_posts_filtered: false,
all_posts_hidden_nsfw: false,
no_posts: false,
})
} else {
// Request user posts/comments from Reddit
match Post::fetch(&path, false).await {
Ok((mut posts, after)) => {
let (_, all_posts_filtered) = filter_posts(&mut posts, &filters);
let all_posts_hidden_nsfw = posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on";
let no_posts = posts.is_empty();
let all_posts_hidden_nsfw = !no_posts && (posts.iter().all(|p| p.flags.nsfw) && setting(&req, "show_nsfw") != "on");
template(UserTemplate {
user,
posts,
@ -80,6 +83,7 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
is_filtered: false,
all_posts_filtered,
all_posts_hidden_nsfw,
no_posts,
})
}
// If there is an error show error page

View File

@ -1118,22 +1118,16 @@ summary.comment_data {
}
.prefs {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 20px;
padding: 10px 20px 20px;
background: var(--post);
border-radius: 5px;
margin-bottom: 20px;
}
.prefs > div {
display: flex;
justify-content: space-between;
width: 100%;
height: 35px;
align-items: center;
margin-top: 7px;
.prefs fieldset {
border: 0;
padding: 10px 0;
margin: 0 0 5px;
}
.prefs legend {
@ -1141,11 +1135,25 @@ summary.comment_data {
border-bottom: 1px solid var(--highlighted);
font-size: 18px;
padding-bottom: 10px;
margin-bottom: 7px;
width: 100%;
float: left; /* places the legend inside the (invisible) border, instead of vertically centered on top border*/
}
.prefs legend:not(:first-child) {
padding-top: 10px;
margin-top: 15px;
.prefs-group {
display: flex;
width: 100%;
height: 35px;
align-items: center;
margin-top: 7px;
}
.prefs-group > *:not(:last-child) {
margin-right: 1ch;
}
.prefs-group > *:last-child {
margin-left: auto;
}
.prefs select {
@ -1163,7 +1171,8 @@ aside.prefs {
background: var(--highlighted);
padding: 10px 15px;
border-radius: 5px;
margin-top: 20px;
margin-top: 5px;
width: 100%
}
input[type="submit"] {

View File

@ -48,7 +48,7 @@
<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
</a>
<a id="code" href="https://github.com/spikecodes/libreddit">
<a id="code" href="https://github.com/libreddit/libreddit" target="_blank" rel="noopener noreferrer">
<span>code</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<title>code</title>

View File

@ -61,6 +61,10 @@
<span class="listing_warn">All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</span>
{% endif %}
{% if no_posts %}
<center>No posts were found.</center>
{% endif %}
{% if all_posts_filtered %}
<span class="listing_warn">(All content on this page has been filtered)</span>
{% else if is_filtered %}

View File

@ -11,74 +11,79 @@
<div id="settings">
<form action="/settings" method="POST">
<div class="prefs">
<legend>Appearance</legend>
<div id="theme">
<label for="theme">Theme:</label>
<select name="theme">
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
</select>
</div>
<legend>Interface</legend>
<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="hidden" value="off" name="wide">
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
<legend>Content</legend>
<div id="post_sort">
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
<select name="post_sort">
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
</select>
</div>
<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="show_nsfw">
<label for="show_nsfw">Show NSFW posts:</label>
<input type="hidden" value="off" name="show_nsfw">
<input type="checkbox" name="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
</div>
<div id="blur_nsfw">
<label for="blur_nsfw">Blur NSFW previews:</label>
<input type="hidden" value="off" name="blur_nsfw">
<input type="checkbox" name="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
</div>
<div id="autoplay_videos">
<label for="autoplay_videos">Autoplay videos</label>
<input type="hidden" value="off" name="autoplay_videos">
<input type="checkbox" name="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
</div>
<div id="use_hls">
<label for="use_hls">Use HLS for videos
<fieldset>
<legend>Appearance</legend>
<div class="prefs-group">
<label for="theme">Theme:</label>
<select name="theme" id="theme">
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
</select>
</div>
</fieldset>
<fieldset>
<legend>Interface</legend>
<div class="prefs-group">
<label for="front_page">Front page:</label>
<select name="front_page" id="front_page">
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
</select>
</div>
<div class="prefs-group">
<label for="layout">Layout:</label>
<select name="layout" id="layout">
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
</select>
</div>
<div class="prefs-group">
<label for="wide">Wide UI:</label>
<input type="hidden" value="off" name="wide">
<input type="checkbox" name="wide" id="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
</fieldset>
<fieldset>
<legend>Content</legend>
<div class="prefs-group">
<label for="post_sort" title="Applies only to subreddit feeds">Default subreddit post sort:</label>
<select name="post_sort">
{% call utils::options(prefs.post_sort, ["hot", "new", "top", "rising", "controversial"], "hot") %}
</select>
</div>
<div class="prefs-group">
<label for="comment_sort">Default comment sort:</label>
<select name="comment_sort" id="comment_sort">
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select>
</div>
<div class="prefs-group">
<label for="show_nsfw">Show NSFW posts:</label>
<input type="hidden" value="off" name="show_nsfw">
<input type="checkbox" name="show_nsfw" id="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="blur_nsfw">Blur NSFW previews:</label>
<input type="hidden" value="off" name="blur_nsfw">
<input type="checkbox" name="blur_nsfw" id="blur_nsfw" {% if prefs.blur_nsfw == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="autoplay_videos">Autoplay videos</label>
<input type="hidden" value="off" name="autoplay_videos">
<input type="checkbox" name="autoplay_videos" id="autoplay_videos" {% if prefs.autoplay_videos == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="use_hls">Use HLS for videos</label>
<details id="feeds">
<summary>Why?</summary>
<div id="feed_list" class="helper">Reddit videos require JavaScript (via HLS.js) to be enabled to be played with audio. Therefore, this toggle lets you either use Libreddit JS-free or utilize this feature.</div>
</details>
</label>
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
</div>
<div id="hide_hls_notification">
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
<input type="hidden" value="off" name="hide_hls_notification">
<input type="checkbox" name="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
</div>
<input type="hidden" value="off" name="use_hls">
<input type="checkbox" name="use_hls" id="use_hls" {% if prefs.use_hls == "on" %}checked{% endif %}>
</div>
<div class="prefs-group">
<label for="hide_hls_notification">Hide notification about possible HLS usage</label>
<input type="hidden" value="off" name="hide_hls_notification">
<input type="checkbox" name="hide_hls_notification" id="hide_hls_notification" {% if prefs.hide_hls_notification == "on" %}checked{% endif %}>
</div>
</fieldset>
<input id="save" type="submit" value="Save">
</div>
</form>

View File

@ -50,6 +50,10 @@
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
{% endif %}
{% if no_posts %}
<center>No posts were found.</center>
{% endif %}
{% if all_posts_filtered %}
<center>(All content on this page has been filtered)</center>
{% else %}

View File

@ -36,6 +36,10 @@
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
{% endif %}
{% if no_posts %}
<center>No posts were found.</center>
{% endif %}
{% if all_posts_filtered %}
<center>(All content on this page has been filtered)</center>
{% else %}

View File

@ -115,7 +115,7 @@
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
<script src="/hls.min.js"></script>
<div class="post_media_content">
<video class="post_media_video short {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls>
<video class="post_media_video short {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls>
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
<source src="{{ post.media.url }}" type="video/mp4" />
</video>
@ -213,19 +213,19 @@
</div>
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "gif" %}
<div class="post_media_content">
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls loop {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls loop {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
</div>
{% else if (prefs.layout.is_empty() || prefs.layout == "card") && post.post_type == "video" %}
{% if prefs.use_hls == "on" && !post.media.alt_url.is_empty() %}
<div class="post_media_content">
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %} {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" controls preload="none">
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %} {% if prefs.autoplay_videos == "on" %}hls_autoplay{% endif %}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" controls preload="none">
<source src="{{ post.media.alt_url }}" type="application/vnd.apple.mpegurl" />
<source src="{{ post.media.url }}" type="video/mp4" />
</video>
</div>
{% else %}
<div class="post_media_content">
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" width="{{ post.media.width }}" height="{{ post.media.height }}" poster="{{ post.media.poster }}" preload="none" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
<video class="post_media_video short {%if post.flags.nsfw && prefs.blur_nsfw=="on" %}post_nsfw_blur{% endif %}" src="{{ post.media.url }}" {% if post.media.width > 0 && post.media.height > 0 %}width="{{ post.media.width }}" height="{{ post.media.height }}"{% endif %} poster="{{ post.media.poster }}" preload="none" controls {% if prefs.autoplay_videos == "on" %}autoplay{% endif %}><a href={{ post.media.url }}>Video</a></video>
</div>
{% call render_hls_notification(format!("{}%23{}", &self.url[1..].replace("&", "%26").replace("+", "%2B"), post.id)) %}
{% endif %}