diff --git a/Cargo.lock b/Cargo.lock index b5148b2..adc1cbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3320,7 +3320,7 @@ dependencies = [ [[package]] name = "tetratto-core" -version = "12.0.1" +version = "12.0.2" dependencies = [ "async-recursion", "base16ct", @@ -3353,7 +3353,7 @@ dependencies = [ [[package]] name = "tetratto-shared" -version = "12.0.5" +version = "12.0.6" dependencies = [ "ammonia", "chrono", diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 1504ba5..82cf197 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -4,15 +4,11 @@ use nanoneo::{ }; use pathbufd::PathBufD; use regex::Regex; -use std::{ - collections::HashMap, - fs::{exists, read_to_string, write}, - sync::LazyLock, - time::SystemTime, -}; +use std::{collections::HashMap, fs::read_to_string, sync::LazyLock, time::SystemTime}; use tera::Context; use tetratto_core::{ config::Config, + html::{pull_icons, ICONS}, model::{ auth::{DefaultTimelineChoice, User}, permissions::{FinePermission, SecondaryPermission}, @@ -157,38 +153,6 @@ pub const TETRATTO_BUNNY: &[u8] = include_bytes!("./public/images/tetratto_bunny pub(crate) static HTML_FOOTER: LazyLock> = LazyLock::new(|| RwLock::new(String::new())); -/// A container for all loaded icons. -pub(crate) static ICONS: LazyLock>> = - LazyLock::new(|| RwLock::new(HashMap::new())); - -/// Pull an icon given its name and insert it into [`ICONS`]. -pub(crate) async fn pull_icon(icon: &str, icons_dir: &str) { - let writer = &mut ICONS.write().await; - - let icon_url = format!( - "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/{icon}.svg" - ); - - let file_path = PathBufD::current().extend(&[icons_dir, &format!("{icon}.svg")]); - - if exists(&file_path).unwrap() { - writer.insert(icon.to_string(), read_to_string(&file_path).unwrap()); - return; - } - - println!("download icon: {icon}"); - let svg = reqwest::get(icon_url) - .await - .unwrap() - .text() - .await - .unwrap() - .replace("\n", ""); - - write(&file_path, &svg).unwrap(); - writer.insert(icon.to_string(), svg); -} - macro_rules! vendor_icon { ($name:literal, $icon:ident, $icons_dir:expr) => {{ let writer = &mut ICONS.write().await; @@ -261,56 +225,8 @@ pub(crate) async fn replace_in_html( input = input.replace(cap.get(0).unwrap().as_str(), &replace_with); } - // icon (with class) - let icon_with_class = - Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*c\\((.*?)\\)\\s*(\\}\\})").unwrap(); - - for cap in icon_with_class.captures_iter(&input.clone()) { - let cap_str = &cap.get(3).unwrap().as_str().replace("\"", ""); - let icon = &(if cap_str.contains(" }}") { - cap_str.split(" }}").next().unwrap().to_string() - } else { - cap_str.to_string() - }); - - pull_icon(icon, &config.dirs.icons).await; - - let reader = ICONS.read().await; - let icon_text = reader.get(icon).unwrap().replace( - "", &format!("{reader}"), 1); diff --git a/crates/app/src/public/html/profile/base.lisp b/crates/app/src/public/html/profile/base.lisp index 7e4d6fb..e10dec9 100644 --- a/crates/app/src/public/html/profile/base.lisp +++ b/crates/app/src/public/html/profile/base.lisp @@ -107,6 +107,7 @@ (p (text "{{ profile.settings.status }}")) (text "{%- endif %}") + (text "{% if not profile.settings.hide_social_follows or (user and user.id == profile.id) -%}") (div ("class" "w-full flex") (a @@ -123,6 +124,7 @@ (text "{{ profile.following_count }}")) (span (text "{{ text \"auth:label.following\" }}")))) + (text "{%- endif %}") (text "{% if is_following_you -%}") (b ("class" "notification chip w-content flex items-center gap-2") diff --git a/crates/app/src/public/html/profile/settings.lisp b/crates/app/src/public/html/profile/settings.lisp index 59b64d0..64d3a30 100644 --- a/crates/app/src/public/html/profile/settings.lisp +++ b/crates/app/src/public/html/profile/settings.lisp @@ -1889,6 +1889,14 @@ \"{{ profile.settings.hide_from_social_lists }}\", \"checkbox\", ], + [ + [ + \"hide_social_follows\", + \"Hide followers/following links on my profile\", + ], + \"{{ profile.settings.hide_social_follows }}\", + \"checkbox\", + ], [[], \"Questions\", \"title\"], [ [ diff --git a/crates/app/src/routes/pages/profile.rs b/crates/app/src/routes/pages/profile.rs index 15a3ee8..3f6274b 100644 --- a/crates/app/src/routes/pages/profile.rs +++ b/crates/app/src/routes/pages/profile.rs @@ -731,6 +731,21 @@ pub async fn following_request( check_user_blocked_or_private!(user, other_user, data, jar); + // check hide_social_follows + if other_user.settings.hide_social_follows { + if let Some(ref ua) = user { + if ua.id != other_user.id { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &user).await, + )); + } + } else { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &user).await, + )); + } + } + // fetch data let list = match data .0 @@ -826,6 +841,21 @@ pub async fn followers_request( check_user_blocked_or_private!(user, other_user, data, jar); + // check hide_social_follows + if other_user.settings.hide_social_follows { + if let Some(ref ua) = user { + if ua.id != other_user.id { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &user).await, + )); + } + } else { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &user).await, + )); + } + } + // fetch data let list = match data .0 diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 71faf6c..bd7ac03 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tetratto-core" description = "The core behind Tetratto" -version = "12.0.1" +version = "12.0.2" edition = "2024" authors.workspace = true repository.workspace = true @@ -18,7 +18,7 @@ default = ["database", "types", "sdk"] pathbufd = "0.1.4" serde = { version = "1.0.219", features = ["derive"] } toml = "0.9.2" -tetratto-shared = { version = "12.0.0", path = "../shared" } +tetratto-shared = { version = "12.0.6", path = "../shared" } tetratto-l10n = { version = "12.0.0", path = "../l10n" } serde_json = "1.0.141" totp-rs = { version = "5.7.0", features = ["qr", "gen_secret"], optional = true } @@ -35,6 +35,4 @@ oiseau = { version = "0.1.2", default-features = false, features = [ "redis", ], optional = true } paste = { version = "1.0.15", optional = true } - -[dev-dependencies] tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] } diff --git a/crates/core/src/html.rs b/crates/core/src/html.rs new file mode 100644 index 0000000..73c42e1 --- /dev/null +++ b/crates/core/src/html.rs @@ -0,0 +1,97 @@ +use std::{ + collections::HashMap, + fs::{exists, read_to_string, write}, + sync::LazyLock, +}; +use tokio::sync::RwLock; + +use pathbufd::PathBufD; + +/// A container for all loaded icons. +pub static ICONS: LazyLock>> = + LazyLock::new(|| RwLock::new(HashMap::new())); + +/// Pull an icon given its name and insert it into [`ICONS`]. +pub async fn pull_icon(icon: &str, icons_dir: &str) { + let writer = &mut ICONS.write().await; + + let icon_url = format!( + "https://raw.githubusercontent.com/lucide-icons/lucide/refs/heads/main/icons/{icon}.svg" + ); + + let file_path = PathBufD::current().extend(&[icons_dir, &format!("{icon}.svg")]); + + if exists(&file_path).unwrap_or(false) { + writer.insert(icon.to_string(), read_to_string(&file_path).unwrap()); + return; + } + + println!("download icon: {icon}"); + let svg = reqwest::get(icon_url) + .await + .unwrap() + .text() + .await + .unwrap() + .replace("\n", ""); + + write(&file_path, &svg).unwrap(); + writer.insert(icon.to_string(), svg); +} + +/// Read a string and pull all icons found within it. +pub async fn pull_icons(mut input: String, icon_dir: &str) -> String { + // icon (with class) + let icon_with_class = + regex::Regex::new("(\\{\\{)\\s*(icon)\\s*(.*?)\\s*c\\((.*?)\\)\\s*(\\}\\})").unwrap(); + + for cap in icon_with_class.captures_iter(&input.clone()) { + let cap_str = &cap.get(3).unwrap().as_str().replace("\"", ""); + let icon = &(if cap_str.contains(" }}") { + cap_str.split(" }}").next().unwrap().to_string() + } else { + cap_str.to_string() + }); + + pull_icon(icon, icon_dir).await; + + let reader = ICONS.read().await; + let icon_text = reader.get(icon).unwrap().replace( + "