Merge pull request #5 from spikecodes/master

Merge upstream
This commit is contained in:
robrobinbin 2021-02-01 20:23:35 +01:00 committed by GitHub
commit 5030c418de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 507 additions and 269 deletions

181
Cargo.lock generated
View File

@ -346,15 +346,6 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "async-mutex"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e"
dependencies = [
"event-listener",
]
[[package]] [[package]]
name = "async-recursion" name = "async-recursion"
version = "0.3.1" version = "0.3.1"
@ -489,9 +480,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.5.0" version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59" checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -520,40 +511,6 @@ dependencies = [
"bytes 1.0.1", "bytes 1.0.1",
] ]
[[package]]
name = "cached"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e2afe73808fbaac302e39c9754bfc3c4b4d0f99c9c240b9f4e4efc841ad1b74"
dependencies = [
"async-mutex",
"async-trait",
"cached_proc_macro",
"cached_proc_macro_types",
"futures",
"hashbrown",
"once_cell",
]
[[package]]
name = "cached_proc_macro"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf857ae42d910aede5c5186e62684b0d7a597ce2fe3bd14448ab8f7ef439848c"
dependencies = [
"async-mutex",
"cached_proc_macro_types",
"darling",
"quote",
"syn",
]
[[package]]
name = "cached_proc_macro_types"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.66" version = "1.0.66"
@ -574,9 +531,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chunked_transfer" name = "chunked_transfer"
version = "1.3.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca" checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]] [[package]]
name = "const_fn" name = "const_fn"
@ -616,41 +573,6 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
] ]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "0.99.11" version = "0.99.11"
@ -704,17 +626,11 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "event-listener"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"crc32fast", "crc32fast",
@ -969,12 +885,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.0" version = "0.2.0"
@ -1034,9 +944,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.46" version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1078,19 +988,18 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.82" version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff"
[[package]] [[package]]
name = "libreddit" name = "libreddit"
version = "0.2.8" version = "0.2.9"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"askama", "askama",
"async-recursion", "async-recursion",
"base64 0.13.0", "base64 0.13.0",
"cached",
"futures", "futures",
"regex", "regex",
"serde", "serde",
@ -1117,11 +1026,11 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.13" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 1.0.0",
] ]
[[package]] [[package]]
@ -1222,9 +1131,9 @@ dependencies = [
[[package]] [[package]]
name = "nom" name = "nom"
version = "6.0.1" version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88034cfd6b4a0d54dd14f4a507eceee36c0b70e5a02236c4e4df571102be17f0" checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1"
dependencies = [ dependencies = [
"bitvec", "bitvec",
"lexical-core", "lexical-core",
@ -1576,18 +1485,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.120" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab" checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.120" version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775" checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1738,17 +1647,11 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.59" version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cb8b1b4ebf86a89ee88cbd201b022b94138c623644d035185c84d3f41b7e66" checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1783,9 +1686,9 @@ dependencies = [
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.1" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301bdd13d23c49672926be451130892d274d3ba0b410c18e00daa7990ff38d99" checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915"
dependencies = [ dependencies = [
"once_cell", "once_cell",
] ]
@ -1801,9 +1704,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.2.24" version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7" checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7"
dependencies = [ dependencies = [
"const_fn", "const_fn",
"libc", "libc",
@ -1839,9 +1742,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.1.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -1854,9 +1757,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "0.2.24" version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
dependencies = [ dependencies = [
"bytes 0.5.6", "bytes 0.5.6",
"futures-core", "futures-core",
@ -2062,9 +1965,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.69" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -2072,9 +1975,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.69" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
@ -2087,9 +1990,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.69" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -2097,9 +2000,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.69" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2110,15 +2013,15 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.69" version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.46" version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",

View File

@ -3,7 +3,7 @@ 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.2.8" version = "0.2.9"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"] authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2018" edition = "2018"
@ -12,11 +12,10 @@ base64 = "0.13"
actix-web = { version = "3.3", features = ["rustls"] } actix-web = { version = "3.3", features = ["rustls"] }
futures = "0.3" futures = "0.3"
askama = "0.10" askama = "0.10"
ureq = "2.0" ureq = "2"
serde = { version = "1.0", default_features = false, features = ["derive"] } serde = { version = "1.0", default_features = false, features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
async-recursion = "0.3" async-recursion = "0.3"
url = "2.2" url = "2.2"
regex = "1.4" regex = "1.4"
time = "0.2" time = "0.2"
cached = "0.23.0"

View File

@ -2,7 +2,7 @@
> An alternative private front-end to Reddit > An alternative private front-end to Reddit
![screenshot](https://i.ibb.co/FxxbKM6/libreddit-rust.png) ![screenshot](https://i.ibb.co/F0JsY5K/image.png)
--- ---
@ -40,7 +40,9 @@ Feel free to [open an issue](https://github.com/spikecodes/libreddit/issues/new)
| [libreddit.dothq.co](https://libreddit.dothq.co) | 🇺🇸 US | ✅ | | [libreddit.dothq.co](https://libreddit.dothq.co) | 🇺🇸 US | ✅ |
| [libreddit.insanity.wtf](https://libreddit.insanity.wtf) | 🇺🇸 US | ✅ | | [libreddit.insanity.wtf](https://libreddit.insanity.wtf) | 🇺🇸 US | ✅ |
| [libreddit.kavin.rocks](https://libreddit.kavin.rocks) | 🇮🇳 IN | ✅ | | [libreddit.kavin.rocks](https://libreddit.kavin.rocks) | 🇮🇳 IN | ✅ |
| [libreddit.himiko.cloud](https://libreddit.himiko.cloud) | 🇧🇬 BG | |
| [spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion](http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion) | 🇮🇳 IN | | | [spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion](http://spjmllawtheisznfs7uryhxumin26ssv2draj7oope3ok3wuhy43eoyd.onion) | 🇮🇳 IN | |
| [fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion](http://fwhhsbrbltmrct5hshrnqlqygqvcgmnek3cnka55zj4y7nuus5muwyyd.onion) | 🇩🇪 DE | |
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. 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.
@ -123,7 +125,7 @@ Results from Google Lighthouse ([Libreddit Report](https://lighthouse-dot-webdot
For transparency, I hope to describe all the ways Libreddit handles user privacy. For transparency, I hope to describe all the ways Libreddit handles user privacy.
**Logging:** In production (when running the binary, hosting with docker, or using the official instances), Libreddit logs nothing. When debugging (running from source without `--release`), Libreddit logs post IDs and URL paths fetched to aid with troubleshooting. **Logging:** In production (when running the binary, hosting with docker, or using the official instances), Libreddit logs when Reddit is ratelimiting Libreddit and when Reddit's JSON responses can't be parsed. When debugging (running from source without `--release`), Libreddit logs post IDs and URL paths fetched to aid with troubleshooting.
**DNS:** Both official domains (`libredd.it` and `libreddit.spike.codes`) use Cloudflare as the DNS resolver. Though, the sites are not proxied through Cloudflare meaning Cloudflare doesn't have access to user traffic. **DNS:** Both official domains (`libredd.it` and `libreddit.spike.codes`) use Cloudflare as the DNS resolver. Though, the sites are not proxied through Cloudflare meaning Cloudflare doesn't have access to user traffic.

View File

@ -19,6 +19,21 @@ async fn style() -> HttpResponse {
HttpResponse::Ok().content_type("text/css").body(include_str!("../static/style.css")) HttpResponse::Ok().content_type("text/css").body(include_str!("../static/style.css"))
} }
// Required for creating a PWA
async fn manifest() -> HttpResponse {
HttpResponse::Ok().content_type("application/json").body(include_str!("../static/manifest.json"))
}
// Required for the manifest to be valid
async fn pwa_logo() -> HttpResponse {
HttpResponse::Ok().content_type("image/png").body(include_bytes!("../static/logo.png").as_ref())
}
// Required for iOS App Icons
async fn iphone_logo() -> HttpResponse {
HttpResponse::Ok().content_type("image/png").body(include_bytes!("../static/touch-icon-iphone.png").as_ref())
}
async fn robots() -> HttpResponse { async fn robots() -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400") .header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
@ -27,6 +42,7 @@ async fn robots() -> HttpResponse {
async fn favicon() -> HttpResponse { async fn favicon() -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_type("image/x-icon")
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400") .header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
.body(include_bytes!("../static/favicon.ico").as_ref()) .body(include_bytes!("../static/favicon.ico").as_ref())
} }
@ -53,7 +69,7 @@ async fn main() -> std::io::Result<()> {
.wrap_fn(move |req, srv| { .wrap_fn(move |req, srv| {
let secure = req.connection_info().scheme() == "https"; let secure = req.connection_info().scheme() == "https";
let https_url = format!("https://{}{}", req.connection_info().host(), req.uri().to_string()); let https_url = format!("https://{}{}", req.connection_info().host(), req.uri().to_string());
srv.call(req).map(move |res: Result<ServiceResponse, _>| srv.call(req).map(move |res: Result<ServiceResponse, _>| {
if force_https && !secure { if force_https && !secure {
Ok(ServiceResponse::new( Ok(ServiceResponse::new(
res.unwrap().request().to_owned(), res.unwrap().request().to_owned(),
@ -62,16 +78,30 @@ async fn main() -> std::io::Result<()> {
} else { } else {
res res
} }
) })
}) })
// Append trailing slash and remove double slashes // Append trailing slash and remove double slashes
.wrap(middleware::NormalizePath::default()) .wrap(middleware::NormalizePath::default())
// Apply default headers for security
.wrap(
middleware::DefaultHeaders::new()
.header("Referrer-Policy", "no-referrer")
.header("X-Content-Type-Options", "nosniff")
.header("X-Frame-Options", "DENY")
.header(
"Content-Security-Policy",
"default-src 'none'; manifest-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self'; frame-ancestors 'none';",
),
)
// Default service in case no routes match // Default service in case no routes match
.default_service(web::get().to(|| utils::error("Nothing here".to_string()))) .default_service(web::get().to(|| utils::error("Nothing here".to_string())))
// Read static files // Read static files
.route("/style.css/", web::get().to(style)) .route("/style.css/", web::get().to(style))
.route("/favicon.ico/", web::get().to(favicon)) .route("/favicon.ico/", web::get().to(favicon))
.route("/robots.txt/", web::get().to(robots)) .route("/robots.txt/", web::get().to(robots))
.route("/manifest.json/", web::get().to(manifest))
.route("/logo.png/", web::get().to(pwa_logo))
.route("/touch-icon-iphone.png/", web::get().to(iphone_logo))
// Proxy media through Libreddit // Proxy media through Libreddit
.route("/proxy/{url:.*}/", web::get().to(proxy::handler)) .route("/proxy/{url:.*}/", web::get().to(proxy::handler))
// Browse user profile // Browse user profile
@ -92,6 +122,8 @@ async fn main() -> std::io::Result<()> {
// See posts and info about subreddit // See posts and info about subreddit
.route("/", web::get().to(subreddit::page)) .route("/", web::get().to(subreddit::page))
.route("/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page)) .route("/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page))
// Handle subscribe/unsubscribe
.route("/{action:subscribe|unsubscribe}/", web::post().to(subreddit::subscriptions))
// View post on subreddit // View post on subreddit
.service( .service(
web::scope("/comments/{id}/{title}") web::scope("/comments/{id}/{title}")

View File

@ -171,7 +171,11 @@ async fn parse_comments(json: &serde_json::Value) -> Vec<Comment> {
}, },
distinguished: val(&comment, "distinguished"), distinguished: val(&comment, "distinguished"),
}, },
score: format_num(score), score: if comment["data"]["score_hidden"].as_bool().unwrap_or_default() {
"".to_string()
} else {
format_num(score)
},
rel_time, rel_time,
created, created,
replies, replies,

View File

@ -21,29 +21,27 @@ pub async fn handler(web::Path(b64): web::Path<String>) -> Result<HttpResponse>
"v.redd.it", "v.redd.it",
]; ];
match decode(b64) { let decoded = decode(b64).map(|bytes| String::from_utf8(bytes).unwrap_or_default());
Ok(bytes) => {
let media = String::from_utf8(bytes).unwrap_or_default();
match Url::parse(media.as_str()) { match decoded {
Ok(url) => { Ok(media) => match Url::parse(media.as_str()) {
let domain = url.domain().unwrap_or_default(); Ok(url) => {
let domain = url.domain().unwrap_or_default();
if domains.contains(&domain) { if domains.contains(&domain) {
Client::default().get(media.replace("&amp;", "&")).send().await.map_err(Error::from).map(|res| { Client::default().get(media.replace("&amp;", "&")).send().await.map_err(Error::from).map(|res| {
HttpResponse::build(res.status()) HttpResponse::build(res.status())
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400") .header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
.header("Content-Length", res.headers().get("Content-Length").unwrap().to_owned()) .header("Content-Length", res.headers().get("Content-Length").unwrap().to_owned())
.header("Content-Type", res.headers().get("Content-Type").unwrap().to_owned()) .header("Content-Type", res.headers().get("Content-Type").unwrap().to_owned())
.streaming(res) .streaming(res)
}) })
} else { } else {
Err(error::ErrorForbidden("Resource must be from Reddit")) Err(error::ErrorForbidden("Resource must be from Reddit"))
}
} }
_ => Err(error::ErrorBadRequest("Can't parse base64 into URL")),
} }
} _ => Err(error::ErrorBadRequest("Can't parse base64 into URL")),
},
_ => Err(error::ErrorBadRequest("Can't decode base64")), _ => Err(error::ErrorBadRequest("Can't decode base64")),
} }
} }

View File

@ -33,7 +33,7 @@ struct SearchTemplate {
// SERVICES // SERVICES
pub async fn find(req: HttpRequest) -> HttpResponse { pub async fn find(req: HttpRequest) -> HttpResponse {
let nsfw_results = if cookie(&req, "hide_nsfw") != "on" { "&include_over_18=on" } else { "" }; let nsfw_results = if cookie(&req, "show_nsfw") == "on" { "&include_over_18=on" } else { "" };
let path = format!("{}.json?{}{}", req.path(), req.query_string(), nsfw_results); let path = format!("{}.json?{}{}", req.path(), req.query_string(), nsfw_results);
let sub = req.match_info().get("sub").unwrap_or("").to_string(); let sub = req.match_info().get("sub").unwrap_or("").to_string();

View File

@ -18,7 +18,7 @@ pub struct SettingsForm {
layout: Option<String>, layout: Option<String>,
wide: Option<String>, wide: Option<String>,
comment_sort: Option<String>, comment_sort: Option<String>,
hide_nsfw: Option<String>, show_nsfw: Option<String>,
} }
// FUNCTIONS // FUNCTIONS
@ -33,8 +33,8 @@ pub async fn get(req: HttpRequest) -> HttpResponse {
pub async fn set(_req: HttpRequest, form: Form<SettingsForm>) -> HttpResponse { pub async fn set(_req: HttpRequest, form: Form<SettingsForm>) -> HttpResponse {
let mut res = HttpResponse::Found(); let mut res = HttpResponse::Found();
let names = vec!["theme", "front_page", "layout", "wide", "comment_sort", "hide_nsfw"]; let names = vec!["theme", "front_page", "layout", "wide", "comment_sort", "show_nsfw"];
let values = vec![&form.theme, &form.front_page, &form.layout, &form.wide, &form.comment_sort, &form.hide_nsfw]; let values = vec![&form.theme, &form.front_page, &form.layout, &form.wide, &form.comment_sort, &form.show_nsfw];
for (i, name) in names.iter().enumerate() { for (i, name) in names.iter().enumerate() {
match values[i] { match values[i] {

View File

@ -1,7 +1,8 @@
// CRATES // CRATES
use crate::utils::*; use crate::utils::*;
use actix_web::{HttpRequest, HttpResponse, Result}; use actix_web::{cookie::Cookie, HttpRequest, HttpResponse, Result};
use askama::Template; use askama::Template;
use time::{Duration, OffsetDateTime};
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -25,23 +26,43 @@ struct WikiTemplate {
// SERVICES // SERVICES
pub async fn page(req: HttpRequest) -> HttpResponse { pub async fn page(req: HttpRequest) -> HttpResponse {
let path = format!("{}.json?{}", req.path(), req.query_string()); let subscribed = cookie(&req, "subscriptions");
let default = cookie(&req, "front_page"); let front_page = cookie(&req, "front_page");
let sub_name = req let sort = req.match_info().get("sort").unwrap_or("hot").to_string();
let sub = req
.match_info() .match_info()
.get("sub") .get("sub")
.unwrap_or(if default.is_empty() { "popular" } else { default.as_str() }) .map(String::from)
.to_string(); .unwrap_or(if front_page == "default" || front_page.is_empty() {
let sort = req.match_info().get("sort").unwrap_or("hot").to_string(); if subscribed.is_empty() {
"popular".to_string()
} else {
subscribed.to_owned()
}
} else {
front_page.to_owned()
});
let path = format!("/r/{}/{}.json?{}", sub, sort, req.query_string());
match fetch_posts(&path, String::new()).await { match fetch_posts(&path, String::new()).await {
Ok((posts, after)) => { Ok((posts, after)) => {
// If you can get subreddit posts, also request subreddit metadata // If you can get subreddit posts, also request subreddit metadata
let sub = if !sub_name.contains('+') && sub_name != "popular" && sub_name != "all" { let sub = if !sub.contains('+') && sub != subscribed && sub != "popular" && sub != "all" {
subreddit(&sub_name).await.unwrap_or_default() // Regular subreddit
} else if sub_name.contains('+') { subreddit(&sub).await.unwrap_or_default()
} else if sub == subscribed {
// Subscription feed
if req.path().starts_with("/r/") {
subreddit(&sub).await.unwrap_or_default()
} else {
Subreddit::default()
}
} else if sub.contains('+') {
// Multireddit
Subreddit { Subreddit {
name: sub_name, name: sub,
..Subreddit::default() ..Subreddit::default()
} }
} else { } else {
@ -63,6 +84,50 @@ pub async fn page(req: HttpRequest) -> HttpResponse {
} }
} }
// Sub or unsub by setting subscription cookie using response "Set-Cookie" header
pub async fn subscriptions(req: HttpRequest) -> HttpResponse {
let mut res = HttpResponse::Found();
let sub = req.match_info().get("sub").unwrap_or_default().to_string();
let action = req.match_info().get("action").unwrap_or_default().to_string();
let mut sub_list = prefs(req.to_owned()).subs;
// Modify sub list based on action
if action == "subscribe" && !sub_list.contains(&sub) {
sub_list.push(sub.to_owned());
sub_list.sort();
} else if action == "unsubscribe" {
sub_list.retain(|s| s != &sub);
}
// Delete cookie if empty, else set
if sub_list.is_empty() {
res.del_cookie(&Cookie::build("subscriptions", "").path("/").finish());
} else {
res.cookie(
Cookie::build("subscriptions", sub_list.join("+"))
.path("/")
.http_only(true)
.expires(OffsetDateTime::now_utc() + Duration::weeks(52))
.finish(),
);
}
// Redirect back to subreddit
// check for redirect parameter if unsubscribing from outside sidebar
let redirect_path = param(&req.uri().to_string(), "redirect");
let path = if !redirect_path.is_empty() && redirect_path.starts_with('/') {
redirect_path
} else {
format!("/r/{}", sub)
};
res
.content_type("text/html")
.set_header("Location", path.to_owned())
.body(format!("Redirecting to <a href=\"{0}\">{0}</a>...", path))
}
pub async fn wiki(req: HttpRequest) -> HttpResponse { pub async fn wiki(req: HttpRequest) -> HttpResponse {
let sub = req.match_info().get("sub").unwrap_or("reddit.com").to_string(); let sub = req.match_info().get("sub").unwrap_or("reddit.com").to_string();
let page = req.match_info().get("page").unwrap_or("index").to_string(); let page = req.match_info().get("page").unwrap_or("index").to_string();

View File

@ -9,7 +9,7 @@ use serde_json::{from_str, Value};
use std::collections::HashMap; use std::collections::HashMap;
use time::{Duration, OffsetDateTime}; use time::{Duration, OffsetDateTime};
use url::Url; use url::Url;
use cached::proc_macro::cached; // use cached::proc_macro::cached;
// //
// STRUCTS // STRUCTS
@ -127,8 +127,9 @@ pub struct Preferences {
pub front_page: String, pub front_page: String,
pub layout: String, pub layout: String,
pub wide: String, pub wide: String,
pub hide_nsfw: String, pub show_nsfw: String,
pub comment_sort: String, pub comment_sort: String,
pub subs: Vec<String>,
} }
// //
@ -142,8 +143,9 @@ pub fn prefs(req: HttpRequest) -> Preferences {
front_page: cookie(&req, "front_page"), front_page: cookie(&req, "front_page"),
layout: cookie(&req, "layout"), layout: cookie(&req, "layout"),
wide: cookie(&req, "wide"), wide: cookie(&req, "wide"),
hide_nsfw: cookie(&req, "hide_nsfw"), show_nsfw: cookie(&req, "show_nsfw"),
comment_sort: cookie(&req, "comment_sort"), comment_sort: cookie(&req, "comment_sort"),
subs: cookie(&req, "subscriptions").split('+').map(String::from).filter(|s| !s.is_empty()).collect(),
} }
} }
@ -341,7 +343,11 @@ pub async fn fetch_posts(path: &str, fallback_title: String) -> Result<(Vec<Post
}, },
distinguished: val(post, "distinguished"), distinguished: val(post, "distinguished"),
}, },
score: format_num(score), score: if post["data"]["hide_score"].as_bool().unwrap_or_default() {
"".to_string()
} else {
format_num(score)
},
upvote_ratio: ratio as i64, upvote_ratio: ratio as i64,
post_type, post_type,
thumbnail: Media { thumbnail: Media {
@ -393,7 +399,7 @@ pub async fn error(msg: String) -> HttpResponse {
} }
// Make a request to a Reddit API and parse the JSON response // Make a request to a Reddit API and parse the JSON response
#[cached(size=1000,time=60, result = true)] // #[cached(size=100,time=60, result = true)]
pub async fn request(path: String) -> Result<Value, String> { pub async fn request(path: String) -> Result<Value, String> {
let url = format!("https://www.reddit.com{}", path); let url = format!("https://www.reddit.com{}", path);
let user_agent = format!("web:libreddit:{}", env!("CARGO_PKG_VERSION")); let user_agent = format!("web:libreddit:{}", env!("CARGO_PKG_VERSION"));
@ -459,11 +465,11 @@ pub async fn request(path: String) -> Result<Value, String> {
// If response is success // If response is success
Ok(response) => { Ok(response) => {
// Parse the response from Reddit as JSON // Parse the response from Reddit as JSON
match from_str(&response.into_string().unwrap()) { let json_string = &response.into_string().unwrap_or_default();
match from_str(json_string) {
Ok(json) => Ok(json), Ok(json) => Ok(json),
Err(_) => { Err(e) => {
#[cfg(debug_assertions)] println!("{} - Failed to parse page JSON data: {} - {}", url, e, json_string);
dbg!(format!("{} - Failed to parse page JSON data", url));
Err("Failed to parse page JSON data".to_string()) Err("Failed to parse page JSON data".to_string())
} }
} }
@ -475,9 +481,8 @@ pub async fn request(path: String) -> Result<Value, String> {
Err("Page not found".to_string()) Err("Page not found".to_string())
} }
// If failed to send request // If failed to send request
Err(_e) => { Err(e) => {
#[cfg(debug_assertions)] println!("{} - Couldn't send request to Reddit: {}", url, e);
dbg!(format!("{} - {}", url, _e));
Err("Couldn't send request to Reddit, this instance may be being rate-limited. Try another.".to_string()) Err("Couldn't send request to Reddit, this instance may be being rate-limited. Try another.".to_string())
} }
} }

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

15
static/manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "Libreddit",
"short_name": "Libreddit",
"display": "standalone",
"background_color": "#2A3443",
"description": "An alternative private front-end to Reddit",
"theme_color": "#2A3443",
"icons": [
{
"src": "./logo.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

View File

@ -58,6 +58,11 @@
background: var(--accent); background: var(--accent);
} }
:focus {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
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 {
margin: 0; margin: 0;
@ -68,11 +73,12 @@ pre, form, fieldset, table, th, td, select, input {
body { body {
background: var(--background); background: var(--background);
font-size: 15px; font-size: 15px;
padding-top: 60px;
} }
nav { nav {
display: grid; display: grid;
grid-template-areas: "logo searchbox code"; grid-template-areas: "logo searchbox links";
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@ -83,7 +89,7 @@ nav {
font-size: 20px; font-size: 20px;
z-index: 1; z-index: 2;
top: 0; top: 0;
padding: 5px 15px; padding: 5px 15px;
min-height: 40px; min-height: 40px;
@ -92,23 +98,49 @@ nav {
} }
nav * { color: var(--text); } nav * { color: var(--text); }
nav #reddit, #code { color: var(--accent); } nav #reddit, #code > span { color: var(--accent); }
nav #logo { grid-area: logo; } nav #code > svg { stroke: var(--accent); }
nav #code { grid-area: code; }
nav #version { opacity: 50%; } nav #logo {
grid-area: logo;
white-space: nowrap;
margin-right: 5px;
}
nav #links {
grid-area: links;
margin-left: 10px;
display: flex;
}
nav #links svg {
display: none;
}
nav #version {
opacity: 50%;
vertical-align: -2px;
margin-right: 10px;
}
nav #libreddit {
vertical-align: -2px;
}
#settings_link { #settings_link {
font-size: 18px;
margin-left: 20px;
opacity: 0.8; opacity: 0.8;
} }
#code {
margin-left: 5px;
}
main { main {
display: flex; display: flex;
justify-content: center; justify-content: center;
max-width: 1000px; max-width: 1000px;
padding: 10px 20px; padding: 10px 20px;
margin: 60px auto 20px auto margin: 0 auto;
} }
.wide main { .wide main {
@ -232,6 +264,71 @@ aside {
color: var(--accent); color: var(--accent);
} }
/* Subscriptions */
#sub_subscription {
margin-top: 20px;
}
.subscribe, .unsubscribe {
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
.subscribe {
color: var(--foreground);
background-color: var(--accent);
}
.unsubscribe {
color: var(--text);
background-color: var(--highlighted);
}
/* Subscribed subreddit list */
#subscriptions {
position: relative;
border-radius: 5px;
border: var(--panel-border);
background-color: var(--outside);
align-items: center;
box-sizing: border-box;
font-size: 15px;
display: inline-block;
}
#subscriptions > summary {
padding: 8px 15px;
}
#sub_list {
position: absolute;
display: flex;
min-width: 100%;
border-radius: 5px;
box-shadow: var(--shadow);
background: var(--outside);
flex-direction: column;
overflow: auto;
z-index: 1;
}
#sub_list > a {
padding: 10px 20px;
transition: 0.2s background;
}
#sub_list > .selected {
background-color: var(--accent);
color: var(--foreground);
}
#sub_list > a:not(.selected):hover {
background-color: var(--foreground);
}
/* Wiki Pages */ /* Wiki Pages */
#wiki { #wiki {
@ -304,6 +401,7 @@ select, #search {
align-items: center; align-items: center;
border-right: 2px var(--outside) solid; border-right: 2px var(--outside) solid;
padding: 0 10px; padding: 0 10px;
max-width: 50%;
} }
#restrict_sr { margin-right: 5px; } #restrict_sr { margin-right: 5px; }
@ -451,10 +549,6 @@ a.search_subreddit:hover {
.post:not(:last-child) { margin-bottom: 10px; } .post:not(:last-child) { margin-bottom: 10px; }
.post.highlighted {
margin: 20px 0;
}
.post:hover { .post:hover {
background: var(--foreground); background: var(--foreground);
} }
@ -788,7 +882,7 @@ a.search_subreddit:hover {
/* Settings */ /* Settings */
#settings { #settings, #settings > form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@ -801,7 +895,7 @@ a.search_subreddit:hover {
opacity: 0.75; opacity: 0.75;
} }
#prefs { .prefs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -811,7 +905,7 @@ a.search_subreddit:hover {
border-radius: 5px; border-radius: 5px;
} }
#prefs > div { .prefs > div {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
@ -819,17 +913,21 @@ a.search_subreddit:hover {
align-items: center; align-items: center;
} }
#prefs > div:not(:last-of-type) { .prefs > div:not(:last-of-type) {
margin-bottom: 10px; margin-bottom: 10px;
} }
#prefs select { .prefs select {
border-radius: 5px; border-radius: 5px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
margin-left: 20px; margin-left: 20px;
background: var(--foreground); background: var(--foreground);
} }
aside.prefs {
margin-top: 20px;
}
#save { #save {
background: var(--highlighted); background: var(--highlighted);
padding: 10px 15px; padding: 10px 15px;
@ -842,6 +940,27 @@ input[type="submit"] {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
} }
#settings_subs {
list-style: none;
padding: 0;
}
#settings_subs > li {
display: flex;
margin: 10px 0;
}
#settings_subs > li:last-of-type { margin-bottom: 0; }
#settings_subs > li > span {
padding: 10px 0;
margin-right: auto;
}
#settings_subs .unsubscribe {
margin-left: 30px;
}
/* Markdown */ /* Markdown */
.md > *:not(:first-child) { .md > *:not(:first-child) {
@ -915,6 +1034,8 @@ td, th {
/* Mobile */ /* Mobile */
@media screen and (max-width: 480px) { @media screen and (max-width: 480px) {
#version { display: none; }
.post { .post {
grid-template: "post_header post_header post_thumbnail" auto grid-template: "post_header post_header post_thumbnail" auto
"post_title post_title post_thumbnail" 1fr "post_title post_title post_thumbnail" 1fr
@ -953,25 +1074,39 @@ td, th {
} }
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
body { padding-top: 120px }
main { main {
flex-direction: column-reverse; flex-direction: column-reverse;
padding: 10px; padding: 10px;
margin: 100px 0 10px 0; margin: 0 0 10px 0;
max-width: 100%; max-width: 100%;
} }
nav { nav {
grid-template-areas: 'logo code' 'searchbox searchbox'; grid-template-areas: 'logo links' 'searchbox searchbox';
padding: 10px; padding: 10px;
width: calc(100% - 20px); width: calc(100% - 20px);
} }
nav #links { margin-left: auto; }
nav #links span { display: none; }
nav #links svg { display: block; }
#subscriptions { position: unset; }
#sub_list {
left: 10px;
right: 10px;
min-width: auto;
}
aside, #subreddit, #user { aside, #subreddit, #user {
margin: 0; margin: 0;
max-width: 100%; max-width: 100%;
} }
#user, #sidebar { margin: 20px 0; } #user, #sidebar { margin: 20px 0; }
#logo { margin: 5px auto; } #logo, #links { margin-bottom: 5px; }
#searchbox { width: 100%; } #searchbox { width: calc(100vw - 35px); }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -3,13 +3,24 @@
<head> <head>
{% block head %} {% block head %}
<title>{% block title %}Libreddit{% endblock %}</title> <title>{% block title %}Libreddit{% endblock %}</title>
<meta http-equiv="Referrer-Policy" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; base-uri 'none'; img-src 'self' data:; form-action 'self';">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="description" content="View on Libreddit, an alternative private front-end to Reddit."> <meta name="description" content="View on Libreddit, an alternative private front-end to Reddit.">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- General PWA -->
<meta name="theme-color" content="#009a9a"/>
<!-- iOS Application -->
<meta name="apple-mobile-web-app-title" content="Libreddit">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<!-- Android -->
<meta name="theme-color" content="#2A3443">
<meta name="mobile-web-app-capable" content="yes">
<!-- iOS Logo -->
<link href="/touch-icon-iphone.png" rel="apple-touch-icon">
<!-- PWA Manifest -->
<link rel="manifest" type="application/json" href="/manifest.json">
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"> <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" type="text/css" href="/style.css">
{% endblock %} {% endblock %}
</head> </head>
<body class=" <body class="
@ -18,15 +29,22 @@
{% if prefs.theme != "system" %} {{ prefs.theme }}{% endif %}"> {% if prefs.theme != "system" %} {{ prefs.theme }}{% endif %}">
<!-- NAVIGATION BAR --> <!-- NAVIGATION BAR -->
<nav> <nav>
<p id="logo"> <div id="logo">
<a id="libreddit" href="/"> <a id="libreddit" href="/"><span id="lib">lib</span><span id="reddit">reddit.</span></a>
<span id="lib">lib</span><span id="reddit">reddit.</span>
</a>
<span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span> <span id="version">v{{ env!("CARGO_PKG_VERSION") }}</span>
<a id="settings_link" href="/settings">settings</a> {% block subscriptions %}{% endblock %}
</p> </div>
{% block search %}{% endblock %} {% block search %}{% endblock %}
<a id="code" href="https://github.com/spikecodes/libreddit">code</a> <div id="links">
<a id="settings_link" href="/settings">
<span>settings</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"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</a>
<a id="code" href="https://github.com/spikecodes/libreddit">
<span>code</span>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
</a>
</div>
</nav> </nav>
<!-- MAIN CONTENT --> <!-- MAIN CONTENT -->

View File

@ -13,6 +13,10 @@
<meta name="author" content="u/{{ post.author.name }}"> <meta name="author" content="u/{{ post.author.name }}">
{% endblock %} {% endblock %}
{% block subscriptions %}
{% call utils::sub_list(post.community.as_str()) %}
{% endblock %}
<!-- OPEN COMMENT MACRO --> <!-- OPEN COMMENT MACRO -->
{% macro comment(item) -%} {% macro comment(item) -%}
<div id="{{ item.id }}" class="comment"> <div id="{{ item.id }}" class="comment">

View File

@ -3,6 +3,10 @@
{% block title %}Libreddit: search results - {{ params.q }}{% endblock %} {% block title %}Libreddit: search results - {{ params.q }}{% endblock %}
{% block subscriptions %}
{% call utils::sub_list("") %}
{% endblock %}
{% block content %} {% block content %}
<div id="column_one"> <div id="column_one">
<form id="search_sort"> <form id="search_sort">
@ -42,7 +46,7 @@
{% endif %} {% endif %}
{% for post in posts %} {% for post in posts %}
{% if post.flags.nsfw && prefs.hide_nsfw == "on" %} {% if post.flags.nsfw && prefs.show_nsfw != "on" %}
{% else if post.title != "Comment" %} {% else if post.title != "Comment" %}
<div class="post {% if post.flags.stickied %}stickied{% endif %}"> <div class="post {% if post.flags.stickied %}stickied{% endif %}">
<p class="post_header"> <p class="post_header">

View File

@ -8,45 +8,63 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<form id="settings" action="/settings" method="POST"> <div id="settings">
<div id="prefs"> <form action="/settings" method="POST">
<p>Appearance</p> <div class="prefs">
<div id="theme"> <p>Appearance</p>
<label for="theme">Theme:</label> <div id="theme">
<select name="theme"> <label for="theme">Theme:</label>
{% call utils::options(prefs.theme, ["system", "light", "dark"], "system") %} <select name="theme">
</select> {% call utils::options(prefs.theme, ["system", "light", "dark"], "system") %}
</select>
</div>
<p>Interface</p>
<div id="front_page">
<label for="front_page">Front page:</label>
<select name="front_page">
{% call utils::options(prefs.front_page, ["default", "popular", "all"], "default") %}
</select>
</div>
<div id="layout">
<label for="layout">Layout:</label>
<select name="layout">
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %}
</select>
</div>
<div id="wide">
<label for="wide">Wide UI:</label>
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}>
</div>
<p>Content</p>
<div id="comment_sort">
<label for="comment_sort">Default comment sort:</label>
<select name="comment_sort">
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select>
</div>
<div id="show_nsfw">
<label for="show_nsfw">Show NSFW posts:</label>
<input type="checkbox" name="show_nsfw" {% if prefs.show_nsfw == "on" %}checked{% endif %}>
</div>
</div> </div>
<p>Interface</p> <p id="settings_note"><b>Note:</b> settings are saved in browser cookies. Clearing your cookie data will reset them.</p>
<div id="front_page"> <input id="save" type="submit" value="Save">
<label for="front_page">Front page:</label> </form>
<select name="front_page"> {% if prefs.subs.len() > 0 %}
{% call utils::options(prefs.front_page, ["popular", "all"], "popular") %} <aside class="prefs">
</select> <p>Subscribed Subreddits</p>
</div> <ul id="settings_subs">
<div id="layout"> {% for sub in prefs.subs %}
<label for="layout">Layout:</label> <li>
<select name="layout"> <span>{{ sub }}</span>
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "card") %} <form action="/r/{{ sub }}/unsubscribe/?redirect=/settings" method="POST">
</select> <button class="unsubscribe">Unsubscribe</button>
</div> </form>
<div id="wide"> </li>
<label for="wide">Wide UI:</label> {% endfor %}
<input type="checkbox" name="wide" {% if prefs.wide == "on" %}checked{% endif %}> </ul>
</div> </aside>
<p>Content</p> {% endif %}
<div id="comment_sort"> </div>
<label for="comment_sort">Default comment sort:</label>
<select name="comment_sort">
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %}
</select>
</div>
<div id="hide_nsfw">
<label for="hide_nsfw">Hide NSFW posts:</label>
<input type="checkbox" name="hide_nsfw" {% if prefs.hide_nsfw == "on" %}checked{% endif %}>
</div>
</div>
<p id="settings_note"><b>Note:</b> settings are saved in browser cookies. Clearing your cookie data will reset them.</p>
<input id="save" type="submit" value="Save">
</form>
{% endblock %} {% endblock %}

View File

@ -11,6 +11,10 @@
{% call utils::search(["/r/", sub.name.as_str()].concat(), "") %} {% call utils::search(["/r/", sub.name.as_str()].concat(), "") %}
{% endblock %} {% endblock %}
{% block subscriptions %}
{% call utils::sub_list(sub.name.as_str(), "wide") %}
{% endblock %}
{% block body %} {% block body %}
<main> <main>
<div id="column_one"> <div id="column_one">
@ -37,7 +41,7 @@
<div id="posts"> <div id="posts">
{% for post in posts %} {% for post in posts %}
{% if !(post.flags.nsfw && prefs.hide_nsfw == "on") %} {% if !(post.flags.nsfw && prefs.show_nsfw != "on") %}
<hr class="sep" /> <hr class="sep" />
<div class="post {% if post.flags.stickied %}stickied{% endif %}"> <div class="post {% if post.flags.stickied %}stickied{% endif %}">
<p class="post_header"> <p class="post_header">
@ -121,6 +125,17 @@
<div>{{ sub.members }}</div> <div>{{ sub.members }}</div>
<div>{{ sub.active }}</div> <div>{{ sub.active }}</div>
</div> </div>
<div id="sub_subscription">
{% if prefs.subs.contains(sub.name) %}
<form action="/r/{{ sub.name }}/unsubscribe" method="POST">
<button class="unsubscribe">Unsubscribe</button>
</form>
{% else %}
<form action="/r/{{ sub.name }}/subscribe" method="POST">
<button class="subscribe">Subscribe</button>
</form>
{% endif %}
</div>
</div> </div>
</div> </div>
<details class="panel" id="sidebar"> <details class="panel" id="sidebar">

View File

@ -7,6 +7,10 @@
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %} {% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %}
{% block subscriptions %}
{% call utils::sub_list("") %}
{% endblock %}
{% block body %} {% block body %}
<main> <main>
<div id="column_one"> <div id="column_one">
@ -27,7 +31,7 @@
<div id="posts"> <div id="posts">
{% for post in posts %} {% for post in posts %}
{% if post.flags.nsfw && prefs.hide_nsfw == "on" %} {% if post.flags.nsfw && prefs.show_nsfw != "on" %}
{% else if post.title != "Comment" %} {% else if post.title != "Comment" %}
<div class="post {% if post.flags.stickied %}stickied{% endif %}"> <div class="post {% if post.flags.stickied %}stickied{% endif %}">
<p class="post_header"> <p class="post_header">

View File

@ -39,3 +39,16 @@
{% else if flair_part.flair_part_type == "text" %}<span>{{ flair_part.value }}</span>{% endif %} {% else if flair_part.flair_part_type == "text" %}<span>{{ flair_part.value }}</span>{% endif %}
{% endfor %} {% endfor %}
{%- endmacro %} {%- endmacro %}
{% macro sub_list(current) -%}
{% if prefs.subs.len() > 0 %}
<details id="subscriptions">
<summary>Subscriptions</summary>
<div id="sub_list">
{% for sub in prefs.subs %}
<a href="/r/{{ sub }}" {% if sub == current %}class="selected"{% endif %}>{{ sub }}</a>
{% endfor %}
</div>
</details>
{% endif %}
{%- endmacro %}

View File

@ -10,6 +10,10 @@
{% call utils::search(["/r/", sub.as_str()].concat(), "") %} {% call utils::search(["/r/", sub.as_str()].concat(), "") %}
{% endblock %} {% endblock %}
{% block subscriptions %}
{% call utils::sub_list(sub.as_str()) %}
{% endblock %}
{% block body %} {% block body %}
<main> <main>
<div class="panel" id="column_one"> <div class="panel" id="column_one">