add: better pinned posts ui

This commit is contained in:
trisua 2025-08-06 23:13:31 -04:00
parent b5f841a990
commit 81a7628861
12 changed files with 25 additions and 192 deletions

View file

@ -502,7 +502,7 @@ select:focus {
} }
input[type="checkbox"] { input[type="checkbox"] {
--color: #c9b1bc; --color: #93c5fd;
appearance: none; appearance: none;
border-radius: var(--radius); border-radius: var(--radius);
transition: transition:

View file

@ -1,18 +1,7 @@
(text "{% import \"components.html\" as components %} {% extends \"communities/base.html\" %} {% block content %}") (text "{% import \"components.html\" as components %} {% extends \"communities/base.html\" %} {% block content %}")
(div (div
("class" "flex flex_col gap_4 w_full") ("class" "flex flex_col gap_4 w_full")
(text "{{ macros::community_nav(community=community, selected=\"posts\") }} {% if pinned|length != 0 %}") (text "{{ macros::community_nav(community=community, selected=\"posts\") }}")
(div
("class" "card_nest")
(div
("class" "card small flex gap_2 items_center")
(text "{{ icon \"pin\" }}")
(span
(text "{{ text \"communities:label.pinned\" }}")))
(div
("class" "card flex flex_col gap_4")
(text "{% for post in pinned %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %}")))
(text "{%- endif %}")
(div (div
("class" "card_nest") ("class" "card_nest")
(div (div
@ -22,5 +11,6 @@
(text "{{ text \"communities:label.posts\" }}"))) (text "{{ text \"communities:label.posts\" }}")))
(div (div
("class" "card flex flex_col gap_4") ("class" "card flex flex_col gap_4")
(text "{% for post in pinned %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %}")
(text "{% for post in feed %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=feed|length) }}")))) (text "{% for post in feed %} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[2], post=post[0], owner=post[1], secondary=true, show_community=false, can_manage_post=can_manage_posts) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[3], secondary=true, show_community=false, can_manage_post=can_manage_posts, poll=post[4]) }} {%- endif %} {% endfor %} {{ components::pagination(page=page, items=feed|length) }}"))))
(text "{% endblock %}") (text "{% endblock %}")

View file

@ -358,8 +358,7 @@
("class" "flush flex gap_1 items_center") ("class" "flush flex gap_1 items_center")
(text "{{ self::community_avatar(id=post.community, community=community) }}") (text "{{ self::community_avatar(id=post.community, community=community) }}")
(b (b
(text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}")) (text "{% if community.context.display_name -%} {{ community.context.display_name }} {% else %} {{ community.title }} {%- endif %}"))))
(text "{% if post.context.is_pinned or post.context.is_profile_pinned -%} {{ icon \"pin\" }} {%- endif %}")))
(text "{%- endif %} {%- endif %}") (text "{%- endif %} {%- endif %}")
(div (div
("class" "card flex flex_col post gap_2 post:{{ post.id }} {% if secondary -%}secondary{%- endif %}") ("class" "card flex flex_col post gap_2 post:{{ post.id }} {% if secondary -%}secondary{%- endif %}")
@ -386,7 +385,8 @@
(span (span
("class" "name") ("class" "name")
(text "{{ self::full_username(user=owner) }}")) (text "{{ self::full_username(user=owner) }}"))
(text "{{ self::post_info(post=post, community=community) }}")) (text "{{ self::post_info(post=post, community=community) }}")
(text "{% if post.context.is_pinned or post.context.is_profile_pinned -%} {{ icon \"pin\" }} {%- endif %}"))
(text "{% if not dont_show_title and post.title and community and community.context.enable_titles -%}") (text "{% if not dont_show_title and post.title and community and community.context.enable_titles -%}")
; post has a title AND whatever is rendering this component wants to see it ; post has a title AND whatever is rendering this component wants to see it
(a (a

View file

@ -170,16 +170,6 @@
(text "Posts")) (text "Posts"))
(span (span
(text "{{ profile.post_count }}"))) (text "{{ profile.post_count }}")))
(text "{% if gpa and gpa > 0 and (not user.settings.disable_gpa_fun or is_helper) -%}")
(div
("class" "w_full flex justify_between items_center")
("title" "great post average (limited time fun)")
(span
("class" "notification chip")
(text "GPA"))
(span
(text "{{ gpa|round(method=\"floor\", precision=2) }}")))
(text "{%- endif %}")
(text "{% if not profile.settings.private_last_seen or is_self or is_helper %}") (text "{% if not profile.settings.private_last_seen or is_self or is_helper %}")
(div (div
("class" "w_full flex justify_between items_center") ("class" "w_full flex justify_between items_center")

View file

@ -3,19 +3,8 @@
("style" "display: contents") ("style" "display: contents")
(text "{{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header, drawing_enabled=profile.settings.enable_drawings, allow_anonymous=profile.settings.allow_anonymous_questions) }}")) (text "{{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header, drawing_enabled=profile.settings.enable_drawings, allow_anonymous=profile.settings.allow_anonymous_questions) }}"))
(text "{%- endif %} {% if not tag and pinned|length != 0 -%}") (text "{%- endif %}")
(div (text "{{ macros::profile_nav(selected=\"posts\") }}")
("class" "card_nest")
(div
("class" "card small flex gap_2 items_center")
(text "{{ icon \"pin\" }}")
(span
(text "{{ text \"communities:label.pinned\" }}")))
(div
("class" "card flex flex_col gap_4")
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")))
(text "{%- endif %} {{ macros::profile_nav(selected=\"posts\") }}")
(div (div
("class" "card_nest") ("class" "card_nest")
(div (div
@ -42,6 +31,12 @@
(div (div
("class" "card w_full flex flex_col gap_2") ("class" "card w_full flex flex_col gap_2")
("ui_ident" "io_data_load") ("ui_ident" "io_data_load")
; pinned
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
(text "{% if pinned|length > 0 -%}")
(div ("class" "squig"))
(text "{%- endif %}")
; ...
(div ("ui_ident" "io_data_marker")))) (div ("ui_ident" "io_data_marker"))))
(text "{% set paged = user and user.settings.paged_timelines %}") (text "{% set paged = user and user.settings.paged_timelines %}")

View file

@ -3,19 +3,8 @@
("style" "display: contents") ("style" "display: contents")
(text "{{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header, drawing_enabled=profile.settings.enable_drawings, allow_anonymous=profile.settings.allow_anonymous_questions) }}")) (text "{{ components::create_question_form(receiver=profile.id, header=profile.settings.motivational_header, drawing_enabled=profile.settings.enable_drawings, allow_anonymous=profile.settings.allow_anonymous_questions) }}"))
(text "{%- endif %} {% if not tag and pinned|length != 0 -%}") (text "{%- endif %}")
(div (text "{{ macros::profile_nav(selected=\"responses\") }}")
("class" "card_nest")
(div
("class" "card small flex gap_2 items_center")
(text "{{ icon \"pin\" }}")
(span
(text "{{ text \"communities:label.pinned\" }}")))
(div
("class" "card flex flex_col gap_4")
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")))
(text "{%- endif %} {{ macros::profile_nav(selected=\"responses\") }}")
(div (div
("class" "card_nest") ("class" "card_nest")
(div (div
@ -42,6 +31,12 @@
(div (div
("class" "card w_full flex flex_col gap_2") ("class" "card w_full flex flex_col gap_2")
("ui_ident" "io_data_load") ("ui_ident" "io_data_load")
; pinned
(text "{% for post in pinned %} {% if post[2].read_access == \"Everybody\" -%} {% if post[0].context.repost and post[0].context.repost.reposting -%} {{ components::repost(repost=post[3], post=post[0], owner=post[1], secondary=true, community=post[2], show_community=true, can_manage_post=is_self) }} {% else %} {{ components::post(post=post[0], owner=post[1], question=post[4], secondary=true, community=post[2], can_manage_post=is_self, poll=post[5]) }} {%- endif %} {%- endif %} {% endfor %}")
(text "{% if pinned|length > 0 -%}")
(div ("class" "squig"))
(text "{%- endif %}")
; ...
(div ("ui_ident" "io_data_marker")))) (div ("ui_ident" "io_data_marker"))))
(text "{% set paged = user and user.settings.paged_timelines %}") (text "{% set paged = user and user.settings.paged_timelines %}")

View file

@ -1970,11 +1970,6 @@
\"text\", \"text\",
], ],
[[], \"Fun\", \"title\"], [[], \"Fun\", \"title\"],
[
[\"disable_gpa_fun\", \"Disable GPA\"],
\"{{ profile.settings.disable_gpa_fun }}\",
\"checkbox\",
],
[ [
[\"disable_achievements\", \"Disable achievements\"], [\"disable_achievements\", \"Disable achievements\"],
\"{{ profile.settings.disable_achievements }}\", \"{{ profile.settings.disable_achievements }}\",

View file

@ -869,36 +869,6 @@ pub async fn post_to_socket_request(
}) })
} }
/// Calculate the user's great post average.
pub async fn get_user_gpa_request(
jar: CookieJar,
Path(id): Path<usize>,
Extension(data): Extension<State>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadProfile) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
let gpa = data.calculate_user_gpa(id).await;
return Json(ApiReturn {
ok: true,
message: if gpa >= 3.0 {
"cool".to_string()
} else if gpa >= 4.0 {
"extraordinary".to_string()
} else {
"ok".to_string()
},
payload: Some(gpa),
});
}
/// Remove a grant token. /// Remove a grant token.
pub async fn remove_grant_request( pub async fn remove_grant_request(
jar: CookieJar, jar: CookieJar,

View file

@ -405,10 +405,6 @@ pub fn routes() -> Router {
get(auth::profile::redirect_from_stripe_id), get(auth::profile::redirect_from_stripe_id),
) )
.route("/auth/ip/{ip}/block", post(auth::social::ip_block_request)) .route("/auth/ip/{ip}/block", post(auth::social::ip_block_request))
.route(
"/auth/user/{id}/gpa",
get(auth::profile::get_user_gpa_request),
)
.route( .route(
"/auth/user/{id}/_connect/{stream}", "/auth/user/{id}/_connect/{stream}",
any(auth::profile::subscription_handler), any(auth::profile::subscription_handler),

View file

@ -371,7 +371,6 @@ pub async fn posts_request(
context.insert("pinned", &pinned); context.insert("pinned", &pinned);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("tag", &props.tag); context.insert("tag", &props.tag);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&user, &user,
@ -488,7 +487,6 @@ pub async fn replies_request(
context.insert("posts", &posts); context.insert("posts", &posts);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&user, &user,
@ -601,7 +599,6 @@ pub async fn media_request(
context.insert("posts", &posts); context.insert("posts", &posts);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&user, &user,
@ -696,7 +693,6 @@ pub async fn outbox_request(
context.insert("questions", &questions); context.insert("questions", &questions);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&Some(user), &Some(user),
@ -806,7 +802,6 @@ pub async fn following_request(
context.insert("list", &list); context.insert("list", &list);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&user, &user,
@ -916,7 +911,6 @@ pub async fn followers_request(
context.insert("list", &list); context.insert("list", &list);
context.insert("page", &props.page); context.insert("page", &props.page);
context.insert("gpa", &data.0.calculate_user_gpa(other_user.id).await);
profile_context( profile_context(
&mut context, &mut context,
&user, &user,

View file

@ -13,9 +13,7 @@ use crate::model::{
}; };
use tetratto_shared::unix_epoch_timestamp; use tetratto_shared::unix_epoch_timestamp;
use crate::{auto_method, DataManager}; use crate::{auto_method, DataManager};
use oiseau::{PostgresRow, execute, get, query_row, query_rows, params, cache::Cache};
use oiseau::{PostgresRow, cache::redis::Commands};
use oiseau::{execute, get, query_row, query_rows, params, cache::Cache};
pub type FullPost = ( pub type FullPost = (
Post, Post,
@ -816,95 +814,6 @@ impl DataManager {
Ok(res.unwrap()) Ok(res.unwrap())
} }
/// Calculate the GPA (great post average) of a given user.
///
/// To be considered a "great post", a post must have a score ((likes - dislikes) / (likes + dislikes))
/// of at least 0.6.
///
/// GPA is calculated based on the user's last 48 posts.
pub async fn calculate_user_gpa(&self, id: usize) -> f32 {
// just for note, this is SUPER bad for performance... which is why we
// only calculate this when it expires in the cache (every day)
if let Some(cached) = self.0.1.get(format!("atto.user.gpa:{}", id)).await {
if let Ok(c) = cached.parse() {
return c;
}
}
// ...
let conn = match self.0.connect().await {
Ok(c) => c,
Err(_) => return 0.0,
};
let res = query_rows!(
&conn,
&format!("SELECT * FROM posts WHERE owner = $1 ORDER BY created DESC LIMIT 48"),
&[&(id as i64)],
|x| { Self::get_post_from_row(x) }
);
if res.is_err() {
return 0.0;
}
// ...
let mut real_posts_count: usize = 0; // posts which can be scored
let mut good_posts: usize = 0;
// let mut bad_posts: usize = 0;
let posts = res.unwrap();
for post in posts {
if post.likes == 0 && post.dislikes == 0 {
// post has no likes or dislikes... doesn't count
if good_posts > 8 {
good_posts -= 1; // we're going to say this is a bad post because it isn't liked enough
}
continue;
}
real_posts_count += 1;
// likes percentage / total likes
let score: f32 = (post.likes as f32 - post.dislikes as f32)
/ (post.likes as f32 + post.dislikes as f32);
if score.is_sign_negative() {
// bad_posts += 1;
continue;
}
if score > 0.6 {
good_posts += 1;
}
// } else {
// bad_posts += 1;
// }
}
let gpa = (good_posts as f32 / real_posts_count as f32) * 4.0;
let gpa_rounded = format!("{gpa:.2}").parse::<f32>().unwrap();
let mut redis_con = self.0.1.get_con().await;
// expires in one day
if redis_con
.set_ex::<String, String, usize>(
format!("atto.user.gpa:{}", id),
gpa_rounded.to_string(),
86400,
)
.is_err()
{
return 0.0;
};
// ...
gpa_rounded
}
/// Get all replies from the given user (from most recent). /// Get all replies from the given user (from most recent).
/// ///
/// # Arguments /// # Arguments
@ -1924,6 +1833,8 @@ impl DataManager {
} else { } else {
return Err(Error::GeneralNotFound("topic".to_string())); return Err(Error::GeneralNotFound("topic".to_string()));
} }
} else if data.topic != 0 {
return Err(Error::DoesNotSupportField("Community".to_string()));
} }
// ... // ...

View file

@ -294,9 +294,6 @@ pub struct UserSettings {
/// If extra post tabs are hidden (replies, media). /// If extra post tabs are hidden (replies, media).
#[serde(default)] #[serde(default)]
pub hide_extra_post_tabs: bool, pub hide_extra_post_tabs: bool,
/// If the GPA experiment is disabled.
#[serde(default)]
pub disable_gpa_fun: bool,
/// A list of strings the user has muted. /// A list of strings the user has muted.
#[serde(default)] #[serde(default)]
pub muted: Vec<String>, pub muted: Vec<String>,