add: safety_content_warning, master_pass env var
This commit is contained in:
parent
a6a289c594
commit
15b417c6d6
9 changed files with 150 additions and 29 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
BIN
app/public/fluffle_bunny.webp
Normal file
BIN
app/public/fluffle_bunny.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -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);
|
||||
|
|
|
@ -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 %}")
|
||||
|
|
54
app/templates_src/warning.lisp
Normal file
54
app/templates_src/warning.lisp
Normal 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 %}")
|
19
src/model.rs
19
src/model.rs
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue