mirror of https://github.com/spikecodes/libreddit
Merge branch 'master' into fix-templating
This commit is contained in:
commit
b0516a5a36
|
@ -1 +1,2 @@
|
||||||
liberapay: spike
|
liberapay: spike
|
||||||
|
custom: ['https://www.buymeacoffee.com/spikecodes']
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: 🐛 Bug report
|
name: 🐛 Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: ''
|
title: '🐛 Bug Report: '
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ assignees: ''
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## To reproduce
|
## Steps to reproduce the bug
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
@ -22,12 +22,12 @@ Steps to reproduce the behavior:
|
||||||
4. See error
|
4. See error
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Expected behavior
|
## What's the expected behavior?
|
||||||
<!--
|
<!--
|
||||||
A clear and concise description of what you expected to happen.
|
A clear and concise description of what you expected to happen.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Additional context
|
## Additional context / screenshot
|
||||||
<!--
|
<!--
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: ✨ Feature parity
|
name: ✨ Feature parity
|
||||||
about: Suggest implementing a feature into Libreddit that is found in Reddit.com
|
about: Suggest implementing a feature into Libreddit that is found in Reddit.com
|
||||||
title: ''
|
title: '✨ Feature parity: '
|
||||||
labels: feature parity
|
labels: feature parity
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ assignees: ''
|
||||||
A clear and concise description of what the feature is.
|
A clear and concise description of what the feature is.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Describe the implementation into Libreddit
|
## Describe how this could be implemented into Libreddit
|
||||||
<!--
|
<!--
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
-->
|
-->
|
||||||
|
@ -22,7 +22,7 @@ assignees: ''
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Additional context
|
## Additional context / screenshot
|
||||||
<!--
|
<!--
|
||||||
Add any other context or screenshots about the feature parity request here.
|
Add any other context or screenshots about the feature parity request here.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: 💡 Feature request
|
name: 💡 Feature request
|
||||||
about: Suggest a feature for Libreddit that is not found in Reddit
|
about: Suggest a feature for Libreddit that is not found in Reddit
|
||||||
title: ''
|
title: '💡 Feature request: '
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ assignees: ''
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Describe the solution you'd like
|
## Describe the feature you would like to be implemented
|
||||||
<!--
|
<!--
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
-->
|
-->
|
||||||
|
@ -22,7 +22,7 @@ assignees: ''
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Additional context
|
## Additional context / screenshot
|
||||||
<!--
|
<!--
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -34,3 +34,5 @@ jobs:
|
||||||
platforms: linux/arm64
|
platforms: linux/arm64
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:arm
|
tags: spikecodes/libreddit:arm
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
@ -37,3 +37,5 @@ jobs:
|
||||||
platforms: linux/arm/v7
|
platforms: linux/arm/v7
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:armv7
|
tags: spikecodes/libreddit:armv7
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
@ -34,4 +34,5 @@ jobs:
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: spikecodes/libreddit:latest
|
tags: spikecodes/libreddit:latest
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
21
Cargo.toml
21
Cargo.toml
|
@ -3,23 +3,24 @@ name = "libreddit"
|
||||||
description = " Alternative private front-end to Reddit"
|
description = " Alternative private front-end to Reddit"
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
repository = "https://github.com/spikecodes/libreddit"
|
repository = "https://github.com/spikecodes/libreddit"
|
||||||
version = "0.21.7"
|
version = "0.22.8"
|
||||||
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
|
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { version = "0.11.0", default-features = false }
|
askama = { version = "0.11.1", default-features = false }
|
||||||
async-recursion = "1.0.0"
|
async-recursion = "1.0.0"
|
||||||
cached = "0.26.2"
|
cached = "0.34.0"
|
||||||
clap = { version = "3.0.5", default-features = false, features = ["std"] }
|
clap = { version = "3.1.18", default-features = false, features = ["std"] }
|
||||||
regex = "1.5.4"
|
regex = "1.5.6"
|
||||||
serde = { version = "1.0.133", features = ["derive"] }
|
serde = { version = "1.0.137", features = ["derive"] }
|
||||||
cookie = "0.16.0"
|
cookie = "0.16.0"
|
||||||
futures-lite = "1.12.0"
|
futures-lite = "1.12.0"
|
||||||
hyper = { version = "0.14.16", features = ["full"] }
|
hyper = { version = "0.14.18", features = ["full"] }
|
||||||
hyper-rustls = "0.23.0"
|
hyper-rustls = "0.23.0"
|
||||||
route-recognizer = "0.3.1"
|
route-recognizer = "0.3.1"
|
||||||
serde_json = "1.0.74"
|
serde_json = "1.0.81"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.18.2", features = ["full"] }
|
||||||
time = "0.3.5"
|
time = "0.3.9"
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
|
rust-embed = "6.4.0"
|
||||||
|
|
12
FUNDING.yml
12
FUNDING.yml
|
@ -1,12 +0,0 @@
|
||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
||||||
patreon: # Replace with a single Patreon username
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: spike
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
custom: ['https://www.buymeacoffee.com/spikecodes']
|
|
53
README.md
53
README.md
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
**10 second pitch:** Libreddit is a portmanteau of "libre" (meaning freedom) and "Reddit". It is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://libreddit.spike.codes/r/unpopularopinion) without being [tracked](#reddit).
|
**10 second pitch:** Libreddit is a portmanteau of "libre" (meaning freedom) and "Reddit". It is a private front-end like [Invidious](https://github.com/iv-org/invidious) but for Reddit. Browse the coldest takes of [r/unpopularopinion](https://libreddit.spike.codes/r/unpopularopinion) without being [tracked](#reddit).
|
||||||
|
|
||||||
- 🚀 Fast: written in Rust for blazing fast speeds and memory safety
|
- 🚀 Fast: written in Rust for blazing-fast speeds and memory safety
|
||||||
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
|
- ☁️ Light: no JavaScript, no ads, no tracking, no bloat
|
||||||
- 🕵 Private: all requests are proxied through the server, including media
|
- 🕵 Private: all requests are proxied through the server, including media
|
||||||
- 🔒 Secure: strong [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents browser requests to Reddit
|
- 🔒 Secure: strong [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) prevents browser requests to Reddit
|
||||||
|
@ -17,11 +17,13 @@
|
||||||
|
|
||||||
I appreciate any donations! Your support allows me to continue developing Libreddit.
|
I appreciate any donations! Your support allows me to continue developing Libreddit.
|
||||||
|
|
||||||
<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: 50px" ></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)
|
|
||||||
|
|
||||||
**Monero:** [45FJrEuFPtG2o7QZz2Nps77TbHD4sPqxViwbdyV9A6ktfHiWs47UngG5zXPcLoDXAc8taeuBgeNjfeprwgeXYXhN3C9tVSR](monero:45FJrEuFPtG2o7QZz2Nps77TbHD4sPqxViwbdyV9A6ktfHiWs47UngG5zXPcLoDXAc8taeuBgeNjfeprwgeXYXhN3C9tVSR)
|
**Bitcoin:** `bc1qwyxjnafpu3gypcpgs025cw9wa7ryudtecmwa6y`
|
||||||
|
|
||||||
|
**Monero:** `45FJrEuFPtG2o7QZz2Nps77TbHD4sPqxViwbdyV9A6ktfHiWs47UngG5zXPcLoDXAc8taeuBgeNjfeprwgeXYXhN3C9tVSR`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -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!
|
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 |
|
| Website | Country | Cloudflare |
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
| [libredd.it](https://libredd.it) (official) | 🇺🇸 US | |
|
| [libredd.it](https://libredd.it) (official) | 🇺🇸 US | |
|
||||||
|
@ -39,9 +43,9 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
|
||||||
| [reddit.invak.id](https://reddit.invak.id) | 🇧🇬 BG | |
|
| [reddit.invak.id](https://reddit.invak.id) | 🇧🇬 BG | |
|
||||||
| [reddit.phii.me](https://reddit.phii.me) | 🇺🇸 US | |
|
| [reddit.phii.me](https://reddit.phii.me) | 🇺🇸 US | |
|
||||||
| [lr.riverside.rocks](https://lr.riverside.rocks) | 🇺🇸 US | |
|
| [lr.riverside.rocks](https://lr.riverside.rocks) | 🇺🇸 US | |
|
||||||
| [libreddit.silkky.cloud](https://libreddit.silkky.cloud) | 🇫🇮 FI | ✅ |
|
| [libreddit.strongthany.cc](https://libreddit.strongthany.cc) | 🇺🇸 US | |
|
||||||
| [libreddit.database.red](https://libreddit.database.red) | 🇺🇸 US | ✅ |
|
| [libreddit.database.red](https://libreddit.database.red) | 🇺🇸 US | ✅ |
|
||||||
| [libreddit.exonip.de](https://libreddit.exonip.de) | 🇩🇪 DE | |
|
| [libreddit.privacy.com.de](https://libreddit.privacy.com.de) | 🇩🇪 DE | |
|
||||||
| [libreddit.domain.glass](https://libreddit.domain.glass) | 🇺🇸 US | ✅ |
|
| [libreddit.domain.glass](https://libreddit.domain.glass) | 🇺🇸 US | ✅ |
|
||||||
| [libreddit.sugoma.tk](https://libreddit.sugoma.tk) | 🇺🇸 US | |
|
| [libreddit.sugoma.tk](https://libreddit.sugoma.tk) | 🇺🇸 US | |
|
||||||
| [libreddit.jamiethalacker.dev](https://libreddit.jamiethalacker.dev) | 🇺🇸 US | ✅ |
|
| [libreddit.jamiethalacker.dev](https://libreddit.jamiethalacker.dev) | 🇺🇸 US | ✅ |
|
||||||
|
@ -55,23 +59,39 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
|
||||||
| [libreddit.igna.rocks](https://libreddit.igna.rocks) | 🇺🇸 US | |
|
| [libreddit.igna.rocks](https://libreddit.igna.rocks) | 🇺🇸 US | |
|
||||||
| [libreddit.autarkic.org](https://libreddit.autarkic.org) | 🇺🇸 US | |
|
| [libreddit.autarkic.org](https://libreddit.autarkic.org) | 🇺🇸 US | |
|
||||||
| [libreddit.flux.industries](https://libreddit.flux.industries) | 🇩🇪 DE | ✅ |
|
| [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 | |
|
| [lr.oversold.host](https://lr.oversold.host) | 🇱🇺 LU | |
|
||||||
| [libreddit.de](https://libreddit.de) | 🇩🇪 DE | |
|
| [libreddit.de](https://libreddit.de) | 🇩🇪 DE | |
|
||||||
| [libreddit.pussthecat.org](https://libreddit.pussthecat.org) | 🇩🇪 DE | |
|
| [libreddit.pussthecat.org](https://libreddit.pussthecat.org) | 🇩🇪 DE | |
|
||||||
| [libreddit.mutahar.rocks](https://libreddit.mutahar.rocks) | 🇫🇷 FR | |
|
| [libreddit.mutahar.rocks](https://libreddit.mutahar.rocks) | 🇫🇷 FR | |
|
||||||
| [libreddit.northboot.xyz](https://libreddit.northboot.xyz) | 🇩🇪 DE | |
|
| [libreddit.northboot.xyz](https://libreddit.northboot.xyz) | 🇩🇪 DE | |
|
||||||
| [leddit.xyz](https://www.leddit.xyz) | 🇺🇸 US | |
|
| [leddit.xyz](https://leddit.xyz) | 🇺🇸 US | |
|
||||||
|
| [de.leddit.xyz](https://de.leddit.xyz) | 🇩🇪 DE | |
|
||||||
| [lr.cowfee.moe](https://lr.cowfee.moe) | 🇺🇸 US | |
|
| [lr.cowfee.moe](https://lr.cowfee.moe) | 🇺🇸 US | |
|
||||||
| [libreddit.hu](https://libreddit.hu) | 🇫🇮 FI | ✅ |
|
| [libreddit.hu](https://libreddit.hu) | 🇫🇮 FI | ✅ |
|
||||||
| [libreddit.totaldarkness.net](https://libreddit.totaldarkness.net) | 🇨🇦 CA | |
|
| [libreddit.totaldarkness.net](https://libreddit.totaldarkness.net) | 🇨🇦 CA | |
|
||||||
| [libreddit.esmailelbob.xyz](https://libreddit.esmailelbob.xyz) | 🇪🇬 EG | |
|
| [libreddit.esmailelbob.xyz](https://libreddit.esmailelbob.xyz) | 🇨🇦 CA | |
|
||||||
|
| [lr.vern.cc](https://lr.vern.cc) | 🇵🇱 PL | |
|
||||||
| [libreddit.nl](https://libreddit.nl) | 🇳🇱 NL | |
|
| [libreddit.nl](https://libreddit.nl) | 🇳🇱 NL | |
|
||||||
| [lr.stilic.ml](https://lr.stilic.ml) | 🇫🇷 FR | ✅ |
|
| [lr.stilic.ml](https://lr.stilic.ml) | 🇫🇷 FR | ✅ |
|
||||||
| [reddi.tk](https://reddi.tk) | 🇺🇸 US | ✅ |
|
| [reddi.tk](https://reddi.tk) | 🇺🇸 US | ✅ |
|
||||||
| [libreddit.bus-hit.me](https://libreddit.bus-hit.me) | 🇨🇦 CA | |
|
| [libreddit.bus-hit.me](https://libreddit.bus-hit.me) | 🇨🇦 CA | |
|
||||||
| [lr.rfl890.cf](https://lr.rfl890.cf) | 🇺🇸 US | |
|
|
||||||
| [libreddit.datatunnel.xyz](https://libreddit.datatunnel.xyz) | 🇫🇮 FI | |
|
| [libreddit.datatunnel.xyz](https://libreddit.datatunnel.xyz) | 🇫🇮 FI | |
|
||||||
|
| [libreddit.crewz.me](https://libreddit.crewz.me) | 🇳🇱 NL | ✅ |
|
||||||
|
| [r.walkx.org](https://r.walkx.org) | 🇳🇱 NL | ✅ |
|
||||||
|
| [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 | |
|
||||||
|
| [libreddit.lunar.icu](https://libreddit.lunar.icu) | 🇩🇪 DE | ✅ |
|
||||||
|
| [libreddit.privacydev.net](https://libreddit.privacydev.net) | 🇺🇸 US | |
|
||||||
|
| [libreddit.notyourcomputer.net](https://libreddit.notyourcomputer.net) | 🇺🇸 US | |
|
||||||
|
| [r.ahwx.org](https://r.ahwx.org) | 🇳🇱 NL | ✅ |
|
||||||
|
| [bob.fr.to](https://bob.fr.to) | 🇺🇸 US | |
|
||||||
|
| [reddit.beparanoid.de](https://reddit.beparanoid.de) | 🇨🇭 CH | |
|
||||||
|
| [libreddit.dcs0.hu](https://libreddit.dcs0.hu) | 🇭🇺 HU | |
|
||||||
|
| [reddit.dr460nf1r3.org](https://reddit.dr460nf1r3.org) | 🇩🇪 DE | ✅ |
|
||||||
| [spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion](http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion) | 🇮🇳 IN | |
|
| [spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion](http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion) | 🇮🇳 IN | |
|
||||||
| [fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion](http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion) | 🇩🇪 DE | |
|
| [fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion](http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion) | 🇩🇪 DE | |
|
||||||
| [kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion](http://kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion) | 🇳🇱 NL | |
|
| [kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion](http://kphht2jcflojtqte4b4kyx7p2ahagv4debjj32nre67dxz7y57seqwyd.onion) | 🇳🇱 NL | |
|
||||||
|
@ -82,9 +102,12 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
|
||||||
| [ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion](http://ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion) | 🇺🇸 US | |
|
| [ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion](http://ledditqo2mxfvlgobxnlhrkq4dh34jss6evfkdkb2thlvy6dn4f4gpyd.onion) | 🇺🇸 US | |
|
||||||
| [libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion](http://libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion) | 🇺🇸 US | |
|
| [libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion](http://libredoxhxwnmsb6dvzzd35hmgzmawsq5i764es7witwhddvpc2razid.onion) | 🇺🇸 US | |
|
||||||
| [libreddit.2syis2nnyytz6jnusnjurva4swlaizlnleiks5mjp46phuwjbdjqwgqd.onion](http://libreddit.2syis2nnyytz6jnusnjurva4swlaizlnleiks5mjp46phuwjbdjqwgqd.onion) | 🇪🇬 EG | |
|
| [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 | |
|
||||||
|
| [reddit.prnoid54e44a4bduq5due64jkk7wcnkxcp5kv3juncm7veptjcqudgyd.onion](http://reddit.prnoid54e44a4bduq5due64jkk7wcnkxcp5kv3juncm7veptjcqudgyd.onion) | 🇨🇭 CH | |
|
||||||
|
|
||||||
|
A checkmark in the "Cloudflare" category here refers to the use of the reverse proxy, [Cloudflare](https://cloudflare.com). The checkmark will not be listed for a site that uses Cloudflare DNS but rather the proxying service which grants Cloudflare the ability to monitor traffic to the website.
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -147,7 +170,7 @@ Results from Google Lighthouse ([Libreddit Report](https://lighthouse-dot-webdot
|
||||||
- The requested URL
|
- The requested URL
|
||||||
- Search terms
|
- Search terms
|
||||||
|
|
||||||
**Location:** The same privacy policy goes on to describe location data may be collected through the use of:
|
**Location:** The same privacy policy goes on to describe that location data may be collected through the use of:
|
||||||
- GPS (consensual)
|
- GPS (consensual)
|
||||||
- Bluetooth (consensual)
|
- Bluetooth (consensual)
|
||||||
- Content associated with a location (consensual)
|
- Content associated with a location (consensual)
|
||||||
|
@ -171,7 +194,7 @@ For transparency, I hope to describe all the ways Libreddit handles user privacy
|
||||||
|
|
||||||
**Cookies:** Libreddit uses optional cookies to store any configured settings in [the settings menu](https://libreddit.spike.codes/settings). These are not cross-site cookies and the cookies hold no personal data.
|
**Cookies:** Libreddit uses optional cookies to store any configured settings in [the settings menu](https://libreddit.spike.codes/settings). These are not cross-site cookies and the cookies hold no personal data.
|
||||||
|
|
||||||
**Hosting:** The official instances are hosted on [Replit](https://replit.com/) which monitors usage to prevent abuse. I can understand if this invalidates certain users' threat models and therefore, selfhosting, using unofficial instances and browsing through Tor are welcomed.
|
**Hosting:** The official instances are hosted on [Replit](https://replit.com/) which monitors usage to prevent abuse. I can understand if this invalidates certain users' threat models and therefore, self-hosting, using unofficial instances, and browsing through Tor are welcomed.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -262,7 +285,7 @@ LIBREDDIT_DEFAULT_WIDE=on LIBREDDIT_DEFAULT_THEME=dark libreddit -r
|
||||||
|
|
||||||
## Proxying using NGINX
|
## Proxying using NGINX
|
||||||
|
|
||||||
**NOTE** If you're [proxying Libreddit through a NGINX Reverse Proxy](https://github.com/spikecodes/libreddit/issues/122#issuecomment-782226853), add
|
**NOTE** If you're [proxying Libreddit through an NGINX Reverse Proxy](https://github.com/spikecodes/libreddit/issues/122#issuecomment-782226853), add
|
||||||
```nginx
|
```nginx
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
```
|
```
|
||||||
|
|
|
@ -11,5 +11,27 @@ Environment=PORT=8080
|
||||||
EnvironmentFile=-/etc/libreddit.conf
|
EnvironmentFile=-/etc/libreddit.conf
|
||||||
ExecStart=/usr/bin/libreddit -a ${ADDRESS} -p ${PORT}
|
ExecStart=/usr/bin/libreddit -a ${ADDRESS} -p ${PORT}
|
||||||
|
|
||||||
|
# Hardening
|
||||||
|
DeviceAllow=
|
||||||
|
LockPersonality=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
ProcSubset=pid
|
||||||
|
ProtectClock=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectHostname=yes
|
||||||
|
ProtectKernelLogs=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectProc=invisible
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6
|
||||||
|
RestrictNamespaces=yes
|
||||||
|
RestrictRealtime=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service ~@privileged ~@resources
|
||||||
|
UMask=0077
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=default.target
|
WantedBy=default.target
|
||||||
|
|
26
src/main.rs
26
src/main.rs
|
@ -11,7 +11,7 @@ mod user;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
// Import Crates
|
// Import Crates
|
||||||
use clap::{App as cli, Arg};
|
use clap::{Arg, Command};
|
||||||
|
|
||||||
use futures_lite::FutureExt;
|
use futures_lite::FutureExt;
|
||||||
use hyper::{header::HeaderValue, Body, Request, Response};
|
use hyper::{header::HeaderValue, Body, Request, Response};
|
||||||
|
@ -19,7 +19,7 @@ use hyper::{header::HeaderValue, Body, Request, Response};
|
||||||
mod client;
|
mod client;
|
||||||
use client::proxy;
|
use client::proxy;
|
||||||
use server::RequestExt;
|
use server::RequestExt;
|
||||||
use utils::{error, redirect};
|
use utils::{error, redirect, ThemeAssets};
|
||||||
|
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
@ -85,9 +85,26 @@ async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Respons
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn style() -> Result<Response<Body>, String> {
|
||||||
|
let mut res = include_str!("../static/style.css").to_string();
|
||||||
|
for file in ThemeAssets::iter() {
|
||||||
|
res.push('\n');
|
||||||
|
let theme = ThemeAssets::get(file.as_ref()).unwrap();
|
||||||
|
res.push_str(std::str::from_utf8(theme.data.as_ref()).unwrap());
|
||||||
|
}
|
||||||
|
Ok(
|
||||||
|
Response::builder()
|
||||||
|
.status(200)
|
||||||
|
.header("content-type", "text/css")
|
||||||
|
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
|
||||||
|
.body(res.to_string().into())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let matches = cli::new("Libreddit")
|
let matches = Command::new("Libreddit")
|
||||||
.version(env!("CARGO_PKG_VERSION"))
|
.version(env!("CARGO_PKG_VERSION"))
|
||||||
.about("Private front-end for Reddit written in Rust ")
|
.about("Private front-end for Reddit written in Rust ")
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -152,7 +169,7 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read static files
|
// Read static files
|
||||||
app.at("/style.css").get(|_| resource(include_str!("../static/style.css"), "text/css", false).boxed());
|
app.at("/style.css").get(|_| style().boxed());
|
||||||
app
|
app
|
||||||
.at("/manifest.json")
|
.at("/manifest.json")
|
||||||
.get(|_| resource(include_str!("../static/manifest.json"), "application/json", false).boxed());
|
.get(|_| resource(include_str!("../static/manifest.json"), "application/json", false).boxed());
|
||||||
|
@ -193,6 +210,7 @@ async fn main() {
|
||||||
|
|
||||||
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account".to_string()).boxed());
|
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").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").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").get(|r| post::item(r).boxed());
|
||||||
app.at("/user/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
app.at("/user/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
||||||
|
|
14
src/post.rs
14
src/post.rs
|
@ -99,9 +99,12 @@ async fn parse_post(json: &serde_json::Value) -> Post {
|
||||||
let permalink = val(post, "permalink");
|
let permalink = val(post, "permalink");
|
||||||
|
|
||||||
let body = if val(post, "removed_by_category") == "moderator" {
|
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)
|
format!(
|
||||||
|
"<div class=\"md\"><p>[removed] — <a href=\"https://www.reveddit.com{}\">view removed post</a></p></div>",
|
||||||
|
permalink
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
rewrite_urls(&val(post, "selftext_html")).replace("\\", "")
|
rewrite_urls(&val(post, "selftext_html"))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build a post using data parsed from Reddit post API
|
// Build a post using data parsed from Reddit post API
|
||||||
|
@ -198,9 +201,12 @@ fn parse_comments(json: &serde_json::Value, post_link: &str, post_author: &str,
|
||||||
let highlighted = id == highlighted_comment;
|
let highlighted = id == highlighted_comment;
|
||||||
|
|
||||||
let body = if val(&comment, "author") == "[deleted]" && val(&comment, "body") == "[removed]" {
|
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)
|
format!(
|
||||||
|
"<div class=\"md\"><p>[removed] — <a href=\"https://www.reveddit.com{}{}\">view removed comment</a></p></div>",
|
||||||
|
post_link, id
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
rewrite_urls(&val(&comment, "body_html")).to_string()
|
rewrite_urls(&val(&comment, "body_html"))
|
||||||
};
|
};
|
||||||
|
|
||||||
let author = Author {
|
let author = Author {
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct SubredditTemplate {
|
||||||
ends: (String, String),
|
ends: (String, String),
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
url: String,
|
url: String,
|
||||||
|
redirect_url: String,
|
||||||
/// Whether the subreddit itself is filtered.
|
/// Whether the subreddit itself is filtered.
|
||||||
is_filtered: bool,
|
is_filtered: bool,
|
||||||
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
|
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
|
||||||
|
@ -85,18 +86,17 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
} else {
|
} else {
|
||||||
Subreddit::default()
|
Subreddit::default()
|
||||||
}
|
}
|
||||||
} else if sub_name.contains('+') {
|
} else {
|
||||||
// Multireddit
|
// Multireddit, all, popular
|
||||||
Subreddit {
|
Subreddit {
|
||||||
name: sub_name.clone(),
|
name: sub_name.clone(),
|
||||||
..Subreddit::default()
|
..Subreddit::default()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Subreddit::default()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = format!("/r/{}/{}.json?{}&raw_json=1", sub_name.clone(), sort, req.uri().query().unwrap_or_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 url = String::from(req.uri().path_and_query().map_or("", |val| val.as_str()));
|
||||||
|
let redirect_url = url[1..].replace('?', "%3F").replace('&', "%26").replace('+', "%2B");
|
||||||
let filters = get_filters(&req);
|
let filters = get_filters(&req);
|
||||||
|
|
||||||
// If all requested subs are filtered, we don't need to fetch posts.
|
// If all requested subs are filtered, we don't need to fetch posts.
|
||||||
|
@ -108,6 +108,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
url,
|
url,
|
||||||
|
redirect_url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
})
|
})
|
||||||
|
@ -123,6 +124,7 @@ pub async fn community(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
url,
|
url,
|
||||||
|
redirect_url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
})
|
})
|
||||||
|
@ -252,7 +254,7 @@ pub async fn subscriptions_filters(req: Request<Body>) -> Result<Response<Body>,
|
||||||
// Redirect back to subreddit
|
// Redirect back to subreddit
|
||||||
// check for redirect parameter if unsubscribing/unfiltering from outside sidebar
|
// check for redirect parameter if unsubscribing/unfiltering from outside sidebar
|
||||||
let path = if let Some(redirect_path) = param(&format!("?{}", query), "redirect") {
|
let path = if let Some(redirect_path) = param(&format!("?{}", query), "redirect") {
|
||||||
format!("/{}/", redirect_path)
|
format!("/{}", redirect_path)
|
||||||
} else {
|
} else {
|
||||||
format!("/r/{}", sub)
|
format!("/r/{}", sub)
|
||||||
};
|
};
|
||||||
|
@ -333,10 +335,10 @@ pub async fn sidebar(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
match json(path, quarantined).await {
|
match json(path, quarantined).await {
|
||||||
// If success, receive JSON in response
|
// If success, receive JSON in response
|
||||||
Ok(response) => template(WikiTemplate {
|
Ok(response) => template(WikiTemplate {
|
||||||
wiki: rewrite_urls(&val(&response, "description_html").replace("\\", "")),
|
wiki: rewrite_urls(&val(&response, "description_html")),
|
||||||
// wiki: format!(
|
// wiki: format!(
|
||||||
// "{}<hr><h1>Moderators</h1><br><ul>{}</ul>",
|
// "{}<hr><h1>Moderators</h1><br><ul>{}</ul>",
|
||||||
// rewrite_urls(&val(&response, "description_html").replace("\\", "")),
|
// rewrite_urls(&val(&response, "description_html"),
|
||||||
// moderators(&sub, quarantined).await.unwrap_or(vec!["Could not fetch moderators".to_string()]).join(""),
|
// moderators(&sub, quarantined).await.unwrap_or(vec!["Could not fetch moderators".to_string()]).join(""),
|
||||||
// ),
|
// ),
|
||||||
sub,
|
sub,
|
||||||
|
@ -408,7 +410,7 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
||||||
name: val(&res, "display_name"),
|
name: val(&res, "display_name"),
|
||||||
title: val(&res, "title"),
|
title: val(&res, "title"),
|
||||||
description: val(&res, "public_description"),
|
description: val(&res, "public_description"),
|
||||||
info: rewrite_urls(&val(&res, "description_html").replace("\\", "")),
|
info: rewrite_urls(&val(&res, "description_html"),
|
||||||
// moderators: moderators_list(sub, quarantined).await.unwrap_or_default(),
|
// moderators: moderators_list(sub, quarantined).await.unwrap_or_default(),
|
||||||
icon: format_url(&icon),
|
icon: format_url(&icon),
|
||||||
members: format_num(members),
|
members: format_num(members),
|
||||||
|
|
17
src/user.rs
17
src/user.rs
|
@ -4,7 +4,7 @@ use crate::server::RequestExt;
|
||||||
use crate::utils::{error, filter_posts, format_url, get_filters, param, template, Post, Preferences, User};
|
use crate::utils::{error, filter_posts, format_url, get_filters, param, template, Post, Preferences, User};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
use time::{OffsetDateTime, macros::format_description};
|
use time::{macros::format_description, OffsetDateTime};
|
||||||
|
|
||||||
// STRUCTS
|
// STRUCTS
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
|
@ -14,8 +14,11 @@ struct UserTemplate {
|
||||||
posts: Vec<Post>,
|
posts: Vec<Post>,
|
||||||
sort: (String, String),
|
sort: (String, String),
|
||||||
ends: (String, String),
|
ends: (String, String),
|
||||||
|
/// "overview", "comments", or "submitted"
|
||||||
|
listing: String,
|
||||||
prefs: Preferences,
|
prefs: Preferences,
|
||||||
url: String,
|
url: String,
|
||||||
|
redirect_url: String,
|
||||||
/// Whether the user themself is filtered.
|
/// Whether the user themself is filtered.
|
||||||
is_filtered: bool,
|
is_filtered: bool,
|
||||||
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
|
/// Whether all fetched posts are filtered (to differentiate between no posts fetched in the first place,
|
||||||
|
@ -25,13 +28,17 @@ struct UserTemplate {
|
||||||
|
|
||||||
// FUNCTIONS
|
// FUNCTIONS
|
||||||
pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
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
|
// Build the Reddit JSON API path
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"/user/{}.json?{}&raw_json=1",
|
"/user/{}/{}.json?{}&raw_json=1",
|
||||||
req.param("name").unwrap_or_else(|| "reddit".to_string()),
|
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 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
|
// Retrieve other variables from Libreddit request
|
||||||
let sort = param(&path, "sort").unwrap_or_default();
|
let sort = param(&path, "sort").unwrap_or_default();
|
||||||
|
@ -45,8 +52,10 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
posts: Vec::new(),
|
posts: Vec::new(),
|
||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
ends: (param(&path, "after").unwrap_or_default(), "".to_string()),
|
||||||
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
url,
|
url,
|
||||||
|
redirect_url,
|
||||||
is_filtered: true,
|
is_filtered: true,
|
||||||
all_posts_filtered: false,
|
all_posts_filtered: false,
|
||||||
})
|
})
|
||||||
|
@ -61,8 +70,10 @@ pub async fn profile(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||||
posts,
|
posts,
|
||||||
sort: (sort, param(&path, "t").unwrap_or_default()),
|
sort: (sort, param(&path, "t").unwrap_or_default()),
|
||||||
ends: (param(&path, "after").unwrap_or_default(), after),
|
ends: (param(&path, "after").unwrap_or_default(), after),
|
||||||
|
listing,
|
||||||
prefs: Preferences::new(req),
|
prefs: Preferences::new(req),
|
||||||
url,
|
url,
|
||||||
|
redirect_url,
|
||||||
is_filtered: false,
|
is_filtered: false,
|
||||||
all_posts_filtered,
|
all_posts_filtered,
|
||||||
})
|
})
|
||||||
|
|
59
src/utils.rs
59
src/utils.rs
|
@ -6,10 +6,11 @@ use askama::Template;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use hyper::{Body, Request, Response};
|
use hyper::{Body, Request, Response};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use time::{Duration, OffsetDateTime, macros::format_description};
|
use time::{macros::format_description, Duration, OffsetDateTime};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
// Post flair with content, background color and foreground color
|
// Post flair with content, background color and foreground color
|
||||||
|
@ -217,24 +218,19 @@ pub struct Post {
|
||||||
impl Post {
|
impl Post {
|
||||||
// Fetch posts of a user or subreddit and return a vector of posts and the "after" value
|
// Fetch posts of a user or subreddit and return a vector of posts and the "after" value
|
||||||
pub async fn fetch(path: &str, quarantine: bool) -> Result<(Vec<Self>, String), String> {
|
pub async fn fetch(path: &str, quarantine: bool) -> Result<(Vec<Self>, String), String> {
|
||||||
let res;
|
|
||||||
let post_list;
|
|
||||||
|
|
||||||
// Send a request to the url
|
// Send a request to the url
|
||||||
match json(path.to_string(), quarantine).await {
|
let res = match json(path.to_string(), quarantine).await {
|
||||||
// If success, receive JSON in response
|
// If success, receive JSON in response
|
||||||
Ok(response) => {
|
Ok(response) => response,
|
||||||
res = response;
|
|
||||||
}
|
|
||||||
// If the Reddit API returns an error, exit this function
|
// If the Reddit API returns an error, exit this function
|
||||||
Err(msg) => return Err(msg),
|
Err(msg) => return Err(msg),
|
||||||
}
|
};
|
||||||
|
|
||||||
// Fetch the list of posts from the JSON response
|
// Fetch the list of posts from the JSON response
|
||||||
match res["data"]["children"].as_array() {
|
let post_list = match res["data"]["children"].as_array() {
|
||||||
Some(list) => post_list = list,
|
Some(list) => list,
|
||||||
None => return Err("No posts found".to_string()),
|
None => return Err("No posts found".to_string()),
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut posts: Vec<Self> = Vec::new();
|
let mut posts: Vec<Self> = Vec::new();
|
||||||
|
|
||||||
|
@ -445,6 +441,7 @@ pub struct Params {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Preferences {
|
pub struct Preferences {
|
||||||
|
pub available_themes: Vec<String>,
|
||||||
pub theme: String,
|
pub theme: String,
|
||||||
pub front_page: String,
|
pub front_page: String,
|
||||||
pub layout: String,
|
pub layout: String,
|
||||||
|
@ -459,10 +456,23 @@ pub struct Preferences {
|
||||||
pub filters: Vec<String>,
|
pub filters: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "static/themes/"]
|
||||||
|
#[include = "*.css"]
|
||||||
|
pub struct ThemeAssets;
|
||||||
|
|
||||||
impl Preferences {
|
impl Preferences {
|
||||||
// Build preferences from cookies
|
// Build preferences from cookies
|
||||||
pub fn new(req: Request<Body>) -> Self {
|
pub fn new(req: Request<Body>) -> Self {
|
||||||
|
// Read available theme names from embedded css files.
|
||||||
|
// Always make the default "system" theme available.
|
||||||
|
let mut themes = vec!["system".to_string()];
|
||||||
|
for file in ThemeAssets::iter() {
|
||||||
|
let chunks: Vec<&str> = file.as_ref().split(".css").collect();
|
||||||
|
themes.push(chunks[0].to_owned())
|
||||||
|
}
|
||||||
Self {
|
Self {
|
||||||
|
available_themes: themes,
|
||||||
theme: setting(&req, "theme"),
|
theme: setting(&req, "theme"),
|
||||||
front_page: setting(&req, "front_page"),
|
front_page: setting(&req, "front_page"),
|
||||||
layout: setting(&req, "layout"),
|
layout: setting(&req, "layout"),
|
||||||
|
@ -607,8 +617,11 @@ pub fn format_url(url: &str) -> String {
|
||||||
|
|
||||||
// Rewrite Reddit links to Libreddit in body of text
|
// Rewrite Reddit links to Libreddit in body of text
|
||||||
pub fn rewrite_urls(input_text: &str) -> String {
|
pub fn rewrite_urls(input_text: &str) -> String {
|
||||||
let text1 =
|
let text1 = Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|)(reddit\.com|redd\.it)/"#)
|
||||||
Regex::new(r#"href="(https|http|)://(www\.|old\.|np\.|amp\.|)(reddit\.com|redd\.it)/"#).map_or(String::new(), |re| re.replace_all(input_text, r#"href="/"#).to_string());
|
.map_or(String::new(), |re| re.replace_all(input_text, r#"href="/"#).to_string())
|
||||||
|
// Remove (html-encoded) "\" from URLs.
|
||||||
|
.replace("%5C", "")
|
||||||
|
.replace('\\', "");
|
||||||
|
|
||||||
// Rewrite external media previews to Libreddit
|
// Rewrite external media previews to Libreddit
|
||||||
Regex::new(r"https://external-preview\.redd\.it(.*)[^?]").map_or(String::new(), |re| {
|
Regex::new(r"https://external-preview\.redd\.it(.*)[^?]").map_or(String::new(), |re| {
|
||||||
|
@ -652,7 +665,12 @@ pub fn time(created: f64) -> (String, String) {
|
||||||
format!("{}m ago", time_delta.whole_minutes())
|
format!("{}m ago", time_delta.whole_minutes())
|
||||||
};
|
};
|
||||||
|
|
||||||
(rel_time, time.format(format_description!("[month repr:short] [day] [year], [hour]:[minute]:[second] UTC")).unwrap_or_default())
|
(
|
||||||
|
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
|
// val() function used to parse JSON from Reddit APIs
|
||||||
|
@ -699,6 +717,7 @@ pub async fn error(req: Request<Body>, msg: String) -> Result<Response<Body>, St
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::format_num;
|
use super::format_num;
|
||||||
|
use super::rewrite_urls;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn format_num_works() {
|
fn format_num_works() {
|
||||||
|
@ -708,4 +727,14 @@ mod tests {
|
||||||
assert_eq!(format_num(1001), ("1.0k".to_string(), "1001".to_string()));
|
assert_eq!(format_num(1001), ("1.0k".to_string(), "1001".to_string()));
|
||||||
assert_eq!(format_num(1_999_999), ("2.0m".to_string(), "1999999".to_string()));
|
assert_eq!(format_num(1_999_999), ("2.0m".to_string(), "1999999".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rewrite_urls_removes_backslashes() {
|
||||||
|
let comment_body_html =
|
||||||
|
r#"<a href=\"https://www.reddit.com/r/linux%5C_gaming/comments/x/just%5C_a%5C_test%5C/\">https://www.reddit.com/r/linux\\_gaming/comments/x/just\\_a\\_test/</a>"#;
|
||||||
|
assert_eq!(
|
||||||
|
rewrite_urls(comment_body_html),
|
||||||
|
r#"<a href="https://www.reddit.com/r/linux_gaming/comments/x/just_a_test/">https://www.reddit.com/r/linux_gaming/comments/x/just_a_test/</a>"#
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
139
static/style.css
139
static/style.css
|
@ -45,124 +45,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light theme setting */
|
/* Other themes are located in the "themes" folder */
|
||||||
.light {
|
|
||||||
--accent: #009a9a;
|
|
||||||
--green: #00a229;
|
|
||||||
--text: black;
|
|
||||||
--foreground: #f5f5f5;
|
|
||||||
--background: #ddd;
|
|
||||||
--outside: #ececec;
|
|
||||||
--post: #eee;
|
|
||||||
--panel-border: 1px solid #ccc;
|
|
||||||
--highlighted: white;
|
|
||||||
--visited: #555;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Black theme setting */
|
|
||||||
.black {
|
|
||||||
--accent: #009a9a;
|
|
||||||
--green: #00a229;
|
|
||||||
--text: white;
|
|
||||||
--foreground: #0f0f0f;
|
|
||||||
--background: black;
|
|
||||||
--outside: black;
|
|
||||||
--post: black;
|
|
||||||
--panel-border: 2px solid #0f0f0f;
|
|
||||||
--highlighted: #0f0f0f;
|
|
||||||
--visited: #aaa;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dracula theme setting */
|
|
||||||
.dracula {
|
|
||||||
--accent: #bd93f9;
|
|
||||||
--green: #50fa7b;
|
|
||||||
--text: #f8f8f2;
|
|
||||||
--foreground: #3d4051;
|
|
||||||
--background: #282a36;
|
|
||||||
--outside: #393c4d;
|
|
||||||
--post: #333544;
|
|
||||||
--panel-border: 2px solid #44475a;
|
|
||||||
--highlighted: #4e5267;
|
|
||||||
--visited: #969692;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Nord theme setting */
|
|
||||||
.nord {
|
|
||||||
--accent: #8fbcbb;
|
|
||||||
--green: #a3be8c;
|
|
||||||
--text: #eceff4;
|
|
||||||
--foreground: #3b4252;
|
|
||||||
--background: #2e3440;
|
|
||||||
--outside: #434c5e;
|
|
||||||
--post: #434c5e;
|
|
||||||
--panel-border: 2px solid #4c566a;
|
|
||||||
--highlighted: #3b4252;
|
|
||||||
--visited: #a3a5aa;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Laserwave theme setting */
|
|
||||||
.laserwave {
|
|
||||||
--accent: #eb64b9;
|
|
||||||
--green: #74dfc4;
|
|
||||||
--text: #e0dfe1;
|
|
||||||
--foreground: #302a36;
|
|
||||||
--background: #27212e;
|
|
||||||
--outside: #3e3647;
|
|
||||||
--post: #3e3647;
|
|
||||||
--panel-border: 2px solid #2f2738;
|
|
||||||
--highlighted: #302a36;
|
|
||||||
--visited: #91889b;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Violet theme setting */
|
|
||||||
.violet {
|
|
||||||
--accent: #7c71dd;
|
|
||||||
--green: #5cff85;
|
|
||||||
--text: white;
|
|
||||||
--foreground: #1F2347;
|
|
||||||
--background: #12152b;
|
|
||||||
--outside: #181c3a;
|
|
||||||
--post: #181c3a;
|
|
||||||
--panel-border: 1px solid #1F2347;
|
|
||||||
--highlighted: #1F2347;
|
|
||||||
--visited: #aaa;
|
|
||||||
--shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Gold theme setting */
|
|
||||||
.gold {
|
|
||||||
--accent: #f2aa4c;
|
|
||||||
--green: #5cff85;
|
|
||||||
--text: white;
|
|
||||||
--foreground: #234;
|
|
||||||
--background: #101820;
|
|
||||||
--outside: #1b2936;
|
|
||||||
--post: #1b2936;
|
|
||||||
--panel-border: 0px solid black;
|
|
||||||
--highlighted: #234;
|
|
||||||
--visited: #aaa;
|
|
||||||
--shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rosebox theme setting */
|
|
||||||
.rosebox {
|
|
||||||
--accent: #a57562;
|
|
||||||
--green: #a3be8c;
|
|
||||||
--text: white;
|
|
||||||
--foreground: #222;
|
|
||||||
--background: #262626;
|
|
||||||
--outside: #222;
|
|
||||||
--post: #222;
|
|
||||||
--panel-border: 1px solid #222;
|
|
||||||
--highlighted: #262626;
|
|
||||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* General */
|
/* General */
|
||||||
|
|
||||||
|
@ -177,6 +60,7 @@
|
||||||
|
|
||||||
html, body, div, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, blockquote,
|
html, body, div, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, blockquote,
|
||||||
pre, form, fieldset, table, th, td, select, input {
|
pre, form, fieldset, table, th, td, select, input {
|
||||||
|
accent-color: var(--accent);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: "Inter", sans-serif;
|
font-family: "Inter", sans-serif;
|
||||||
|
@ -487,7 +371,7 @@ aside {
|
||||||
|
|
||||||
/* Sorting and Search */
|
/* Sorting and Search */
|
||||||
|
|
||||||
select, #search, #sort_options, #inside, #searchbox > *, #sort_submit {
|
select, #search, #sort_options, #listing_options, #inside, #searchbox > *, #sort_submit {
|
||||||
height: 38px;
|
height: 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +447,11 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||||
border-radius: 5px 0px 0px 5px;
|
border-radius: 5px 0px 0px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#listing_options + #sort_select {
|
||||||
|
margin-left: 10px;
|
||||||
|
border-radius: 5px 0px 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
#search_sort {
|
#search_sort {
|
||||||
background: var(--highlighted);
|
background: var(--highlighted);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
@ -591,7 +480,7 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_options, footer > a {
|
#sort_options, #listing_options, footer > a {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
|
@ -600,7 +489,7 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_options > a, footer > a {
|
#sort_options > a, #listing_options > a, footer > a {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -608,12 +497,12 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||||
transition: 0.2s background;
|
transition: 0.2s background;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_options > a.selected {
|
#sort_options > a.selected, #listing_options > a.selected {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
#sort_options > a:not(.selected):hover {
|
#sort_options > a:not(.selected):hover, #listing_options > a:not(.selected):hover {
|
||||||
background: var(--foreground);
|
background: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,6 +638,7 @@ a.search_subreddit:hover {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
margin: 5px 15px 5px 12px;
|
margin: 5px 15px 5px 12px;
|
||||||
grid-area: post_title;
|
grid-area: post_title;
|
||||||
}
|
}
|
||||||
|
@ -878,6 +768,7 @@ a.search_subreddit:hover {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
margin: 5px 12px;
|
margin: 5px 12px;
|
||||||
grid-area: post_media;
|
grid-area: post_media;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post_body {
|
.post_body {
|
||||||
|
@ -1311,6 +1202,8 @@ input[type="submit"] {
|
||||||
.md table {
|
.md table {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
max-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md code {
|
.md code {
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Black theme setting */
|
||||||
|
.black {
|
||||||
|
--accent: #009a9a;
|
||||||
|
--green: #00a229;
|
||||||
|
--text: white;
|
||||||
|
--foreground: #0f0f0f;
|
||||||
|
--background: black;
|
||||||
|
--outside: black;
|
||||||
|
--post: black;
|
||||||
|
--panel-border: 2px solid #0f0f0f;
|
||||||
|
--highlighted: #0f0f0f;
|
||||||
|
--visited: #aaa;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Dracula theme setting */
|
||||||
|
.dracula {
|
||||||
|
--accent: #bd93f9;
|
||||||
|
--green: #50fa7b;
|
||||||
|
--text: #f8f8f2;
|
||||||
|
--foreground: #3d4051;
|
||||||
|
--background: #282a36;
|
||||||
|
--outside: #393c4d;
|
||||||
|
--post: #333544;
|
||||||
|
--panel-border: 2px solid #44475a;
|
||||||
|
--highlighted: #4e5267;
|
||||||
|
--visited: #969692;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Gold theme setting */
|
||||||
|
.gold {
|
||||||
|
--accent: #f2aa4c;
|
||||||
|
--green: #5cff85;
|
||||||
|
--text: white;
|
||||||
|
--foreground: #234;
|
||||||
|
--background: #101820;
|
||||||
|
--outside: #1b2936;
|
||||||
|
--post: #1b2936;
|
||||||
|
--panel-border: 0px solid black;
|
||||||
|
--highlighted: #234;
|
||||||
|
--visited: #aaa;
|
||||||
|
--shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Laserwave theme setting */
|
||||||
|
.laserwave {
|
||||||
|
--accent: #eb64b9;
|
||||||
|
--green: #74dfc4;
|
||||||
|
--text: #e0dfe1;
|
||||||
|
--foreground: #302a36;
|
||||||
|
--background: #27212e;
|
||||||
|
--outside: #3e3647;
|
||||||
|
--post: #3e3647;
|
||||||
|
--panel-border: 2px solid #2f2738;
|
||||||
|
--highlighted: #302a36;
|
||||||
|
--visited: #91889b;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Light theme setting */
|
||||||
|
.light {
|
||||||
|
--accent: #009a9a;
|
||||||
|
--green: #00a229;
|
||||||
|
--text: black;
|
||||||
|
--foreground: #f5f5f5;
|
||||||
|
--background: #ddd;
|
||||||
|
--outside: #ececec;
|
||||||
|
--post: #eee;
|
||||||
|
--panel-border: 1px solid #ccc;
|
||||||
|
--highlighted: white;
|
||||||
|
--visited: #555;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Nord theme setting */
|
||||||
|
.nord {
|
||||||
|
--accent: #8fbcbb;
|
||||||
|
--green: #a3be8c;
|
||||||
|
--text: #eceff4;
|
||||||
|
--foreground: #3b4252;
|
||||||
|
--background: #2e3440;
|
||||||
|
--outside: #434c5e;
|
||||||
|
--post: #434c5e;
|
||||||
|
--panel-border: 2px solid #4c566a;
|
||||||
|
--highlighted: #3b4252;
|
||||||
|
--visited: #a3a5aa;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* Rosebox theme setting */
|
||||||
|
.rosebox {
|
||||||
|
--accent: #a57562;
|
||||||
|
--green: #a3be8c;
|
||||||
|
--text: white;
|
||||||
|
--foreground: #222;
|
||||||
|
--background: #262626;
|
||||||
|
--outside: #222;
|
||||||
|
--post: #222;
|
||||||
|
--panel-border: 1px solid #222;
|
||||||
|
--highlighted: #262626;
|
||||||
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* Violet theme setting */
|
||||||
|
.violet {
|
||||||
|
--accent: #7c71dd;
|
||||||
|
--green: #5cff85;
|
||||||
|
--text: white;
|
||||||
|
--foreground: #1F2347;
|
||||||
|
--background: #12152b;
|
||||||
|
--outside: #181c3a;
|
||||||
|
--post: #181c3a;
|
||||||
|
--panel-border: 1px solid #1F2347;
|
||||||
|
--highlighted: #1F2347;
|
||||||
|
--visited: #aaa;
|
||||||
|
--shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
|
@ -15,7 +15,7 @@
|
||||||
<div id="theme">
|
<div id="theme">
|
||||||
<label for="theme">Theme:</label>
|
<label for="theme">Theme:</label>
|
||||||
<select name="theme">
|
<select name="theme">
|
||||||
{% call utils::options(prefs.theme, ["system", "light", "dark", "black", "dracula", "nord", "laserwave", "violet", "gold", "rosebox"], "system") %}
|
{% call utils::options(prefs.theme, prefs.available_themes, "system") %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<legend>Interface</legend>
|
<legend>Interface</legend>
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
|
|
||||||
<div id="settings_note">
|
<div id="settings_note">
|
||||||
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
<p><b>Note:</b> settings and subscriptions are saved in browser cookies. Clearing your cookies will reset them.</p><br>
|
||||||
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
<p>You can restore your current settings and subscriptions after clearing your cookies using <a href="/settings/restore/?theme={{ prefs.theme }}&front_page={{ prefs.front_page }}&layout={{ prefs.layout }}&wide={{ prefs.wide }}&post_sort={{ prefs.post_sort }}&comment_sort={{ prefs.comment_sort }}&show_nsfw={{ prefs.show_nsfw }}&use_hls={{ prefs.use_hls }}&hide_hls_notification={{ prefs.hide_hls_notification }}&subscriptions={{ prefs.subscriptions.join("%2B") }}&filters={{ prefs.filters.join("%2B") }}">this link</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if sub.name.contains("+") %}
|
{% 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>
|
<button id="multisub" class="subscribe" title="Subscribe to each sub in this multireddit">Subscribe to Multireddit</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -74,12 +74,12 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_filtered || (!sub.name.is_empty() && !sub.name.contains("+")) %}
|
{% if is_filtered || (!sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+")) %}
|
||||||
<aside>
|
<aside>
|
||||||
{% if is_filtered %}
|
{% if is_filtered %}
|
||||||
<center>(Content from r/{{ sub.name }} has been filtered)</center>
|
<center>(Content from r/{{ sub.name }} has been filtered)</center>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if !sub.name.is_empty() && !sub.name.contains("+") %}
|
{% if !sub.name.is_empty() && sub.name != "all" && sub.name != "popular" && !sub.name.contains("+") %}
|
||||||
<div class="panel" id="subreddit">
|
<div class="panel" id="subreddit">
|
||||||
{% if sub.wiki %}
|
{% if sub.wiki %}
|
||||||
<div id="top">
|
<div id="top">
|
||||||
|
@ -101,22 +101,22 @@
|
||||||
<div id="sub_actions">
|
<div id="sub_actions">
|
||||||
<div id="sub_subscription">
|
<div id="sub_subscription">
|
||||||
{% if prefs.subscriptions.contains(sub.name) %}
|
{% 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>
|
<button class="unsubscribe">Unsubscribe</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% 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>
|
<button class="subscribe">Subscribe</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div id="sub_filter">
|
<div id="sub_filter">
|
||||||
{% if prefs.filters.contains(sub.name) %}
|
{% 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>
|
<button class="unfilter">Unfilter</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% 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>
|
<button class="filter">Filter</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
{% if !is_filtered %}
|
{% if !is_filtered %}
|
||||||
<div id="column_one">
|
<div id="column_one">
|
||||||
<form id="sort">
|
<form id="sort">
|
||||||
<select name="sort">
|
<div id="listing_options">
|
||||||
{% call utils::options(sort.0, ["hot", "new", "top"], "") %}
|
{% call utils::sort(["/user/", user.name.as_str()].concat(), ["overview", "comments", "submitted"], listing) %}
|
||||||
</select>{% if sort.0 == "top" %}<select id="timeframe" name="t">
|
</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") %}
|
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
|
||||||
</select>{% endif %}<button id="sort_submit" class="submit">
|
</select>{% endif %}<button id="sort_submit" class="submit">
|
||||||
<svg width="15" viewBox="0 0 110 100" fill="none" stroke-width="10" stroke-linecap="round">
|
<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("") %}
|
{% let name = ["u_", user.name.as_str()].join("") %}
|
||||||
<div id="user_subscription">
|
<div id="user_subscription">
|
||||||
{% if prefs.subscriptions.contains(name) %}
|
{% 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>
|
<button class="unsubscribe">Unfollow</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="/r/{{ name }}/subscribe" method="POST">
|
<form action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}" method="POST">
|
||||||
<button class="subscribe">Follow</button>
|
<button class="subscribe">Follow</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div id="user_filter">
|
<div id="user_filter">
|
||||||
{% if prefs.filters.contains(name) %}
|
{% 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>
|
<button class="unfilter">Unfilter</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form action="/r/{{ name }}/filter" method="POST">
|
<form action="/r/{{ name }}/filter?redirect={{ redirect_url }}" method="POST">
|
||||||
<button class="filter">Filter</button>
|
<button class="filter">Filter</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
Loading…
Reference in New Issue