generated from t/malachite
add: message notifications
This commit is contained in:
parent
ca1eca967c
commit
361d3d8e30
9 changed files with 100 additions and 13 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -2998,7 +2998,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tawny"
|
name = "tawny"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"axum",
|
"axum",
|
||||||
|
@ -3015,7 +3015,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tera",
|
"tera",
|
||||||
"tetratto-core 16.0.2",
|
"tetratto-core 16.0.3",
|
||||||
"tetratto-shared",
|
"tetratto-shared",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.9.5",
|
"toml 0.9.5",
|
||||||
|
@ -3098,9 +3098,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tetratto-core"
|
name = "tetratto-core"
|
||||||
version = "16.0.2"
|
version = "16.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "380eed8dec18b0dcda3440d47375a1bacf94e42fdcd93d464e27682d005bf356"
|
checksum = "9e3e81378d7f02a6f7d18bf9ca58e3885c6cb8611ca4d0536c76b320e6e4017a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"base16ct",
|
"base16ct",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tawny"
|
name = "tawny"
|
||||||
version = "1.0.3"
|
version = "1.0.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = ["trisuaso"]
|
authors = ["trisuaso"]
|
||||||
repository = "https://trisua.com/t/tawny"
|
repository = "https://trisua.com/t/tawny"
|
||||||
|
@ -8,7 +8,7 @@ license = "AGPL-3.0-or-later"
|
||||||
homepage = "https://tawny.cc"
|
homepage = "https://tawny.cc"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tetratto-core = "16.0.2"
|
tetratto-core = "16.0.3"
|
||||||
tetratto-shared = "12.0.6"
|
tetratto-shared = "12.0.6"
|
||||||
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.47.1", features = ["macros", "rt-multi-thread"] }
|
||||||
pathbufd = "0.1.4"
|
pathbufd = "0.1.4"
|
||||||
|
|
|
@ -12,6 +12,7 @@ const STATE = {
|
||||||
function create_streamer(chat_id, hook_element) {
|
function create_streamer(chat_id, hook_element) {
|
||||||
STATE.chat_id = chat_id;
|
STATE.chat_id = chat_id;
|
||||||
STATE.stream_element = hook_element.parentElement;
|
STATE.stream_element = hook_element.parentElement;
|
||||||
|
clear_notifications();
|
||||||
|
|
||||||
STATE.observer = new IntersectionObserver(
|
STATE.observer = new IntersectionObserver(
|
||||||
(entries) => {
|
(entries) => {
|
||||||
|
@ -112,6 +113,10 @@ function sock_con() {
|
||||||
|
|
||||||
if (msg.method === "MessageCreate") {
|
if (msg.method === "MessageCreate") {
|
||||||
render_message(msg.body);
|
render_message(msg.body);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
clear_notifications();
|
||||||
|
}, 150);
|
||||||
} else if (msg.method === "MessageDelete") {
|
} else if (msg.method === "MessageDelete") {
|
||||||
if (document.getElementById(`message_${msg.body}`)) {
|
if (document.getElementById(`message_${msg.body}`)) {
|
||||||
document.getElementById(`message_${msg.body}`).remove();
|
document.getElementById(`message_${msg.body}`).remove();
|
||||||
|
@ -420,3 +425,15 @@ function clear_replying_to() {
|
||||||
STATE.replying_to = undefined;
|
STATE.replying_to = undefined;
|
||||||
document.getElementById("replying_to_zone").classList.add("hidden");
|
document.getElementById("replying_to_zone").classList.add("hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clear_notifications() {
|
||||||
|
fetch(`/api/v1/chats/${STATE.chat_id}/notifications`, {
|
||||||
|
method: "DELETE",
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
show_message(res.message, res.ok);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -837,3 +837,22 @@ menu.col {
|
||||||
.message .body p:last-of-type {
|
.message .body p:last-of-type {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message_reply_wrapper .message {
|
||||||
|
opacity: 75%;
|
||||||
|
padding: 0 4px;
|
||||||
|
|
||||||
|
& .body {
|
||||||
|
min-height: unset;
|
||||||
|
height: 26px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
& p {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .avatar {
|
||||||
|
--size: 18px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
|
|
||||||
(text "{% if replying_to -%}")
|
(text "{% if replying_to -%}")
|
||||||
(div
|
(div
|
||||||
("style" "transform: scale(0.8); opacity: 75%; width: 110%")
|
("class" "message_reply_wrapper")
|
||||||
(text "{{ self::message(message=replying_to, hide_actions=true) }}"))
|
(text "{{ self::message(message=replying_to, hide_actions=true) }}"))
|
||||||
(text "{%- endif %}")
|
(text "{%- endif %}")
|
||||||
(text "{%- endmacro %}")
|
(text "{%- endmacro %}")
|
||||||
|
|
|
@ -58,7 +58,9 @@
|
||||||
("class" "card_nest w_full")
|
("class" "card_nest w_full")
|
||||||
("style" "max-width: 25rem")
|
("style" "max-width: 25rem")
|
||||||
(div
|
(div
|
||||||
("class" "card banner"))
|
("class" "card banner")
|
||||||
|
(img
|
||||||
|
("src" "{{ config.service_hosts.buckets }}/banners/{{ profile.id }}")))
|
||||||
(div
|
(div
|
||||||
("class" "card flex flex_col gap_ch")
|
("class" "card flex flex_col gap_ch")
|
||||||
(text "{{ components::avatar(id=profile.id, size=\"160px\") }}")
|
(text "{{ components::avatar(id=profile.id, size=\"160px\") }}")
|
||||||
|
@ -134,12 +136,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile .banner {
|
.profile .banner {
|
||||||
background-image: url(\"{{ config.service_hosts.buckets }}/banners/{{ profile.id }}\") !important;
|
|
||||||
background-repeat: no-repeat !important;
|
|
||||||
background-position: center !important;
|
|
||||||
background-size: cover !important;
|
|
||||||
border-radius: var(--radius) var(--radius) 0 0;
|
border-radius: var(--radius) var(--radius) 0 0;
|
||||||
height: 225px;
|
height: 225px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile .banner img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card_nest .card:nth-child(2) {
|
.card_nest .card:nth-child(2) {
|
||||||
|
|
|
@ -540,3 +540,29 @@ pub async fn remove_pin_request(
|
||||||
Err(e) => Json(e.into()),
|
Err(e) => Json(e.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn clear_chat_notifications(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data.2) {
|
||||||
|
Some(x) => x,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = data
|
||||||
|
.2
|
||||||
|
.delete_all_notifications_by_tag(&user, &id.to_string())
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Json(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "Success".to_string(),
|
||||||
|
payload: (),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use axum_extra::extract::CookieJar;
|
||||||
use axum_image::{encode::save_webp_buffer, extract::JsonMultipart};
|
use axum_image::{encode::save_webp_buffer, extract::JsonMultipart};
|
||||||
use buckets_core::model::{MediaType, MediaUpload};
|
use buckets_core::model::{MediaType, MediaUpload};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tetratto_core::model::{ApiReturn, Error, permissions::FinePermission};
|
use tetratto_core::model::{ApiReturn, Error, auth::Notification, permissions::FinePermission};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CreateMessage {
|
pub struct CreateMessage {
|
||||||
|
@ -121,6 +121,20 @@ pub async fn create_request(
|
||||||
if let Err(e) = data.2.incr_user_missed_messages(member).await {
|
if let Err(e) = data.2.incr_user_missed_messages(member).await {
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut notif = Notification::new(
|
||||||
|
"You've received a new message".to_string(),
|
||||||
|
format!(
|
||||||
|
"[@{}](/api/v1/auth/user/find/{}) has sent you a message in a [chat]({}/chats/{})",
|
||||||
|
user.username, user.id, data.0.0.host, chat.id
|
||||||
|
),
|
||||||
|
member,
|
||||||
|
);
|
||||||
|
|
||||||
|
notif.tag = chat.id.to_string();
|
||||||
|
if let Err(e) = data.2.create_notification(notif).await {
|
||||||
|
return Json(e.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
|
|
@ -38,6 +38,10 @@ pub fn routes() -> Router {
|
||||||
"/chats/{id}/pins/{message}",
|
"/chats/{id}/pins/{message}",
|
||||||
delete(chats::remove_pin_request),
|
delete(chats::remove_pin_request),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/chats/{id}/notifications",
|
||||||
|
delete(chats::clear_chat_notifications),
|
||||||
|
)
|
||||||
// messages
|
// messages
|
||||||
.route("/messages/{id}", post(messages::create_request))
|
.route("/messages/{id}", post(messages::create_request))
|
||||||
.route("/messages/{id}", delete(messages::delete_request))
|
.route("/messages/{id}", delete(messages::delete_request))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue