add: user status

This commit is contained in:
trisua 2025-05-03 11:29:31 -04:00
parent 1724f798ca
commit a009ef9e34
10 changed files with 259 additions and 138 deletions

View file

@ -107,7 +107,7 @@ article {
padding: 0;
}
body .card:not(.card *):not(#stream *),
body .card:not(.card *):not(#stream *):not(.user_plate),
body .pillmenu:not(.card *) > a,
body .card-nest:not(.card *) > .card,
body .banner {
@ -340,6 +340,18 @@ img.contain {
max-height: 350px;
}
.user_status span {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user_status .icon {
min-width: 1em;
min-height: 1em;
}
/* table */
table {
border-collapse: collapse;
@ -416,6 +428,10 @@ table ol {
padding: 0.5rem 1rem;
}
.card.tiny {
padding: 0.5rem;
}
.card.secondary {
background: var(--color-surface);
}
@ -477,6 +493,12 @@ button.small,
font-size: 16px;
}
button.small.square,
.button.small.square {
width: 32px;
height: 32px;
}
button.big_icon svg,
.button.big_icon svg {
height: 16px;

View file

@ -1,6 +1,7 @@
{% extends "root.html" %} {% block head %}
<title>Chats - {{ config.name }}</title>
{% endblock %} {% block body %} {{ macros::nav(selected="chats") }}
{% endblock %} {% block body %} {{ macros::nav(selected="chats",
hide_user_menu=true) }}
<nav class="chats_nav">
<button
class="flex gap-2 items-center active"
@ -44,59 +45,64 @@
{% endif %} {% endfor %}
</div>
<div class="sidebar flex flex-col gap-2" id="channels_list">
<div class="title flex justify-between">
{% if community %}
<b class="name shorter">
{% if community.context.display_name %} {{
community.context.display_name }} {% else %} {{ community.title
}} {% endif %}
</b>
{% else %}
<b>{{ text "chats:label.my_chats" }}</b>
{% endif %}
<div class="dropdown">
<button
class="camo small"
onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown"
>
{{ icon "ellipsis" }}
</button>
<div class="sidebar flex flex-col gap-2 justify-between" id="channels_list">
<div class="flex flex-col gap-2 w-full">
<div class="title flex justify-between">
{% if community %}
<b class="name shorter">
{% if community.context.display_name %} {{
community.context.display_name }} {% else %} {{
community.title }} {% endif %}
</b>
{% else %}
<b>{{ text "chats:label.my_chats" }}</b>
{% endif %}
<div class="dropdown">
<button
class="camo small"
onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown"
>
{{ icon "ellipsis" }}
</button>
<div class="inner">
{% if selected_community != 0 %}
<a href="/community/{{ selected_community }}">
{{ icon "book-heart" }}
<span
>{{ text "communities:label.show_community" }}</span
>
</a>
{% endif %} {% if can_manage_channels %}
<a href="/community/{{ selected_community }}/manage">
{{ icon "settings" }}
<span>{{ text "general:action.manage" }}</span>
</a>
{% endif %}
<div class="inner">
{% if selected_community != 0 %}
<a href="/community/{{ selected_community }}">
{{ icon "book-heart" }}
<span
>{{ text "communities:label.show_community"
}}</span
>
</a>
{% endif %} {% if can_manage_channels %}
<a href="/community/{{ selected_community }}/manage">
{{ icon "settings" }}
<span>{{ text "general:action.manage" }}</span>
</a>
{% endif %}
</div>
</div>
</div>
{% if can_manage_channels %}
<a
class="button w-full justify-start quaternary"
href="/community/{{ selected_community }}/manage#/channels"
>
{{ icon "plus" }}
<span>{{ text "communities:action.create_channel" }}</span>
</a>
{% endif %}
<turbo-frame
id="channels_list_frame"
src="/chats/{{ selected_community }}/{{ selected_channel }}/_channels"
target="_top"
></turbo-frame>
</div>
{% if can_manage_channels %}
<a
class="button w-full justify-start quaternary"
href="/community/{{ selected_community }}/manage#/channels"
>
{{ icon "plus" }}
<span>{{ text "communities:action.create_channel" }}</span>
</a>
{% endif %}
<turbo-frame
id="channels_list_frame"
src="/chats/{{ selected_community }}/{{ selected_channel }}/_channels"
target="_top"
></turbo-frame>
{{ components::user_plate(user=user, show_menu=true) }}
</div>
{% if channel %}
@ -253,7 +259,7 @@
z-index: 1;
}
.sidebar .title {
.sidebar .title:not(.dropdown *) {
padding: 1rem;
border-bottom: solid 1px var(--color-super-lowered);
}
@ -321,6 +327,10 @@
.chats_nav {
display: flex;
}
.sidebar {
height: calc(100dvh - 42px * 2);
}
}
</style>

View file

@ -973,4 +973,128 @@ can_manage_message=false, grouped=false) -%}
</div>
</div>
</div>
{%- endmacro %} {% macro user_menu() -%}
<div class="inner">
<b class="title">{{ user.username }}</b>
<a href="/@{{ user.username }}">
{{ icon "circle-user-round" }}
<span>{{ text "auth:link.my_profile" }}</span>
</a>
<a href="/settings">
{{ icon "settings" }}
<span>{{ text "auth:link.settings" }}</span>
</a>
{% if is_helper %}
<b class="title">{{ text "general:label.mod" }}</b>
<a href="/mod_panel/audit_log">
{{ icon "scroll-text" }}
<span>{{ text "general:link.audit_log" }}</span>
</a>
<a href="/mod_panel/reports">
{{ icon "flag" }}
<span>{{ text "general:link.reports" }}</span>
</a>
<a href="/mod_panel/ip_bans">
{{ icon "ban" }}
<span>{{ text "general:link.ip_bans" }}</span>
</a>
<a href="/mod_panel/stats">
{{ icon "chart-line" }}
<span>{{ text "general:link.stats" }}</span>
</a>
{% endif %}
<b class="title">{{ config.name }}</b>
<a href="https://github.com/trisuaso/tetratto">
{{ icon "code" }}
<span>{{ text "general:link.source_code" }}</span>
</a>
<a href="https://trisuaso.github.io/tetratto">
{{ icon "book" }}
<span>{{ text "general:link.reference" }}</span>
</a>
<div class="title"></div>
<button onclick="trigger('me::switch_account')">
{{ icon "ellipsis" }}
<span>{{ text "general:action.switch_account" }}</span>
</button>
<button class="red" onclick="trigger('me::logout')">
{{ icon "log-out" }}
<span>{{ text "auth:action.logout" }}</span>
</button>
</div>
{%- endmacro %} {% macro user_status(other_user) -%} {% if
other_user.settings.status %}
<div class="flex items-center gap-2">
<span>{{ other_user.settings.status }}</span>
<!-- connection icon -->
{% if (other_user.connections.LastFm[1].data and
other_user.connections.LastFm[1].data.track) or
(other_user.connections.Spotify[1].data and
other_user.connections.Spotify[1].data.track) %} {{ icon "music" }} {% endif
%}
</div>
{% elif other_user.connections.LastFm[0].data.name and
other_user.connections.LastFm[1].data and
other_user.connections.LastFm[1].data.track %}
<div class="flex items-center gap-2">
{{ icon "music" }}
<span
><b>Listening to</b> {{ other_user.connections.LastFm[1].data.artist
}}</span
>
</div>
{% elif other_user.connections.Spotify[0].data.name and
other_user.connections.Spotify[1].data and
other_user.connections.Spotify[1].data.track %}
<div class="flex items-center gap-2">
{{ icon "music" }}
<span
><b>Listening to</b> {{ other_user.connections.Spotify[1].data.artist
}}</span
>
</div>
{% endif %} {%- endmacro %} {% macro user_plate(user, show_menu=false,
secondary=false) -%}
<div
class="flex gap-2 items-center card tiny user_plate {% if secondary %}secondary{% endif %}"
>
<a href="/@{{ user.username }}">
{{ self::avatar(username=user.username, size="42px",
selector_type="username") }}
</a>
<div
class="flex justify-center flex-col"
style="{% if show_menu %}width: 60%{% endif %}"
>
{{ self::full_username(user=user) }}
<div class="user_status">{{ self::user_status(other_user=user) }}</div>
</div>
{% if show_menu %}
<div class="dropdown">
<button
class="camo small square"
onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown"
>
{{ icon "settings" c(dropdown-arrow) }}
</button>
{{ self::user_menu() }}
</div>
{% endif %}
</div>
{%- endmacro %}

View file

@ -1,4 +1,4 @@
{% macro nav(selected="", show_lhs=true) -%}
{% macro nav(selected="", show_lhs=true, hide_user_menu=false) -%}
<nav>
<div class="content_container">
<div class="flex nav_side">
@ -72,79 +72,21 @@
>
</a>
{% if not hide_user_menu %}
<div class="dropdown">
<!-- prettier-ignore -->
<button
class="flex-row title"
onclick="trigger('atto::hooks::dropdown', [event])"
exclude="dropdown"
style="gap: 0.25rem !important"
>
{{ components::avatar(username=user.username, size="24px") }}
{{ icon "chevron-down" c(dropdown-arrow) }}
{{ components::avatar(username=user.username, size="24px")
}} {{ icon "chevron-down" c(dropdown-arrow) }}
</button>
<div class="inner">
<b class="title">{{ user.username }}</b>
<a href="/@{{ user.username }}">
{{ icon "circle-user-round" }}
<span>{{ text "auth:link.my_profile" }}</span>
</a>
<a href="/settings">
{{ icon "settings" }}
<span>{{ text "auth:link.settings" }}</span>
</a>
{% if is_helper %}
<b class="title">{{ text "general:label.mod" }}</b>
<a href="/mod_panel/audit_log">
{{ icon "scroll-text" }}
<span>{{ text "general:link.audit_log" }}</span>
</a>
<a href="/mod_panel/reports">
{{ icon "flag" }}
<span>{{ text "general:link.reports" }}</span>
</a>
<a href="/mod_panel/ip_bans">
{{ icon "ban" }}
<span>{{ text "general:link.ip_bans" }}</span>
</a>
<a href="/mod_panel/stats">
{{ icon "chart-line" }}
<span>{{ text "general:link.stats" }}</span>
</a>
{% endif %}
<b class="title">{{ config.name }}</b>
<a href="https://github.com/trisuaso/tetratto">
{{ icon "code" }}
<span>{{ text "general:link.source_code" }}</span>
</a>
<a href="https://trisuaso.github.io/tetratto">
{{ icon "book" }}
<span>{{ text "general:link.reference" }}</span>
</a>
<div class="title"></div>
<button onclick="trigger('me::switch_account')">
{{ icon "ellipsis" }}
<span>{{ text "general:action.switch_account" }}</span>
</button>
<button class="red" onclick="trigger('me::logout')">
{{ icon "log-out" }}
<span>{{ text "auth:action.logout" }}</span>
</button>
</div>
{{ components::user_menu() }}
</div>
{% else %}
{% endif %} {% else %}
<div class="dropdown">
<button
class="title"

View file

@ -5,17 +5,23 @@
<span>{{ text "auth:label.followers" }}</span>
</div>
<div class="card flex flex-col gap-4">
<div class="card flex flex-wrap gap-4 flex-collapse">
<!-- prettier-ignore -->
{% for item in list %}
<div class="card-nest">
<div class="card small">
Since <span class="date">{{ item[0].created }}</span>
</div>
{{ components::user_card(user=item[1]) }}
</div>
{{ components::user_plate(user=item[1], secondary=true) }}
{% endfor %} {{ components::pagination(page=page, items=list|length) }}
</div>
</div>
<style>
.user_plate {
width: calc(50% - 0.5rem);
}
@media screen and (max-width: 900px) {
.user_plate {
width: 100%;
}
}
</style>
{% endblock %}

View file

@ -5,17 +5,23 @@
<span>{{ text "auth:label.following" }}</span>
</div>
<div class="card flex flex-col gap-4">
<div class="card flex flex-wrap gap-4 flex-collapse">
<!-- prettier-ignore -->
{% for item in list %}
<div class="card-nest">
<div class="card small">
Since <span class="date">{{ item[0].created }}</span>
</div>
{{ components::user_card(user=item[1]) }}
</div>
{{ components::user_plate(user=item[1], secondary=true) }}
{% endfor %} {{ components::pagination(page=page, items=list|length) }}
</div>
</div>
<style>
.user_plate {
width: calc(50% - 0.5rem);
}
@media screen and (max-width: 900px) {
.user_plate {
width: 100%;
}
}
</style>
{% endblock %}

View file

@ -926,6 +926,11 @@
settings.biography,
"textarea",
],
[
["status", "Status"],
settings.status,
"textarea",
],
[
["warning", "Profile warning"],
settings.warning,

View file

@ -51,6 +51,8 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
let mut user: Option<User> = None;
let mut headers: Option<SocketHeaders> = None;
let channel_id = format!("chats/{community_id}");
// handle incoming messages on socket
let dbc = db.clone();
if let Some(Ok(WsMessage::Text(text))) = stream.next().await {
@ -185,12 +187,13 @@ pub async fn handle_socket(socket: WebSocket, db: DataManager, community_id: Str
});
let dbc = db.clone();
let channel_id_c = channel_id.clone();
let mut redis_task = tokio::spawn(async move {
// forward messages from redis to the socket
let mut pubsub = dbc.2.client.get_async_pubsub().await.unwrap();
pubsub.subscribe(user.id).await.unwrap();
pubsub.subscribe(community_id.clone()).await.unwrap();
pubsub.subscribe(channel_id_c).await.unwrap();
// listen for pubsub messages
let mut pubsub = pubsub.into_on_message();

View file

@ -165,13 +165,13 @@ impl DataManager {
// post event
let mut con = self.2.get_con().await;
if let Err(e) = con.publish::<usize, String, ()>(
if let Err(e) = con.publish::<String, String, ()>(
if channel.community != 0 {
// broadcast to community ws
channel.community
format!("chats/{}", channel.community)
} else {
// broadcast to channel ws
channel.id
format!("chats/{}", channel.id)
},
serde_json::to_string(&SocketMessage {
method: SocketMethod::Message,
@ -224,13 +224,13 @@ impl DataManager {
// post event
let mut con = self.2.get_con().await;
if let Err(e) = con.publish::<usize, String, ()>(
if let Err(e) = con.publish::<String, String, ()>(
if channel.community != 0 {
// broadcast to community ws
channel.community
format!("chats/{}", channel.community)
} else {
// broadcast to channel ws
channel.id
format!("chats/{}", channel.id)
},
serde_json::to_string(&SocketMessage {
method: SocketMethod::Delete,

View file

@ -200,6 +200,9 @@ pub struct UserSettings {
/// If other users that you aren't following can add you to chats.
#[serde(default)]
pub private_chats: bool,
/// The user's status. Shows over connection info.
#[serde(default)]
pub status: String,
}
impl Default for User {