generated from t/malachite
add: message replies
This commit is contained in:
parent
dfa1abe2d9
commit
ca1eca967c
13 changed files with 113 additions and 22 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2998,7 +2998,7 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||
|
||||
[[package]]
|
||||
name = "tawny"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"axum",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "tawny"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
edition = "2024"
|
||||
authors = ["trisuaso"]
|
||||
repository = "https://trisua.com/t/tawny"
|
||||
|
|
|
@ -5,6 +5,7 @@ const STATE = {
|
|||
is_loading: false,
|
||||
stream_element: null,
|
||||
last_message_time: 0,
|
||||
replying_to: undefined,
|
||||
last_read_receipt_load: 0,
|
||||
};
|
||||
|
||||
|
@ -144,6 +145,7 @@ function create_message(e) {
|
|||
"body",
|
||||
JSON.stringify({
|
||||
content: e.target.content.value,
|
||||
replying_to: STATE.replying_to,
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -154,6 +156,7 @@ function create_message(e) {
|
|||
if (res.ok) {
|
||||
e.target.reset();
|
||||
document.getElementById("images_zone").classList.add("hidden");
|
||||
clear_replying_to();
|
||||
} else {
|
||||
show_message(res.message, res.ok);
|
||||
}
|
||||
|
@ -395,3 +398,25 @@ function remove_file_from_picker(input_id, idx) {
|
|||
// render
|
||||
display_pending_images({ target: input });
|
||||
}
|
||||
|
||||
function reply_to_message(id) {
|
||||
STATE.replying_to = id;
|
||||
document.getElementById("replying_to_zone").classList.remove("hidden");
|
||||
document.getElementById(`message_${id}`).classList.add("card");
|
||||
document.getElementById(`message_${id}`).classList.add("surface");
|
||||
scroll_bottom();
|
||||
}
|
||||
|
||||
function clear_replying_to() {
|
||||
if (STATE.replying_to) {
|
||||
document
|
||||
.getElementById(`message_${STATE.replying_to}`)
|
||||
.classList.remove("card");
|
||||
document
|
||||
.getElementById(`message_${STATE.replying_to}`)
|
||||
.classList.remove("surface");
|
||||
}
|
||||
|
||||
STATE.replying_to = undefined;
|
||||
document.getElementById("replying_to_zone").classList.add("hidden");
|
||||
}
|
||||
|
|
|
@ -31,7 +31,13 @@
|
|||
("id" "messages_stream")
|
||||
(div ("ui_ident" "data_marker")))
|
||||
(div ("id" "read_receipt_zone") ("class" "card") ("style" "min-height: 32.5px; position: sticky; bottom: 0"))
|
||||
(div ("id" "images_zone") ("class" "card hidden flex gap_2 flex_wrap")))
|
||||
(div ("id" "images_zone") ("class" "card hidden flex gap_2 flex_wrap"))
|
||||
(div
|
||||
("id" "replying_to_zone") ("class" "card hidden flex flex_col gap_1")
|
||||
(div
|
||||
("class" "flex items_center gap_ch")
|
||||
(text "{{ icon \"reply\" }} Replying to message"))
|
||||
(button ("class" "button surface red") ("onclick" "clear_replying_to()") (text "cancel"))))
|
||||
(form
|
||||
("class" "card flex flex_row items_center gap_2")
|
||||
("onsubmit" "create_message(event)")
|
||||
|
|
|
@ -41,10 +41,11 @@
|
|||
(text "{%- endif %}")
|
||||
(text "{%- endmacro %}")
|
||||
|
||||
(text "{% macro message(message, is_pinned=false) -%}")
|
||||
(text "{% macro message(message, replying_to=false, is_pinned=false, hide_actions=false) -%}")
|
||||
(div
|
||||
("class" "flex w_full gap_ch message {%- if user.id == message.owner %} justify_right mine {%- endif %}")
|
||||
("id" "message_{{ message.id }}")
|
||||
("id" "{% if not hide_actions -%} message_{{ message.id }} {%- endif %}")
|
||||
(text "{% if not hide_actions -%}")
|
||||
(div
|
||||
("class" "dropdown hidden")
|
||||
(button
|
||||
|
@ -61,6 +62,11 @@
|
|||
(text "pin"))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(button
|
||||
("class" "button surface")
|
||||
("onclick" "reply_to_message('{{ message.id }}')")
|
||||
(text "reply"))
|
||||
|
||||
(text "{% if message.owner == user.id -%}")
|
||||
(button
|
||||
("class" "button surface")
|
||||
|
@ -71,6 +77,7 @@
|
|||
("onclick" "delete_message('{{ message.id }}')")
|
||||
(text "delete"))
|
||||
(text "{%- endif %}")))
|
||||
(text "{%- endif %}")
|
||||
|
||||
(div
|
||||
("class" "body no_p_margin")
|
||||
|
@ -101,6 +108,12 @@
|
|||
("href" "/@{{ message.owner }}?redirect=true")
|
||||
("target" "_blank")
|
||||
(text "{{ self::avatar(id=message.owner) }}")))
|
||||
|
||||
(text "{% if replying_to -%}")
|
||||
(div
|
||||
("style" "transform: scale(0.8); opacity: 75%; width: 110%")
|
||||
(text "{{ self::message(message=replying_to, hide_actions=true) }}"))
|
||||
(text "{%- endif %}")
|
||||
(text "{%- endmacro %}")
|
||||
|
||||
(text "{% macro theme(user, theme_preference) -%} {% if user %} {% if user.settings.theme_hue -%}")
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
(text "{%- import \"components.lisp\" as components -%}")
|
||||
(text "{{ components::message(message=message) }}")
|
||||
(text "{{ components::message(message=message, replying_to=replying_to) }}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(text "{%- import \"components.lisp\" as components -%}")
|
||||
(text "{% for message in messages -%}")
|
||||
(text "{{ components::message(message=message, is_pinned=message.id in pins) }}")
|
||||
(text "{{ components::message(message=message[0], replying_to=message[1], is_pinned=message[0].id in pins) }}")
|
||||
(text "{%- endfor %}")
|
||||
|
||||
(div
|
||||
|
|
|
@ -22,6 +22,7 @@ impl DataManager {
|
|||
chat: get!(x->4(i64)) as usize,
|
||||
content: get!(x->5(String)),
|
||||
uploads: serde_json::from_str(&get!(x->6(String))).unwrap(),
|
||||
replying_to: get!(x->7(i64)) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +92,7 @@ impl DataManager {
|
|||
/// * `data` - a mock [`Message`] object to insert
|
||||
pub async fn create_message(&self, data: Message) -> Result<Message> {
|
||||
// check values
|
||||
if data.content.trim().len() < 2 {
|
||||
if data.content.trim().len() < 2 && data.uploads.is_empty() {
|
||||
return Err(Error::DataTooShort("content".to_string()));
|
||||
} else if data.content.len() > 2048 {
|
||||
return Err(Error::DataTooLong("content".to_string()));
|
||||
|
@ -105,7 +106,7 @@ impl DataManager {
|
|||
|
||||
let res = execute!(
|
||||
&conn,
|
||||
"INSERT INTO t_messages VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
"INSERT INTO t_messages VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
|
||||
params![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -114,6 +115,7 @@ impl DataManager {
|
|||
&(data.chat as i64),
|
||||
&data.content,
|
||||
&serde_json::to_string(&data.uploads).unwrap(),
|
||||
&(data.replying_to as i64)
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS t_messages (
|
|||
owner BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
chat BIGINT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
uploads TEXT NOT NULL
|
||||
uploads TEXT NOT NULL,
|
||||
replying_to BIGINT NOT NULL
|
||||
);
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
-- chats pinned_messages
|
||||
ALTER TABLE t_chats ADD COLUMN IF NOT EXISTS pinned_messages TEXT NOT NULL DEFAULT '[]';
|
||||
|
||||
-- messages replying_to
|
||||
ALTER TABLE t_messages ADD COLUMN IF NOT EXISTS replying_to BIGINT NOT NULL DEFAULT 0;
|
||||
|
|
|
@ -68,6 +68,7 @@ pub struct Message {
|
|||
pub chat: usize,
|
||||
pub content: String,
|
||||
pub uploads: Vec<usize>,
|
||||
pub replying_to: usize,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
|
@ -83,6 +84,7 @@ impl Message {
|
|||
chat,
|
||||
content,
|
||||
uploads,
|
||||
replying_to: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ use tetratto_core::model::{ApiReturn, Error, permissions::FinePermission};
|
|||
#[derive(Deserialize)]
|
||||
pub struct CreateMessage {
|
||||
pub content: String,
|
||||
pub replying_to: Option<String>,
|
||||
}
|
||||
|
||||
const MAXIMUM_UPLOAD_SIZE: usize = 4_194_304; // 4 MiB
|
||||
|
@ -28,7 +29,7 @@ pub async fn create_request(
|
|||
};
|
||||
|
||||
// check fields
|
||||
if req.content.trim().len() < 2 {
|
||||
if req.content.trim().len() < 2 && byte_parts.is_empty() {
|
||||
return Json(Error::DataTooShort("content".to_string()).into());
|
||||
} else if req.content.len() > 2048 {
|
||||
return Json(Error::DataTooLong("content".to_string()).into());
|
||||
|
@ -69,16 +70,29 @@ pub async fn create_request(
|
|||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
}
|
||||
|
||||
// ...
|
||||
let mut msg = Message::new(
|
||||
user.id,
|
||||
chat.id,
|
||||
req.content,
|
||||
uploads.iter().map(|x| x.0.id).collect(),
|
||||
);
|
||||
|
||||
if let Some(replying_to) = req.replying_to {
|
||||
let replying_to: usize = replying_to.parse().unwrap_or(0);
|
||||
|
||||
if replying_to != 0 {
|
||||
let replying_to_msg = match data.get_message_by_id(replying_to).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Json(e.into()),
|
||||
};
|
||||
|
||||
msg.replying_to = replying_to_msg.id;
|
||||
}
|
||||
}
|
||||
|
||||
// create message
|
||||
match data
|
||||
.create_message(Message::new(
|
||||
user.id,
|
||||
chat.id,
|
||||
req.content,
|
||||
uploads.iter().map(|x| x.0.id).collect(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
match data.create_message(msg).await {
|
||||
Ok(x) => {
|
||||
// store uploads
|
||||
for (upload, part) in uploads {
|
||||
|
|
|
@ -104,8 +104,22 @@ pub async fn single_message_request(
|
|||
}
|
||||
};
|
||||
|
||||
let replying_to = if message.replying_to != 0 {
|
||||
match data.get_message_by_id(message.replying_to).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut ctx = default_context(&data.0.0, &build_code, &Some(user));
|
||||
|
||||
ctx.insert("message", &message);
|
||||
ctx.insert("replying_to", &replying_to);
|
||||
|
||||
Ok(Html(tera.render("message.lisp", &ctx).unwrap()))
|
||||
}
|
||||
|
||||
|
@ -164,7 +178,18 @@ pub async fn messages_request(
|
|||
}
|
||||
}
|
||||
|
||||
y.push(z);
|
||||
let replying_to = if z.replying_to != 0 {
|
||||
match data.get_message_by_id(z.replying_to).await {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
return Err(render_error(e, tera, data.0.0.clone(), Some(user)).await);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
y.push((z, replying_to));
|
||||
}
|
||||
|
||||
y
|
||||
|
@ -179,7 +204,7 @@ pub async fn messages_request(
|
|||
ctx.insert(
|
||||
"last_message_time",
|
||||
&match messages.last() {
|
||||
Some(x) => x.created,
|
||||
Some(x) => x.0.created,
|
||||
None => 0,
|
||||
},
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue