Compare commits

...

76 Commits

Author SHA1 Message Date
471d181284 Disable production error logging 2021-01-16 15:17:08 -08:00
0e48c66b8c Fix user agent 2021-01-16 15:13:34 -08:00
a0bc1732cf Moderator and admin distinguishers 2021-01-16 15:02:24 -08:00
6d5fd1dbf6 Use main instance more in Readme 2021-01-16 11:56:13 -08:00
0f6e73dd87 Reformat code 2021-01-16 11:50:12 -08:00
151490faf0 Add space next to comment collapse marker 2021-01-16 11:49:49 -08:00
fdf60e7255 Separate datetime into relative and absolute 2021-01-16 11:40:32 -08:00
ab102ca32c Merge pull request #57 from robrobinbin/master
Improve support for text-only browsers
2021-01-16 10:49:41 -08:00
998b301229 Improve support for text-only browsers 2021-01-16 11:00:15 +01:00
d7839899e6 Merge pull request #3 from spikecodes/master
Merge upstream
2021-01-16 09:20:14 +01:00
2385fa33ec Use ureq until AWC IO error is fixed 2021-01-15 21:26:51 -08:00
1fd688eeed Improve awc error log 2021-01-15 20:57:51 -08:00
65543a43b2 Make User-Agent Reddit-compliant 2021-01-15 20:29:34 -08:00
0099021478 Refactor flair spacing 2021-01-15 15:55:10 -08:00
3a9b2dba32 Fix error log 2021-01-15 15:35:09 -08:00
59021b9331 Switch back to ureq temporarily 2021-01-15 15:28:51 -08:00
078d6fe25b Request about pages before posts 2021-01-15 15:05:55 -08:00
373ce55203 Recommend secondary instance 2021-01-15 11:27:06 -08:00
aef0442e9d Add rate-limit warning 2021-01-15 11:24:12 -08:00
21ff8d7b6f Fix #56 2021-01-15 11:21:59 -08:00
bca2a7e540 Error logging 2021-01-15 10:58:53 -08:00
0c014ad41b Comment utils.rs 2021-01-14 15:13:52 -08:00
32b8637c7e Handle failed redirects 2021-01-14 14:56:28 -08:00
5ed122d92c Merge pull request #55 from robrobinbin/master
Add placeholder image for posts without thumbnail
2021-01-14 14:26:40 -08:00
45660816ce Add cardview to search results too 2021-01-14 21:53:07 +01:00
d19e73f059 Add placeholder for posts without thumbnail 2021-01-14 21:45:43 +01:00
18684c934b Refactor subreddit searching 2021-01-14 11:45:04 -08:00
cf4c5e1fe8 Implement #53 2021-01-14 10:57:50 -08:00
7ef4a20aff Merge pull request #54 from robrobinbin/master
Add subreddits to search results, closes #18
2021-01-14 10:54:20 -08:00
292f8fbbb7 remove lines that aren't used yet 2021-01-14 19:33:17 +01:00
735f79d80b Merge pull request #2 from spikecodes/master
Merge upstream into code
2021-01-14 19:29:06 +01:00
a85a4278f6 Add subreddits to search results 2021-01-14 19:22:50 +01:00
dbe617d7eb Switch to awc 2021-01-14 09:53:54 -08:00
842d97e9fa Fix short post IDs 2021-01-13 21:02:48 -08:00
0bf5576427 Categorize routes and refactor error handlers 2021-01-13 19:53:52 -08:00
dd027bff4b Refactor flair parsing 2021-01-13 18:19:40 -08:00
f95ef51017 Add days to time() 2021-01-13 16:31:24 -08:00
740641cb4e Move nested_val() to user.rs 2021-01-13 15:55:10 -08:00
09c98c8da6 Refactor code 2021-01-13 12:52:00 -08:00
33c8bdffb9 Merge pull request #1 from spikecodes/master
Merge upstream
2021-01-13 20:22:43 +01:00
5ab88567de Merge pull request #50 from robrobinbin/rich-flairs
Add support for rich flairs with "Emoji"
2021-01-13 10:48:25 -08:00
c6627ceece Merge branch 'master' into rich-flairs 2021-01-13 08:27:39 +01:00
d9affcdefc Rich flairs 2021-01-13 08:23:48 +01:00
96607256fc Add Favicon 2021-01-12 20:18:20 -08:00
eb9a0dcb4a Fix GIFs 2021-01-12 19:52:02 -08:00
89fa0d5489 Merge pull request #47 from robrobinbin/scrollbar-for-overflowing-code
Add overflow "auto" to "pre"
2021-01-12 15:16:02 -08:00
22589c8296 Merge pull request #48 from robrobinbin/relative_timestamps
Relative timestamps for posts younger than 24h
2021-01-12 15:15:37 -08:00
b0540d2c57 Rich flairs 2021-01-13 00:10:06 +01:00
41c4661bbb Rich flairs 2021-01-12 23:55:35 +01:00
d2314580a9 Rich flairs 2021-01-12 23:50:50 +01:00
a4d77926b6 Rich flairs 2021-01-12 23:34:16 +01:00
bbe7024323 Start richtext flairs 2021-01-12 22:43:03 +01:00
32e1469e11 whitespace 2021-01-12 20:03:54 +01:00
2d4ca2379f whitespace 2021-01-12 20:02:35 +01:00
374f53eb32 Relative timestamps for recent posts 2021-01-12 19:59:32 +01:00
add7efea3c Update style.css 2021-01-12 18:53:10 +01:00
065d82a5f5 Merge pull request #45 from somoso/long-label-fix
Stop label from being long for joined subreddits
2021-01-12 08:46:35 -08:00
1895bbc025 Merge pull request #46 from somoso/ios-thumbnail-fix
Fix the thumbnail issue on iOS
2021-01-12 08:46:17 -08:00
65f1a2afb2 Stop label from being long for joined subreddits
Browsing with a long joined subreddit list will cause the label to look a bit weird.

Example: https://libredd.it/r/Android+AnimalsBeingBros+AnimalsBeingDerps+AnimalsBeingJerks+AppleWatch+CatSlaps+CatSmiles+CatsBeingAdorable+FreeTube+Games+Ijustwatched+IllegallySmol+IllegallySmolCats+IpodClassic+LearnRubyonRails+Megadrive+MovieDetails+Music+NetflixBestOf+NintendoSwitch+Possums+Teefers+UKPersonalFinance+airplaneears+apple+aww+brushybrushy+cats+catswhotrill+curledfeetsies+cyberpunkgame+dataisbeautiful+dechonkers+digital_ocean+dogs+dogsareliquid+dogswithjobs+emulation+greebles+happycowgifs+hardware+iOSDevelopment+iOSProgramming+iosdev+kittykankles+learnruby+likeus+mac+mashups+microsoft+movies+netflix+netsec+pihole+playstation+programming+rarepuppers+raspberry_pi+redditsync+rubyonrails+satelliteears+shittymoviedetails+spookyteefies+technology+teefies+vampirecats+velvethippos

That will cause the label to be excessively long
2021-01-12 15:47:39 +00:00
6eb9e6f0c0 Fix the thumbnail issue on iOS 2021-01-12 15:43:27 +00:00
eb735a42fe Handle comment parsing errors 2021-01-11 18:05:13 -08:00
541c741bde Parse GIFs correctly 2021-01-11 17:47:14 -08:00
7a33ed3434 Card thumbnails for users 2021-01-11 17:38:35 -08:00
48d2943f72 Fix subreddits not showing sidebars 2021-01-11 16:44:31 -08:00
6bbc90bc0d Clean Subreddit struct 2021-01-11 16:35:50 -08:00
4d18dc0bb8 Merge pull request #44 from robrobinbin/master
Make thumbnail clickable and bring behavior closer to reddit.
2021-01-11 16:35:14 -08:00
6dbd002acd Add direct link to thumbnail 2021-01-11 23:08:12 +01:00
bf6245a505 Fix multireddit sidebars 2021-01-11 10:39:36 -08:00
91746908a1 Switch to ureq 2021-01-11 10:33:48 -08:00
bb8273bab4 Fix #41 2021-01-11 10:33:42 -08:00
62bcc31305 Fix Wide UI on Mobile 2021-01-10 18:48:08 -08:00
08683fa5a6 Light theme 2021-01-10 18:15:34 -08:00
c58b077330 Update Dependencies 2021-01-10 13:20:47 -08:00
f445c42f55 Wide UI Mode 2021-01-10 13:08:36 -08:00
a0866b251e Update issue templates 2021-01-10 11:23:53 -08:00
aa819544f6 Update Matrix Address 2021-01-09 13:08:08 -08:00
23 changed files with 919 additions and 567 deletions

View File

@ -1,8 +1,8 @@
--- ---
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 | [title]
labels: '' labels: bug
assignees: '' assignees: ''
--- ---
@ -10,7 +10,7 @@ assignees: ''
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
**To Reproduce** **To reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
@ -20,19 +20,5 @@ Steps to reproduce the behavior:
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View File

@ -1,8 +1,8 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: Feature Request | [title]
labels: '' labels: enhancement
assignees: '' assignees: ''
--- ---

359
Cargo.lock generated
View File

@ -7,7 +7,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570" checksum = "78d1833b3838dbe990df0f1f87baf640cf6146e898166afe401839d1b001e570"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes 0.5.6",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"log", "log",
@ -31,7 +31,7 @@ dependencies = [
"futures-util", "futures-util",
"http", "http",
"log", "log",
"rustls", "rustls 0.18.1",
"tokio-rustls", "tokio-rustls",
"trust-dns-proto", "trust-dns-proto",
"trust-dns-resolver", "trust-dns-resolver",
@ -54,7 +54,7 @@ dependencies = [
"base64 0.13.0", "base64 0.13.0",
"bitflags", "bitflags",
"brotli2", "brotli2",
"bytes", "bytes 0.5.6",
"cookie", "cookie",
"copyless", "copyless",
"derive_more", "derive_more",
@ -75,7 +75,7 @@ dependencies = [
"log", "log",
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project 1.0.3", "pin-project 1.0.4",
"rand", "rand",
"regex", "regex",
"serde", "serde",
@ -98,9 +98,9 @@ dependencies = [
[[package]] [[package]]
name = "actix-router" name = "actix-router"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd1f7dbda1645bf7da33554db60891755f6c01c1b2169e2f4c492098d30c235" checksum = "b8be584b3b6c705a18eabc11c4059cf83b255bdd8511673d1d569f4ce40c69de"
dependencies = [ dependencies = [
"bytestring", "bytestring",
"http", "http",
@ -193,10 +193,10 @@ dependencies = [
"actix-service", "actix-service",
"actix-utils", "actix-utils",
"futures-util", "futures-util",
"rustls", "rustls 0.18.1",
"tokio-rustls", "tokio-rustls",
"webpki", "webpki",
"webpki-roots", "webpki-roots 0.20.0",
] ]
[[package]] [[package]]
@ -209,7 +209,7 @@ dependencies = [
"actix-rt", "actix-rt",
"actix-service", "actix-service",
"bitflags", "bitflags",
"bytes", "bytes 0.5.6",
"either", "either",
"futures-channel", "futures-channel",
"futures-sink", "futures-sink",
@ -238,7 +238,7 @@ dependencies = [
"actix-utils", "actix-utils",
"actix-web-codegen", "actix-web-codegen",
"awc", "awc",
"bytes", "bytes 0.5.6",
"derive_more", "derive_more",
"encoding_rs", "encoding_rs",
"futures-channel", "futures-channel",
@ -247,9 +247,9 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"mime", "mime",
"pin-project 1.0.3", "pin-project 1.0.4",
"regex", "regex",
"rustls", "rustls 0.18.1",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -385,7 +385,7 @@ dependencies = [
"actix-rt", "actix-rt",
"actix-service", "actix-service",
"base64 0.13.0", "base64 0.13.0",
"bytes", "bytes 0.5.6",
"cfg-if 1.0.0", "cfg-if 1.0.0",
"derive_more", "derive_more",
"futures-core", "futures-core",
@ -393,7 +393,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"rand", "rand",
"rustls", "rustls 0.18.1",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -486,9 +486,9 @@ checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.3.4" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]] [[package]]
name = "bytes" name = "bytes"
@ -497,12 +497,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]] [[package]]
name = "bytestring" name = "bytes"
version = "0.1.5" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363" checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "bytestring"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d"
dependencies = [ dependencies = [
"bytes", "bytes 1.0.1",
] ]
[[package]] [[package]]
@ -523,6 +529,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chunked_transfer"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca"
[[package]] [[package]]
name = "const_fn" name = "const_fn"
version = "0.4.5" version = "0.4.5"
@ -666,9 +678,9 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b3b0c040a1fe6529d30b3c5944b280c7f0dcb2930d2c3062bca967b602583d0" checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -680,9 +692,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -690,21 +702,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77408a692f1f97bcc61dc001d752e00643408fbc922e4d634c655df50d595556" checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
dependencies = [ dependencies = [
"proc-macro-hack", "proc-macro-hack",
"proc-macro2", "proc-macro2",
@ -714,24 +726,24 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f878195a49cee50e006b02b93cf7e0a95a38ac7b776b4c4d9cc1207cd20fcb3d" checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c554eb5bf48b2426c4771ab68c6b14468b6e76cc90996f528c3338d761a4d0d" checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
dependencies = [ dependencies = [
"once_cell", "once_cell",
] ]
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.8" version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d304cff4a7b99cfb7986f7d43fbe93d175e72e704a8860787cc95e9ffd85cbd2" checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -740,7 +752,7 @@ dependencies = [
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
"pin-project 1.0.3", "pin-project-lite 0.2.4",
"pin-utils", "pin-utils",
"proc-macro-hack", "proc-macro-hack",
"proc-macro-nested", "proc-macro-nested",
@ -789,7 +801,7 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535"
dependencies = [ dependencies = [
"bytes", "bytes 0.5.6",
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -840,83 +852,27 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84129d298a6d57d246960ff8eb831ca4af3f96d29e2e28848dae275408658e26" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
dependencies = [ dependencies = [
"bytes", "bytes 1.0.1",
"fnv", "fnv",
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
dependencies = [
"bytes",
"http",
]
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.3.4" version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
name = "httpdate"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]] [[package]]
name = "humansize" name = "humansize"
version = "1.1.0" 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 = "hyper"
version = "0.13.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project 1.0.3",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6"
dependencies = [
"bytes",
"futures-util",
"hyper",
"log",
"rustls",
"tokio",
"tokio-rustls",
"webpki",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.0" version = "0.2.0"
@ -965,15 +921,9 @@ dependencies = [
"socket2", "socket2",
"widestring", "widestring",
"winapi 0.3.9", "winapi 0.3.9",
"winreg 0.6.2", "winreg",
] ]
[[package]]
name = "ipnet"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.7" version = "0.4.7"
@ -1032,25 +982,25 @@ checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
[[package]] [[package]]
name = "libreddit" name = "libreddit"
version = "0.2.6" version = "0.2.7"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"askama", "askama",
"async-recursion", "async-recursion",
"base64 0.13.0", "base64 0.13.0",
"regex", "regex",
"reqwest",
"serde", "serde",
"serde_json", "serde_json",
"time", "time",
"ureq",
"url", "url",
] ]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.3" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1063,9 +1013,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.11" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
] ]
@ -1103,16 +1053,6 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
"mime",
"unicase",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.3" version = "0.4.3"
@ -1267,11 +1207,11 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a83804639aad6ba65345661744708855f9fbcb71176ea8d28d05aeb11d975e7" checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
dependencies = [ dependencies = [
"pin-project-internal 1.0.3", "pin-project-internal 1.0.4",
] ]
[[package]] [[package]]
@ -1287,9 +1227,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7bcc46b8f73443d15bc1c5fecbb315718491fa9187fa483f0e359323cde8b3a" checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1304,9 +1244,9 @@ checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.1" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36743d754ccdf9954c2e352ce2d4b106e024c814f6499c2dadff80da9a442d8" checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1328,9 +1268,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro-nested" name = "proc-macro-nested"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -1411,9 +1351,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.2" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c" checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1423,45 +1363,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.21" version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[package]]
name = "reqwest"
version = "0.10.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
dependencies = [
"base64 0.13.0",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite 0.2.1",
"rustls",
"serde",
"serde_urlencoded",
"tokio",
"tokio-rustls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg 0.7.0",
]
[[package]] [[package]]
name = "resolv-conf" name = "resolv-conf"
@ -1516,6 +1420,19 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "rustls"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
dependencies = [
"base64 0.13.0",
"log",
"ring",
"sct",
"webpki",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -1555,18 +1472,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.118" version = "1.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.118" version = "1.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1632,9 +1549,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a55ca5f3b68e41c979bf8c46a6f1da892ca4db8f94023ce0bd32407573b1ac0" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]] [[package]]
name = "socket2" name = "socket2"
@ -1774,9 +1691,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.2.23" version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcdaeea317915d59b2b4cd3b5efcd156c309108664277793f5351700c02ce98b" checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7"
dependencies = [ dependencies = [
"const_fn", "const_fn",
"libc", "libc",
@ -1831,8 +1748,7 @@ version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48"
dependencies = [ dependencies = [
"bytes", "bytes 0.5.6",
"fnv",
"futures-core", "futures-core",
"iovec", "iovec",
"lazy_static", "lazy_static",
@ -1853,7 +1769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"rustls", "rustls 0.18.1",
"tokio", "tokio",
"webpki", "webpki",
] ]
@ -1864,7 +1780,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [ dependencies = [
"bytes", "bytes 0.5.6",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"log", "log",
@ -1881,12 +1797,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "tower-service"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.22" version = "0.1.22"
@ -1895,7 +1805,7 @@ checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"log", "log",
"pin-project-lite 0.2.1", "pin-project-lite 0.2.4",
"tracing-core", "tracing-core",
] ]
@ -1958,27 +1868,12 @@ dependencies = [
"trust-dns-proto", "trust-dns-proto",
] ]
[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.12.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.4"
@ -2015,6 +1910,22 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "ureq"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96014ded8c85822677daee4f909d18acccca744810fd4f8ffc492c284f2324bc"
dependencies = [
"base64 0.13.0",
"chunked_transfer",
"log",
"once_cell",
"rustls 0.19.0",
"url",
"webpki",
"webpki-roots 0.21.0",
]
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.0" version = "2.2.0"
@ -2033,16 +1944,6 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.9.0+wasi-snapshot-preview1" version = "0.9.0+wasi-snapshot-preview1"
@ -2056,8 +1957,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"serde",
"serde_json",
"wasm-bindgen-macro", "wasm-bindgen-macro",
] ]
@ -2076,18 +1975,6 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.69" version = "0.2.69"
@ -2146,6 +2033,15 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "webpki-roots"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
dependencies = [
"webpki",
]
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.3" version = "0.4.3"
@ -2195,15 +2091,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "ws2_32-sys" name = "ws2_32-sys"
version = "0.2.1" version = "0.2.1"

View File

@ -3,15 +3,15 @@ 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.6" version = "0.2.7"
authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"] authors = ["spikecodes <19519553+spikecodes@users.noreply.github.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
base64 = "0.13.0" base64 = "0.13.0"
actix-web = { version = "3.3.2", features = ["rustls"] } actix-web = { version = "3.3.2", features = ["rustls"] }
reqwest = { version = "0.10", default_features = false, features = ["rustls-tls"] }
askama = "0.10.5" askama = "0.10.5"
ureq = "2.0.1"
serde = { version = "1.0.118", default_features = false, features = ["derive"] } serde = { version = "1.0.118", default_features = false, features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
async-recursion = "0.3.1" async-recursion = "0.3.1"

View File

@ -54,7 +54,7 @@ A checkmark in the "Cloudflare" category here refers to the use of the reverse p
### Elsewhere ### Elsewhere
Find Libreddit on... Find Libreddit on...
- 💬 Matrix: [#libreddit:matrix.org](https://matrix.to/#/#libreddit:matrix.org) - 💬 Matrix: [#libreddit:kde.org](https://matrix.to/#/#libreddit:matrix.org)
- 🐋 Docker: [spikecodes/libreddit](https://hub.docker.com/r/spikecodes/libreddit) - 🐋 Docker: [spikecodes/libreddit](https://hub.docker.com/r/spikecodes/libreddit)
- :octocat: GitHub: [spikecodes/libreddit](https://github.com/spikecodes/libreddit) - :octocat: GitHub: [spikecodes/libreddit](https://github.com/spikecodes/libreddit)
- 🦊 GitLab: [spikecodes/libreddit](https://gitlab.com/spikecodes/libreddit) - 🦊 GitLab: [spikecodes/libreddit](https://gitlab.com/spikecodes/libreddit)
@ -123,7 +123,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 troubleshooting but nothing else. **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.
**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

@ -1,5 +1,5 @@
// Import Crates // Import Crates
use actix_web::{get, middleware::NormalizePath, web, App, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, HttpResponse, HttpServer}; // dev::Service
// Reference local files // Reference local files
mod post; mod post;
@ -18,24 +18,25 @@ async fn style() -> HttpResponse {
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")
.body(include_str!("../static/robots.txt")) .body("User-agent: *\nAllow: /")
} }
#[get("/favicon.ico")]
async fn favicon() -> HttpResponse { async fn favicon() -> HttpResponse {
HttpResponse::Ok().body("") HttpResponse::Ok()
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
.body(include_bytes!("../static/favicon.ico").as_ref())
} }
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
let args: Vec<String> = std::env::args().collect();
let mut address = "0.0.0.0:8080".to_string(); let mut address = "0.0.0.0:8080".to_string();
// let mut https = false;
if args.len() > 1 { for arg in std::env::args().collect::<Vec<String>>() {
for arg in args { match arg.split('=').collect::<Vec<&str>>()[0] {
if arg.starts_with("--address=") || arg.starts_with("-a=") { "--address" | "-a" => address = arg.split('=').collect::<Vec<&str>>()[1].to_string(),
address = arg.split('=').collect::<Vec<&str>>()[1].to_string(); // "--redirect-https" | "-r" => https = true,
} _ => (),
} }
} }
@ -44,43 +45,71 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
// TRAILING SLASH MIDDLEWARE // Redirect to HTTPS
.wrap(NormalizePath::default()) // .wrap_fn(|req, srv| { let fut = srv.call(req); async { let mut res = fut.await?; if https {} Ok(res) } })
// DEFAULT SERVICE // Append trailing slash and remove double slashes
.wrap(middleware::NormalizePath::default())
// 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())))
// GENERAL SERVICES // Read static files
.route("/style.css/", web::get().to(style)) .route("/style.css/", web::get().to(style))
.route("/favicon.ico/", web::get().to(HttpResponse::Ok)) .route("/favicon.ico/", web::get().to(favicon))
.route("/robots.txt/", web::get().to(robots)) .route("/robots.txt/", web::get().to(robots))
// SETTINGS SERVICE // Proxy media through Libreddit
.route("/settings/", web::get().to(settings::get))
.route("/settings/", web::post().to(settings::set))
// PROXY SERVICE
.route("/proxy/{url:.*}/", web::get().to(proxy::handler)) .route("/proxy/{url:.*}/", web::get().to(proxy::handler))
// SEARCH SERVICES // Browse user profile
.route("/search/", web::get().to(search::find)) .service(
.route("r/{sub}/search/", web::get().to(search::find)) web::scope("/{scope:user|u}").service(
// USER SERVICES web::scope("/{username}").route("/", web::get().to(user::profile)).service(
.route("/u/{username}/", web::get().to(user::profile)) web::scope("/comments/{id}/{title}")
.route("/user/{username}/", web::get().to(user::profile)) .route("/", web::get().to(post::item))
// WIKI SERVICES .route("/{comment_id}/", web::get().to(post::item)),
.route("/wiki/", web::get().to(subreddit::wiki)) ),
.route("/wiki/{page}/", web::get().to(subreddit::wiki)) ),
.route("/r/{sub}/wiki/", web::get().to(subreddit::wiki)) )
.route("/r/{sub}/wiki/{page}/", web::get().to(subreddit::wiki)) // Configure settings
// SUBREDDIT SERVICES .service(web::resource("/settings/").route(web::get().to(settings::get)).route(web::post().to(settings::set)))
.route("/r/{sub}/", web::get().to(subreddit::page)) // Subreddit services
.route("/r/{sub}/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page)) .service(
// POPULAR SERVICES web::scope("/r/{sub}")
.route("/", web::get().to(subreddit::page)) // See posts and info about subreddit
.route("/{sort:best|hot|new|top|rising|controversial}/", web::get().to(subreddit::page)) .route("/", web::get().to(subreddit::page))
// POST SERVICES .route("/{sort:hot|new|top|rising|controversial}/", web::get().to(subreddit::page))
.route("/{id:.{5,6}}/", web::get().to(post::item)) // View post on subreddit
.route("/r/{sub}/comments/{id}/{title}/", web::get().to(post::item)) .service(
.route("/r/{sub}/comments/{id}/{title}/{comment_id}/", web::get().to(post::item)) web::scope("/comments/{id}/{title}")
.route("/", web::get().to(post::item))
.route("/{comment_id}/", web::get().to(post::item)),
)
// Search inside subreddit
.route("/search/", web::get().to(search::find))
// View wiki of subreddit
.service(
web::scope("/wiki")
.route("/", web::get().to(subreddit::wiki))
.route("/{page}/", web::get().to(subreddit::wiki)),
),
)
// Universal services
.service(
web::scope("")
// Front page
.route("/", web::get().to(subreddit::page))
.route("/{sort:best|hot|new|top|rising|controversial}/", web::get().to(subreddit::page))
// View Reddit wiki
.service(
web::scope("/wiki")
.route("/", web::get().to(subreddit::wiki))
.route("/{page}/", web::get().to(subreddit::wiki)),
)
// Search all of Reddit
.route("/search/", web::get().to(search::find))
// Short link for post
.route("/{id:.{5,6}}/", web::get().to(post::item)),
)
}) })
.bind(&address) .bind(&address)
.unwrap_or_else(|_| panic!("Cannot bind to the address: {}", address)) .unwrap_or_else(|e| panic!("Cannot bind to the address {}: {}", address, e))
.run() .run()
.await .await
} }

View File

@ -1,11 +1,10 @@
// CRATES // CRATES
use crate::utils::{cookie, error, format_num, format_url, media, param, request, rewrite_url, val, Comment, Flags, Flair, Post}; use crate::utils::*;
use actix_web::{HttpRequest, HttpResponse}; use actix_web::{HttpRequest, HttpResponse};
use async_recursion::async_recursion; use async_recursion::async_recursion;
use askama::Template; use askama::Template;
use time::OffsetDateTime;
// STRUCTS // STRUCTS
#[derive(Template)] #[derive(Template)]
@ -14,6 +13,7 @@ struct PostTemplate {
comments: Vec<Comment>, comments: Vec<Comment>,
post: Post, post: Post,
sort: String, sort: String,
prefs: Preferences,
} }
pub async fn item(req: HttpRequest) -> HttpResponse { pub async fn item(req: HttpRequest) -> HttpResponse {
@ -45,11 +45,18 @@ pub async fn item(req: HttpRequest) -> HttpResponse {
let comments = parse_comments(&res[1]).await; let comments = parse_comments(&res[1]).await;
// Use the Post and Comment structs to generate a website to show users // Use the Post and Comment structs to generate a website to show users
let s = PostTemplate { comments, post, sort }.render().unwrap(); let s = PostTemplate {
comments,
post,
sort,
prefs: prefs(req),
}
.render()
.unwrap();
HttpResponse::Ok().content_type("text/html").body(s) HttpResponse::Ok().content_type("text/html").body(s)
} }
// If the Reddit API returns an error, exit and send error page to user // If the Reddit API returns an error, exit and send error page to user
Err(msg) => error(msg.to_string()).await, Err(msg) => error(msg).await,
} }
} }
@ -59,7 +66,7 @@ async fn parse_post(json: &serde_json::Value) -> Post {
let post: &serde_json::Value = &json["data"]["children"][0]; let post: &serde_json::Value = &json["data"]["children"][0];
// Grab UTC time as unix timestamp // Grab UTC time as unix timestamp
let unix_time: i64 = post["data"]["created_utc"].as_f64().unwrap_or_default().round() as i64; let (rel_time, created) = time(post["data"]["created_utc"].as_f64().unwrap_or_default());
// Parse post score and upvote ratio // Parse post score and upvote ratio
let score = post["data"]["score"].as_i64().unwrap_or_default(); let score = post["data"]["score"].as_i64().unwrap_or_default();
let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0; let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0;
@ -73,32 +80,45 @@ async fn parse_post(json: &serde_json::Value) -> Post {
title: val(post, "title"), title: val(post, "title"),
community: val(post, "subreddit"), community: val(post, "subreddit"),
body: rewrite_url(&val(post, "selftext_html")), body: rewrite_url(&val(post, "selftext_html")),
author: val(post, "author"), author: Author {
author_flair: Flair( name: val(post, "author"),
val(post, "author_flair_text"), flair: Flair {
val(post, "author_flair_background_color"), flair_parts: parse_rich_flair(
val(post, "author_flair_text_color"), val(post, "author_flair_type"),
), post["data"]["author_flair_richtext"].as_array(),
post["data"]["author_flair_text"].as_str(),
),
background_color: val(post, "author_flair_background_color"),
foreground_color: val(post, "author_flair_text_color"),
},
distinguished: val(post, "distinguished"),
},
permalink: val(post, "permalink"), permalink: val(post, "permalink"),
score: format_num(score), score: format_num(score),
upvote_ratio: ratio as i64, upvote_ratio: ratio as i64,
post_type, post_type,
thumbnail: format_url(val(post, "thumbnail")), thumbnail: format_url(val(post, "thumbnail").as_str()),
flair: Flair( flair: Flair {
val(post, "link_flair_text"), flair_parts: parse_rich_flair(
val(post, "link_flair_background_color"), val(post, "link_flair_type"),
if val(post, "link_flair_text_color") == "dark" { post["data"]["link_flair_richtext"].as_array(),
post["data"]["link_flair_text"].as_str(),
),
background_color: val(post, "link_flair_background_color"),
foreground_color: if val(post, "link_flair_text_color") == "dark" {
"black".to_string() "black".to_string()
} else { } else {
"white".to_string() "white".to_string()
}, },
), },
flags: Flags { flags: Flags {
nsfw: post["data"]["over_18"].as_bool().unwrap_or(false), nsfw: post["data"]["over_18"].as_bool().unwrap_or(false),
stickied: post["data"]["stickied"].as_bool().unwrap_or(false), stickied: post["data"]["stickied"].as_bool().unwrap_or(false),
}, },
media, media,
time: OffsetDateTime::from_unix_timestamp(unix_time).format("%b %d %Y %H:%M UTC"), domain: val(post, "domain"),
rel_time,
created,
} }
} }
@ -106,19 +126,23 @@ async fn parse_post(json: &serde_json::Value) -> Post {
#[async_recursion] #[async_recursion]
async fn parse_comments(json: &serde_json::Value) -> Vec<Comment> { async fn parse_comments(json: &serde_json::Value) -> Vec<Comment> {
// Separate the comment JSON into a Vector of comments // Separate the comment JSON into a Vector of comments
let comment_data = json["data"]["children"].as_array().unwrap(); let comment_data = match json["data"]["children"].as_array() {
Some(f) => f.to_owned(),
None => Vec::new(),
};
let mut comments: Vec<Comment> = Vec::new(); let mut comments: Vec<Comment> = Vec::new();
// For each comment, retrieve the values to build a Comment object // For each comment, retrieve the values to build a Comment object
for comment in comment_data { for comment in comment_data {
let unix_time: i64 = comment["data"]["created_utc"].as_f64().unwrap_or(0.0).round() as i64; let unix_time = comment["data"]["created_utc"].as_f64().unwrap_or_default();
if unix_time == 0 { if unix_time == 0.0 {
continue; continue;
} }
let (rel_time, created) = time(unix_time);
let score = comment["data"]["score"].as_i64().unwrap_or(0); let score = comment["data"]["score"].as_i64().unwrap_or(0);
let body = rewrite_url(&val(comment, "body_html")); let body = rewrite_url(&val(&comment, "body_html"));
let replies: Vec<Comment> = if comment["data"]["replies"].is_object() { let replies: Vec<Comment> = if comment["data"]["replies"].is_object() {
parse_comments(&comment["data"]["replies"]).await parse_comments(&comment["data"]["replies"]).await
@ -127,17 +151,25 @@ async fn parse_comments(json: &serde_json::Value) -> Vec<Comment> {
}; };
comments.push(Comment { comments.push(Comment {
id: val(comment, "id"), id: val(&comment, "id"),
body, body,
author: val(comment, "author"), author: Author {
name: val(&comment, "author"),
flair: Flair {
flair_parts: parse_rich_flair(
val(&comment, "author_flair_type"),
comment["data"]["author_flair_richtext"].as_array(),
comment["data"]["author_flair_text"].as_str(),
),
background_color: val(&comment, "author_flair_background_color"),
foreground_color: val(&comment, "author_flair_text_color"),
},
distinguished: val(&comment, "distinguished"),
},
score: format_num(score), score: format_num(score),
time: OffsetDateTime::from_unix_timestamp(unix_time).format("%b %d %Y %H:%M UTC"), rel_time,
created,
replies, replies,
flair: Flair(
val(comment, "author_flair_text"),
val(comment, "author_flair_background_color"),
val(comment, "author_flair_text_color"),
),
}); });
} }

View File

@ -8,6 +8,8 @@ pub async fn handler(web::Path(b64): web::Path<String>) -> Result<HttpResponse>
// THUMBNAILS // THUMBNAILS
"a.thumbs.redditmedia.com", "a.thumbs.redditmedia.com",
"b.thumbs.redditmedia.com", "b.thumbs.redditmedia.com",
// EMOJI
"emoji.redditmedia.com",
// ICONS // ICONS
"styles.redditmedia.com", "styles.redditmedia.com",
"www.redditstatic.com", "www.redditstatic.com",
@ -39,9 +41,9 @@ pub async fn handler(web::Path(b64): web::Path<String>) -> Result<HttpResponse>
Err(error::ErrorForbidden("Resource must be from Reddit")) Err(error::ErrorForbidden("Resource must be from Reddit"))
} }
} }
Err(_) => Err(error::ErrorBadRequest("Can't parse base64 into URL")), _ => Err(error::ErrorBadRequest("Can't parse base64 into URL")),
} }
} }
Err(_) => Err(error::ErrorBadRequest("Can't decode base64")), _ => Err(error::ErrorBadRequest("Can't decode base64")),
} }
} }

View File

@ -1,5 +1,5 @@
// CRATES // CRATES
use crate::utils::{error, fetch_posts, param, prefs, Post, Preferences}; use crate::utils::{error, fetch_posts, param, prefs, request, val, Post, Preferences};
use actix_web::{HttpRequest, HttpResponse}; use actix_web::{HttpRequest, HttpResponse};
use askama::Template; use askama::Template;
@ -13,10 +13,19 @@ struct SearchParams {
restrict_sr: String, restrict_sr: String,
} }
// STRUCTS
struct Subreddit {
name: String,
url: String,
description: String,
subscribers: i64,
}
#[derive(Template)] #[derive(Template)]
#[template(path = "search.html", escape = "none")] #[template(path = "search.html", escape = "none")]
struct SearchTemplate { struct SearchTemplate {
posts: Vec<Post>, posts: Vec<Post>,
subreddits: Vec<Subreddit>,
sub: String, sub: String,
params: SearchParams, params: SearchParams,
prefs: Preferences, prefs: Preferences,
@ -25,17 +34,25 @@ struct SearchTemplate {
// SERVICES // SERVICES
pub async fn find(req: HttpRequest) -> HttpResponse { pub async fn find(req: HttpRequest) -> HttpResponse {
let path = format!("{}.json?{}", req.path(), req.query_string()); let path = format!("{}.json?{}", req.path(), req.query_string());
let sub = req.match_info().get("sub").unwrap_or("").to_string();
let sort = if param(&path, "sort").is_empty() { let sort = if param(&path, "sort").is_empty() {
"relevance".to_string() "relevance".to_string()
} else { } else {
param(&path, "sort") param(&path, "sort")
}; };
let sub = req.match_info().get("sub").unwrap_or("").to_string();
let subreddits = if param(&path, "restrict_sr").is_empty() {
search_subreddits(param(&path, "q")).await
} else {
Vec::new()
};
match fetch_posts(&path, String::new()).await { match fetch_posts(&path, String::new()).await {
Ok((posts, after)) => HttpResponse::Ok().content_type("text/html").body( Ok((posts, after)) => HttpResponse::Ok().content_type("text/html").body(
SearchTemplate { SearchTemplate {
posts, posts,
subreddits,
sub, sub,
params: SearchParams { params: SearchParams {
q: param(&path, "q"), q: param(&path, "q"),
@ -50,6 +67,32 @@ pub async fn find(req: HttpRequest) -> HttpResponse {
.render() .render()
.unwrap(), .unwrap(),
), ),
Err(msg) => error(msg.to_string()).await, Err(msg) => error(msg).await,
}
}
async fn search_subreddits(q: String) -> Vec<Subreddit> {
let subreddit_search_path = format!("/subreddits/search.json?q={}&limit=3", q.replace(' ', "+"));
// Send a request to the url
match request(&subreddit_search_path).await {
// If success, receive JSON in response
Ok(response) => {
match response["data"]["children"].as_array() {
// For each subreddit from subreddit list
Some(list) => list
.iter()
.map(|subreddit| Subreddit {
name: val(subreddit, "display_name_prefixed"),
url: val(subreddit, "url"),
description: val(subreddit, "public_description"),
subscribers: subreddit["data"]["subscribers"].as_u64().unwrap_or_default() as i64,
})
.collect::<Vec<Subreddit>>(),
_ => Vec::new(),
}
}
// If the Reddit API returns an error, exit this function
_ => Vec::new(),
} }
} }

View File

@ -13,8 +13,10 @@ struct SettingsTemplate {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
pub struct SettingsForm { pub struct SettingsForm {
theme: Option<String>,
front_page: Option<String>, front_page: Option<String>,
layout: Option<String>, layout: Option<String>,
wide: Option<String>,
comment_sort: Option<String>, comment_sort: Option<String>,
hide_nsfw: Option<String>, hide_nsfw: Option<String>,
} }
@ -31,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!["front_page", "layout", "comment_sort", "hide_nsfw"]; let names = vec!["theme", "front_page", "layout", "wide", "comment_sort", "hide_nsfw"];
let values = vec![&form.front_page, &form.layout, &form.comment_sort, &form.hide_nsfw]; let values = vec![&form.theme, &form.front_page, &form.layout, &form.wide, &form.comment_sort, &form.hide_nsfw];
for (i, name) in names.iter().enumerate() { for (i, name) in names.iter().enumerate() {
match values[i] { match values[i] {

View File

@ -1,5 +1,5 @@
// CRATES // CRATES
use crate::utils::{cookie, error, fetch_posts, format_num, format_url, param, prefs, request, rewrite_url, val, Post, Preferences, Subreddit}; use crate::utils::*;
use actix_web::{HttpRequest, HttpResponse, Result}; use actix_web::{HttpRequest, HttpResponse, Result};
use askama::Template; use askama::Template;
@ -20,6 +20,7 @@ struct WikiTemplate {
sub: String, sub: String,
wiki: String, wiki: String,
page: String, page: String,
prefs: Preferences,
} }
// SERVICES // SERVICES
@ -33,14 +34,20 @@ pub async fn page(req: HttpRequest) -> HttpResponse {
.to_string(); .to_string();
let sort = req.match_info().get("sort").unwrap_or("hot").to_string(); let sort = req.match_info().get("sort").unwrap_or("hot").to_string();
let sub = if !&sub_name.contains('+') && sub_name != "popular" && sub_name != "all" {
subreddit(&sub_name).await.unwrap_or_default()
} else {
Subreddit::default()
};
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
let sub = if !sub_name.contains('+') && sub_name != "popular" && sub_name != "all" {
subreddit(&sub_name).await.unwrap_or_default()
} else if sub_name.contains('+') {
Subreddit {
name: sub_name,
..Subreddit::default()
}
} else {
Subreddit::default()
};
let s = SubredditTemplate { let s = SubredditTemplate {
sub, sub,
posts, posts,
@ -52,34 +59,35 @@ pub async fn page(req: HttpRequest) -> HttpResponse {
.unwrap(); .unwrap();
HttpResponse::Ok().content_type("text/html").body(s) HttpResponse::Ok().content_type("text/html").body(s)
} }
Err(msg) => error(msg.to_string()).await, Err(msg) => error(msg).await,
} }
} }
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"); let sub = req.match_info().get("sub").unwrap_or("reddit.com").to_string();
let page = req.match_info().get("page").unwrap_or("index"); let page = req.match_info().get("page").unwrap_or("index").to_string();
let path: String = format!("r/{}/wiki/{}.json?raw_json=1", sub, page); let path: String = format!("/r/{}/wiki/{}.json?raw_json=1", sub, page);
match request(&path).await { match request(&path).await {
Ok(res) => { Ok(res) => {
let s = WikiTemplate { let s = WikiTemplate {
sub: sub.to_string(), sub,
wiki: rewrite_url(res["data"]["content_html"].as_str().unwrap_or_default()), wiki: rewrite_url(res["data"]["content_html"].as_str().unwrap_or_default()),
page: page.to_string(), page,
prefs: prefs(req),
} }
.render() .render()
.unwrap(); .unwrap();
HttpResponse::Ok().content_type("text/html").body(s) HttpResponse::Ok().content_type("text/html").body(s)
} }
Err(msg) => error(msg.to_string()).await, Err(msg) => error(msg).await,
} }
} }
// SUBREDDIT // SUBREDDIT
async fn subreddit(sub: &str) -> Result<Subreddit, &'static str> { async fn subreddit(sub: &str) -> Result<Subreddit, String> {
// Build the Reddit JSON API url // Build the Reddit JSON API url
let path: String = format!("r/{}/about.json?raw_json=1", sub); let path: String = format!("/r/{}/about.json?raw_json=1", sub);
// Send a request to the url // Send a request to the url
match request(&path).await { match request(&path).await {
@ -98,7 +106,7 @@ async fn subreddit(sub: &str) -> Result<Subreddit, &'static str> {
title: val(&res, "title"), title: val(&res, "title"),
description: val(&res, "public_description"), description: val(&res, "public_description"),
info: rewrite_url(&val(&res, "description_html").replace("\\", "")), info: rewrite_url(&val(&res, "description_html").replace("\\", "")),
icon: format_url(icon), icon: format_url(icon.as_str()),
members: format_num(members), members: format_num(members),
active: format_num(active), active: format_num(active),
wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(), wiki: res["data"]["wiki_enabled"].as_bool().unwrap_or_default(),

View File

@ -1,5 +1,5 @@
// CRATES // CRATES
use crate::utils::{error, fetch_posts, format_url, nested_val, param, prefs, request, Post, Preferences, User}; use crate::utils::{error, fetch_posts, format_url, param, prefs, request, Post, Preferences, User};
use actix_web::{HttpRequest, HttpResponse, Result}; use actix_web::{HttpRequest, HttpResponse, Result};
use askama::Template; use askama::Template;
use time::OffsetDateTime; use time::OffsetDateTime;
@ -24,12 +24,14 @@ pub async fn profile(req: HttpRequest) -> HttpResponse {
let sort = param(&path, "sort"); let sort = param(&path, "sort");
let username = req.match_info().get("username").unwrap_or("").to_string(); let username = req.match_info().get("username").unwrap_or("").to_string();
// Request user profile data and user posts/comments from Reddit // Request user posts/comments from Reddit
let user = user(&username).await.unwrap_or_default();
let posts = fetch_posts(&path, "Comment".to_string()).await; let posts = fetch_posts(&path, "Comment".to_string()).await;
match posts { match posts {
Ok((posts, after)) => { Ok((posts, after)) => {
// If you can get user posts, also request user data
let user = user(&username).await.unwrap_or_default();
let s = UserTemplate { let s = UserTemplate {
user, user,
posts, posts,
@ -42,14 +44,14 @@ pub async fn profile(req: HttpRequest) -> HttpResponse {
HttpResponse::Ok().content_type("text/html").body(s) HttpResponse::Ok().content_type("text/html").body(s)
} }
// If there is an error show error page // If there is an error show error page
Err(msg) => error(msg.to_string()).await, Err(msg) => error(msg).await,
} }
} }
// USER // USER
async fn user(name: &str) -> Result<User, &'static str> { async fn user(name: &str) -> Result<User, String> {
// Build the Reddit JSON API path // Build the Reddit JSON API path
let path: String = format!("user/{}/about.json", name); let path: String = format!("/user/{}/about.json", name);
// Send a request to the url // Send a request to the url
match request(&path).await { match request(&path).await {
@ -58,15 +60,18 @@ async fn user(name: &str) -> Result<User, &'static str> {
// Grab creation date as unix timestamp // Grab creation date as unix timestamp
let created: i64 = res["data"]["created"].as_f64().unwrap_or(0.0).round() as i64; let created: i64 = res["data"]["created"].as_f64().unwrap_or(0.0).round() as i64;
// nested_val function used to parse JSON from Reddit APIs
let about = |item| res["data"]["subreddit"][item].as_str().unwrap_or_default().to_string();
// Parse the JSON output into a User struct // Parse the JSON output into a User struct
Ok(User { Ok(User {
name: name.to_string(), name: name.to_string(),
title: nested_val(&res, "subreddit", "title"), title: about("title"),
icon: format_url(nested_val(&res, "subreddit", "icon_img")), icon: format_url(about("icon_img").as_str()),
karma: res["data"]["total_karma"].as_i64().unwrap_or(0), karma: res["data"]["total_karma"].as_i64().unwrap_or(0),
created: OffsetDateTime::from_unix_timestamp(created).format("%b %d '%y"), created: OffsetDateTime::from_unix_timestamp(created).format("%b %d '%y"),
banner: nested_val(&res, "subreddit", "banner_img"), banner: about("banner_img"),
description: nested_val(&res, "subreddit", "public_description"), description: about("public_description"),
}) })
} }
// If the Reddit API returns an error, exit this function // If the Reddit API returns an error, exit this function

View File

@ -5,16 +5,34 @@ use actix_web::{cookie::Cookie, HttpRequest, HttpResponse, Result};
use askama::Template; use askama::Template;
use base64::encode; use base64::encode;
use regex::Regex; use regex::Regex;
use serde_json::from_str; use serde_json::{from_str, Value};
use std::collections::HashMap; use std::collections::HashMap;
use time::OffsetDateTime; use time::{Duration, OffsetDateTime};
use url::Url; use url::Url;
// //
// STRUCTS // STRUCTS
// //
// Post flair with text, background color and foreground color
pub struct Flair(pub String, pub String, pub String); // Post flair with content, background color and foreground color
pub struct Flair {
pub flair_parts: Vec<FlairPart>,
pub background_color: String,
pub foreground_color: String,
}
// Part of flair, either emoji or text
pub struct FlairPart {
pub flair_part_type: String,
pub value: String,
}
pub struct Author {
pub name: String,
pub flair: Flair,
pub distinguished: String,
}
// Post flags with nsfw and stickied // Post flags with nsfw and stickied
pub struct Flags { pub struct Flags {
pub nsfw: bool, pub nsfw: bool,
@ -27,8 +45,7 @@ pub struct Post {
pub title: String, pub title: String,
pub community: String, pub community: String,
pub body: String, pub body: String,
pub author: String, pub author: Author,
pub author_flair: Flair,
pub permalink: String, pub permalink: String,
pub score: String, pub score: String,
pub upvote_ratio: i64, pub upvote_ratio: i64,
@ -37,17 +54,19 @@ pub struct Post {
pub flags: Flags, pub flags: Flags,
pub thumbnail: String, pub thumbnail: String,
pub media: String, pub media: String,
pub time: String, pub domain: String,
pub rel_time: String,
pub created: String,
} }
// Comment with content, post, score and data/time that it was posted // Comment with content, post, score and data/time that it was posted
pub struct Comment { pub struct Comment {
pub id: String, pub id: String,
pub body: String, pub body: String,
pub author: String, pub author: Author,
pub flair: Flair,
pub score: String, pub score: String,
pub time: String, pub rel_time: String,
pub created: String,
pub replies: Vec<Comment>, pub replies: Vec<Comment>,
} }
@ -90,12 +109,16 @@ pub struct Params {
#[derive(Template)] #[derive(Template)]
#[template(path = "error.html", escape = "none")] #[template(path = "error.html", escape = "none")]
pub struct ErrorTemplate { pub struct ErrorTemplate {
pub message: String, pub msg: String,
pub prefs: Preferences,
} }
#[derive(Default)]
pub struct Preferences { pub struct Preferences {
pub theme: String,
pub front_page: String, pub front_page: String,
pub layout: String, pub layout: String,
pub wide: String,
pub hide_nsfw: String, pub hide_nsfw: String,
pub comment_sort: String, pub comment_sort: String,
} }
@ -107,8 +130,10 @@ pub struct Preferences {
// Build preferences from cookies // Build preferences from cookies
pub fn prefs(req: HttpRequest) -> Preferences { pub fn prefs(req: HttpRequest) -> Preferences {
Preferences { Preferences {
theme: cookie(&req, "theme"),
front_page: cookie(&req, "front_page"), front_page: cookie(&req, "front_page"),
layout: cookie(&req, "layout"), layout: cookie(&req, "layout"),
wide: cookie(&req, "wide"),
hide_nsfw: cookie(&req, "hide_nsfw"), hide_nsfw: cookie(&req, "hide_nsfw"),
comment_sort: cookie(&req, "comment_sort"), comment_sort: cookie(&req, "comment_sort"),
} }
@ -116,9 +141,10 @@ pub fn prefs(req: HttpRequest) -> Preferences {
// Grab a query param from a url // Grab a query param from a url
pub fn param(path: &str, value: &str) -> String { pub fn param(path: &str, value: &str) -> String {
let url = Url::parse(format!("https://libredd.it/{}", path).as_str()).unwrap(); match Url::parse(format!("https://libredd.it/{}", path).as_str()) {
let pairs: HashMap<_, _> = url.query_pairs().into_owned().collect(); Ok(url) => url.query_pairs().into_owned().collect::<HashMap<_, _>>().get(value).unwrap_or(&String::new()).to_owned(),
pairs.get(value).unwrap_or(&String::new()).to_owned() _ => String::new(),
}
} }
// Parse Cookie value from request // Parse Cookie value from request
@ -127,7 +153,7 @@ pub fn cookie(req: &HttpRequest, name: &str) -> String {
} }
// Direct urls to proxy if proxy is enabled // Direct urls to proxy if proxy is enabled
pub fn format_url(url: String) -> String { pub fn format_url(url: &str) -> String {
if url.is_empty() || url == "self" || url == "default" || url == "nsfw" || url == "spoiler" { if url.is_empty() || url == "self" || url == "default" || url == "nsfw" || url == "spoiler" {
String::new() String::new()
} else { } else {
@ -152,17 +178,33 @@ pub fn format_num(num: i64) -> String {
} }
} }
pub async fn media(data: &serde_json::Value) -> (String, String) { pub async fn media(data: &Value) -> (String, String) {
let post_type: &str; let post_type: &str;
let url = if !data["preview"]["reddit_video_preview"]["fallback_url"].is_null() { // If post is a video, return the video
let url = if data["preview"]["reddit_video_preview"]["fallback_url"].is_string() {
post_type = "video"; post_type = "video";
format_url(data["preview"]["reddit_video_preview"]["fallback_url"].as_str().unwrap_or_default().to_string()) format_url(data["preview"]["reddit_video_preview"]["fallback_url"].as_str().unwrap_or_default())
} else if !data["secure_media"]["reddit_video"]["fallback_url"].is_null() { } else if data["secure_media"]["reddit_video"]["fallback_url"].is_string() {
post_type = "video"; post_type = "video";
format_url(data["secure_media"]["reddit_video"]["fallback_url"].as_str().unwrap_or_default().to_string()) format_url(data["secure_media"]["reddit_video"]["fallback_url"].as_str().unwrap_or_default())
// Handle images, whether GIFs or pics
} else if data["post_hint"].as_str().unwrap_or("") == "image" { } else if data["post_hint"].as_str().unwrap_or("") == "image" {
post_type = "image"; let preview = data["preview"]["images"][0].clone();
format_url(data["preview"]["images"][0]["source"]["url"].as_str().unwrap_or_default().to_string()) match preview["variants"]["mp4"].as_object() {
// Return the mp4 if the media is a gif
Some(gif) => {
post_type = "gif";
format_url(gif["source"]["url"].as_str().unwrap_or_default())
}
// Return the picture if the media is an image
None => {
post_type = "image";
format_url(preview["source"]["url"].as_str().unwrap_or_default())
}
}
} else if data["is_self"].as_bool().unwrap_or_default() {
post_type = "self";
data["permalink"].as_str().unwrap_or_default().to_string()
} else { } else {
post_type = "link"; post_type = "link";
data["url"].as_str().unwrap_or_default().to_string() data["url"].as_str().unwrap_or_default().to_string()
@ -171,22 +213,70 @@ pub async fn media(data: &serde_json::Value) -> (String, String) {
(post_type.to_string(), url) (post_type.to_string(), url)
} }
pub fn parse_rich_flair(flair_type: String, rich_flair: Option<&Vec<Value>>, text_flair: Option<&str>) -> Vec<FlairPart> {
// Parse type of flair
match flair_type.as_str() {
// If flair contains emojis and text
"richtext" => match rich_flair {
Some(rich) => rich
.iter()
// For each part of the flair, extract text and emojis
.map(|part| {
let value = |name: &str| part[name].as_str().unwrap_or_default();
FlairPart {
flair_part_type: value("e").to_string(),
value: match value("e") {
"text" => value("t").to_string(),
"emoji" => format_url(value("u")),
_ => String::new(),
},
}
})
.collect::<Vec<FlairPart>>(),
None => Vec::new(),
},
// If flair contains only text
"text" => match text_flair {
Some(text) => vec![FlairPart {
flair_part_type: "text".to_string(),
value: text.to_string(),
}],
None => Vec::new(),
},
_ => Vec::new(),
}
}
pub fn time(created: f64) -> (String, String) {
let time = OffsetDateTime::from_unix_timestamp(created.round() as i64);
let time_delta = OffsetDateTime::now_utc() - time;
// If the time difference is more than a month, show full date
let rel_time = if time_delta > Duration::days(30) {
time.format("%b %d '%y")
// Otherwise, show relative date/time
} else if time_delta.whole_days() > 0 {
format!("{}d ago", time_delta.whole_days())
} else if time_delta.whole_hours() > 0 {
format!("{}h ago", time_delta.whole_hours())
} else {
format!("{}m ago", time_delta.whole_minutes())
};
(rel_time, time.format("%b %d %Y, %H:%M UTC"))
}
// //
// JSON PARSING // JSON PARSING
// //
// val() function used to parse JSON from Reddit APIs // val() function used to parse JSON from Reddit APIs
pub fn val(j: &serde_json::Value, k: &str) -> String { pub fn val(j: &Value, k: &str) -> String {
String::from(j["data"][k].as_str().unwrap_or_default()) String::from(j["data"][k].as_str().unwrap_or_default())
} }
// nested_val() function used to parse JSON from Reddit APIs
pub fn nested_val(j: &serde_json::Value, n: &str, k: &str) -> String {
String::from(j["data"][n][k].as_str().unwrap_or_default())
}
// 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_posts(path: &str, fallback_title: String) -> Result<(Vec<Post>, String), &'static str> { pub async fn fetch_posts(path: &str, fallback_title: String) -> Result<(Vec<Post>, String), String> {
let res; let res;
let post_list; let post_list;
@ -203,14 +293,14 @@ pub async fn fetch_posts(path: &str, fallback_title: String) -> Result<(Vec<Post
// Fetch the list of posts from the JSON response // Fetch the list of posts from the JSON response
match res["data"]["children"].as_array() { match res["data"]["children"].as_array() {
Some(list) => post_list = list, Some(list) => post_list = list,
None => return Err("No posts found"), None => return Err("No posts found".to_string()),
} }
let mut posts: Vec<Post> = Vec::new(); let mut posts: Vec<Post> = Vec::new();
// For each post from posts list // For each post from posts list
for post in post_list { for post in post_list {
let unix_time: i64 = post["data"]["created_utc"].as_f64().unwrap_or_default().round() as i64; let (rel_time, created) = time(post["data"]["created_utc"].as_f64().unwrap_or_default());
let score = post["data"]["score"].as_i64().unwrap_or_default(); let score = post["data"]["score"].as_i64().unwrap_or_default();
let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0; let ratio: f64 = post["data"]["upvote_ratio"].as_f64().unwrap_or(1.0) * 100.0;
let title = val(post, "title"); let title = val(post, "title");
@ -223,32 +313,45 @@ pub async fn fetch_posts(path: &str, fallback_title: String) -> Result<(Vec<Post
title: if title.is_empty() { fallback_title.to_owned() } else { title }, title: if title.is_empty() { fallback_title.to_owned() } else { title },
community: val(post, "subreddit"), community: val(post, "subreddit"),
body: rewrite_url(&val(post, "body_html")), body: rewrite_url(&val(post, "body_html")),
author: val(post, "author"), author: Author {
author_flair: Flair( name: val(post, "author"),
val(post, "author_flair_text"), flair: Flair {
val(post, "author_flair_background_color"), flair_parts: parse_rich_flair(
val(post, "author_flair_text_color"), val(post, "author_flair_type"),
), post["data"]["author_flair_richtext"].as_array(),
post["data"]["author_flair_text"].as_str(),
),
background_color: val(post, "author_flair_background_color"),
foreground_color: val(post, "author_flair_text_color"),
},
distinguished: val(post, "distinguished"),
},
score: format_num(score), score: format_num(score),
upvote_ratio: ratio as i64, upvote_ratio: ratio as i64,
post_type, post_type,
thumbnail: format_url(val(post, "thumbnail")), thumbnail: format_url(val(post, "thumbnail").as_str()),
media, media,
flair: Flair( domain: val(post, "domain"),
val(post, "link_flair_text"), flair: Flair {
val(post, "link_flair_background_color"), flair_parts: parse_rich_flair(
if val(post, "link_flair_text_color") == "dark" { val(post, "link_flair_type"),
post["data"]["link_flair_richtext"].as_array(),
post["data"]["link_flair_text"].as_str(),
),
background_color: val(post, "link_flair_background_color"),
foreground_color: if val(post, "link_flair_text_color") == "dark" {
"black".to_string() "black".to_string()
} else { } else {
"white".to_string() "white".to_string()
}, },
), },
flags: Flags { flags: Flags {
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(), nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
stickied: post["data"]["stickied"].as_bool().unwrap_or_default(), stickied: post["data"]["stickied"].as_bool().unwrap_or_default(),
}, },
permalink: val(post, "permalink"), permalink: val(post, "permalink"),
time: OffsetDateTime::from_unix_timestamp(unix_time).format("%b %d '%y"), // %b %e '%y rel_time,
created,
}); });
} }
@ -260,43 +363,101 @@ pub async fn fetch_posts(path: &str, fallback_title: String) -> Result<(Vec<Post
// //
pub async fn error(msg: String) -> HttpResponse { pub async fn error(msg: String) -> HttpResponse {
let body = ErrorTemplate { message: msg }.render().unwrap_or_default(); let body = ErrorTemplate {
msg,
prefs: Preferences::default(),
}
.render()
.unwrap_or_default();
HttpResponse::NotFound().content_type("text/html").body(body) HttpResponse::NotFound().content_type("text/html").body(body)
} }
// Make a request to a Reddit API and parse the JSON response // Make a request to a Reddit API and parse the JSON response
pub async fn request(path: &str) -> Result<serde_json::Value, &'static str> { pub async fn request(path: &str) -> 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"));
// Send request using reqwest // Send request using awc
match reqwest::get(&url).await { // async fn send(url: &str) -> Result<String, (bool, String)> {
Ok(res) => { // let client = actix_web::client::Client::default();
// Read the status from the response // let response = client.get(url).header("User-Agent", format!("web:libreddit:{}", env!("CARGO_PKG_VERSION"))).send().await;
match res.status().is_success() {
true => { // match response {
// Parse the response from Reddit as JSON // Ok(mut payload) => {
match from_str(res.text().await.unwrap_or_default().as_str()) { // // Get first number of response HTTP status code
Ok(json) => Ok(json), // match payload.status().to_string().chars().next() {
Err(_) => { // // If success
#[cfg(debug_assertions)] // Some('2') => Ok(String::from_utf8(payload.body().limit(20_000_000).await.unwrap_or_default().to_vec()).unwrap_or_default()),
dbg!(format!("{} - Failed to parse page JSON data", url)); // // If redirection
Err("Failed to parse page JSON data") // Some('3') => match payload.headers().get("location") {
} // Some(location) => Err((true, location.to_str().unwrap_or_default().to_string())),
} // None => Err((false, "Page not found".to_string())),
} // },
// If Reddit returns error, tell user Page Not Found // // Otherwise
false => { // _ => Err((false, "Page not found".to_string())),
// }
// }
// Err(e) => { dbg!(e); Err((false, "Couldn't send request to Reddit, this instance may be being rate-limited. Try another.".to_string())) },
// }
// }
// // Print error if debugging then return error based on error message
// fn err(url: String, msg: String) -> Result<Value, String> {
// // #[cfg(debug_assertions)]
// dbg!(format!("{} - {}", url, msg));
// Err(msg)
// };
// // Parse JSON from body. If parsing fails, return error
// fn json(url: String, body: String) -> Result<Value, String> {
// match from_str(body.as_str()) {
// Ok(json) => Ok(json),
// Err(_) => err(url, "Failed to parse page JSON data".to_string()),
// }
// }
// // Make request to Reddit using send function
// match send(&url).await {
// // If success, parse and return body
// Ok(body) => json(url, body),
// // Follow any redirects
// Err((true, location)) => match send(location.as_str()).await {
// // If success, parse and return body
// Ok(body) => json(url, body),
// // Follow any redirects again
// Err((true, location)) => err(url, location),
// // Return errors if request fails
// Err((_, msg)) => err(url, msg),
// },
// // Return errors if request fails
// Err((_, msg)) => err(url, msg),
// }
// Send request using ureq
match ureq::get(&url).set("User-Agent", user_agent.as_str()).call() {
// If response is success
Ok(response) => {
// Parse the response from Reddit as JSON
match from_str(&response.into_string().unwrap()) {
Ok(json) => Ok(json),
Err(_) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
dbg!(format!("{} - Page not found", url)); dbg!(format!("{} - Failed to parse page JSON data", url));
Err("Page not found") Err("Failed to parse page JSON data".to_string())
} }
} }
} }
// If can't send request to Reddit, return this to user // If response is error
Err(_e) => { Err(ureq::Error::Status(_, _)) => {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
dbg!(format!("{} - {}", url, _e)); dbg!(format!("{} - Page not found", url));
Err("Couldn't send request to Reddit") Err("Page not found".to_string())
}
// If failed to send request
Err(e) => {
#[cfg(debug_assertions)]
dbg!(format!("{} - {}", url, e));
Err("Couldn't send request to Reddit, this instance may be being rate-limited. Try another.".to_string())
} }
} }
} }

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

View File

@ -2,6 +2,9 @@
:root { :root {
--accent: aqua; --accent: aqua;
--green: #5cff85;
--nsfw: #FF5C5D;
--admin: #ea0027;
--text: white; --text: white;
--foreground: #222; --foreground: #222;
--background: #0F0F0F; --background: #0F0F0F;
@ -12,7 +15,7 @@
} }
::selection { ::selection {
color: var(--background); color: var(--foreground);
background: var(--accent); background: var(--accent);
} }
@ -62,6 +65,15 @@ main {
margin: 60px auto 20px auto margin: 60px auto 20px auto
} }
.wide main {
max-width: calc(100% - 40px);
}
.wide #column_one {
width: 100%;
max-width: 100%;
}
#column_one { #column_one {
max-width: 750px; max-width: 750px;
border-radius: 5px; border-radius: 5px;
@ -117,6 +129,20 @@ aside {
opacity: 0.5; opacity: 0.5;
} }
/* Light Theme */
.light {
--accent: #009a9a;
--green: #00a229;
--text: black;
--foreground: #f5f5f5;
--background: #DDD;
--outside: #ECECEC;
--post: #eee;
--highlighted: white;
--shadow: 0 1px 3px rgba(0,0,0,0.1);
}
/* User & Subreddit */ /* User & Subreddit */
#user, #subreddit, #sidebar { #user, #subreddit, #sidebar {
@ -197,6 +223,13 @@ aside {
/* Sorting and Search */ /* Sorting and Search */
.search_label {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
select { select {
background: var(--outside); background: var(--outside);
transition: 0.2s all; transition: 0.2s all;
@ -215,6 +248,7 @@ select, #search {
#searchbox { #searchbox {
display: flex; display: flex;
box-shadow: var(--shadow); box-shadow: var(--shadow);
border-radius: 5px;
} }
#searchbox > *, #sort_submit { #searchbox > *, #sort_submit {
@ -298,7 +332,7 @@ input[type="submit"]:hover { color: var(--accent); }
} }
#sort_options > a, footer > a { #sort_options > a, footer > a {
color: lightgrey; color: var(--text);
padding: 10px 20px; padding: 10px 20px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
@ -307,15 +341,58 @@ input[type="submit"]:hover { color: var(--accent); }
#sort_options > a.selected { #sort_options > a.selected {
background: var(--accent); background: var(--accent);
color: var(--background); color: var(--foreground);
} }
#sort_options > a:not(.selected):hover { #sort_options > a:not(.selected):hover {
background: var(--foreground); background: var(--foreground);
} }
#search_subreddits {
border-radius: 5px;
background: var(--post);
box-shadow: var(--shadow);
transition: 0.2s all;
border: 1px solid var(--highlighted);
margin-bottom: 20px;
}
.search_subreddit {
padding: 16px 20px;
display: block;
}
a.search_subreddit:hover {
text-decoration: none;
background: var(--foreground);
}
.search_subreddit:not(:last-child) {
border-bottom: 1px solid var(--highlighted);
}
.search_subreddit_header {
margin-bottom: 8px;
}
.search_subreddit_name {
font-weight: bold;
font-size: 16px;
}
.search_subreddit_description {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
opacity: 0.5;
}
/* Post */ /* Post */
.sep {
display: none;
}
.thread { .thread {
word-break: break-word; word-break: break-word;
} }
@ -427,26 +504,71 @@ input[type="submit"]:hover { color: var(--accent); }
} }
.post_thumbnail { .post_thumbnail {
object-fit: cover;
width: auto;
border-radius: 5px; border-radius: 5px;
border: 1px solid var(--foreground); border: 1px solid var(--foreground);
max-width: 20%; width: 20%;
max-width: 140px;
display: grid;
overflow: hidden;
flex-shrink: 0;
background-color: black;
}
.post_thumbnail img {
grid-area: 1 / 1 / 2 / 2;
width: 100%;
object-fit: cover;
align-self: center;
justify-self: center;
}
.post_thumbnail.no_thumbnail {
background-color: var(--highlighted)
}
.post_thumbnail svg {
grid-area: 1 / 1 / 2 / 2;
align-self: center;
justify-self: center;
stroke: var(--text);
}
.post_thumbnail span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
text-align: center;
background-color: rgba(0,0,0,0.8);
color: white;
grid-area: 1 / 1 / 2 / 2;
padding: 5px;
align-self: end;
} }
.post_flair { .post_flair {
background: var(--accent); background: var(--accent);
color: var(--background); color: var(--background);
padding: 5px; padding: 4px;
margin-right: 5px;
border-radius: 5px; border-radius: 5px;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
} }
.emoji {
width: 1em;
height: 1em;
display: inline-block;
background-size: contain;
background-position: 50% 50%;
background-repeat: no-repeat;
vertical-align: middle;
}
.nsfw { .nsfw {
color: #FF5C5D; color: var(--nsfw);
margin-top: 20px; margin-top: 20px;
border: 1px solid #FF5C5D; border: 1px solid var(--nsfw);
padding: 5px; padding: 5px;
font-size: 12px; font-size: 12px;
border-radius: 5px; border-radius: 5px;
@ -454,8 +576,8 @@ input[type="submit"]:hover { color: var(--accent); }
} }
.stickied { .stickied {
--accent: #5cff85; --accent: var(--green);
border: 1px solid #5cff85; border: 1px solid var(--green);
} }
/* Comment */ /* Comment */
@ -482,11 +604,6 @@ input[type="submit"]:hover { color: var(--accent); }
.comment_link { text-decoration: underline; } .comment_link { text-decoration: underline; }
.comment_author { opacity: 0.9; } .comment_author { opacity: 0.9; }
.comment_author.op {
color: var(--accent);
font-weight: bold;
}
.author_flair { .author_flair {
background: var(--highlighted); background: var(--highlighted);
color: var(--text); color: var(--text);
@ -555,7 +672,7 @@ input[type="submit"]:hover { color: var(--accent); }
padding: 5px; padding: 5px;
} }
.datetime { .created {
opacity: 0.5; opacity: 0.5;
} }
@ -565,62 +682,69 @@ input[type="submit"]:hover { color: var(--accent); }
background: var(--foreground); background: var(--foreground);
} }
.moderator, .admin { opacity: 1; }
.op, .moderator, .admin { font-weight: bold; }
.op { color: var(--accent); }
.moderator { color: var(--green); }
.admin { color: var(--admin); }
/* Layouts */ /* Layouts */
#compact .post:not(.highlighted) { .compact .post:not(.highlighted) {
border-radius: 0; border-radius: 0;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#compact .post:first-of-type { .compact .post:first-of-type {
border-radius: 5px 5px 0 0; border-radius: 5px 5px 0 0;
overflow: hidden; overflow: hidden;
} }
#compact .post:last-of-type { .compact .post:last-of-type {
border-radius: 0 0 5px 5px; border-radius: 0 0 5px 5px;
overflow: hidden; overflow: hidden;
} }
#compact .post.highlighted { .compact .post.highlighted {
border-radius: 5px; border-radius: 5px;
} }
#compact .post:not(:last-of-type):not(.highlighted):not(.stickied) { .compact .post:not(:last-of-type):not(.highlighted):not(.stickied) {
border-bottom: 0; border-bottom: 0;
} }
#compact .post_left { .compact .post_left {
border-radius: 0; border-radius: 0;
} }
#compact .post_header { .compact .post_header {
font-size: 14px; font-size: 14px;
} }
#compact .post_title { .compact .post_title {
margin-top: 5px; margin-top: 5px;
} }
#compact .post_text { .compact .post_text {
padding: 10px; padding: 10px;
} }
#compact .post_thumbnail { .compact .post_thumbnail {
max-width: 75px; width: 75px;
max-height: 75px; height: 75px;
} }
#compact footer { .compact footer {
margin-top: 20px; margin-top: 20px;
} }
#card .post_right { .card_post .post_right {
flex-direction: column; flex-direction: column;
} }
#card .post:not(.highlighted) .post_media { .card_post:not(.highlighted) .post_media {
margin-top: 0; margin-top: 0;
margin-bottom: 15px; margin-bottom: 15px;
} }
@ -666,6 +790,7 @@ input[type="submit"]:hover { color: var(--accent); }
border-radius: 5px; border-radius: 5px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
margin-left: 20px; margin-left: 20px;
background: var(--foreground);
} }
#save { #save {
@ -722,6 +847,7 @@ input[type="submit"] {
margin-top: 10px; margin-top: 10px;
border-radius: 5px; border-radius: 5px;
box-shadow: var(--shadow); box-shadow: var(--shadow);
overflow: auto;
} }
.md table { .md table {
@ -778,9 +904,15 @@ td, th {
padding: 5px 0; padding: 5px 0;
} }
.datetime { .comment_left {
width: 100%; min-width: 45px;
padding: 5px 0px;
} }
.comment_author { margin-left: 10px; }
.comment_score { min-width: 35px; }
.comment_data::marker { font-size: 18px; }
.created { width: 100%; }
} }
@media screen and (max-width: 800px) { @media screen and (max-width: 800px) {
@ -788,6 +920,7 @@ td, th {
flex-direction: column-reverse; flex-direction: column-reverse;
padding: 10px; padding: 10px;
margin: 100px 0 10px 0; margin: 100px 0 10px 0;
max-width: 100%;
} }
nav { nav {

View File

@ -8,10 +8,14 @@
<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">
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href="/style.css">
{% endblock %} {% endblock %}
</head> </head>
<body id="{% block layout %}{% endblock %}"> <body class="
{% if prefs.layout != "" %}{{ prefs.layout }}{% endif %}
{% if prefs.wide == "on" %} wide{% endif %}
{% if prefs.theme == "light" %} light{% endif %}">
<!-- NAVIGATION BAR --> <!-- NAVIGATION BAR -->
<nav> <nav>
<p id="logo"> <p id="logo">

View File

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Error: {{ message }}{% endblock %} {% block title %}Error: {{ msg }}{% endblock %}
{% block sortstyle %}{% endblock %} {% block sortstyle %}{% endblock %}
{% block content %} {% block content %}
<h1 style="text-align: center; font-size: 50px;">{{ message }}</h1> <h1 style="text-align: center; font-size: 50px;">{{ msg }}</h1>
{% endblock %} {% endblock %}

View File

@ -10,7 +10,7 @@
{% block root %}/r/{{ post.community }}{% endblock %}{% block location %}r/{{ post.community }}{% endblock %} {% block root %}/r/{{ post.community }}{% endblock %}{% block location %}r/{{ post.community }}{% endblock %}
{% block head %} {% block head %}
{% call super() %} {% call super() %}
<meta name="author" content="u/{{ post.author }}"> <meta name="author" content="u/{{ post.author.name }}">
{% endblock %} {% endblock %}
<!-- OPEN COMMENT MACRO --> <!-- OPEN COMMENT MACRO -->
@ -21,13 +21,14 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<details class="comment_right" open> <details class="comment_right" open>
<summary class="comment_data"><a class="comment_author {% if item.author == post.author %}op{% endif %}" href="/u/{{ item.author }}">u/{{ item.author }}</a> <summary class="comment_data">
{% if item.flair.0 != "" %} <a class="comment_author {{ item.author.distinguished }} {% if item.author.name == post.author.name %}op{% endif %}" href="/u/{{ item.author.name }}">u/{{ item.author.name }}</a>
<small class="author_flair">{{ item.flair.0 }}</small> {% if item.author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{% call utils::render_flair(item.author.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<span class="datetime">{{ item.time }}</span> <span class="created" title="{{ post.created }}">{{ item.rel_time }}</span>
</summary> </summary>
<p class="comment_body">{{ item.body }}</p> <div class="comment_body">{{ item.body }}</div>
{%- endmacro %} {%- endmacro %}
<!-- CLOSE COMMENT MACRO --> <!-- CLOSE COMMENT MACRO -->
@ -49,24 +50,24 @@
<p class="post_header"> <p class="post_header">
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a> <a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<a class="post_author" href="/u/{{ post.author }}">u/{{ post.author }}</a> <a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
{% if post.author_flair.0 != "" %} {% if post.author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{{ post.author_flair.0 }}</small> <small class="author_flair">{% call utils::render_flair(post.author.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</p> </p>
<a href="{{ post.permalink }}" class="post_title"> <a href="{{ post.permalink }}" class="post_title">
{{ post.title }} {{ post.title }}
{% if post.flair.0 != "" %} {% if post.flair.flair_parts.len() > 0 %}
<small class="post_flair" style="color:{{ post.flair.2 }}; background:{{ post.flair.1 }}">{{ post.flair.0 }}</small> <small class="post_flair" style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }}">{% call utils::render_flair(post.flair.flair_parts) %}</small>
{% endif %} {% endif %}
</a> </a>
<!-- POST MEDIA --> <!-- POST MEDIA -->
{% if post.post_type == "image" %} {% if post.post_type == "image" %}
<img class="post_media" src="{{ post.media }}"/> <img class="post_media" src="{{ post.media }}"/>
{% else if post.post_type == "video" %} {% else if post.post_type == "video" || post.post_type == "gif" %}
<video class="post_media" src="{{ post.media }}" controls autoplay loop></video> <video class="post_media" src="{{ post.media }}" controls autoplay loop></video>
{% else if post.post_type == "link" %} {% else if post.post_type == "link" %}
<a id="post_url" href="{{ post.media }}">{{ post.media }}</a> <a id="post_url" href="{{ post.media }}">{{ post.media }}</a>
@ -98,11 +99,11 @@
<div class="thread"> <div class="thread">
<!-- EACH COMMENT --> <!-- EACH COMMENT -->
{% call comment(c) %} {% call comment(c) %}
<div class="replies">{% for reply1 in c.replies %}{% call comment(reply1) %} <blockquote class="replies">{% for reply1 in c.replies %}{% call comment(reply1) %}
<!-- FIRST-LEVEL REPLIES --> <!-- FIRST-LEVEL REPLIES -->
<div class="replies">{% for reply2 in reply1.replies %}{% call comment(reply2) %} <blockquote class="replies">{% for reply2 in reply1.replies %}{% call comment(reply2) %}
<!-- SECOND-LEVEL REPLIES --> <!-- SECOND-LEVEL REPLIES -->
<div class="replies">{% for reply3 in reply2.replies %}{% call comment(reply3) %} <blockquote class="replies">{% for reply3 in reply2.replies %}{% call comment(reply3) %}
<!-- THIRD-LEVEL REPLIES --> <!-- THIRD-LEVEL REPLIES -->
{% if reply3.replies.len() > 0 %} {% if reply3.replies.len() > 0 %}
<!-- LINK TO CONTINUE REPLIES --> <!-- LINK TO CONTINUE REPLIES -->
@ -110,13 +111,13 @@
{% endif %} {% endif %}
{% call close() %} {% call close() %}
{% endfor %} {% endfor %}
</div>{% call close() %} </blockquote>{% call close() %}
{% endfor %} {% endfor %}
</div>{% call close() %} </blockquote>{% call close() %}
{% endfor %} {% endfor %}
</div>{% call close() %} </blockquote>{% call close() %}
</div> </div>
{%- endfor %} {%- endfor %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -3,8 +3,6 @@
{% block title %}Libreddit: search results - {{ params.q }}{% endblock %} {% block title %}Libreddit: search results - {{ params.q }}{% endblock %}
{% block layout %}{% if prefs.layout != "" %}{{ prefs.layout }}{% endif %}{% endblock %}
{% block content %} {% block content %}
<div id="column_one"> <div id="column_one">
<form id="search_sort"> <form id="search_sort">
@ -12,7 +10,7 @@
{% if sub != "" %} {% if sub != "" %}
<div id="inside"> <div id="inside">
<input type="checkbox" name="restrict_sr" id="restrict_sr" {% if params.restrict_sr != "" %}checked{% endif %}> <input type="checkbox" name="restrict_sr" id="restrict_sr" {% if params.restrict_sr != "" %}checked{% endif %}>
<label for="restrict_sr">in r/{{ sub }}</label> <label for="restrict_sr" class="search_label">in r/{{ sub }}</label>
</div> </div>
{% endif %} {% endif %}
<select id="sort_options" name="sort"> <select id="sort_options" name="sort">
@ -21,11 +19,26 @@
{% call utils::options(params.t, ["hour", "day", "week", "month", "year", "all"], "all") %} {% call utils::options(params.t, ["hour", "day", "week", "month", "year", "all"], "all") %}
</select>{% endif %}<input id="sort_submit" type="submit" value="&rarr;"> </select>{% endif %}<input id="sort_submit" type="submit" value="&rarr;">
</form> </form>
{% if subreddits.len() > 0 %}
<div id="search_subreddits">
{% for subreddit in subreddits %}
<a href="{{ subreddit.url }}" class="search_subreddit">
<p class="search_subreddit_header">
<span class="search_subreddit_name">{{ subreddit.name }}</span>
<span class="dot">&bull;</span>
<span class="search_subreddit_members">{{ subreddit.subscribers }} Members</span>
</p>
<p class="search_subreddit_description">{{ subreddit.description }}</p>
</a>
{% endfor %}
</div>
{% endif %}
{% for post in posts %} {% for post in posts %}
{% if post.flags.nsfw && prefs.hide_nsfw == "on" %} {% if post.flags.nsfw && prefs.hide_nsfw == "on" %}
{% else if post.title != "Comment" %} {% else if post.title != "Comment" %}
<div class="post"> <div class="post {% if prefs.layout == "card" && post.post_type == "image" %}card_post{% endif %}">
<div class="post_left"> <div class="post_left">
<p class="post_score">{{ post.score }}</p> <p class="post_score">{{ post.score }}</p>
{% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %} {% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %}
@ -35,23 +48,36 @@
<p class="post_header"> <p class="post_header">
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a> <a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<a class="post_author" href="/u/{{ post.author }}">u/{{ post.author }}</a> <a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
{% if post.author_flair.0 != "" %} {% if post.author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{{ post.author_flair.0 }}</small> <small class="author_flair">{% call utils::render_flair(post.author.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</p> </p>
<p class="post_title"> <p class="post_title">
{% if post.flair.0 != "" %} {% if post.flair.flair_parts.len() > 0 %}
<small class="post_flair" style="color:{{ post.flair.2 }}; background:{{ post.flair.1 }}">{{ post.flair.0 }}</small> <small class="post_flair" style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }}">{% call utils::render_flair(post.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<a href="{{ post.permalink }}">{{ post.title }}</a> <a href="{{ post.permalink }}">{{ post.title }}</a>
</p> </p>
</div> </div>
<!-- POST THUMBNAIL --> <!-- POST MEDIA/THUMBNAIL -->
<img class="post_thumbnail" src="{{ post.thumbnail }}"> {% if prefs.layout == "card" && post.post_type == "image" %}
<img class="post_media" src="{{ post.media }}"/>
{% else if post.post_type != "self" %}
<a class="post_thumbnail {% if post.thumbnail == "" %}no_thumbnail{% endif %}" href="{% if post.post_type == "link" %}{{ post.media }}{% else %}{{ post.permalink }}{% endif %}">
{% if post.thumbnail == "" %}
<svg viewBox="0 0 100 106" width="50" height="53" xmlns="http://www.w3.org/2000/svg">
<path d="M35,15h-15a10,10 0,0,0 0,20h25a10,10 0,0,0 10,-10m-12.5,0a10, 10 0,0,1 10, -10h25a10,10 0,0,1 0,20h-15" fill="none" stroke-width="5" stroke-linecap="round"/>
</svg>
{% else %}
<img src="{{ post.thumbnail }}">
{% endif %}
<span>{% if post.post_type == "link" %}{{ post.domain }}{% else %}{{ post.post_type }}{% endif %}</span>
</a>
{% endif %}
</div> </div>
</div> </div>
{% else %} {% else %}
@ -63,7 +89,7 @@
<details class="comment_right" open> <details class="comment_right" open>
<summary class="comment_data"> <summary class="comment_data">
<a class="comment_link" href="{{ post.permalink }}">COMMENT</a> <a class="comment_link" href="{{ post.permalink }}">COMMENT</a>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</summary> </summary>
<p class="comment_body">{{ post.body }}</p> <p class="comment_body">{{ post.body }}</p>
</details> </details>

View File

@ -7,35 +7,46 @@
{% call utils::search("".to_owned(), "", "") %} {% call utils::search("".to_owned(), "", "") %}
{% endblock %} {% endblock %}
{% block body %} {% block content %}
<main> <form id="settings" action="/settings" method="POST">
<form id="settings" action="/settings" method="POST"> <div id="prefs">
<div id="prefs"> <p>Appearance</p>
<div id="front_page"> <div id="theme">
<label for="front_page">Front page:</label> <label for="theme">Theme:</label>
<select name="front_page"> <select name="theme">
{% call utils::options(prefs.front_page, ["popular", "all"], "popular") %} {% call utils::options(prefs.theme, ["dark", "light"], "dark") %}
</select> </select>
</div> </div>
<div id="layout"> <p>Interface</p>
<label for="layout">Layout:</label> <div id="front_page">
<select name="layout"> <label for="front_page">Front page:</label>
{% call utils::options(prefs.layout, ["card", "clean", "compact"], "clean") %} <select name="front_page">
</select> {% call utils::options(prefs.front_page, ["popular", "all"], "popular") %}
</div> </select>
<div id="comment_sort"> </div>
<label for="comment_sort">Default comment sort:</label> <div id="layout">
<select name="comment_sort"> <label for="layout">Layout:</label>
{% call utils::options(prefs.comment_sort, ["confidence", "top", "new", "controversial", "old"], "confidence") %} <select name="layout">
</select> {% call utils::options(prefs.layout, ["card", "clean", "compact"], "clean") %}
</div> </select>
<div id="hide_nsfw"> </div>
<label for="hide_nsfw">Hide NSFW posts:</label> <div id="wide">
<input type="checkbox" name="hide_nsfw" {% if prefs.hide_nsfw == "on" %}checked{% endif %}> <label for="wide">Wide UI:</label>
</div> <input type="checkbox" name="wide" {% if prefs.wide == "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> <p>Content</p>
<input id="save" type="submit" value="Save"> <div id="comment_sort">
</form> <label for="comment_sort">Default comment sort:</label>
</main> <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,8 +11,6 @@
{% call utils::search(["/r/", sub.name.as_str()].concat(), "") %} {% call utils::search(["/r/", sub.name.as_str()].concat(), "") %}
{% endblock %} {% endblock %}
{% block layout %}{% if prefs.layout != "" %}{{ prefs.layout }}{% endif %}{% endblock %}
{% block body %} {% block body %}
<main> <main>
<div id="column_one"> <div id="column_one">
@ -33,7 +31,8 @@
<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.hide_nsfw == "on") %}
<div class="post {% if post.flags.stickied %}stickied{% endif %}"> <hr class="sep" />
<div class="post {% if post.flags.stickied %}stickied{% endif %} {% if prefs.layout == "card" && post.post_type == "image" %}card_post{% endif %}">
<div class="post_left"> <div class="post_left">
<p class="post_score">{{ post.score }}</p> <p class="post_score">{{ post.score }}</p>
{% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %} {% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %}
@ -43,13 +42,13 @@
<p class="post_header"> <p class="post_header">
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a> <a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<a class="post_author" href="/u/{{ post.author }}">u/{{ post.author }}</a> <a class="post_author" href="/u/{{ post.author.name }}">u/{{ post.author.name }}</a>
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</p> </p>
<p class="post_title"> <p class="post_title">
{% if post.flair.0 != "" %} {% if post.flair.flair_parts.len() > 0 %}
<small class="post_flair" style="color:{{ post.flair.2 }}; background:{{ post.flair.1 }}">{{ post.flair.0 }}</small> <small class="post_flair" style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }}">{% call utils::render_flair(post.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<a href="{{ post.permalink }}">{{ post.title }}</a> <a href="{{ post.permalink }}">{{ post.title }}</a>
</p> </p>
@ -58,8 +57,17 @@
<!-- POST MEDIA/THUMBNAIL --> <!-- POST MEDIA/THUMBNAIL -->
{% if prefs.layout == "card" && post.post_type == "image" %} {% if prefs.layout == "card" && post.post_type == "image" %}
<img class="post_media" src="{{ post.media }}"/> <img class="post_media" src="{{ post.media }}"/>
{% else if prefs.layout != "card" %} {% else if post.post_type != "self" %}
<img class="post_thumbnail" src="{{ post.thumbnail }}"> <a class="post_thumbnail {% if post.thumbnail == "" %}no_thumbnail{% endif %}" href="{% if post.post_type == "link" %}{{ post.media }}{% else %}{{ post.permalink }}{% endif %}">
{% if post.thumbnail == "" %}
<svg viewBox="0 0 100 106" width="50" height="53" xmlns="http://www.w3.org/2000/svg">
<path d="M35,15h-15a10,10 0,0,0 0,20h25a10,10 0,0,0 10,-10m-12.5,0a10, 10 0,0,1 10, -10h25a10,10 0,0,1 0,20h-15" fill="none" stroke-width="5" stroke-linecap="round"/>
</svg>
{% else %}
<img src="{{ post.thumbnail }}">
{% endif %}
<span>{% if post.post_type == "link" %}{{ post.domain }}{% else %}{{ post.post_type }}{% endif %}</span>
</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -69,15 +77,15 @@
<footer> <footer>
{% if ends.0 != "" %} {% if ends.0 != "" %}
<a href="?sort={{ sort.0 }}&before={{ ends.0 }}">PREV</a> <a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}">PREV</a>
{% endif %} {% endif %}
{% if ends.1 != "" %} {% if ends.1 != "" %}
<a href="?sort={{ sort.0 }}&after={{ ends.1 }}">NEXT</a> <a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}">NEXT</a>
{% endif %} {% endif %}
</footer> </footer>
</div> </div>
{% if sub.name != "" %} {% if sub.name != "" && !sub.name.contains("+") %}
<aside> <aside>
<div class="panel" id="subreddit"> <div class="panel" id="subreddit">
{% if sub.wiki %} {% if sub.wiki %}
@ -106,4 +114,4 @@
</aside> </aside>
{% endif %} {% endif %}
</main> </main>
{% endblock %} {% endblock %}

View File

@ -7,10 +7,8 @@
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %} {% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Libreddit{% endblock %}
{% block layout %}{% if prefs.layout != "" %}{{ prefs.layout }}{% endif %}{% endblock %}
{% block body %} {% block body %}
<main style="max-width: 1000px;"> <main>
<div id="column_one"> <div id="column_one">
<form id="sort"> <form id="sort">
<select name="sort"> <select name="sort">
@ -25,7 +23,7 @@
{% if post.flags.nsfw && prefs.hide_nsfw == "on" %} {% if post.flags.nsfw && prefs.hide_nsfw == "on" %}
{% else if post.title != "Comment" %} {% else if post.title != "Comment" %}
<div class="post"> <div class="post {% if post.flags.stickied %}stickied{% endif %} {% if prefs.layout == "card" && post.post_type == "image" %}card_post{% endif %}">
<div class="post_left"> <div class="post_left">
<p class="post_score">{{ post.score }}</p> <p class="post_score">{{ post.score }}</p>
{% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %} {% if post.flags.nsfw %}<div class="nsfw">NSFW</div>{% endif %}
@ -34,17 +32,17 @@
<div class="post_text"> <div class="post_text">
<p class="post_header"> <p class="post_header">
<a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a> <a class="post_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
{% if post.author_flair.0 != "" %} {% if post.author.flair.flair_parts.len() > 0 %}
<small class="author_flair">{{ post.author_flair.0 }}</small> <small class="author_flair">{% call utils::render_flair(post.author.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<span class="dot">&bull;</span> <span class="dot">&bull;</span>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</p> </p>
<p class="post_title"> <p class="post_title">
{% if post.flair.0 == "Comment" %} {% if post.flair.background_color == "Comment" %}
{% else if post.flair.0 == "" %} {% else if post.flair.background_color == "" %}
{% else %} {% else %}
<small class="post_flair" style="color:{{ post.flair.2 }}; background:{{ post.flair.1 }}">{{ post.flair.0 }}</small> <small class="post_flair" style="color:{{ post.flair.foreground_color }}; background:{{ post.flair.background_color }}">{% call utils::render_flair(post.flair.flair_parts) %}</small>
{% endif %} {% endif %}
<a href="{{ post.permalink }}">{{ post.title }}</a> <a href="{{ post.permalink }}">{{ post.title }}</a>
</p> </p>
@ -53,8 +51,17 @@
<!-- POST MEDIA/THUMBNAIL --> <!-- POST MEDIA/THUMBNAIL -->
{% if prefs.layout == "card" && post.post_type == "image" %} {% if prefs.layout == "card" && post.post_type == "image" %}
<img class="post_media" src="{{ post.media }}"/> <img class="post_media" src="{{ post.media }}"/>
{% else if prefs.layout != "card" %} {% else if post.post_type != "self" %}
<img class="post_thumbnail" src="{{ post.thumbnail }}"> <a class="post_thumbnail {% if post.thumbnail == "" %}no_thumbnail{% endif %}" href="{% if post.post_type == "link" %}{{ post.media }}{% else %}{{ post.permalink }}{% endif %}">
{% if post.thumbnail == "" %}
<svg viewBox="0 0 100 106" width="50" height="53" xmlns="http://www.w3.org/2000/svg">
<path d="M35,15h-15a10,10 0,0,0 0,20h25a10,10 0,0,0 10,-10m-12.5,0a10, 10 0,0,1 10, -10h25a10,10 0,0,1 0,20h-15" fill="none" stroke-width="5" stroke-linecap="round"/>
</svg>
{% else %}
<img src="{{ post.thumbnail }}">
{% endif %}
<span>{% if post.post_type == "link" %}{{ post.domain }}{% else %}{{ post.post_type }}{% endif %}</span>
</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -67,7 +74,7 @@
<details class="comment_right" open> <details class="comment_right" open>
<summary class="comment_data"> <summary class="comment_data">
<a class="comment_link" href="{{ post.permalink }}">COMMENT</a> <a class="comment_link" href="{{ post.permalink }}">COMMENT</a>
<span class="datetime">{{ post.time }}</span> <span class="created" title="{{ post.created }}">{{ post.rel_time }}</span>
</summary> </summary>
<p class="comment_body">{{ post.body }}</p> <p class="comment_body">{{ post.body }}</p>
</details> </details>
@ -79,11 +86,11 @@
<footer> <footer>
{% if ends.0 != "" %} {% if ends.0 != "" %}
<a href="?sort={{ sort.0 }}&before={{ ends.0 }}">PREV</a> <a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}">PREV</a>
{% endif %} {% endif %}
{% if ends.1 != "" %} {% if ends.1 != "" %}
<a href="?sort={{ sort.0 }}&after={{ ends.1 }}">NEXT</a> <a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}">NEXT</a>
{% endif %} {% endif %}
</footer> </footer>
</div> </div>
@ -102,4 +109,4 @@
</div> </div>
</aside> </aside>
</main> </main>
{% endblock %} {% endblock %}

View File

@ -20,9 +20,16 @@
{% if root != "/r/" && !root.is_empty() %} {% if root != "/r/" && !root.is_empty() %}
<div id="inside"> <div id="inside">
<input type="checkbox" name="restrict_sr" id="restrict_sr"> <input type="checkbox" name="restrict_sr" id="restrict_sr">
<label for="restrict_sr">in {{ root }}</label> <label for="restrict_sr" class="search_label">in {{ root }}</label>
</div> </div>
{% endif %} {% endif %}
<input type="submit" value="&rarr;"> <input type="submit" value="&rarr;">
</form> </form>
{%- endmacro %} {%- endmacro %}
{% macro render_flair(flair) -%}
{% for flair_part in flair %}
{% if flair_part.flair_part_type == "emoji" %}<span class="emoji" style="background-image:url('{{ flair_part.value }}')"></span>
{% else if flair_part.flair_part_type == "text" %}<span>{{ flair_part.value }}</span>{% endif %}
{% endfor %}
{%- endmacro %}