From 2ec7e54a8e69eb4d233b0dfcc796766217467e0e Mon Sep 17 00:00:00 2001 From: trisua Date: Fri, 11 Apr 2025 18:08:51 -0400 Subject: [PATCH] add: slightly more advanced theming add: profile "before you view" warning --- crates/app/src/assets.rs | 5 + crates/app/src/langs/en-US.toml | 2 + crates/app/src/main.rs | 5 + crates/app/src/public/css/style.css | 27 ++- .../public/html/communities/create_post.html | 138 +++++++++++++ .../app/src/public/html/communities/post.html | 8 + crates/app/src/public/html/components.html | 142 +++++-------- crates/app/src/public/html/macros.html | 4 +- crates/app/src/public/html/profile/base.html | 66 +++++- .../app/src/public/html/profile/settings.html | 191 +++++++++++++++++- .../app/src/public/html/profile/warning.html | 37 ++++ crates/app/src/public/html/root.html | 23 +-- crates/app/src/public/js/atto.js | 122 +++++++++++ crates/app/src/routes/pages/communities.rs | 54 +++++ crates/app/src/routes/pages/mod.rs | 12 ++ crates/app/src/routes/pages/profile.rs | 19 +- crates/app/src/sanitize.rs | 26 +-- crates/core/src/database/posts.rs | 4 +- crates/core/src/model/auth.rs | 56 ++++- crates/core/src/model/communities.rs | 2 +- 20 files changed, 790 insertions(+), 153 deletions(-) create mode 100644 crates/app/src/public/html/communities/create_post.html create mode 100644 crates/app/src/public/html/profile/warning.html diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index 1b348b3..fb7ccf8 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -45,6 +45,7 @@ pub const PROFILE_POSTS: &str = include_str!("./public/html/profile/posts.html") pub const PROFILE_SETTINGS: &str = include_str!("./public/html/profile/settings.html"); pub const PROFILE_FOLLOWING: &str = include_str!("./public/html/profile/following.html"); pub const PROFILE_FOLLOWERS: &str = include_str!("./public/html/profile/followers.html"); +pub const PROFILE_WARNING: &str = include_str!("./public/html/profile/warning.html"); pub const COMMUNITIES_LIST: &str = include_str!("./public/html/communities/list.html"); pub const COMMUNITIES_BASE: &str = include_str!("./public/html/communities/base.html"); @@ -53,6 +54,8 @@ pub const COMMUNITIES_POST: &str = include_str!("./public/html/communities/post. pub const COMMUNITIES_SETTINGS: &str = include_str!("./public/html/communities/settings.html"); pub const COMMUNITIES_MEMBERS: &str = include_str!("./public/html/communities/members.html"); pub const COMMUNITIES_SEARCH: &str = include_str!("./public/html/communities/search.html"); +pub const COMMUNITIES_CREATE_POST: &str = + include_str!("./public/html/communities/create_post.html"); pub const TIMELINES_HOME: &str = include_str!("./public/html/timelines/home.html"); pub const TIMELINES_FOLLOWING: &str = include_str!("./public/html/timelines/following.html"); @@ -174,6 +177,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"profile/settings.html"(crate::assets::PROFILE_SETTINGS) --config=config); write_template!(html_path->"profile/following.html"(crate::assets::PROFILE_FOLLOWING) --config=config); write_template!(html_path->"profile/followers.html"(crate::assets::PROFILE_FOLLOWERS) --config=config); + write_template!(html_path->"profile/warning.html"(crate::assets::PROFILE_WARNING) --config=config); write_template!(html_path->"communities/list.html"(crate::assets::COMMUNITIES_LIST) -d "communities" --config=config); write_template!(html_path->"communities/base.html"(crate::assets::COMMUNITIES_BASE) --config=config); @@ -182,6 +186,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"communities/settings.html"(crate::assets::COMMUNITIES_SETTINGS) --config=config); write_template!(html_path->"communities/members.html"(crate::assets::COMMUNITIES_MEMBERS) --config=config); write_template!(html_path->"communities/search.html"(crate::assets::COMMUNITIES_SEARCH) --config=config); + write_template!(html_path->"communities/create_post.html"(crate::assets::COMMUNITIES_CREATE_POST) --config=config); write_template!(html_path->"timelines/home.html"(crate::assets::TIMELINES_HOME) -d "timelines" --config=config); write_template!(html_path->"timelines/following.html"(crate::assets::TIMELINES_FOLLOWING) --config=config); diff --git a/crates/app/src/langs/en-US.toml b/crates/app/src/langs/en-US.toml index 0713af7..2cec734 100644 --- a/crates/app/src/langs/en-US.toml +++ b/crates/app/src/langs/en-US.toml @@ -49,6 +49,7 @@ version = "1.0.0" "auth:label.relationship" = "Relationship" "auth:label.joined_communities" = "Joined communities" "auth:label.recent_posts" = "Recent posts" +"auth:label.before_you_view" = "Before you view" "communities:action.create" = "Create" "communities:action.select" = "Select" @@ -93,6 +94,7 @@ version = "1.0.0" "settings:tab.general" = "General" "settings:tab.account" = "Account" "settings:tab.profile" = "Profile" +"settings:tab.theme" = "Theme" "settings:tab.sessions" = "Sessions" "settings:label.change_password" = "Change password" "settings:label.current_password" = "Current password" diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index fc976c2..c0f472d 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -22,6 +22,10 @@ fn render_markdown(value: &Value, _: &HashMap) -> tera::Result) -> tera::Result { + Ok(sanitize::color_escape(value.as_str().unwrap()).into()) +} + #[tokio::main] async fn main() { tracing_subscriber::fmt() @@ -41,6 +45,7 @@ async fn main() { let mut tera = Tera::new(&format!("{html_path}/**/*")).unwrap(); tera.register_filter("markdown", render_markdown); + tera.register_filter("color", color_escape); let client = Client::new(); diff --git a/crates/app/src/public/css/style.css b/crates/app/src/public/css/style.css index bf366ef..bf40783 100644 --- a/crates/app/src/public/css/style.css +++ b/crates/app/src/public/css/style.css @@ -157,10 +157,6 @@ p { margin-bottom: 0; } -.no_p_margin img { - display: block !important; -} - .name { max-width: 250px; overflow: hidden; @@ -313,16 +309,27 @@ img.contain { /* avatar/banner */ .avatar { --size: 50px; + --size-formula: var(--size); border-radius: calc(var(--radius) / 2); - width: var(--size); - min-width: var(--size); - max-width: var(--size); - height: var(--size); - min-height: var(--size); - max-height: var(--size); + width: var(--size-formula); + min-width: var(--size-formula); + max-width: var(--size-formula); + height: var(--size-formula); + min-height: var(--size-formula); + max-height: var(--size-formula); object-fit: cover; } +@media screen and (max-width: 900px) { + .avatar { + --size-formula: clamp(24px, calc(var(--size) * 0.75), 64px); + } + + textarea { + min-height: 12rem !important; + } +} + .banner { border-radius: var(--radius); max-height: 350px; diff --git a/crates/app/src/public/html/communities/create_post.html b/crates/app/src/public/html/communities/create_post.html new file mode 100644 index 0000000..b6b04be --- /dev/null +++ b/crates/app/src/public/html/communities/create_post.html @@ -0,0 +1,138 @@ +{% extends "root.html" %} {% block head %} +Create post - {{ config.name }} +{% endblock %} {% block body %} {{ macros::nav(selected="") }} +
+
+
+ + {{ icon "pen" }} + {{ text "communities:label.create_post" }} + + + +
+ +
+
+
+ {{ components::community_avatar(id=config.town_square, + community=false, size="32px") }} + + +
+ +
+
+ + +
+ + +
+
+ + +
+
+
+ + +{% endblock %} diff --git a/crates/app/src/public/html/communities/post.html b/crates/app/src/public/html/communities/post.html index db536cf..b7a36be 100644 --- a/crates/app/src/public/html/communities/post.html +++ b/crates/app/src/public/html/communities/post.html @@ -125,6 +125,14 @@ "{{ post.context.comments_enabled }}", "checkbox", ], + [ + [ + "reposts_enabled", + "Allow people to repost/quote your post", + ], + "{{ post.context.reposts_enabled }}", + "checkbox", + ], [ ["reposts_enabled", "Allow people to repost your post"], "{{ post.context.reposts_enabled }}", diff --git a/crates/app/src/public/html/components.html b/crates/app/src/public/html/components.html index 8e081a8..7db5326 100644 --- a/crates/app/src/public/html/components.html +++ b/crates/app/src/public/html/components.html @@ -163,7 +163,8 @@ show_community %} {% endif %} - {% if post.context.is_pinned %} {{ icon "pin" }} {% endif %} + {% if post.context.is_pinned or post.context.is_profile_pinned %} {{ + icon "pin" }} {% endif %} {% endif %} @@ -217,25 +218,28 @@ show_community %}
- {% if user %} {% if post.context.repost and - post.context.repost.reposting %} - - {{ icon "expand" }} - {{ text "communities:label.expand_original" }} - - {% else %} + {% if user %}
- {{ components::likes(id=post.id, asset_type="Post", - likes=post.likes, dislikes=post.dislikes) }} + + {% if post.content|length > 0 %} + {{ components::likes(id=post.id, asset_type="Post", likes=post.likes, dislikes=post.dislikes) }} + {% endif %} + + {% if post.context.repost and post.context.repost.reposting %} + + {{ icon "expand" }} + + {% endif %}
- {% endif %} {% else %} + {% else %}
{% endif %} @@ -454,76 +458,8 @@ user.settings.private_last_online or is_helper %}
-{% endif %} {%- endmacro %} {% macro town_square_post_form() -%} {% if -config.town_square and user %} -
-
-
- {{ icon "pencil" }} - {{ text "communities:label.create_post" }} -
- - Posts created here go to the - town square - community! -
- -
-
- - -
- - -
-
- - -{% endif %} {%- endmacro %} {% macro theme(user) -%} {% if user %} {% if -user.settings.theme_hue %} +{% endif %} {%- endmacro %} {% macro theme(user, theme_preference) -%} {% if +user %} {% if user.settings.theme_hue %} -{% if user.settings.theme_preference %} +{% if theme_preference %} -{% endif %} {% endif %} {% endif %} {%- endmacro %} {% macro quote_form() -%} {% -if config.town_square and user %} +{% endif %} {% endif %} + +
+ {{ components::theme_color(color=user.settings.theme_color_surface, css="color-surface") }} + {{ components::theme_color(color=user.settings.theme_color_text, css="color-text") }} + + {{ components::theme_color(color=user.settings.theme_color_lowered, css="color-lowered") }} + {{ components::theme_color(color=user.settings.theme_color_text_lowered, css="color-text-lowered") }} + {{ components::theme_color(color=user.settings.theme_color_super_lowered, css="color-super-lowered") }} + + {{ components::theme_color(color=user.settings.theme_color_raised, css="color-raised") }} + {{ components::theme_color(color=user.settings.theme_color_text_raised, css="color-text-raised") }} + {{ components::theme_color(color=user.settings.theme_color_super_raised, css="color-super-raised") }} + + {{ components::theme_color(color=user.settings.theme_color_primary, css="color-primary") }} + {{ components::theme_color(color=user.settings.theme_color_text_primary, css="color-text-primary") }} + {{ components::theme_color(color=user.settings.theme_color_primary_lowered, css="color-primary-lowered") }} + + {{ components::theme_color(color=user.settings.theme_color_secondary, css="color-secondary") }} + {{ components::theme_color(color=user.settings.theme_color_text_secondary, css="color-text-secondary") }} + {{ components::theme_color(color=user.settings.theme_color_secondary_lowered, css="color-secondary-lowered") }} +
+{% endif %} {%- endmacro %} {% macro theme_color(color, css) -%} {% if color %} + + +{% endif %} {%- endmacro %} {% macro quote_form() -%} {% if config.town_square +and user %}
diff --git a/crates/app/src/public/html/macros.html b/crates/app/src/public/html/macros.html index 707f888..215c00e 100644 --- a/crates/app/src/public/html/macros.html +++ b/crates/app/src/public/html/macros.html @@ -39,7 +39,7 @@
- {% if not is_blocking %} {% if not is_following %} - - {% else %} + - {% endif %}
- -{% if not use_user_theme %} {{ components::theme(user=profile) }} {% endif %} {% -endblock %} +{% if not is_self %} + +{% endif %} {% if not use_user_theme %} {{ components::theme(user=profile, +theme_preference=profile.settings.profile_theme) }} {% endif %} {% endblock %} diff --git a/crates/app/src/public/html/profile/settings.html b/crates/app/src/public/html/profile/settings.html index 941a791..92a21c8 100644 --- a/crates/app/src/public/html/profile/settings.html +++ b/crates/app/src/public/html/profile/settings.html @@ -18,6 +18,10 @@ {{ text "settings:tab.profile" }} + + {{ text "settings:tab.theme" }} + + {{ text "settings:tab.sessions" }} @@ -334,6 +338,46 @@ {% endfor %}
+ + @@ -641,6 +685,7 @@ document.getElementById("account_settings"); const profile_settings = document.getElementById("profile_settings"); + const theme_settings = document.getElementById("theme_settings"); ui.refresh_container(account_settings, [ "change_password", @@ -652,6 +697,7 @@ "change_avatar", "change_banner", ]); + ui.refresh_container(theme_settings, ["profile_theme"]); ui.generate_settings_ui( account_settings, @@ -666,6 +712,11 @@ settings.biography, "textarea", ], + [ + ["warning", "Profile warning"], + settings.warning, + "textarea", + ], ], settings, ); @@ -695,7 +746,14 @@ "{{ profile.settings.private_last_seen }}", "checkbox", ], - [[], "Theme", "title"], + ], + settings, + ); + + ui.generate_settings_ui( + theme_settings, + [ + [[], "Theme builder", "title"], [ ["theme_hue", "Theme hue (integer 0-255)"], "{{ profile.settings.theme_hue }}", @@ -719,6 +777,137 @@ "{{ profile.settings.disable_other_themes }}", "checkbox", ], + [[], "Theme builder", "title"], + [[], "Override individual colors.", "text"], + // surface + [ + ["theme_color_surface", "Surface"], + "{{ profile.settings.theme_color_surface }}", + "color", + { + description: "Page background.", + }, + ], + [ + ["theme_color_text", "Text"], + "{{ profile.settings.theme_color_text }}", + "color", + { + description: + "Text on elements with the surface backgrounds.", + }, + ], + // lowered + [[], "", "divider"], + [ + ["theme_color_lowered", "Lowered"], + "{{ profile.settings.theme_color_lowered }}", + "color", + { + description: + "Some cards, buttons, or anything else with a darker background color than the surface.", + }, + ], + [ + ["theme_color_text_lowered", "Text"], + "{{ profile.settings.theme_color_text_lowered }}", + "color", + { + description: + "Text on elements with the lowered backgrounds.", + }, + ], + [ + ["theme_color_super_lowered", "Super lowered"], + "{{ profile.settings.theme_color_super_lowered }}", + "color", + { + description: "Borders.", + }, + ], + // raised + [[], "", "divider"], + [ + ["theme_color_raised", "Raised"], + "{{ profile.settings.theme_color_raised }}", + "color", + { + description: + "Some cards, buttons, or anything else with a lighter background color than the surface.", + }, + ], + [ + ["theme_color_text_raised", "Text"], + "{{ profile.settings.theme_color_text_raised }}", + "color", + { + description: + "Text on elements with the raised backgrounds.", + }, + ], + [ + ["theme_color_super_raised", "Super raised"], + "{{ profile.settings.theme_color_super_raised }}", + "color", + { + description: "Some borders.", + }, + ], + // primary + [[], "", "divider"], + [ + ["theme_color_primary", "Primary"], + "{{ profile.settings.theme_color_primary }}", + "color", + { + description: + "Primary color; navigation bar, some buttons, etc.", + }, + ], + [ + ["theme_color_text_primary", "Text"], + "{{ profile.settings.theme_color_text_primary }}", + "color", + { + description: + "Text on elements with the primary backgrounds.", + }, + ], + [ + ["theme_color_primary_lowered", "Primary lowered"], + "{{ profile.settings.theme_color_primary_lowered }}", + "color", + { + description: "Hover state for primary buttons.", + }, + ], + // secondary + [[], "", "divider"], + [ + ["theme_color_secondary", "Secondary"], + "{{ profile.settings.theme_color_secondary }}", + "color", + { + description: "Secondary color.", + }, + ], + [ + ["theme_color_text_secondary", "Text"], + "{{ profile.settings.theme_color_text_secondary }}", + "color", + { + description: + "Text on elements with the secondary backgrounds.", + }, + ], + [ + ["theme_color_secondary_lowered", "Secondary lowered"], + "{{ profile.settings.theme_color_secondary_lowered }}", + "color", + { + description: "Hover state for secondary buttons.", + }, + ], ], settings, ); diff --git a/crates/app/src/public/html/profile/warning.html b/crates/app/src/public/html/profile/warning.html new file mode 100644 index 0000000..15a7187 --- /dev/null +++ b/crates/app/src/public/html/profile/warning.html @@ -0,0 +1,37 @@ +{% extends "root.html" %} {% block head %} +{{ profile.username }} (warning) - {{ config.name }} +{% endblock %} {% block body %} {{ macros::nav(selected="") }} +
+
+
+
+ {{ components::avatar(username=profile.username, size="24px") }} + {{ profile.username }} +
+ + {{ text "auth:label.before_you_view" }} +
+ +
+ {{ profile.settings.warning|markdown|safe }} +
+ + + + {{ icon "x" }} + {{ text "general:action.back" }} + +
+
+
+
+{% endblock %} diff --git a/crates/app/src/public/html/root.html b/crates/app/src/public/html/root.html index 8b2105c..c97749a 100644 --- a/crates/app/src/public/html/root.html +++ b/crates/app/src/public/html/root.html @@ -298,26 +298,6 @@ macros -%}
- -
- {{ components::town_square_post_form() }} - -
-
- -
- -
-
-
-
-
{{ components::quote_form() }} @@ -338,6 +318,7 @@ macros -%}
{% endif %} {% if user and use_user_theme %} {{ - components::theme(user=user) }} {% endif %} + components::theme(user=user, + theme_preference=user.settings.theme_preference) }} {% endif %} diff --git a/crates/app/src/public/js/atto.js b/crates/app/src/public/js/atto.js index e2dbe65..0fce76b 100644 --- a/crates/app/src/public/js/atto.js +++ b/crates/app/src/public/js/atto.js @@ -755,11 +755,21 @@ media_theme_pref(); }); self.define("render_settings_ui_field", (_, into_element, option) => { + if (option.input_element_type === "divider") { + into_element.innerHTML += `
`; + return; + } + if (option.input_element_type === "title") { into_element.innerHTML += `
${option.value}`; return; } + if (option.input_element_type === "text") { + into_element.innerHTML += `

${option.value}

`; + return; + } + if (option.input_element_type === "checkbox") { into_element.innerHTML += `
+ + +
+ + + +
+ + ${option.attributes.description} +
`; + + return; + } + into_element.innerHTML += `
@@ -805,6 +845,7 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} label: Array.isArray(option[0]) ? option[0][1] : option[0], value: option[1], input_element_type: option[2], + attributes: option[3], }); } @@ -817,6 +858,43 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} console.log("update", key); }; + + window.preview_color = (key, value) => { + console.log("preview_color", key); + const stylesheet = document.getElementById( + `${key}/color/preview`, + ); + + const css = `*, :root { --${key.replace("theme_", "").replace("_", "-")}: ${value} !important; }`; + + if (stylesheet) { + stylesheet.innerHTML = css; + + if (value === "") { + console.log("remove_stylesheet", key); + stylesheet.remove(); + } + } else { + const stylesheet = document.createElement("style"); + stylesheet.innerHTML = css; + stylesheet.id = `${key}/color/preview`; + document.body.appendChild(stylesheet); + console.log("create_stylesheet", key); + } + }; + + window.update_field_with_color = (key, value) => { + console.log("sync_color_text", key); + document.getElementById(key).value = value; + set_setting_field(key, value); + preview_color(key, value); + }; + + window.update_color_field = (key, value) => { + console.log("sync_color_color", key); + document.getElementById(`${key}/color`).value = value; + preview_color(key, value); + }; }, ); @@ -892,3 +970,47 @@ ${option.input_element_type === "textarea" ? `${option.value}` : ""} }, ); })(); + +(() => { + const self = reg_ns("warnings"); + + const accepted_warnings = JSON.parse( + window.localStorage.getItem("atto:accepted_warnings") || "{}", + ); + + self.define( + "open", + async ({ $ }, warning_id, warning_hash, warning_page = "") => { + // check localStorage for this warning_id + if (accepted_warnings[warning_id] !== undefined) { + // check hash + if (accepted_warnings[warning_id] !== warning_hash) { + // hash is not the same, show dialog again + delete accepted_warnings[warning_id]; + } else { + // return + return; + } + } + + // open page + if (warning_page !== "") { + window.location.href = warning_page; + return; + } + }, + ); + + self.define("accept", ({ _ }, warning_id, warning_hash) => { + accepted_warnings[warning_id] = warning_hash; + + window.localStorage.setItem( + "atto:accepted_warnings", + JSON.stringify(accepted_warnings), + ); + + setTimeout(() => { + window.history.back(); + }, 100); + }); +})(); diff --git a/crates/app/src/routes/pages/communities.rs b/crates/app/src/routes/pages/communities.rs index b9eabcb..8976eb6 100644 --- a/crates/app/src/routes/pages/communities.rs +++ b/crates/app/src/routes/pages/communities.rs @@ -204,6 +204,60 @@ pub async fn search_request( )) } +/// `/communities/intents/post` +pub async fn create_post_request( + jar: CookieJar, + Extension(data): Extension, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &None).await, + )); + } + }; + + let town_square = match data.0.get_community_by_id(data.0.0.town_square).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + let memberships = match data.0.get_memberships_by_owner(user.id).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + let mut communities: Vec = Vec::new(); + for membership in memberships { + if membership.community == data.0.0.town_square { + // we already pulled the town square + continue; + } + + let community = match data.0.get_community_by_id(membership.community).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + communities.push(community) + } + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &Some(user)).await; + + context.insert("town_square", &town_square); + context.insert("communities", &communities); + + // return + Ok(Html( + data.1 + .render("communities/create_post.html", &context) + .unwrap(), + )) +} + pub fn community_context( context: &mut Context, community: &Community, diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index 43cac0e..3a41b3f 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -46,6 +46,10 @@ pub fn routes() -> Router { // communities .route("/communities", get(communities::list_request)) .route("/communities/search", get(communities::search_request)) + .route( + "/communities/intents/post", + get(communities::create_post_request), + ) .route("/community/{title}", get(communities::feed_request)) .route( "/community/{title}/manage", @@ -76,6 +80,14 @@ pub struct PaginatedQuery { pub page: usize, } +#[derive(Deserialize)] +pub struct ProfileQuery { + #[serde(default)] + pub page: usize, + #[serde(default)] + pub warning: bool, +} + #[derive(Deserialize)] pub struct SearchedQuery { #[serde(default)] diff --git a/crates/app/src/routes/pages/profile.rs b/crates/app/src/routes/pages/profile.rs index 895b9d9..74cd925 100644 --- a/crates/app/src/routes/pages/profile.rs +++ b/crates/app/src/routes/pages/profile.rs @@ -1,4 +1,4 @@ -use super::{PaginatedQuery, render_error}; +use super::{render_error, PaginatedQuery, ProfileQuery}; use crate::{assets::initial_context, get_lang, get_user_from_token, sanitize::clean_settings, State}; use axum::{ Extension, @@ -9,6 +9,7 @@ use axum_extra::extract::CookieJar; use serde::Deserialize; use tera::Context; use tetratto_core::model::{Error, auth::User, communities::Community, permissions::FinePermission}; +use tetratto_shared::hash::hash; #[derive(Deserialize)] pub struct SettingsProps { @@ -80,6 +81,7 @@ pub fn profile_context( context.insert("is_following", &is_following); context.insert("is_following_you", &is_following_you); context.insert("is_blocking", &is_blocking); + context.insert("warning_hash", &hash(profile.settings.warning.clone())); context.insert( "is_supporter", @@ -99,7 +101,7 @@ pub fn profile_context( pub async fn posts_request( jar: CookieJar, Path(username): Path, - Query(props): Query, + Query(props): Query, Extension(data): Extension, ) -> impl IntoResponse { let data = data.read().await; @@ -146,6 +148,19 @@ pub async fn posts_request( } } + // check for warning + if props.warning { + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &user).await; + + context.insert("profile", &other_user); + context.insert("warning_hash", &hash(other_user.settings.warning.clone())); + + return Ok(Html( + data.1.render("profile/warning.html", &context).unwrap(), + )); + } + // fetch data let posts = match data .0 diff --git a/crates/app/src/sanitize.rs b/crates/app/src/sanitize.rs index bc860f0..58e45b5 100644 --- a/crates/app/src/sanitize.rs +++ b/crates/app/src/sanitize.rs @@ -2,19 +2,19 @@ use ammonia::Builder; use tetratto_core::model::{auth::UserSettings, communities::CommunityContext}; /// Escape profile colors -// pub fn color_escape(color: &&&String) -> String { -// remove_tags( -// &color -// .replace(";", "") -// .replace("<", "<") -// .replace(">", "%gt;") -// .replace("}", "") -// .replace("{", "") -// .replace("url(\"", "url(\"/api/v0/util/ext/image?img=") -// .replace("url('", "url('/api/v0/util/ext/image?img=") -// .replace("url(https://", "url(/api/v0/util/ext/image?img=https://"), -// ) -// } +pub fn color_escape(color: &str) -> String { + remove_tags( + &color + .replace(";", "") + .replace("<", "<") + .replace(">", "%gt;") + .replace("}", "") + .replace("{", "") + .replace("url(\"", "url(\"/api/v0/util/ext/image?img=") + .replace("url('", "url('/api/v0/util/ext/image?img=") + .replace("url(https://", "url(/api/v0/util/ext/image?img=https://"), + ) +} /// Clean profile metadata pub fn remove_tags(input: &str) -> String { diff --git a/crates/core/src/database/posts.rs b/crates/core/src/database/posts.rs index e0c2e17..481bbf4 100644 --- a/crates/core/src/database/posts.rs +++ b/crates/core/src/database/posts.rs @@ -159,7 +159,7 @@ impl DataManager { } if let Some(is_following) = seen_user_follow_statuses.get(&(user.id, user_id)) { - if !is_following { + if !is_following && (user.id != user_id) { // post owner is not following us continue; } @@ -488,7 +488,7 @@ impl DataManager { // make sure we aren't trying to repost a repost if if let Some(ref repost) = rt.context.repost { - !(!repost.is_repost) + repost.reposting.is_some() } else { false } { diff --git a/crates/core/src/model/auth.rs b/crates/core/src/model/auth.rs index ef9e8b7..e3cd605 100644 --- a/crates/core/src/model/auth.rs +++ b/crates/core/src/model/auth.rs @@ -46,8 +46,7 @@ impl Default for ThemePreference { } } -#[derive(Clone, Debug, Serialize, Deserialize)] -#[derive(Default)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct UserSettings { #[serde(default)] pub policy_consent: bool, @@ -56,11 +55,18 @@ pub struct UserSettings { #[serde(default)] pub biography: String, #[serde(default)] + pub warning: String, + #[serde(default)] pub private_profile: bool, #[serde(default)] pub private_communities: bool, + /// The theme shown to the user. #[serde(default)] pub theme_preference: ThemePreference, + /// The theme used on the user's profile. Setting this to `Auto` will use + /// the viewing user's `theme_preference` setting. + #[serde(default)] + pub profile_theme: ThemePreference, #[serde(default)] pub private_last_seen: bool, #[serde(default)] @@ -69,11 +75,52 @@ pub struct UserSettings { pub theme_sat: String, #[serde(default)] pub theme_lit: String, + /// Page background. + #[serde(default)] + pub theme_color_surface: String, + /// Text on elements with the surface backgrounds. + #[serde(default)] + pub theme_color_text: String, + /// Some cards, buttons, or anything else with a darker background color than the surface. + #[serde(default)] + pub theme_color_lowered: String, + /// Text on elements with the lowered backgrounds. + #[serde(default)] + pub theme_color_text_lowered: String, + /// Borders. + #[serde(default)] + pub theme_color_super_lowered: String, + /// Some cards, buttons, or anything else with a lighter background color than the surface. + #[serde(default)] + pub theme_color_raised: String, + /// Text on elements with the raised backgrounds. + #[serde(default)] + pub theme_color_text_raised: String, + /// Some borders. + #[serde(default)] + pub theme_color_super_raised: String, + /// Primary color; navigation bar, some buttons, etc. + #[serde(default)] + pub theme_color_primary: String, + /// Text on elements with the primary backgrounds. + #[serde(default)] + pub theme_color_text_primary: String, + /// Hover state for primary buttons. + #[serde(default)] + pub theme_color_primary_lowered: String, + /// Secondary color. + #[serde(default)] + pub theme_color_secondary: String, + /// Text on elements with the secondary backgrounds. + #[serde(default)] + pub theme_color_text_secondary: String, + /// Hover state for secondary buttons. + #[serde(default)] + pub theme_color_secondary_lowered: String, #[serde(default)] pub disable_other_themes: bool, } - impl Default for User { fn default() -> Self { Self::new("".to_string(), String::new()) @@ -204,7 +251,8 @@ impl User { self.totp.as_bytes().to_owned(), Some(issuer.unwrap_or("tetratto!".to_string())), self.username.clone(), - ).ok() + ) + .ok() } } diff --git a/crates/core/src/model/communities.rs b/crates/core/src/model/communities.rs index d1c537b..b059bc0 100644 --- a/crates/core/src/model/communities.rs +++ b/crates/core/src/model/communities.rs @@ -198,7 +198,7 @@ impl Default for PostContext { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RepostContext { - /// Should be `false` is `repost_of` is `Some`. + /// Should be `false` is `reposting` is `Some`. /// /// Declares the post to be a repost of another post. pub is_repost: bool,