* Add RSS feeds * feat(rss): feature-ify rss * feat(rss): config-ify rss * fix(rss): update info page * feat(rss): conditionally add RSS feeds to user and sub pages * feat(rss): implement URLs for RSS
This commit is contained in:
parent
9bdb5c8966
commit
374238abc3
234
Cargo.lock
generated
234
Cargo.lock
generated
@ -98,7 +98,7 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -124,7 +124,20 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atom_syndication"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f34613907f31c9dbef0240156db3c9263f34842b6e1a8999d2304ea62c8a30"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder 0.20.0",
|
||||
"diligent-date-parser",
|
||||
"never",
|
||||
"quick-xml 0.31.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -236,10 +249,10 @@ version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "771aa57f3b17da6c8bcacb187bb9ec9bc81c8160e72342e67c329e0e1651a669"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.20.9",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -260,6 +273,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
@ -348,14 +370,38 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"darling_macro 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.20.9",
|
||||
"darling_macro 0.20.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -368,8 +414,19 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
dependencies = [
|
||||
"darling_core 0.14.4",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -378,9 +435,9 @@ version = "0.20.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.20.9",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -398,6 +455,68 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8"
|
||||
dependencies = [
|
||||
"derive_builder_macro 0.12.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7"
|
||||
dependencies = [
|
||||
"derive_builder_macro 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
|
||||
dependencies = [
|
||||
"darling 0.14.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d"
|
||||
dependencies = [
|
||||
"darling 0.20.9",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||
dependencies = [
|
||||
"derive_builder_core 0.12.0",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b"
|
||||
dependencies = [
|
||||
"derive_builder_core 0.20.0",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@ -408,12 +527,30 @@ dependencies = [
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diligent-date-parser"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6cf7fe294274a222363f84bcb63cdea762979a0443b4cf1f4f8fd17c86b1182"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
@ -862,6 +999,12 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "never"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -878,6 +1021,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
@ -1002,6 +1154,26 @@ version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
|
||||
dependencies = [
|
||||
"encoding_rs",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
@ -1061,6 +1233,7 @@ dependencies = [
|
||||
"pretty_env_logger",
|
||||
"regex",
|
||||
"route-recognizer",
|
||||
"rss",
|
||||
"rust-embed",
|
||||
"sealed_test",
|
||||
"serde",
|
||||
@ -1138,6 +1311,18 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
|
||||
|
||||
[[package]]
|
||||
name = "rss"
|
||||
version = "2.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7b2c77eb4450d7d5f98df52c381cd6c4e19b75dad9209a9530b85a44510219a"
|
||||
dependencies = [
|
||||
"atom_syndication",
|
||||
"derive_builder 0.12.0",
|
||||
"never",
|
||||
"quick-xml 0.30.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.4.0"
|
||||
@ -1158,7 +1343,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
@ -1307,7 +1492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77253fb2d4451418d07025826028bcb96ee42d3e58859689a70ce62908009db6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1350,7 +1535,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1437,6 +1622,12 @@ version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@ -1449,6 +1640,17 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
@ -1498,7 +1700,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1576,7 +1778,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1950,7 +2152,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -42,8 +42,10 @@ fastrand = "2.0.1"
|
||||
log = "0.4.20"
|
||||
pretty_env_logger = "0.5.0"
|
||||
dotenvy = "0.15.7"
|
||||
rss = "2.0.7"
|
||||
arc-swap = "1.7.1"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
lipsum = "0.9.0"
|
||||
sealed_test = "1.0.0"
|
||||
|
@ -381,7 +381,8 @@ Assign a default value for each instance-specific setting by passing environment
|
||||
| `ROBOTS_DISABLE_INDEXING` | `["on", "off"]` | `off` | Disables indexing of the instance by search engines. |
|
||||
| `PUSHSHIFT_FRONTEND` | String | `undelete.pullpush.io` | Allows the server to set the Pushshift frontend to be used with "removed" links. |
|
||||
| `PORT` | Integer 0-65535 | `8080` | The **internal** port Redlib listens on. |
|
||||
|
||||
| `ENABLE_RSS` | `["on", "off"]` | `off` | Enables RSS feed generation. |
|
||||
| `FULL_URL` | String | (empty) | Allows for proper URLs (for now, only needed by RSS)
|
||||
## Default user settings
|
||||
|
||||
Assign a default value for each user-modifiable setting by passing environment variables to Redlib in the format `REDLIB_DEFAULT_{Y}`. Replace `{Y}` with the setting name (see list below) in capital letters.
|
||||
|
6
app.json
6
app.json
@ -70,6 +70,12 @@
|
||||
},
|
||||
"REDLIB_PUSHSHIFT_FRONTEND": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_ENABLE_RSS": {
|
||||
"required": false
|
||||
},
|
||||
"REDLIB_FULL_URL": {
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,12 @@ pub struct Config {
|
||||
#[serde(rename = "REDLIB_PUSHSHIFT_FRONTEND")]
|
||||
#[serde(alias = "LIBREDDIT_PUSHSHIFT_FRONTEND")]
|
||||
pub(crate) pushshift: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_ENABLE_RSS")]
|
||||
pub(crate) enable_rss: Option<String>,
|
||||
|
||||
#[serde(rename = "REDLIB_FULL_URL")]
|
||||
pub(crate) full_url: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@ -148,6 +154,8 @@ impl Config {
|
||||
banner: parse("REDLIB_BANNER"),
|
||||
robots_disable_indexing: parse("REDLIB_ROBOTS_DISABLE_INDEXING"),
|
||||
pushshift: parse("REDLIB_PUSHSHIFT_FRONTEND"),
|
||||
enable_rss: parse("REDLIB_ENABLE_RSS"),
|
||||
full_url: parse("REDLIB_FULL_URL"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,6 +183,8 @@ fn get_setting_from_config(name: &str, config: &Config) -> Option<String> {
|
||||
"REDLIB_BANNER" => config.banner.clone(),
|
||||
"REDLIB_ROBOTS_DISABLE_INDEXING" => config.robots_disable_indexing.clone(),
|
||||
"REDLIB_PUSHSHIFT_FRONTEND" => config.pushshift.clone(),
|
||||
"REDLIB_ENABLE_RSS" => config.enable_rss.clone(),
|
||||
"REDLIB_FULL_URL" => config.full_url.clone(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,8 @@ impl InstanceInfo {
|
||||
["Compile mode", &self.compile_mode],
|
||||
["SFW only", &convert(&self.config.sfw_only)],
|
||||
["Pushshift frontend", &convert(&self.config.pushshift)],
|
||||
["RSS enabled", &convert(&self.config.enable_rss)],
|
||||
["Full URL", &convert(&self.config.full_url)],
|
||||
//TODO: fallback to crate::config::DEFAULT_PUSHSHIFT_FRONTEND
|
||||
])
|
||||
.with_header_row(["Settings"]),
|
||||
@ -165,6 +167,8 @@ impl InstanceInfo {
|
||||
Compile mode: {}\n
|
||||
SFW only: {:?}\n
|
||||
Pushshift frontend: {:?}\n
|
||||
RSS enabled: {:?}\n
|
||||
Full URL: {:?}\n
|
||||
Config:\n
|
||||
Banner: {:?}\n
|
||||
Hide awards: {:?}\n
|
||||
@ -189,6 +193,8 @@ impl InstanceInfo {
|
||||
self.deploy_unix_ts,
|
||||
self.compile_mode,
|
||||
self.config.sfw_only,
|
||||
self.config.enable_rss,
|
||||
self.config.full_url,
|
||||
self.config.pushshift,
|
||||
self.config.banner,
|
||||
self.config.default_hide_awards,
|
||||
|
@ -254,6 +254,7 @@ async fn main() {
|
||||
app.at("/u/:name/comments/:id/:title/:comment_id").get(|r| post::item(r).boxed());
|
||||
|
||||
app.at("/user/[deleted]").get(|req| error(req, "User has deleted their account").boxed());
|
||||
app.at("/user/:name.rss").get(|r| user::rss(r).boxed());
|
||||
app.at("/user/:name").get(|r| user::profile(r).boxed());
|
||||
app.at("/user/:name/:listing").get(|r| user::profile(r).boxed());
|
||||
app.at("/user/:name/comments/:id").get(|r| post::item(r).boxed());
|
||||
@ -265,6 +266,9 @@ async fn main() {
|
||||
app.at("/settings/restore").get(|r| settings::restore(r).boxed());
|
||||
app.at("/settings/update").get(|r| settings::update(r).boxed());
|
||||
|
||||
// RSS Subscriptions
|
||||
app.at("/r/:sub.rss").get(|r| subreddit::rss(r).boxed());
|
||||
|
||||
// Subreddit services
|
||||
app
|
||||
.at("/r/:sub")
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::{config, utils};
|
||||
// CRATES
|
||||
use crate::utils::{
|
||||
catch_random, error, filter_posts, format_num, format_url, get_filters, nsfw_landing, param, redirect, rewrite_urls, setting, template, val, Post, Preferences, Subreddit,
|
||||
@ -459,6 +460,56 @@ async fn subreddit(sub: &str, quarantined: bool) -> Result<Subreddit, String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
if config::get_setting("REDLIB_ENABLE_RSS").is_none() {
|
||||
return Ok(error(req, "RSS is disabled on this instance.").await.unwrap_or_default());
|
||||
}
|
||||
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use rss::{ChannelBuilder, Item};
|
||||
|
||||
// Get subreddit
|
||||
let sub = req.param("sub").unwrap_or_default();
|
||||
let post_sort = req.cookie("post_sort").map_or_else(|| "hot".to_string(), |c| c.value().to_string());
|
||||
let sort = req.param("sort").unwrap_or_else(|| req.param("id").unwrap_or(post_sort));
|
||||
|
||||
// Get path
|
||||
let path = format!("/r/{sub}/{sort}.json?{}", req.uri().query().unwrap_or_default());
|
||||
|
||||
// Get subreddit data
|
||||
let subreddit = subreddit(&sub, false).await?;
|
||||
|
||||
// Get posts
|
||||
let (posts, _) = Post::fetch(&path, false).await?;
|
||||
|
||||
// Build the RSS feed
|
||||
let channel = ChannelBuilder::default()
|
||||
.title(&subreddit.title)
|
||||
.description(&subreddit.description)
|
||||
.items(
|
||||
posts
|
||||
.into_iter()
|
||||
.map(|post| Item {
|
||||
title: Some(post.title.to_string()),
|
||||
link: Some(utils::get_post_url(&post)),
|
||||
author: Some(post.author.name),
|
||||
content: Some(rewrite_urls(&post.body)),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.build();
|
||||
|
||||
// Serialize the feed to RSS
|
||||
let body = channel.to_string().into_bytes();
|
||||
|
||||
// Create the HTTP response
|
||||
let mut res = Response::new(Body::from(body));
|
||||
res.headers_mut().insert(CONTENT_TYPE, hyper::header::HeaderValue::from_static("application/rss+xml"));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_fetching_subreddit() {
|
||||
let subreddit = subreddit("rust", false).await;
|
||||
|
51
src/user.rs
51
src/user.rs
@ -2,6 +2,7 @@
|
||||
use crate::client::json;
|
||||
use crate::server::RequestExt;
|
||||
use crate::utils::{error, filter_posts, format_url, get_filters, nsfw_landing, param, setting, template, Post, Preferences, User};
|
||||
use crate::{config, utils};
|
||||
use askama::Template;
|
||||
use hyper::{Body, Request, Response};
|
||||
use time::{macros::format_description, OffsetDateTime};
|
||||
@ -129,6 +130,56 @@ async fn user(name: &str) -> Result<User, String> {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn rss(req: Request<Body>) -> Result<Response<Body>, String> {
|
||||
if config::get_setting("REDLIB_ENABLE_RSS").is_none() {
|
||||
return Ok(error(req, "RSS is disabled on this instance.").await.unwrap_or_default());
|
||||
}
|
||||
use crate::utils::rewrite_urls;
|
||||
use hyper::header::CONTENT_TYPE;
|
||||
use rss::{ChannelBuilder, Item};
|
||||
|
||||
// Get user
|
||||
let user_str = req.param("name").unwrap_or_default();
|
||||
|
||||
let listing = req.param("listing").unwrap_or_else(|| "overview".to_string());
|
||||
|
||||
// Get path
|
||||
let path = format!("/user/{user_str}/{listing}.json?{}&raw_json=1", req.uri().query().unwrap_or_default(),);
|
||||
|
||||
// Get user
|
||||
let user_obj = user(&user_str).await.unwrap_or_default();
|
||||
|
||||
// Get posts
|
||||
let (posts, _) = Post::fetch(&path, false).await?;
|
||||
|
||||
// Build the RSS feed
|
||||
let channel = ChannelBuilder::default()
|
||||
.title(user_str)
|
||||
.description(user_obj.description)
|
||||
.items(
|
||||
posts
|
||||
.into_iter()
|
||||
.map(|post| Item {
|
||||
title: Some(post.title.to_string()),
|
||||
link: Some(utils::get_post_url(&post)),
|
||||
author: Some(post.author.name),
|
||||
content: Some(rewrite_urls(&post.body)),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.build();
|
||||
|
||||
// Serialize the feed to RSS
|
||||
let body = channel.to_string().into_bytes();
|
||||
|
||||
// Create the HTTP response
|
||||
let mut res = Response::new(Body::from(body));
|
||||
res.headers_mut().insert(CONTENT_TYPE, hyper::header::HeaderValue::from_static("application/rss+xml"));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_fetching_user() {
|
||||
let user = user("spez").await;
|
||||
|
30
src/utils.rs
30
src/utils.rs
@ -1,5 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::config::get_setting;
|
||||
use crate::config::{self, get_setting};
|
||||
//
|
||||
// CRATES
|
||||
//
|
||||
@ -15,6 +15,7 @@ use serde_json::Value;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
use time::{macros::format_description, Duration, OffsetDateTime};
|
||||
use url::Url;
|
||||
|
||||
@ -327,6 +328,7 @@ pub struct Post {
|
||||
pub gallery: Vec<GalleryMedia>,
|
||||
pub awards: Awards,
|
||||
pub nsfw: bool,
|
||||
pub out_url: Option<String>,
|
||||
pub ws_url: String,
|
||||
}
|
||||
|
||||
@ -435,6 +437,7 @@ impl Post {
|
||||
awards,
|
||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||
ws_url: val(post, "websocket_url"),
|
||||
out_url: post["data"]["url_overridden_by_dest"].as_str().map(|a| a.to_string()),
|
||||
});
|
||||
}
|
||||
Ok((posts, res["data"]["after"].as_str().unwrap_or_default().to_string()))
|
||||
@ -770,6 +773,7 @@ pub async fn parse_post(post: &Value) -> Post {
|
||||
awards,
|
||||
nsfw: post["data"]["over_18"].as_bool().unwrap_or_default(),
|
||||
ws_url: val(post, "websocket_url"),
|
||||
out_url: post["data"]["url_overridden_by_dest"].as_str().map(|a| a.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1082,6 +1086,16 @@ pub fn sfw_only() -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the config/env variable REDLIB_ENABLE_RSS is set to "on".
|
||||
/// If this variable is set as such, the instance will enable RSS feeds.
|
||||
/// Otherwise, the instance will not provide RSS feeds.
|
||||
pub fn enable_rss() -> bool {
|
||||
match get_setting("REDLIB_ENABLE_RSS") {
|
||||
Some(val) => val == "on",
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if a request shoud redirect to a nsfw landing gate.
|
||||
pub fn should_be_nsfw_gated(req: &Request<Body>, req_url: &str) -> bool {
|
||||
let sfw_instance = sfw_only();
|
||||
@ -1137,6 +1151,20 @@ pub fn url_path_basename(path: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the URL of a post, as needed by RSS feeds
|
||||
pub fn get_post_url(post: &Post) -> String {
|
||||
if let Some(out_url) = &post.out_url {
|
||||
// Handle cross post
|
||||
if out_url.starts_with("/r/") {
|
||||
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), out_url)
|
||||
} else {
|
||||
out_url.to_string()
|
||||
}
|
||||
} else {
|
||||
format!("{}{}", config::get_setting("REDLIB_FULL_URL").unwrap_or_default(), post.permalink)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{format_num, format_url, rewrite_urls};
|
||||
|
442
static/style.css
442
static/style.css
@ -31,14 +31,15 @@
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('/Inter.var.woff2') format('woff2-variations');
|
||||
font-family: "Inter";
|
||||
src: url("/Inter.var.woff2") format("woff2-variations");
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
}
|
||||
|
||||
/* Automatic theme selection */
|
||||
:root, .dark{
|
||||
:root,
|
||||
.dark {
|
||||
/* Default & fallback theme (dark) */
|
||||
--accent: aqua;
|
||||
--green: #5cff85;
|
||||
@ -107,8 +108,31 @@
|
||||
outline: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
html, body, div, h1, h2, h3, h4, h5, h6, ul, ol, dl, li, dt, dd, p, blockquote,
|
||||
pre, form, fieldset, table, th, td, select, input {
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
li,
|
||||
dt,
|
||||
dd,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
form,
|
||||
fieldset,
|
||||
table,
|
||||
th,
|
||||
td,
|
||||
select,
|
||||
input {
|
||||
accent-color: var(--accent);
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
@ -166,9 +190,16 @@ nav.fixed_navbar {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
nav * { color: var(--text); }
|
||||
nav #reddit, #code > span { color: var(--accent); }
|
||||
nav #code > svg { stroke: var(--accent); }
|
||||
nav * {
|
||||
color: var(--text);
|
||||
}
|
||||
nav #reddit,
|
||||
#code > span {
|
||||
color: var(--accent);
|
||||
}
|
||||
nav #code > svg {
|
||||
stroke: var(--accent);
|
||||
}
|
||||
|
||||
nav #logo {
|
||||
grid-area: logo;
|
||||
@ -224,8 +255,8 @@ figcaption {
|
||||
/* all other browsers */
|
||||
@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
|
||||
.popup {
|
||||
-webkit-backdrop-filter: blur(.25rem) brightness(15%);
|
||||
backdrop-filter: blur(.25rem) brightness(15%);
|
||||
-webkit-backdrop-filter: blur(0.25rem) brightness(15%);
|
||||
backdrop-filter: blur(0.25rem) brightness(15%);
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,7 +376,7 @@ body > footer {
|
||||
|
||||
.footer-button {
|
||||
align-items: center;
|
||||
border-radius: .25rem;
|
||||
border-radius: 0.25rem;
|
||||
box-sizing: border-box;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
@ -403,7 +434,8 @@ aside {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.post, .panel {
|
||||
.post,
|
||||
.panel {
|
||||
border: var(--panel-border);
|
||||
}
|
||||
|
||||
@ -414,7 +446,9 @@ aside {
|
||||
|
||||
/* User & Subreddit */
|
||||
|
||||
#user, #subreddit, #sidebar {
|
||||
#user,
|
||||
#subreddit,
|
||||
#sidebar {
|
||||
margin: 40px auto 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -424,19 +458,34 @@ aside {
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#subreddit, #sidebar { min-width: 350px; }
|
||||
#subreddit,
|
||||
#sidebar {
|
||||
min-width: 350px;
|
||||
}
|
||||
|
||||
#user *, #subreddit * { text-align: center; }
|
||||
#user *,
|
||||
#subreddit * {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#user, #sub_meta, #sidebar_contents { padding: 20px; }
|
||||
#user,
|
||||
#sub_meta,
|
||||
#sidebar_contents {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#sidebar, #sidebar_contents { margin-top: 10px; }
|
||||
#sidebar_label, #subreddit_label {
|
||||
#sidebar,
|
||||
#sidebar_contents {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#sidebar_label,
|
||||
#subreddit_label {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#user_icon, #sub_icon {
|
||||
#user_icon,
|
||||
#sub_icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 2px solid var(--accent);
|
||||
@ -445,35 +494,50 @@ aside {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#user_title, #sub_title {
|
||||
#user_title,
|
||||
#sub_title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#user_description, #sub_description {
|
||||
#user_description,
|
||||
#sub_description {
|
||||
margin: 0 15px;
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#user_name, #user_description:not(:empty), #user_icon,
|
||||
#sub_name, #sub_icon, #sub_description:not(:empty) {
|
||||
#user_name,
|
||||
#user_description:not(:empty),
|
||||
#user_icon,
|
||||
#sub_name,
|
||||
#sub_icon,
|
||||
#sub_description:not(:empty) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#user_details, #sub_details, #sub_actions, #user_actions {
|
||||
#user_details,
|
||||
#sub_details,
|
||||
#sub_actions,
|
||||
#user_actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-column-gap: 20px;
|
||||
}
|
||||
|
||||
#user_details > label, #sub_details > label {
|
||||
#user_details > label,
|
||||
#sub_details > label {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Subscriptions */
|
||||
|
||||
#sub_subscription, #user_subscription, #user_filter, #sub_filter {
|
||||
#sub_subscription,
|
||||
#user_subscription,
|
||||
#sub_filter,
|
||||
#user_filter,
|
||||
#sub_rss,
|
||||
#user_rss {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@ -481,18 +545,23 @@ aside {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.subscribe, .unsubscribe, .filter, .unfilter {
|
||||
.subscribe,
|
||||
.unsubscribe,
|
||||
.filter,
|
||||
.unfilter {
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.subscribe, .filter {
|
||||
.subscribe,
|
||||
.filter {
|
||||
color: var(--foreground);
|
||||
background-color: var(--accent);
|
||||
}
|
||||
|
||||
.unsubscribe, .unfilter {
|
||||
.unsubscribe,
|
||||
.unfilter {
|
||||
color: var(--text);
|
||||
background-color: var(--highlighted);
|
||||
}
|
||||
@ -575,7 +644,13 @@ aside {
|
||||
|
||||
/* Sorting and Search */
|
||||
|
||||
select, #search, #sort_options, #listing_options, #inside, #searchbox > *, #sort_submit {
|
||||
select,
|
||||
#search,
|
||||
#sort_options,
|
||||
#listing_options,
|
||||
#inside,
|
||||
#searchbox > *,
|
||||
#sort_submit {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
@ -592,7 +667,8 @@ select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select, #search {
|
||||
select,
|
||||
#search {
|
||||
border: none;
|
||||
padding: 0 10px;
|
||||
|
||||
@ -613,7 +689,10 @@ select, #search {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#searchbox > *, #sort_submit { background: var(--highlighted); }
|
||||
#searchbox > *,
|
||||
#sort_submit {
|
||||
background: var(--highlighted);
|
||||
}
|
||||
|
||||
#search {
|
||||
border-right: 2px var(--outside) solid;
|
||||
@ -629,9 +708,12 @@ select, #search {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
#restrict_sr { margin-right: 5px; }
|
||||
#restrict_sr {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
input[type="submit"], button.submit {
|
||||
input[type="submit"],
|
||||
button.submit {
|
||||
border: 0;
|
||||
border-radius: 0px 5px 5px 0px;
|
||||
}
|
||||
@ -641,10 +723,16 @@ button.submit {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
select:hover { background: var(--foreground); }
|
||||
select:hover {
|
||||
background: var(--foreground);
|
||||
}
|
||||
|
||||
input[type="submit"]:hover { color: var(--accent); }
|
||||
button.submit:hover > svg { stroke: var(--accent); }
|
||||
input[type="submit"]:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
button.submit:hover > svg {
|
||||
stroke: var(--accent);
|
||||
}
|
||||
|
||||
#timeframe {
|
||||
margin: 0 2px;
|
||||
@ -665,7 +753,9 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#search_sort > *, .search_widget_divider_box > *, .search_widget_divider_box #sort_options {
|
||||
#search_sort > *,
|
||||
.search_widget_divider_box > *,
|
||||
.search_widget_divider_box #sort_options {
|
||||
background: var(--highlighted);
|
||||
font-size: 15px;
|
||||
}
|
||||
@ -684,7 +774,8 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
#sort, #search_sort {
|
||||
#sort,
|
||||
#search_sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
@ -750,7 +841,9 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||
}
|
||||
}
|
||||
|
||||
#sort_options, #listing_options, main > * > footer > a {
|
||||
#sort_options,
|
||||
#listing_options,
|
||||
main > * > footer > a {
|
||||
border-radius: 5px;
|
||||
align-items: center;
|
||||
box-shadow: var(--shadow);
|
||||
@ -759,7 +852,9 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
#sort_options > a, #listing_options > a, main > * > footer > a {
|
||||
#sort_options > a,
|
||||
#listing_options > a,
|
||||
main > * > footer > a {
|
||||
color: var(--text);
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
@ -767,12 +862,14 @@ button.submit:hover > svg { stroke: var(--accent); }
|
||||
transition: 0.2s background;
|
||||
}
|
||||
|
||||
#sort_options > a.selected, #listing_options > a.selected {
|
||||
#sort_options > a.selected,
|
||||
#listing_options > a.selected {
|
||||
background: var(--accent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
#sort_options > a:not(.selected):hover, #listing_options > a:not(.selected):hover {
|
||||
#sort_options > a:not(.selected):hover,
|
||||
#listing_options > a:not(.selected):hover {
|
||||
background: var(--foreground);
|
||||
}
|
||||
|
||||
@ -863,7 +960,8 @@ a.search_subreddit:hover {
|
||||
box-shadow: var(--shadow);
|
||||
display: grid;
|
||||
transition: 0.2s background;
|
||||
grid-template: "post_score post_header post_thumbnail" auto
|
||||
grid-template:
|
||||
"post_score post_header post_thumbnail" auto
|
||||
"post_score post_title post_thumbnail" 1fr
|
||||
"post_score post_media post_thumbnail" auto
|
||||
"post_score post_body post_thumbnail" auto
|
||||
@ -873,7 +971,9 @@ a.search_subreddit:hover {
|
||||
/ minmax(40px, auto) minmax(0, 1fr) fit-content(min(20%, 152px));
|
||||
}
|
||||
|
||||
.post:not(:last-child) { margin-bottom: 10px; }
|
||||
.post:not(:last-child) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.post:hover {
|
||||
background: var(--foreground);
|
||||
@ -959,7 +1059,8 @@ a.search_subreddit:hover {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.author_flair:empty, .post_flair:empty {
|
||||
.author_flair:empty,
|
||||
.post_flair:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -992,7 +1093,9 @@ a.search_subreddit:hover {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.post_media_content, .post .__NoScript_PlaceHolder__, .gallery {
|
||||
.post_media_content,
|
||||
.post .__NoScript_PlaceHolder__,
|
||||
.gallery {
|
||||
max-width: calc(100% - 40px);
|
||||
grid-area: post_media;
|
||||
margin: 15px auto 5px auto;
|
||||
@ -1010,7 +1113,8 @@ a.search_subreddit:hover {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.post_media_image.short svg, .post_media_image.short img{
|
||||
.post_media_image.short svg,
|
||||
.post_media_image.short img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
@ -1041,7 +1145,7 @@ a.search_subreddit:hover {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.post_media_image svg{
|
||||
.post_media_image svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
align-self: center;
|
||||
@ -1134,8 +1238,8 @@ a.search_subreddit:hover {
|
||||
|
||||
/* Used only for text post preview */
|
||||
.post_preview {
|
||||
-webkit-mask-image: linear-gradient(180deg,#000 60%,transparent);;
|
||||
mask-image: linear-gradient(180deg,#000 60%,transparent);
|
||||
-webkit-mask-image: linear-gradient(180deg, #000 60%, transparent);
|
||||
mask-image: linear-gradient(180deg, #000 60%, transparent);
|
||||
opacity: 0.8;
|
||||
max-height: 250px;
|
||||
overflow: hidden;
|
||||
@ -1229,7 +1333,7 @@ a.search_subreddit:hover {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
grid-area: 1 / 1 / 2 / 2;
|
||||
padding: 5px;
|
||||
@ -1269,7 +1373,8 @@ a.search_subreddit:hover {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.comment_left, .comment_right {
|
||||
.comment_left,
|
||||
.comment_right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -1281,9 +1386,15 @@ a.search_subreddit:hover {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.comment_title { font-size: 20px; }
|
||||
.comment_link { text-decoration: underline; }
|
||||
.comment_author { opacity: 0.9; }
|
||||
.comment_title {
|
||||
font-size: 20px;
|
||||
}
|
||||
.comment_link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.comment_author {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.author_flair {
|
||||
background: var(--highlighted);
|
||||
@ -1348,7 +1459,8 @@ a.search_subreddit:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.comment_body.highlighted, .comment_body_filtered.highlighted {
|
||||
.comment_body.highlighted,
|
||||
.comment_body_filtered.highlighted {
|
||||
background: var(--highlighted);
|
||||
}
|
||||
|
||||
@ -1413,12 +1525,25 @@ summary.comment_data {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.moderator, .admin { opacity: 1; }
|
||||
.op, .moderator, .admin { font-weight: bold; }
|
||||
.moderator,
|
||||
.admin {
|
||||
opacity: 1;
|
||||
}
|
||||
.op,
|
||||
.moderator,
|
||||
.admin {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.op { color: var(--accent); }
|
||||
.moderator { color: var(--green); }
|
||||
.admin { color: var(--admin); }
|
||||
.op {
|
||||
color: var(--accent);
|
||||
}
|
||||
.moderator {
|
||||
color: var(--green);
|
||||
}
|
||||
.admin {
|
||||
color: var(--admin);
|
||||
}
|
||||
|
||||
/* Layouts */
|
||||
|
||||
@ -1438,8 +1563,12 @@ summary.comment_data {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compact .post.highlighted { border-radius: 5px; }
|
||||
.compact .post:not(:last-of-type):not(.highlighted):not(.stickied) { border-bottom: 0; }
|
||||
.compact .post.highlighted {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.compact .post:not(:last-of-type):not(.highlighted):not(.stickied) {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.compact .post_score {
|
||||
padding-top: 15px;
|
||||
@ -1451,7 +1580,9 @@ summary.comment_data {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.compact .post_title, .compact #post_url, .compact .post_body {
|
||||
.compact .post_title,
|
||||
.compact #post_url,
|
||||
.compact .post_body {
|
||||
margin: 2.5px 15px;
|
||||
}
|
||||
|
||||
@ -1548,7 +1679,7 @@ aside.prefs {
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
width: 100%
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
@ -1602,12 +1733,24 @@ input[type="submit"] {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.md h1 { font-size: 22px; }
|
||||
.md h2 { font-size: 20px; }
|
||||
.md h3 { font-size: 18px; }
|
||||
.md h4 { font-size: 16px; }
|
||||
.md h5 { font-size: 14px; }
|
||||
.md h6 { font-size: 12px; }
|
||||
.md h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
.md h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.md h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.md h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.md h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.md h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.md blockquote {
|
||||
padding: 10px;
|
||||
@ -1616,11 +1759,13 @@ input[type="submit"] {
|
||||
background: var(--post);
|
||||
}
|
||||
|
||||
.md a, .md a * {
|
||||
.md a,
|
||||
.md a * {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.md .md-spoiler-text, .md-spoiler-text a {
|
||||
.md .md-spoiler-text,
|
||||
.md-spoiler-text a {
|
||||
background: var(--highlighted);
|
||||
color: transparent;
|
||||
}
|
||||
@ -1635,8 +1780,12 @@ input[type="submit"] {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.md li { margin: 10px 0; }
|
||||
.toc_child { list-style: none; }
|
||||
.md li {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.toc_child {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.md pre {
|
||||
background: var(--outside);
|
||||
@ -1659,27 +1808,42 @@ input[type="submit"] {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.md code:not(.md pre > code) { background: var(--highlighted); }
|
||||
.md code:not(.md pre > code) {
|
||||
background: var(--highlighted);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
|
||||
table, td, th { border: var(--panel-border); }
|
||||
table,
|
||||
td,
|
||||
th {
|
||||
border: var(--panel-border);
|
||||
}
|
||||
|
||||
table {
|
||||
border-width: 3px;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td, th {
|
||||
td,
|
||||
th {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Errors */
|
||||
|
||||
#error { text-align: center; }
|
||||
#error h1 { margin-bottom: 10px; }
|
||||
#error h3 { opacity: 0.85; }
|
||||
#error a { color: var(--accent); }
|
||||
#error {
|
||||
text-align: center;
|
||||
}
|
||||
#error h1 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#error h3 {
|
||||
opacity: 0.85;
|
||||
}
|
||||
#error a {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
@ -1732,7 +1896,9 @@ td, th {
|
||||
/* Mobile */
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
body.fixed_navbar { padding-top: 120px }
|
||||
body.fixed_navbar {
|
||||
padding-top: 120px;
|
||||
}
|
||||
|
||||
main {
|
||||
flex-direction: column-reverse;
|
||||
@ -1742,16 +1908,24 @@ td, th {
|
||||
}
|
||||
|
||||
nav {
|
||||
grid-template-areas: 'logo links' 'searchbox searchbox';
|
||||
grid-template-areas: "logo links" "searchbox searchbox";
|
||||
padding: 10px;
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
nav #links { margin-left: auto; }
|
||||
nav #links span { display: none; }
|
||||
nav #links svg { display: block; }
|
||||
nav #links {
|
||||
margin-left: auto;
|
||||
}
|
||||
nav #links span {
|
||||
display: none;
|
||||
}
|
||||
nav #links svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#subscriptions { position: unset; }
|
||||
#subscriptions {
|
||||
position: unset;
|
||||
}
|
||||
|
||||
#sub_list {
|
||||
left: 10px;
|
||||
@ -1763,23 +1937,37 @@ td, th {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
aside, #subreddit, #user {
|
||||
aside,
|
||||
#subreddit,
|
||||
#user {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#user, #sidebar { margin: 20px 0; }
|
||||
#logo, #links { margin-bottom: 5px; }
|
||||
#searchbox { width: calc(100vw - 35px); }
|
||||
|
||||
#user,
|
||||
#sidebar {
|
||||
margin: 20px 0;
|
||||
}
|
||||
#logo,
|
||||
#links {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#searchbox {
|
||||
width: calc(100vw - 35px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
body.fixed_navbar { padding-top: 100px; }
|
||||
#version { display: none; }
|
||||
body.fixed_navbar {
|
||||
padding-top: 100px;
|
||||
}
|
||||
#version {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post {
|
||||
grid-template: "post_header post_header post_thumbnail" auto
|
||||
grid-template:
|
||||
"post_header post_header post_thumbnail" auto
|
||||
"post_title post_title post_thumbnail" 1fr
|
||||
"post_media post_media post_thumbnail" auto
|
||||
"post_body post_body post_thumbnail" auto
|
||||
@ -1798,12 +1986,20 @@ td, th {
|
||||
padding: 5px 15px 10px 12px;
|
||||
}
|
||||
|
||||
.compact .post_score { padding: 0; }
|
||||
.compact .post_score {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.post_score::before { content: "↑" }
|
||||
.post_score::before {
|
||||
content: "↑";
|
||||
}
|
||||
|
||||
.post_header { font-size: 14px; }
|
||||
.post_footer { margin-left: 15px; }
|
||||
.post_header {
|
||||
font-size: 14px;
|
||||
}
|
||||
.post_footer {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.replies > .comment {
|
||||
margin-left: -12px;
|
||||
@ -1822,9 +2018,15 @@ td, th {
|
||||
}
|
||||
|
||||
/* .thread { margin-left: -5px; } */
|
||||
.comment_right { padding: 5px 0 10px 2px; }
|
||||
.comment_author { margin-left: 12px; }
|
||||
.comment_data { margin-left: 12px; }
|
||||
.comment_right {
|
||||
padding: 5px 0 10px 2px;
|
||||
}
|
||||
.comment_author {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.comment_data {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.user-comment .comment_data {
|
||||
flex-direction: column;
|
||||
@ -1832,16 +2034,24 @@ td, th {
|
||||
row-gap: 5px;
|
||||
}
|
||||
|
||||
.comment_data::marker { font-size: 25px; }
|
||||
.user-comment .comment_data > .comment_link { order: 2 }
|
||||
.user_comment_data_divider { order: 1; }
|
||||
.comment_data::marker {
|
||||
font-size: 25px;
|
||||
}
|
||||
.user-comment .comment_data > .comment_link {
|
||||
order: 2;
|
||||
}
|
||||
.user_comment_data_divider {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.user_comment_data_divider .dot {
|
||||
display: unset;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.created-in { display: none; }
|
||||
.created-in {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment_score {
|
||||
min-width: 32px;
|
||||
@ -1851,11 +2061,19 @@ td, th {
|
||||
margin-right: -5px;
|
||||
}
|
||||
|
||||
#post_links > li { margin-right: 10px }
|
||||
.post_footer > p > span#upvoted { display: none }
|
||||
#post_links > li {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.post_footer > p > span#upvoted {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.desktop_item { display: none }
|
||||
.mobile_item { display: auto }
|
||||
.desktop_item {
|
||||
display: none;
|
||||
}
|
||||
.mobile_item {
|
||||
display: auto;
|
||||
}
|
||||
|
||||
.popup {
|
||||
width: auto;
|
||||
|
@ -130,7 +130,13 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="sub_rss">
|
||||
<a href="/r/{{ sub.name }}.rss" title="RSS feed for r/{{ sub.name }}">
|
||||
<button class="subscribe">RSS feed</button >
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
<details class="panel" id="sidebar">
|
||||
|
@ -1,30 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% import "utils.html" as utils %}
|
||||
|
||||
{% block search %}
|
||||
{% call utils::search("".to_owned(), "") %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ user.name.replace("u/", "") }} (u/{{ user.name }}) - Redlib{% endblock %}
|
||||
|
||||
{% block subscriptions %}
|
||||
{% call utils::sub_list("") %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<main>
|
||||
{% extends "base.html" %} {% import "utils.html" as utils %} {% block search %}
|
||||
{% call utils::search("".to_owned(), "") %} {% endblock %} {% block title %}{{
|
||||
user.name.replace("u/", "") }} (u/{{ user.name }}) - Redlib{% endblock %} {%
|
||||
block subscriptions %} {% call utils::sub_list("") %} {% endblock %} {% block
|
||||
body %}
|
||||
<main>
|
||||
{% if !is_filtered %}
|
||||
<div id="column_one">
|
||||
<form id="sort">
|
||||
<div id="listing_options">
|
||||
{% call utils::sort(["/user/", user.name.as_str()].concat(), ["overview", "comments", "submitted"], listing) %}
|
||||
{% call utils::sort(["/user/", user.name.as_str()].concat(),
|
||||
["overview", "comments", "submitted"], listing) %}
|
||||
</div>
|
||||
<select id="sort_select" name="sort">
|
||||
{% call utils::options(sort.0, ["hot", "new", "top", "controversial"], "") %}
|
||||
</select>{% if sort.0 == "top" || sort.0 == "controversial" %}<select id="timeframe" name="t">
|
||||
{% call utils::options(sort.1, ["hour", "day", "week", "month", "year", "all"], "all") %}
|
||||
</select>{% endif %}<button id="sort_submit" class="submit">
|
||||
<svg width="15" viewBox="0 0 110 100" fill="none" stroke-width="10" stroke-linecap="round">
|
||||
{% call utils::options(sort.0, ["hot", "new", "top",
|
||||
"controversial"], "") %}</select
|
||||
>{% if sort.0 == "top" || sort.0 == "controversial" %}<select
|
||||
id="timeframe"
|
||||
name="t"
|
||||
>
|
||||
{% call utils::options(sort.1, ["hour", "day", "week", "month",
|
||||
"year", "all"], "all") %}</select
|
||||
>{% endif %}<button id="sort_submit" class="submit">
|
||||
<svg
|
||||
width="15"
|
||||
viewBox="0 0 110 100"
|
||||
fill="none"
|
||||
stroke-width="10"
|
||||
stroke-linecap="round"
|
||||
>
|
||||
<path d="M20 50 H100" />
|
||||
<path d="M75 15 L100 50 L75 85" />
|
||||
→
|
||||
@ -33,50 +36,52 @@
|
||||
</form>
|
||||
|
||||
{% if all_posts_hidden_nsfw %}
|
||||
<center>All posts are hidden because they are NSFW. Enable "Show NSFW posts" in settings to view.</center>
|
||||
{% endif %}
|
||||
|
||||
{% if no_posts %}
|
||||
<center>
|
||||
All posts are hidden because they are NSFW. Enable "Show NSFW posts"
|
||||
in settings to view.
|
||||
</center>
|
||||
{% endif %} {% if no_posts %}
|
||||
<center>No posts were found.</center>
|
||||
{% endif %}
|
||||
|
||||
{% if all_posts_filtered %}
|
||||
{% endif %} {% if all_posts_filtered %}
|
||||
<center>(All content on this page has been filtered)</center>
|
||||
{% else %}
|
||||
<div id="posts">
|
||||
{% for post in posts %}
|
||||
|
||||
{% if post.flags.nsfw && prefs.show_nsfw != "on" %}
|
||||
{% else if !post.title.is_empty() %}
|
||||
{% call utils::post_in_list(post) %}
|
||||
{% else %}
|
||||
{% for post in posts %} {% if post.flags.nsfw && prefs.show_nsfw !=
|
||||
"on" %} {% else if !post.title.is_empty() %} {% call
|
||||
utils::post_in_list(post) %} {% else %}
|
||||
<div class="comment user-comment">
|
||||
<div class="comment_left">
|
||||
<p class="comment_score" title="{{ post.score.1 }}">
|
||||
{% if prefs.hide_score != "on" %}
|
||||
{{ post.score.0 }}
|
||||
{% else %}
|
||||
•
|
||||
{% endif %}
|
||||
{% if prefs.hide_score != "on" %} {{ post.score.0 }} {%
|
||||
else %} • {% endif %}
|
||||
</p>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<details class="comment_right" open>
|
||||
<summary class="comment_data">
|
||||
<a class="comment_link" href="{{ post.permalink }}" title="{{ post.link_title }}">{{ post.link_title }}</a>
|
||||
<a
|
||||
class="comment_link"
|
||||
href="{{ post.permalink }}"
|
||||
title="{{ post.link_title }}"
|
||||
>{{ post.link_title }}</a
|
||||
>
|
||||
<div class="user_comment_data_divider">
|
||||
<span class="created-in"> in </span>
|
||||
<a class="comment_subreddit" href="/r/{{ post.community }}">r/{{ post.community }}</a>
|
||||
<a
|
||||
class="comment_subreddit"
|
||||
href="/r/{{ post.community }}"
|
||||
>r/{{ post.community }}</a
|
||||
>
|
||||
<span class="dot">•</span>
|
||||
<span class="created" title="{{ post.created }}"> {{ post.rel_time }}</span>
|
||||
<span class="created" title="{{ post.created }}"
|
||||
> {{ post.rel_time }}</span
|
||||
>
|
||||
</div>
|
||||
</summary>
|
||||
<p class="comment_body">{{ post.body|safe }}</p>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if prefs.use_hls == "on" %}
|
||||
{% endif %} {% endfor %} {% if prefs.use_hls == "on" %}
|
||||
<script src="/hls.min.js"></script>
|
||||
<script src="/playHLSVideo.js"></script>
|
||||
{% endif %}
|
||||
@ -85,11 +90,17 @@
|
||||
|
||||
<footer>
|
||||
{% if ends.0 != "" %}
|
||||
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}" accesskey="P">PREV</a>
|
||||
{% endif %}
|
||||
|
||||
{% if ends.1 != "" %}
|
||||
<a href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}" accesskey="N">NEXT</a>
|
||||
<a
|
||||
href="?sort={{ sort.0 }}&t={{ sort.1 }}&before={{ ends.0 }}"
|
||||
accesskey="P"
|
||||
>PREV</a
|
||||
>
|
||||
{% endif %} {% if ends.1 != "" %}
|
||||
<a
|
||||
href="?sort={{ sort.0 }}&t={{ sort.1 }}&after={{ ends.1 }}"
|
||||
accesskey="N"
|
||||
>NEXT</a
|
||||
>
|
||||
{% endif %}
|
||||
</footer>
|
||||
</div>
|
||||
@ -99,7 +110,12 @@
|
||||
<center>(Content from u/{{ user.name }} has been filtered)</center>
|
||||
{% endif %}
|
||||
<div class="panel" id="user">
|
||||
<img loading="lazy" id="user_icon" src="{{ user.icon }}" alt="User icon">
|
||||
<img
|
||||
loading="lazy"
|
||||
id="user_icon"
|
||||
src="{{ user.icon }}"
|
||||
alt="User icon"
|
||||
/>
|
||||
<h1 id="user_title">{{ user.title }}</h1>
|
||||
<p id="user_name">u/{{ user.name }}</p>
|
||||
<div id="user_description">{{ user.description }}</div>
|
||||
@ -113,28 +129,50 @@
|
||||
{% let name = ["u_", user.name.as_str()].join("") %}
|
||||
<div id="user_subscription">
|
||||
{% if prefs.subscriptions.contains(name) %}
|
||||
<form action="/r/{{ name }}/unsubscribe?redirect={{ redirect_url }}" method="POST">
|
||||
<form
|
||||
action="/r/{{ name }}/unsubscribe?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="unsubscribe">Unfollow</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}" method="POST">
|
||||
<form
|
||||
action="/r/{{ name }}/subscribe?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="subscribe">Follow</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="user_filter">
|
||||
{% if prefs.filters.contains(name) %}
|
||||
<form action="/r/{{ name }}/unfilter?redirect={{ redirect_url }}" method="POST">
|
||||
<form
|
||||
action="/r/{{ name }}/unfilter?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="unfilter">Unfilter</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="/r/{{ name }}/filter?redirect={{ redirect_url }}" method="POST">
|
||||
<form
|
||||
action="/r/{{ name }}/filter?redirect={{ redirect_url }}"
|
||||
method="POST"
|
||||
>
|
||||
<button class="filter">Filter</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if crate::utils::enable_rss() %}
|
||||
<div id="user_rss">
|
||||
<a
|
||||
href="/u/{{ user.name }}.rss"
|
||||
title="RSS feed for u/{{ user.name }}"
|
||||
>
|
||||
<button class="subscribe">RSS feed</button>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user