Compare commits

...

51 Commits

Author SHA1 Message Date
f0a6bdc21b Fix sorting buttons on r/all and r/popular (#402)
* Fix sorting buttons on r/all and r/popular

* Bump version to v0.22.2

* Fix empty sidebar in r/all and r/popular

Co-authored-by: spikecodes <19519553+spikecodes@users.noreply.github.com>
2022-03-15 03:39:39 +00:00
3eef60d486 Add instances (#432, #433, #436, #438) 2022-03-14 01:01:09 +00:00
59043456ba Wrap long post titles (fixes #435) 2022-03-13 12:59:15 -07:00
90c7088da2 Link Privacy Redirect as well 2022-03-13 19:19:56 +00:00
9e65a65556 Promote LibRedirect in README 2022-03-13 19:15:47 +00:00
8cfbde2710 Add LiberaPay "Donate" button back 2022-03-13 19:11:39 +00:00
70ff150ab4 Add user listing buttons (#400)
* Add user listing buttons

* Update to v0.22

Co-authored-by: spikecodes <19519553+spikecodes@users.noreply.github.com>
2022-03-13 19:06:27 +00:00
388779c1f2 Update instances (#421)
close #411 
close #412 
close #417

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2022-03-13 18:38:35 +00:00
6b605d859f Add German leddit.xyz instance (#429)
* new DE instance

* new hidden service

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2022-03-13 18:37:01 +00:00
0ae48c400c Add kylrth instances (#446)
Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2022-03-13 18:35:13 +00:00
a6ed18d674 Changed location of my VPS :) (#415)
* Changed location of my VPS :)

So my tor url changed too along side my VPS's country so yup!

* Update README.md
2022-03-13 18:32:26 +00:00
838cdd95d1 Update libreddit.drivet.xyz (#360)
* Remove Cloudflare Proxy from libreddit.drivet.xyz

I'm testing some stuff and as a result i have disabled proxy for libreddit.drivet.xyz. It exposes my public ip, but also gives more privacy i guess

* Move libreddit.drivet.xyz to Poland

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2022-03-13 18:32:10 +00:00
bc95b08ffd Update libreddit.datatunnel.xyz to Finland 2022-01-21 17:36:34 +00:00
e6190267e4 Add libreddit.datatunnel.xyz instance. Closes #401 2022-01-20 22:34:47 +00:00
3ceeac5fb0 Add lr.rfl890.cf instance. Closes #399 2022-01-19 18:14:51 +00:00
60eb0137c2 Add libreddit.bus-hit.me to instances (#398) 2022-01-19 18:13:46 +00:00
b6bca68d4e Add reddi.tk instance. Closes #397 2022-01-17 20:13:38 +00:00
91bff826f0 Fix and improve admin/mod distinguishers (#386)
* Fix regression with comments from deleted mods

Starting with https://github.com/spikecodes/libreddit/pull/367/files
comments from deleted moderators and admins(?) aren't highlighted.

* Highlight mod and admin usernames in posts

Works like on reddit + shows highlight for mods on the search page.
2022-01-09 02:50:53 +00:00
af6606a855 leddit.xyz instance location change (#387) 2022-01-09 01:14:05 +00:00
977cd0763a Fix #379 2022-01-05 16:46:45 -08:00
fcadd44cb3 Update dependencies 2022-01-05 16:39:56 -08:00
9c325c2cbf Search fixes (#384)
* Default to searching within subreddit

* Redirect to subreddit from search
2022-01-05 14:06:41 -08:00
e9038f4fe2 Add stilic.ml instance. Closes #380 2021-12-31 20:33:53 +00:00
8b8f55e09a Fix sort button scrollbars 2021-12-31 10:42:44 -08:00
f1b3749cf0 Fix #378 — formatting of dates/times 2021-12-29 12:48:57 -08:00
0708fdfb37 Cover more Reddit domains with libreddit link rewrites 2021-12-29 11:38:35 -08:00
cad29e9544 Add libreddit.nl instance. Closes #377 2021-12-28 16:16:53 +00:00
6b59976fcf Fix #376 2021-12-27 23:16:01 -08:00
f9b3981448 Fix debug log in post.rs 2021-12-27 19:56:37 -08:00
db3196df5a Use Reveddit to show removed posts/comments. Closes #299 2021-12-27 19:40:35 -08:00
b3d4f6f91c Show external page links again 2021-12-27 18:00:19 -08:00
45b875b85d Continue Rust workflow if version is already published 2021-12-27 14:21:06 -08:00
992d7889c4 Automatically publish to crates.io 2021-12-27 13:53:58 -08:00
3188f9d8e7 Tweak settings page design 2021-12-27 13:43:44 -08:00
90fa0b5496 Automatically generate release notes 2021-12-27 10:15:25 -08:00
7aeabfc4bc Rewrite Reddit post links to Libreddit equivalents 2021-12-26 21:18:20 -08:00
150ebe38f3 Add Buy Me a Coffee button as donation option 2021-12-27 01:38:14 +00:00
2905d114fa Add Buy Me a Coffee donation option 2021-12-27 00:48:56 +00:00
40e97cc75d Add esmailelbob.xyz instance (#369)
* add new instance

I want to add my own instance - i'm new to selfhosting so i can't guarantee it will be running all the time but i can guarantee whenever it's down i will fix it within hours!

* Fix onion protocol for instance

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2021-12-25 05:11:30 +00:00
7c73e352ce Fix [deleted] user link color 2021-12-19 17:12:33 -08:00
341c623be8 Refactor Media parsing (#334)
* Parse video data from cross_post_parent_list as vanilla Reddit does.

introduce testdata directory for testing JSON parsing functions.

refactor Media::parse for slightly more readability.

Add various test cases.

* Trim down to just refactoring

Co-authored-by: Spike <19519553+spikecodes@users.noreply.github.com>
2021-12-20 01:07:20 +00:00
4c8b724a9d Merge pull request #367 from alyaeanyx/no-href-to-deleted-user
Don't create hrefs to u/[deleted]
2021-12-19 16:58:04 -08:00
227d74b187 Add totaldarkness.net instance. Closes #366 2021-12-20 00:50:20 +00:00
f05a818edd Don't create hrefs to u/[deleted] 2021-12-19 12:20:37 +01:00
ceee13cfb7 docs: add the missing AUTOPLAY_VIDEOS in README
Merge pull request #361 from xatier/patch-2
2021-12-14 22:03:43 -08:00
a39495b3cb Update README.md
This config was introduced in 1d4ea50a45 , but missed from the documentation.
2021-12-12 14:41:00 -08:00
38cfe4ad71 Add libreddit.hu instsance. Closes #357 2021-12-08 23:13:09 +00:00
0b89539c2b Add lr.cowfee.moe instance. Closes #353 2021-12-01 03:44:44 +00:00
046b8b3edc Add new onion instance. Closes #349 2021-12-01 03:42:54 +00:00
0656756d21 Fix #196 2021-11-29 22:29:41 -08:00
43551f70fd Add leddit onion instance 2021-11-30 04:09:41 +00:00
21 changed files with 512 additions and 525 deletions

View File

@ -22,6 +22,10 @@ jobs:
- name: Build
run: cargo build --release
- name: Publish to crates.io
continue-on-error: true
run: cargo publish --no-verify --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
- uses: actions/upload-artifact@v2.2.1
name: Upload a Build Artifact
@ -43,12 +47,13 @@ jobs:
if: github.base_ref != 'master'
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.version }} - NAME
name: ${{ steps.version.outputs.version }} - ${{ github.event.head_commit.message }}
draft: true
files: |
target/release/libreddit
libreddit.sha512
body: |
- ${{ github.event.head_commit.message }} ${{ github.sha }}
- ${{ github.event.head_commit.message }} ${{ github.sha }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

673
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,23 +3,23 @@ name = "libreddit"
description = " Alternative private front-end to Reddit"
license = "AGPL-3.0"
repository = "https://github.com/spikecodes/libreddit"
version = "0.20.1"
version = "0.22.2"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2018"
edition = "2021"
[dependencies]
askama = { version = "0.10.5", default-features = false }
async-recursion = "0.3.2"
cached = "0.26.2"
clap = { version = "2.33.3", default-features = false }
regex = "1.5.4"
serde = { version = "1.0.130", features = ["derive"] }
cookie = "0.15.1"
askama = { version = "0.11.1", default-features = false }
async-recursion = "1.0.0"
cached = "0.34.0"
clap = { version = "3.1.6", default-features = false, features = ["std"] }
regex = "1.5.5"
serde = { version = "1.0.136", features = ["derive"] }
cookie = "0.16.0"
futures-lite = "1.12.0"
hyper = { version = "0.14.15", features = ["full"] }
hyper = { version = "0.14.17", features = ["full"] }
hyper-rustls = "0.23.0"
route-recognizer = "0.3.1"
serde_json = "1.0.72"
tokio = { version = "1.14.0", features = ["full"] }
time = "0.2.7"
serde_json = "1.0.79"
tokio = { version = "1.17.0", features = ["full"] }
time = "0.3.7"
url = "2.2.2"

View File

@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: spike
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom: ['https://www.buymeacoffee.com/spikecodes']

View File

@ -17,7 +17,9 @@
I appreciate any donations! Your support allows me to continue developing Libreddit.
**Liberapay:** <a href="https://liberapay.com/spike/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
<a href="https://www.buymeacoffee.com/spikecodes" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px" ></a>
<a href="https://liberapay.com/spike/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg" style="height: 40px"></a>
**Bitcoin:** [bc1qwyxjnafpu3gypcpgs025cw9wa7ryudtecmwa6y](bitcoin:bc1qwyxjnafpu3gypcpgs025cw9wa7ryudtecmwa6y)
@ -29,6 +31,8 @@ I appreciate any donations! Your support allows me to continue developing Libred
Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new) to have your [selfhosted instance](#deployment) listed here!
🔗 **Want to automatically redirect Reddit links to Libreddit? Use [LibRedirect](https://github.com/libredirect/libredirect) or [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect)!**
| Website | Country | Cloudflare |
|-|-|-|
| [libredd.it](https://libredd.it) (official) | 🇺🇸 US | |
@ -55,13 +59,30 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
| [libreddit.igna.rocks](https://libreddit.igna.rocks) | 🇺🇸 US | |
| [libreddit.autarkic.org](https://libreddit.autarkic.org) | 🇺🇸 US | |
| [libreddit.flux.industries](https://libreddit.flux.industries) | 🇩🇪 DE | ✅ |
| [libreddit.drivet.xyz](https://libreddit.drivet.xyz) | 🇫🇮 FI | |
| [libreddit.drivet.xyz](https://libreddit.drivet.xyz) | 🇵🇱 PL | |
| [lr.oversold.host](https://lr.oversold.host) | 🇱🇺 LU | |
| [libreddit.de](https://libreddit.de) | 🇩🇪 DE | |
| [libreddit.pussthecat.org](https://libreddit.pussthecat.org) | 🇩🇪 DE | |
| [libreddit.mutahar.rocks](https://libreddit.mutahar.rocks) | 🇫🇷 FR | |
| [libreddit.northboot.xyz](https://libreddit.northboot.xyz) | 🇩🇪 DE | |
| [leddit.xyz](https://www.leddit.xyz) | 🇩🇪 DE | |
| [leddit.xyz](https://leddit.xyz) | 🇺🇸 US | |
| [de.leddit.xyz](https://de.leddit.xyz) | 🇩🇪 DE | |
| [lr.cowfee.moe](https://lr.cowfee.moe) | 🇺🇸 US | |
| [libreddit.hu](https://libreddit.hu) | 🇫🇮 FI | ✅ |
| [libreddit.totaldarkness.net](https://libreddit.totaldarkness.net) | 🇨🇦 CA | |
| [libreddit.esmailelbob.xyz](https://libreddit.esmailelbob.xyz) | 🇨🇦 CA | |
| [libreddit.nl](https://libreddit.nl) | 🇳🇱 NL | |
| [lr.stilic.ml](https://lr.stilic.ml) | 🇫🇷 FR | ✅ |
| [reddi.tk](https://reddi.tk) | 🇺🇸 US | ✅ |
| [libreddit.bus-hit.me](https://libreddit.bus-hit.me) | 🇨🇦 CA | |
| [libreddit.datatunnel.xyz](https://libreddit.datatunnel.xyz) | 🇫🇮 FI | |
| [libreddit.crewz.me](https://libreddit.crewz.me) | 🇳🇱 NL | ✅ |
| [r.walkx.org](https://r.walkx.org) | 🇩🇪 DE | ✅ |
| [libreddit.kylrth.com](https://libreddit.kylrth.com) | 🇨🇦 CA | |
| [libreddit.yonalee.eu](https://libreddit.yonalee.eu) | 🇱🇺 LU | ✅ |
| [libreddit.winscloud.net](https://libreddit.winscloud.net) | 🇹🇭 TH | ✅ |
| [libreddit.tiekoetter.com](https://libreddit.tiekoetter.com) | 🇩🇪 DE | |
| [reddit.rtrace.io](https://reddit.rtrace.io) | 🇩🇪 DE | |
| [spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion](http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion) | 🇮🇳 IN | |
| [fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion](http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion) | 🇩🇪 DE | |
| [kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion](http://kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion) | 🇳🇱 NL | |
@ -69,7 +90,12 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
| [liredejj74h5xjqr2dylnl5howb2bpikfowqoveub55ru27x43357iid.onion](http://liredejj74h5xjqr2dylnl5howb2bpikfowqoveub55ru27x43357iid.onion) | 🇩🇪 DE | |
| [kzhfp3nvb4qp575vy23ccbrgfocezjtl5dx66uthgrhu7nscu6rcwjyd.onion](http://kzhfp3nvb4qp575vy23ccbrgfocezjtl5dx66uthgrhu7nscu6rcwjyd.onion) | 🇺🇸 US | |
| [ecue64ybzvn6vjzl37kcsnwt4ycmbsyf74nbttyg7rkc3t3qwnj7mcyd.onion](http://ecue64ybzvn6vjzl37kcsnwt4ycmbsyf74nbttyg7rkc3t3qwnj7mcyd.onion) | 🇩🇪 DE | |
| [ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion](http://ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion) | 🇺🇸 US | |
| [libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion](http://libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion) | 🇺🇸 US | |
| [libreddit.2syis2nnyytz6jnusnjurva4swlaizlnleiks5mjp46phuwjbdjqwgqd.onion](http://libreddit.2syis2nnyytz6jnusnjurva4swlaizlnleiks5mjp46phuwjbdjqwgqd.onion) | 🇪🇬 EG | |
| [ol5begilptoou34emq2sshf3may3hlblvipdjtybbovpb7c7zodxmtqd.onion](http://ol5begilptoou34emq2sshf3may3hlblvipdjtybbovpb7c7zodxmtqd.onion) | 🇩🇪 DE | |
| [lbrdtjaj7567ptdd4rv74lv27qhxfkraabnyphgcvptl64ijx2tijwid.onion](http://lbrdtjaj7567ptdd4rv74lv27qhxfkraabnyphgcvptl64ijx2tijwid.onion) | 🇨🇦 CA | |
| [libreddit.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd.onion](http://libreddit.lqs5fjmajyp7rvp4qvyubwofzi6d4imua7vs237rkc4m5qogitqwrgyd.onion/) | 🇨🇦 CA | |
A checkmark in the "Cloudflare" category here refers to the use of the reverse proxy, [Cloudflare](https://cloudflare). The checkmark will not be listed for a site which uses Cloudflare DNS but rather the proxying service which grants Cloudflare the ability to monitor traffic to the website.
@ -235,6 +261,7 @@ Assign a default value for each setting by passing environment variables to Libr
| `SHOW_NSFW` | `["on", "off"]` | `off` |
| `USE_HLS` | `["on", "off"]` | `off` |
| `HIDE_HLS_NOTIFICATION` | `["on", "off"]` | `off` |
| `AUTOPLAY_VIDEOS` | `["on", "off"]` | `off` |
### Examples

View File

@ -2,7 +2,7 @@ use cached::proc_macro::cached;
use futures_lite::{future::Boxed, FutureExt};
use hyper::{body::Buf, client, Body, Request, Response, Uri};
use serde_json::Value;
use std::{result::Result, str::FromStr};
use std::result::Result;
use crate::server::RequestExt;
@ -20,7 +20,7 @@ pub async fn proxy(req: Request<Body>, format: &str) -> Result<Response<Body>, S
async fn stream(url: &str, req: &Request<Body>) -> Result<Response<Body>, String> {
// First parameter is target URL (mandatory).
let url = Uri::from_str(url).map_err(|_| "Couldn't parse URL".to_string())?;
let uri = url.parse::<Uri>().map_err(|_| "Couldn't parse URL".to_string())?;
// Prepare the HTTPS connector.
let https = hyper_rustls::HttpsConnectorBuilder::new().with_native_roots().https_only().enable_http1().build();
@ -28,7 +28,7 @@ async fn stream(url: &str, req: &Request<Body>) -> Result<Response<Body>, String
// Build the hyper client from the HTTPS connector.
let client: client::Client<_, hyper::Body> = client::Client::builder().build(https);
let mut builder = Request::get(url);
let mut builder = Request::get(uri);
// Copy useful headers from original request
for &key in &["Range", "If-Modified-Since", "Cache-Control"] {
@ -89,7 +89,10 @@ fn request(url: String, quarantine: bool) -> Boxed<Result<Response<Body>, String
response
.headers()
.get("Location")
.map(|val| val.to_str().unwrap_or_default())
.map(|val| {
let new_url = val.to_str().unwrap_or_default();
format!("{}{}raw_json=1", new_url, if new_url.contains('?') { "&" } else { "?" })
})
.unwrap_or_default()
.to_string(),
quarantine,

View File

@ -1,13 +1,6 @@
// Global specifiers
#![forbid(unsafe_code)]
#![warn(clippy::pedantic, clippy::all)]
#![allow(
clippy::needless_pass_by_value,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::manual_find_map,
clippy::unused_async
)]
#![allow(clippy::cmp_owned)]
// Reference local files
mod post;
@ -18,7 +11,7 @@ mod user;
mod utils;
// Import Crates
use clap::{App as cli, Arg};
use clap::{Command, Arg};
use futures_lite::FutureExt;
use hyper::{header::HeaderValue, Body, Request, Response};
@ -94,19 +87,19 @@ async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Respons
#[tokio::main]
async fn main() {
let matches = cli::new("Libreddit")
let matches = Command::new("Libreddit")
.version(env!("CARGO_PKG_VERSION"))
.about("Private front-end for Reddit written in Rust ")
.arg(
Arg::with_name("redirect-https")
.short("r")
Arg::new("redirect-https")
.short('r')
.long("redirect-https")
.help("Redirect all HTTP requests to HTTPS (no longer functional)")
.takes_value(false),
)
.arg(
Arg::with_name("address")
.short("a")
Arg::new("address")
.short('a')
.long("address")
.value_name("ADDRESS")
.help("Sets address to listen on")
@ -114,8 +107,8 @@ async fn main() {
.takes_value(true),
)
.arg(
Arg::with_name("port")
.short("p")
Arg::new("port")
.short('p')
.long("port")
.value_name("PORT")
.help("Port to listen on")
@ -123,8 +116,8 @@ async fn main() {
.takes_value(true),
)
.arg(
Arg::with_name("hsts")
.short("H")
Arg::new("hsts")
.short('H')
.long("hsts")
.value_name("EXPIRE_TIME")
.help("HSTS header to tell browsers that this site should only be accessed over HTTPS")
@ -200,6 +193,7 @@ async fn main() {
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account".to_string()).boxed());
app.at("/user/:name").get(|r| user::profile(r).boxed());
app.at("/user/:name/:listing").get(|r| user::profile(r).boxed());
app.at("/user/:name/comments/:id").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());

View File

@ -97,12 +97,20 @@ async fn parse_post(json: &serde_json::Value) -> Post {
let awards: Awards = Awards::parse(&post["data"]["all_awardings"]);
let permalink = val(post, "permalink");
let body = if val(post, "removed_by_category") == "moderator" {
format!("<div class=\"md\"><p>[removed] — <a href=\"https://www.reveddit.com{}\">view removed post</a></p></div>", permalink)
} else {
rewrite_urls(&val(post, "selftext_html")).replace("\\", "")
};
// Build a post using data parsed from Reddit post API
Post {
id: val(post, "id"),
title: esc!(post, "title"),
community: val(post, "subreddit"),
body: rewrite_urls(&val(post, "selftext_html")).replace("\\", ""),
body,
author: Author {
name: val(post, "author"),
flair: Flair {
@ -117,7 +125,7 @@ async fn parse_post(json: &serde_json::Value) -> Post {
},
distinguished: val(post, "distinguished"),
},
permalink: val(post, "permalink"),
permalink,
score: format_num(score),
upvote_ratio: ratio as i64,
post_type,
@ -174,7 +182,6 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
let edited = data["edited"].as_f64().map_or((String::new(), String::new()), time);
let score = data["score"].as_i64().unwrap_or(0);
let body = rewrite_urls(&val(&comment, "body_html"));
// If this comment contains replies, handle those too
let replies: Vec<Comment> = if data["replies"].is_object() {
@ -191,6 +198,12 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
let id = val(&comment, "id");
let highlighted = id == highlighted_comment;
let body = if val(&comment, "author") == "[deleted]" && val(&comment, "body") == "[removed]" {
format!("<div class=\"md\"><p>[removed] — <a href=\"https://www.reveddit.com{}{}\">view removed comment</a></p></div>", post_link, id)
} else {
rewrite_urls(&val(&comment, "body_html")).to_string()
};
let author = Author {
name: val(&comment, "author"),
flair: Flair {

View File

@ -47,13 +47,17 @@ struct SearchTemplate {
// 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?{}{}", req.uri().path(), req.uri().query().unwrap_or_default(), nsfw_results);
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();
if query.is_empty() {
return Ok(redirect("/".to_string()));
}
if query.starts_with("r/") {
return Ok(redirect(format!("/{}", query)));
}
let sub = req.param("sub").unwrap_or_default();
let quarantined = can_access_quarantine(&req, &sub);
// Handle random subreddits
@ -78,7 +82,7 @@ pub async fn find(req: Request<Body>) -> Result<Response<Body>, String> {
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
// If all requested subs are filtered, we don't need to fetch posts.
if sub.split("+").all(|s| filters.contains(s)) {
if sub.split('+').all(|s| filters.contains(s)) {
template(SearchTemplate {
posts: Vec::new(),
subreddits,

View File

@ -105,7 +105,7 @@ impl ResponseExt for Response<Body> {
fn remove_cookie(&mut self, name: String) {
let mut cookie = Cookie::named(name);
cookie.set_path("/");
cookie.set_max_age(Duration::second());
cookie.set_max_age(Duration::seconds(1));
if let Ok(val) = HeaderValue::from_str(&cookie.to_string()) {
self.headers_mut().append("Set-Cookie", val);
}

View File

@ -19,6 +19,7 @@ struct SubredditTemplate {
ends: (String, String),
prefs: Preferences,
url: String,
redirect_url: String,
/// Whether the subreddit itself is filtered.
is_filtered: bool,
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
@ -86,22 +87,21 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
} else {
Subreddit::default()
}
} else if sub_name.contains('+') {
// Multireddit
} else {
// Multireddit, all, popular
Subreddit {
name: sub_name.clone(),
..Subreddit::default()
}
} else {
Subreddit::default()
};
let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_default());
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);
// 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)) {
template(SubredditTemplate {
sub,
posts: Vec::new(),
@ -109,6 +109,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
prefs: Preferences::new(req),
url,
redirect_url,
is_filtered: true,
all_posts_filtered: false,
})
@ -124,6 +125,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
ends: (param(&path, "after").unwrap_or_default(), after),
prefs: Preferences::new(req),
url,
redirect_url,
is_filtered: false,
all_posts_filtered,
})
@ -211,7 +213,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
.unwrap_or_default();
// Find each subreddit name (separated by '+') in sub parameter
for part in sub.split('+') {
for part in sub.split('+').filter(|x| x != &"") {
// Retrieve display name for the subreddit
let display;
let part = if part.starts_with("u_") {
@ -253,7 +255,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
// Redirect back to subreddit
// check for redirect parameter if unsubscribing/unfiltering from outside sidebar
let path = if let Some(redirect_path) = param(&format!("?{}", query), "redirect") {
format!("/{}/", redirect_path)
format!("/{}", redirect_path)
} else {
format!("/r/{}", sub)
};

View File

@ -5,7 +5,7 @@ use crate::server::RequestExt;
use crate::utils::{error, filter_posts, format_url, get_filters, param, template, Post, Preferences, User};
use askama::Template;
use hyper::{Body, Request, Response};
use time::OffsetDateTime;
use time::{OffsetDateTime, macros::format_description};
// STRUCTS
#[derive(Template)]
@ -15,8 +15,11 @@ struct UserTemplate {
posts: Vec<Post>,
sort: (String, String),
ends: (String, String),
/// "overview", "comments", or "submitted"
listing: String,
prefs: Preferences,
url: String,
redirect_url: String,
/// Whether the user themself is filtered.
is_filtered: bool,
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
@ -26,13 +29,17 @@ struct UserTemplate {
// FUNCTIONS
pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
let listing = req.param("listing").unwrap_or_else(|| "overview".to_string());
// Build the Reddit JSON API path
let path = format!(
"/user/{}.json?{}&raw_json=1",
"/user/{}/{}.json?{}&raw_json=1",
req.param("name").unwrap_or_else(|| "reddit".to_string()),
req.uri().query().unwrap_or_default()
listing,
req.uri().query().unwrap_or_default(),
);
let url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26");
// Retrieve other variables from Libreddit request
let sort = param(&path, "sort").unwrap_or_default();
@ -46,8 +53,10 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
posts: Vec::new(),
sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
listing,
prefs: Preferences::new(req),
url,
redirect_url,
is_filtered: true,
all_posts_filtered: false,
})
@ -62,8 +71,10 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
posts,
sort: (sort, param(&path, "t").unwrap_or_default()),
ends: (param(&path, "after").unwrap_or_default(), after),
listing,
prefs: Preferences::new(req),
url,
redirect_url,
is_filtered: false,
all_posts_filtered,
})
@ -82,7 +93,8 @@ async fn user(name: &str) -> Result<User, String> {
// Send a request to the url
json(path, false).await.map(|res| {
// Grab creation date as unix timestamp
let created: i64 = res["data"]["created"].as_f64().unwrap_or(0.0).round() as i64;
let created_unix = res["data"]["created"].as_f64().unwrap_or(0.0).round() as i64;
let created = OffsetDateTime::from_unix_timestamp(created_unix).unwrap_or(OffsetDateTime::UNIX_EPOCH);
// Closure used to parse JSON from Reddit APIs
let about = |item| res["data"]["subreddit"][item].as_str().unwrap_or_default().to_string();
@ -93,7 +105,7 @@ async fn user(name: &str) -> Result<User, String> {
title: esc!(about("title")),
icon: format_url(&about("icon_img")),
karma: res["data"]["total_karma"].as_i64().unwrap_or(0),
created: OffsetDateTime::from_unix_timestamp(created).format("%b %d '%y"),
created: created.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default(),
banner: esc!(about("banner_img")),
description: about("public_description"),
}

View File

@ -9,7 +9,7 @@ use regex::Regex;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
use time::{Duration, OffsetDateTime};
use time::{Duration, OffsetDateTime, macros::format_description};
use url::Url;
// Post flair with content, background color and foreground color
@ -21,6 +21,7 @@ pub struct Flair {
}
// Part of flair, either emoji or text
#[derive(Clone)]
pub struct FlairPart {
pub flair_part_type: String,
pub value: String,
@ -74,6 +75,7 @@ pub struct Flags {
pub stickied: bool,
}
#[derive(Debug)]
pub struct Media {
pub url: String,
pub alt_url: String,
@ -86,28 +88,29 @@ impl Media {
pub async fn parse(data: &Value) -> (String, Self, Vec<GalleryMedia>) {
let mut gallery = Vec::new();
// Define the various known places that Reddit might put video URLs.
let data_preview = &data["preview"]["reddit_video_preview"];
let secure_media = &data["secure_media"]["reddit_video"];
let crosspost_parent_media = &data["crosspost_parent_list"][0]["secure_media"]["reddit_video"];
// If post is a video, return the video
let (post_type, url_val, alt_url_val) = if data["preview"]["reddit_video_preview"]["fallback_url"].is_string() {
// Return reddit video
let (post_type, url_val, alt_url_val) = if data_preview["fallback_url"].is_string() {
(
if data["preview"]["reddit_video_preview"]["is_gif"].as_bool().unwrap_or(false) {
"gif"
} else {
"video"
},
&data["preview"]["reddit_video_preview"]["fallback_url"],
Some(&data["preview"]["reddit_video_preview"]["hls_url"]),
if data_preview["is_gif"].as_bool().unwrap_or(false) { "gif" } else { "video" },
&data_preview["fallback_url"],
Some(&data_preview["hls_url"]),
)
} else if data["secure_media"]["reddit_video"]["fallback_url"].is_string() {
// Return reddit video
} else if secure_media["fallback_url"].is_string() {
(
if data["preview"]["reddit_video_preview"]["is_gif"].as_bool().unwrap_or(false) {
"gif"
} else {
"video"
},
&data["secure_media"]["reddit_video"]["fallback_url"],
Some(&data["secure_media"]["reddit_video"]["hls_url"]),
if secure_media["is_gif"].as_bool().unwrap_or(false) { "gif" } else { "video" },
&secure_media["fallback_url"],
Some(&secure_media["hls_url"]),
)
} else if crosspost_parent_media["fallback_url"].is_string() {
(
if crosspost_parent_media["is_gif"].as_bool().unwrap_or(false) { "gif" } else { "video" },
&crosspost_parent_media["fallback_url"],
Some(&crosspost_parent_media["hls_url"]),
)
} else if data["post_hint"].as_str().unwrap_or("") == "image" {
// Handle images, whether GIFs or pics
@ -140,18 +143,12 @@ impl Media {
let source = &data["preview"]["images"][0]["source"];
let url = if post_type == "self" || post_type == "link" {
url_val.as_str().unwrap_or_default().to_string()
} else {
format_url(url_val.as_str().unwrap_or_default())
};
let alt_url = alt_url_val.map_or(String::new(), |val| format_url(val.as_str().unwrap_or_default()));
(
post_type.to_string(),
Self {
url,
url: format_url(url_val.as_str().unwrap_or_default()),
alt_url,
width: source["width"].as_i64().unwrap_or_default(),
height: source["height"].as_i64().unwrap_or_default(),
@ -251,13 +248,13 @@ impl Post {
let title = esc!(post, "title");
// Determine the type of media along with the media URL
let (post_type, media, gallery) = Media::parse(&data).await;
let (post_type, media, gallery) = Media::parse(data).await;
let awards = Awards::parse(&data["all_awardings"]);
// selftext_html is set for text posts when browsing.
let mut body = rewrite_urls(&val(post, "selftext_html"));
if body == "" {
body = rewrite_urls(&val(post, "body_html"))
if body.is_empty() {
body = rewrite_urls(&val(post, "body_html"));
}
posts.push(Self {
@ -484,7 +481,7 @@ impl Preferences {
/// Gets a `HashSet` of filters from the cookie in the given `Request`.
pub fn get_filters(req: &Request<Body>) -> HashSet<String> {
setting(&req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect::<HashSet<String>>()
setting(req, "filters").split('+').map(String::from).filter(|s| !s.is_empty()).collect::<HashSet<String>>()
}
/// Filters a `Vec<Post>` by the given `HashSet` of filters (each filter being a subreddit name or a user name). If a
@ -551,7 +548,7 @@ pub fn format_url(url: &str) -> String {
if url.is_empty() || url == "self" || url == "default" || url == "nsfw" || url == "spoiler" {
String::new()
} else {
Url::parse(url).map_or(String::new(), |parsed| {
Url::parse(url).map_or(url.to_string(), |parsed| {
let domain = parsed.domain().unwrap_or_default();
let capture = |regex: &str, format: &str, segments: i16| {
@ -586,8 +583,12 @@ pub fn format_url(url: &str) -> String {
}
match domain {
"www.reddit.com" => capture(r"https://www\.reddit\.com/(.*)", "/", 1),
"old.reddit.com" => capture(r"https://old\.reddit\.com/(.*)", "/", 1),
"np.reddit.com" => capture(r"https://np\.reddit\.com/(.*)", "/", 1),
"reddit.com" => capture(r"https://reddit\.com/(.*)", "/", 1),
"v.redd.it" => chain!(
capture(r"https://v\.redd\.it/(.*)/DASH_([0-9]{2,4}(\.mp4|$))", "/vid/", 2),
capture(r"https://v\.redd\.it/(.*)/DASH_([0-9]{2,4}(\.mp4|$|\?source=fallback))", "/vid/", 2),
capture(r"https://v\.redd\.it/(.+)/(HLSPlaylist\.m3u8.*)$", "/hls/", 2)
),
"i.redd.it" => capture(r"https://i\.redd\.it/(.*)", "/img/", 1),
@ -598,7 +599,7 @@ pub fn format_url(url: &str) -> String {
"external-preview.redd.it" => capture(r"https://external\-preview\.redd\.it/(.*)", "/preview/external-pre/", 1),
"styles.redditmedia.com" => capture(r"https://styles\.redditmedia\.com/(.*)", "/style/", 1),
"www.redditstatic.com" => capture(r"https://www\.redditstatic\.com/(.*)", "/static/", 1),
_ => String::new(),
_ => url.to_string(),
}
})
}
@ -636,12 +637,12 @@ pub fn format_num(num: i64) -> (String, String) {
// Parse a relative and absolute time from a UNIX timestamp
pub fn time(created: f64) -> (String, String) {
let time = OffsetDateTime::from_unix_timestamp(created.round() as i64);
let time = OffsetDateTime::from_unix_timestamp(created.round() as i64).unwrap_or(OffsetDateTime::UNIX_EPOCH);
let time_delta = OffsetDateTime::now_utc() - time;
// If the time difference is more than a month, show full date
let rel_time = if time_delta > Duration::days(30) {
time.format("%b %d '%y")
time.format(format_description!("[month repr:short] [day] '[year repr:last_two]")).unwrap_or_default()
// Otherwise, show relative date/time
} else if time_delta.whole_days() > 0 {
format!("{}d ago", time_delta.whole_days())
@ -651,7 +652,7 @@ pub fn time(created: f64) -> (String, String) {
format!("{}m ago", time_delta.whole_minutes())
};
(rel_time, time.format("%b %d %Y, %H:%M:%S UTC"))
(rel_time, time.format(format_description!("[month repr:short] [day] [year], [hour]:[minute]:[second] UTC")).unwrap_or_default())
}
// val() function used to parse JSON from Reddit APIs

View File

@ -465,6 +465,7 @@ aside {
#wiki {
background: var(--foreground);
padding: 35px;
overflow-wrap: anywhere;
}
#top {
@ -486,8 +487,8 @@ aside {
/* Sorting and Search */
select, #search, #sort_options, #inside, #searchbox > *, #sort_submit {
height: 40px;
select, #search, #sort_options, #listing_options, #inside, #searchbox > *, #sort_submit {
height: 38px;
}
.search_label {
@ -504,7 +505,7 @@ select {
select, #search {
border: none;
padding: 0 15px;
padding: 0 10px;
appearance: none;
-webkit-appearance: none;
@ -562,6 +563,11 @@ button.submit:hover > svg { stroke: var(--accent); }
border-radius: 5px 0px 0px 5px;
}
#listing_options + #sort_select {
margin-left: 10px;
border-radius: 5px 0px 0px 5px;
}
#search_sort {
background: var(--highlighted);
border-radius: 5px;
@ -590,15 +596,16 @@ button.submit:hover > svg { stroke: var(--accent); }
margin-bottom: 20px;
}
#sort_options, footer > a {
#sort_options, #listing_options, footer > a {
border-radius: 5px;
align-items: center;
box-shadow: var(--shadow);
background: var(--outside);
display: flex;
overflow: auto;
overflow: hidden;
}
#sort_options > a, footer > a {
#sort_options > a, #listing_options > a, footer > a {
color: var(--text);
padding: 10px 20px;
text-align: center;
@ -606,12 +613,12 @@ button.submit:hover > svg { stroke: var(--accent); }
transition: 0.2s background;
}
#sort_options > a.selected {
#sort_options > a.selected, #listing_options > a.selected {
background: var(--accent);
color: var(--foreground);
}
#sort_options > a:not(.selected):hover {
#sort_options > a:not(.selected):hover, #listing_options > a:not(.selected):hover {
background: var(--foreground);
}
@ -718,7 +725,7 @@ a.search_subreddit:hover {
}
.post_score {
padding-top: 16px;
padding-top: 19px;
padding-left: 12px;
font-size: 13px;
font-weight: bold;
@ -747,6 +754,7 @@ a.search_subreddit:hover {
font-size: 16px;
font-weight: 500;
line-height: 1.5;
overflow-wrap: anywhere;
margin: 5px 15px 5px 12px;
grid-area: post_title;
}
@ -874,7 +882,7 @@ a.search_subreddit:hover {
#post_url {
color: var(--accent);
margin: 5px 15px;
margin: 5px 12px;
grid-area: post_media;
}
@ -901,7 +909,7 @@ a.search_subreddit:hover {
opacity: 0.5;
font-size: 14px;
grid-area: post_footer;
margin: 5px 20px 15px 15px;
margin: 5px 20px 15px 12px;
}
.post_comments {
@ -1179,12 +1187,10 @@ summary.comment_data {
color: var(--accent);
}
.prefs {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 20px;
background: var(--post);
border-radius: 5px;
@ -1197,11 +1203,19 @@ summary.comment_data {
width: 100%;
height: 35px;
align-items: center;
margin-top: 10px;
margin-top: 7px;
}
.prefs > p {
.prefs legend {
font-weight: 500;
border-bottom: 1px solid var(--highlighted);
font-size: 18px;
padding-bottom: 10px;
}
.prefs legend:not(:first-child) {
padding-top: 10px;
margin-top: 15px;
}
.prefs select {

View File

@ -35,7 +35,7 @@
</div>
{% block search %}{% endblock %}
<div id="links">
<a id="reddit_link" href="https://www.reddit.com{{ url }}">
<a id="reddit_link" href="https://www.reddit.com{{ url }}" rel="nofollow">
<span>reddit</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">
<path d="M23 12.0737C23 10.7308 21.9222 9.64226 20.5926 9.64226C19.9435 9.64226 19.3557 9.90274 18.923 10.3244C17.2772 9.12492 15.0099 8.35046 12.4849 8.26135L13.5814 3.05002L17.1643 3.8195C17.2081 4.73947 17.9539 5.47368 18.8757 5.47368C19.8254 5.47368 20.5951 4.69626 20.5951 3.73684C20.5951 2.77769 19.8254 2 18.8758 2C18.2001 2 17.6214 2.39712 17.3404 2.96952L13.3393 2.11066C13.2279 2.08679 13.1116 2.10858 13.016 2.17125C12.9204 2.23393 12.8533 2.33235 12.8295 2.44491L11.6051 8.25987C9.04278 8.33175 6.73904 9.10729 5.07224 10.3201C4.63988 9.90099 4.05398 9.64226 3.40757 9.64226C2.0781 9.64226 1 10.7308 1 12.0737C1 13.0618 1.58457 13.9105 2.4225 14.2909C2.38466 14.5342 2.36545 14.78 2.36505 15.0263C2.36505 18.7673 6.67626 21.8 11.9945 21.8C17.3131 21.8 21.6243 18.7673 21.6243 15.0263C21.6243 14.7794 21.6043 14.5359 21.5678 14.2957C22.4109 13.9175 23 13.0657 23 12.0737Z"/>

View File

@ -10,7 +10,11 @@
</div>
<details class="comment_right" {% if !collapsed || highlighted %}open{% endif %}>
<summary class="comment_data">
<a class="comment_author {{ author.distinguished }} {% if author.name == post_author %}op{% endif %}" href="/user/{{ author.name }}">u/{{ author.name }}</a>
{% if author.name != "[deleted]" %}
<a class="comment_author {{ author.distinguished }} {% if author.name == post_author %}op{% endif %}" href="/user/{{ author.name }}">u/{{ author.name }}</a>
{% else %}
<span class="comment_author {{ author.distinguished }}">u/[deleted]</span>
{% endif %}
{% if author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{% call utils::render_flair(author.flair.flair_parts) %}</small>
{% endif %}

View File

@ -37,7 +37,7 @@
<p class="post_header">
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
<span class="dot">&bull;</span>
<a class="post_author" href="/user/{{ post.author.name }}">u/{{ post.author.name }}</a>
<a class="post_author {{ post.author.distinguished }}" href="/user/{{ post.author.name }}">u/{{ post.author.name }}</a>
{% if post.author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{% call utils::render_flair(post.author.flair.flair_parts) %}</small>
{% endif %}
@ -56,7 +56,7 @@
{% endif %}
</p>
<p class="post_title">
<a href="{{ post.permalink }}">{{ post.title }}</a>
{{ post.title }}
{% if post.flair.flair_parts.len() > 0 %}
<a href="/r/{{ post.community }}/search?q=flair_name%3A%22{{ post.flair.text }}%22&restrict_sr=on"
class="post_flair"

View File

@ -11,14 +11,14 @@
<div id="settings">
<form action="/settings" method="POST">
<div class="prefs">
<p>Appearance</p>
<legend>Appearance</legend>
<div id="theme">
<label for="theme">Theme:</label>
<select name="theme">
{% call utils::options(prefs.theme, ["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox"], "system") %}
</select>
</div>
<p>Interface</p>
<legend>Interface</legend>
<div id="front_page">
<label for="front_page">Front page:</label>
<select name="front_page">
@ -36,7 +36,7 @@
<input type="hidden" value="off" name="wide">
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
<p>Content</p>
<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">
@ -79,7 +79,7 @@
</form>
{% if prefs.subscriptions.len() > 0 %}
<div class="prefs" id="settings_subs">
<p>Subscribed Feeds</p>
<legend>Subscribed Feeds</legend>
{% for sub in prefs.subscriptions %}
<div>
{% let feed -%}
@ -94,7 +94,7 @@
{% endif %}
{% if !prefs.filters.is_empty() %}
<div class="prefs" id="settings_filters">
<p>Filtered Feeds</p>
<legend>Filtered Feeds</legend>
{% for sub in prefs.filters %}
<div>
{% let feed -%}

View File

@ -41,7 +41,7 @@
</form>
{% if sub.name.contains("+") %}
<form action="/r/{{ sub.name }}/subscribe" method="POST">
<form action="/r/{{ sub.name }}/subscribe?redirect={{ redirect_url }}" method="POST">
<button id="multisub" class="subscribe" title="Subscribe to each sub in this multireddit">Subscribe to Multireddit</button>
</form>
{% endif %}
@ -64,22 +64,22 @@
{% endif %}
<footer>
{% if ends.0 != "" %}
{% if !ends.0.is_empty() %}
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}">PREV</a>
{% endif %}
{% if ends.1 != "" %}
{% if !ends.1.is_empty() %}
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}">NEXT</a>
{% endif %}
</footer>
</div>
{% endif %}
{% if is_filtered || (sub.name != "" && !sub.name.contains("+")) %}
{% if is_filtered || (sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) %}
<aside>
{% if is_filtered %}
<center>(Content from r/{{ sub.name }} has been filtered)</center>
{% endif %}
{% if sub.name != "" && !sub.name.contains("+") %}
{% if sub.name != "all" && sub.name != "popular" && !sub.name.contains("+") %}
<div class="panel" id="subreddit">
{% if sub.wiki %}
<div id="top">
@ -101,22 +101,22 @@
<div id="sub_actions">
<div id="sub_subscription">
{% if prefs.subscriptions.contains(sub.name) %}
<form action="/r/{{ sub.name }}/unsubscribe" method="POST">
<form action="/r/{{ sub.name }}/unsubscribe?redirect={{ redirect_url }}" method="POST">
<button class="unsubscribe">Unsubscribe</button>
</form>
{% else %}
<form action="/r/{{ sub.name }}/subscribe" method="POST">
<form action="/r/{{ sub.name }}/subscribe?redirect={{ redirect_url }}" method="POST">
<button class="subscribe">Subscribe</button>
</form>
{% endif %}
</div>
<div id="sub_filter">
{% if prefs.filters.contains(sub.name) %}
<form action="/r/{{ sub.name }}/unfilter" method="POST">
<form action="/r/{{ sub.name }}/unfilter?redirect={{ redirect_url }}" method="POST">
<button class="unfilter">Unfilter</button>
</form>
{% else %}
<form action="/r/{{ sub.name }}/filter" method="POST">
<form action="/r/{{ sub.name }}/filter?redirect={{ redirect_url }}" method="POST">
<button class="filter">Filter</button>
</form>
{% endif %}

View File

@ -16,9 +16,12 @@
{% if !is_filtered %}
<div id="column_one">
<form id="sort">
<select name="sort">
{% call utils::options(sort.0, ["hot", "new", "top"], "") %}
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
<div id="listing_options">
{% call utils::sort(["/user/", user.name.as_str()].concat(), ["overview", "comments", "submitted"], listing) %}
</div>
<select id="sort_select" name="sort">
{% call utils::options(sort.0, ["hot", "new", "top", "controversial"], "") %}
</select>{% if sort.0 == "top" || sort.0 == "controversial" %}<select id="timeframe" name="t">
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
</select>{% endif %}<button id="sort_submit" class="submit">
<svg width="15" viewBox="0 0 110 100" fill="none" stroke-width="10" stroke-linecap="round">
@ -91,22 +94,22 @@
{% let name = ["u_", user.name.as_str()].join("") %}
<div id="user_subscription">
{% if prefs.subscriptions.contains(name) %}
<form action="/r/{{ name }}/unsubscribe" method="POST">
<form action="/r/{{ name }}/unsubscribe?redirect={{ redirect_url }}" method="POST">
<button class="unsubscribe">Unfollow</button>
</form>
{% else %}
<form action="/r/{{ name }}/subscribe" method="POST">
<form action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}" method="POST">
<button class="subscribe">Follow</button>
</form>
{% endif %}
</div>
<div id="user_filter">
{% if prefs.filters.contains(name) %}
<form action="/r/{{ name }}/unfilter" method="POST">
<form action="/r/{{ name }}/unfilter?redirect={{ redirect_url }}" method="POST">
<button class="unfilter">Unfilter</button>
</form>
{% else %}
<form action="/r/{{ name }}/filter" method="POST">
<form action="/r/{{ name }}/filter?redirect={{ redirect_url }}" method="POST">
<button class="filter">Filter</button>
</form>
{% endif %}

View File

@ -1,6 +1,6 @@
{% macro options(current, values, default) -%}
{% for value in values %}
<option value="{{ value }}" {% if current == value || (current == "" && value == default) %}selected{% endif %}>
<option value="{{ value }}" {% if current == value.to_string() || (current == "" && value.to_string() == default.to_string()) %}selected{% endif %}>
{{ format!("{}{}", value.get(0..1).unwrap_or_default().to_uppercase(), value.get(1..).unwrap_or_default()) }}
</option>
{% endfor %}
@ -8,7 +8,7 @@
{% macro sort(root, methods, selected) -%}
{% for method in methods %}
<a {% if method == selected %}class="selected"{% endif %} href="{{ root }}/{{ method }}">
<a {% if method.to_string() == selected.to_string() %}class="selected"{% endif %} href="{{ root }}/{{ method }}">
{{ format!("{}{}", method.get(0..1).unwrap_or_default().to_uppercase(), method.get(1..).unwrap_or_default()) }}
</a>
{% endfor %}
@ -19,7 +19,7 @@
<input id="search" type="text" name="q" placeholder="Search" title="Search libreddit" value="{{ search }}">
{% if root != "/r/" && !root.is_empty() %}
<div id="inside">
<input type="checkbox" name="restrict_sr" id="restrict_sr">
<input type="checkbox" name="restrict_sr" id="restrict_sr" checked>
<label for="restrict_sr" class="search_label" title="Restrict search to this subreddit">in {{ root }}</label>
</div>
{% endif %}
@ -34,7 +34,7 @@
{%- endmacro %}
{% macro render_flair(flair_parts) -%}
{% for flair_part in flair_parts %}{% if flair_part.flair_part_type == "emoji" %}<span class="emoji" style="background-image:url('{{ flair_part.value }}');"></span>{% else if flair_part.flair_part_type == "text" && !flair_part.value.is_empty() %}<span>{{ flair_part.value }}</span>{% endif %}{% endfor %}
{% for flair_part in flair_parts.clone() %}{% if flair_part.flair_part_type == "emoji" %}<span class="emoji" style="background-image:url('{{ flair_part.value }}');"></span>{% else if flair_part.flair_part_type == "text" && !flair_part.value.is_empty() %}<span>{{ flair_part.value }}</span>{% endif %}{% endfor %}
{%- endmacro %}
{% macro sub_list(current) -%}
@ -72,7 +72,7 @@
{% endif -%}
<a class="post_subreddit" href="/{{ community }}">{{ community }}</a>
<span class="dot">&bull;</span>
<a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
<a class="post_author {{ post.author.distinguished }}" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
<span class="dot">&bull;</span>
<span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
{% if !post.awards.is_empty() %}