add: safety_content_warning, master_pass env var

This commit is contained in:
trisua 2025-08-02 01:30:11 -04:00
parent a6a289c594
commit 15b417c6d6
9 changed files with 150 additions and 29 deletions

46
Cargo.lock generated
View file

@ -571,7 +571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -623,7 +623,7 @@ dependencies = [
"tetratto-core",
"tetratto-shared",
"tokio",
"toml 0.9.2",
"toml 0.9.4",
"tower-http",
"tracing",
"tracing-subscriber",
@ -997,7 +997,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"socket2 0.5.10",
"system-configuration",
"tokio",
"tower-service",
@ -1951,7 +1951,7 @@ dependencies = [
"pin-project-lite",
"ryu",
"sha1_smol",
"socket2",
"socket2 0.5.10",
"tokio",
"tokio-util",
"url",
@ -2082,7 +2082,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -2205,9 +2205,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.141"
version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [
"itoa",
"memchr",
@ -2397,6 +2397,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -2513,7 +2523,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -2571,7 +2581,7 @@ dependencies = [
"tetratto-l10n",
"tetratto-shared",
"tokio",
"toml 0.9.2",
"toml 0.9.4",
"totp-rs",
]
@ -2583,7 +2593,7 @@ checksum = "d96f5e41633c757e3519efb47c9b85d00d14322c1961360e126d0ecc0ea79b86"
dependencies = [
"pathbufd",
"serde",
"toml 0.9.2",
"toml 0.9.4",
]
[[package]]
@ -2711,9 +2721,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.46.1"
version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [
"backtrace",
"bytes",
@ -2723,9 +2733,9 @@ dependencies = [
"parking_lot",
"pin-project-lite",
"slab",
"socket2",
"socket2 0.6.0",
"tokio-macros",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -2769,7 +2779,7 @@ dependencies = [
"postgres-protocol",
"postgres-types",
"rand 0.9.1",
"socket2",
"socket2 0.5.10",
"tokio",
"tokio-util",
"whoami",
@ -2824,9 +2834,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.9.2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac"
checksum = "41ae868b5a0f67631c14589f7e250c1ea2c574ee5ba21c6c8dd4b1485705a5a1"
dependencies = [
"indexmap",
"serde",
@ -3385,7 +3395,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]

View file

@ -10,7 +10,7 @@ homepage = "https://fluffle.cc"
[dependencies]
tetratto-core = "12.0.2"
tetratto-shared = "12.0.6"
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
pathbufd = "0.1.4"
serde = { version = "1.0.219", features = ["derive"] }
tera = "1.20.0"
@ -27,7 +27,7 @@ axum-extra = { version = "0.10.1", features = ["cookie"] }
nanoneo = "0.2.0"
dotenv = "0.15.0"
glob = "0.3.2"
serde_json = "1.0.141"
toml = "0.9.2"
serde_json = "1.0.142"
toml = "0.9.4"
serde_valid = { version = "1.0.5", features = ["toml"] }
regex = "1.11.1"

View file

@ -38,6 +38,8 @@ Once you've built the binary, it'll be located at (from the root `fluffle/` dire
All templates are compiled with [nanoneo](https://trisua.com/t/nanoneo), so it's recommended that you familiarize yourself with that syntax.
You can set a master password to be able to freely edit all entries by using the `MASTER_PASS` environment variable.
## Attribution
Fluffle is licensed under the AGPL-3.0 license. Tetratto is also licensed under the AGPL-3.0 license.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -301,7 +301,7 @@ input {
border-left: solid 0px transparent;
}
input:focus {
input:not([type="checkbox"]):focus {
outline: solid 2px var(--color-primary);
box-shadow: 0 0 0 4px oklch(87% 0.065 274.039 / 25%);
background: var(--color-super-raised);
@ -316,6 +316,10 @@ input.surface {
background: var(--color-surface);
}
input[type="checkbox"] {
height: max-content;
}
/* typo */
p {
margin-bottom: var(--pad-4);

View file

@ -61,6 +61,14 @@
(button
("class" "button camo fade no_fill")
("title" "Toggle high-contrast")
("id" "toggle_high_contrast_button")
("onclick" "toggle_metadata_css(event)")
(text "{{ icon \"contrast\" }}"))
(text "{% if \"EntryHighContrast\" in flags -%}")
(script
(text "setTimeout(() => {
toggle_metadata_css({ target: document.getElementById(\"toggle_high_contrast_button\") });
}, 150);"))
(text "{%- endif %}")
(text "{% endblock %}")

View file

@ -0,0 +1,54 @@
(text "{% extends \"root.lisp\" %} {% block head %}")
(text "{% if not metadata.page_title -%}")
(title
(text "{{ entry.slug }}"))
(text "{%- endif %} {{ metadata_head|safe }}")
(text "{% if not metadata.share_title -%}")
(meta ("property" "og:title") ("content" "{{ entry.slug }}"))
(meta ("property" "twitter:title") ("content" "{{ entry.slug }}"))
(text "{%- endif %}")
(text "{% if metadata.page_icon|length == 0 -%}")
(link ("rel" "icon") ("href" "/public/favicon.svg"))
(text "{%- endif %}")
(text "{% endblock %} {% block body %}")
(div
("class" "card container flex flex-col gap-1")
("id" "content_rect")
(p ("class" "fade") (text "Content warning:"))
(div (text "{{ metadata.safety_content_warning|markdown|safe }}"))
(hr)
(div
("class" "flex flex-col gap-4")
(label
("class" "flex flex-row gap-2 items-center")
("for" "open_in_high_contrast")
(input
("type" "checkbox")
("id" "open_in_high_contrast")
("name" "open_in_high_contrast"))
(span (text "Open in high contrast")))
(div
("class" "flex gap-2")
(button
("class" "button surface green")
("onclick" "accept()")
(text "Continue"))
(button
("class" "button surface red")
("onclick" "window.history.back()")
(text "Cancel")))))
(script
(text "const QFLAGS = [\"AcceptWarning\"];
function accept() {
if (document.getElementById(\"open_in_high_contrast\").checked) {
QFLAGS.push(\"EntryHighContrast\");
}
document.cookie = `Atto-QFlags=\"${JSON.stringify(QFLAGS)}\"; path=/`;
window.location.reload();
}"))
(text "{% endblock %}")

View file

@ -342,6 +342,7 @@ pub struct EntryMetadata {
pub content_font_weight: u32,
/// The text size of elements (separated by space).
#[serde(default, alias = "CONTENT_TEXT_SIZE")]
#[validate(max_length = 128)]
pub content_text_size: String,
/// The text size of elements by element tag.
///
@ -357,13 +358,19 @@ pub struct EntryMetadata {
pub content_text_align: TextAlignment,
/// The base text color.
#[serde(default, alias = "CONTENT_TEXT_COLOR")]
#[validate(max_length = 128)]
pub content_text_color: String,
/// The color of links.
#[serde(default, alias = "CONTENT_LINK_COLOR")]
#[validate(max_length = 128)]
pub content_link_color: String,
/// If paragraph elements have a margin below them.
#[serde(default, alias = "CONTENT_DISABLE_PARAGRAPH_MARGIN")]
pub content_disable_paragraph_margin: bool,
/// The content warning shown before viewing this entry.
#[serde(default, alias = "SAFETY_CONTENT_WARNING")]
#[validate(max_length = 512)]
pub safety_content_warning: String,
}
macro_rules! metadata_css {
@ -684,3 +691,15 @@ impl EntryMetadata {
output
}
}
/// Flags that can be provided through the "Atto-QFlags" cookie to customize the
/// resulting page.
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum QuickFlag {
/// If the entry's warning is automatically skipped.
AcceptWarning,
/// If the entry is automatically set to high-contrast mode.
EntryHighContrast,
}
pub type QuickFlags = Vec<QuickFlag>;

View file

@ -2,7 +2,7 @@ use std::env::var;
use crate::{
State,
model::{Entry, EntryMetadata},
model::{Entry, EntryMetadata, QuickFlag, QuickFlags},
};
use axum::{
Extension, Json, Router,
@ -126,11 +126,20 @@ pub struct ViewQuery {
}
async fn view_request(
jar: CookieJar,
Extension(data): Extension<State>,
Path(mut slug): Path<String>,
Query(props): Query<ViewQuery>,
) -> impl IntoResponse {
let (ref data, ref tera, ref build_code) = *data.read().await;
let qflags: QuickFlags = match jar.get("Atto-QFlags") {
Some(x) => match serde_json::from_str(&x.value_trimmed()) {
Ok(x) => x,
Err(_) => QuickFlags::default(),
},
None => QuickFlags::default(),
};
slug = slug.to_lowercase();
let entry = match data
@ -222,8 +231,15 @@ async fn view_request(
ctx.insert("metadata_head", &metadata.head_tags());
ctx.insert("metadata_css", &metadata.css());
ctx.insert("password", &props.key);
ctx.insert("flags", &qflags);
if metadata.safety_content_warning.is_empty() | qflags.contains(&QuickFlag::AcceptWarning) {
// regular view
Html(tera.render("view.lisp", &ctx).unwrap())
} else {
// warning
Html(tera.render("warning.lisp", &ctx).unwrap())
}
}
async fn editor_request(
@ -614,7 +630,15 @@ async fn edit_request(
let using_modify_code = edit_code == entry.modify_code;
// check edit code
if edit_code
let mut using_master = false;
if let Ok(master_pass) = var("MASTER_PASS") {
if req.edit_code == master_pass {
using_master = true;
}
}
if !using_master
&& edit_code
!= *if using_modify_code {
&entry.modify_code
} else {