From b10a472ed7e706786f47fce09d49a14afa83f2f5 Mon Sep 17 00:00:00 2001 From: Lukian Date: Sun, 11 May 2025 21:10:46 +0200 Subject: [PATCH] add: added replies to messages --- back/api/channels.js | 75 ++-- back/api/lastmessages.js | 1 + back/api/messages.js | 156 +++++++ back/api/users.js | 1 + back/libs/mysql.js | 495 +++++++++++----------- front/src/components/MessageComponent.tsx | 157 ++++++- front/src/pages/ChannelPage.tsx | 9 - front/src/pages/Home.tsx | 1 - front/src/styles/MessageComponent.css | 16 +- front/src/types.tsx | 2 + 10 files changed, 607 insertions(+), 306 deletions(-) create mode 100644 back/api/messages.js diff --git a/back/api/channels.js b/back/api/channels.js index 803534a..ebafd8c 100644 --- a/back/api/channels.js +++ b/back/api/channels.js @@ -4,17 +4,18 @@ const { getChannels, getChannel, addChannel, - getMessages, getMessage, + getMessages, addMessage, deleteMessage, addMention, getMentions, getUserByUsername, - deleMentions, - deleteChannelMentions, deleteChannelMessages, - deleteChannel + deleteChannel, + setHasReplies, + addReply, + getMessageReplies } = require('../libs/mysql'); const rateLimit = require("express-rate-limit"); const slowDown = require("express-slow-down"); @@ -70,7 +71,6 @@ router.post('/:name/purge', async (req, res) => { return res.status(401).send({ error: 'Unauthorized' }); } - await deleteChannelMentions(connection, channel[0].id); await deleteChannelMessages(connection, channel[0].id); connection.end(); @@ -111,6 +111,23 @@ router.post('/:name/delete', async (req, res) => { res.send({ message: 'Channel deleted' }); }); +async function getReplies(message, connection) { + const replies = await getMessageReplies(connection, message.id); + for (const reply of replies) { + if (reply.has_replies) { + const subReplies = await getReplies(reply, connection); + reply.replies = subReplies; + } + if (reply.content.includes('@')) { + const mentions = await getMentions(connection, reply.id); + reply.mentions = mentions; + } else { + reply.mentions = []; + } + } + return replies; +} + router.get('/:name/messages', async (req, res) => { const name = req.params.name; const connection = await getConnection(); @@ -128,6 +145,14 @@ router.get('/:name/messages', async (req, res) => { } else { message.mentions = []; } + + if (message.has_replies) { + const replies = await getReplies(message, connection); + message.replies = replies; + } + else { + message.replies = []; + } } connection.end(); @@ -175,46 +200,6 @@ router.post('/:name/messages/send', speedLimiter, limiter, checkAuth, async (req res.send({ message: 'Message sent' }); }); -router.post('/:name/messages/delete', checkAuth, async (req, res) => { - const { message_id } = req.body; - const name = req.params.name; - const user = req.user; - - if (!message_id) { - return res.status(400).send({ error: 'Missing message_id' }); - } - - const connection = await getConnection(); - - const message = await getMessage(connection, message_id); - if (!message[0]) { - connection.end(); - return res.status(400).send({ error: 'No message found' }); - } - - const channel = await getChannel(connection, name); - if (!channel[0]) { - connection.end(); - return res.status(400).send({ error: 'No channel found' }); - } - - if (user.id !== channel[0].owner_id && user.id !== message[0].user_id && user.admin !== 1) { - connection.end(); - return res.status(401).send({ error: 'Unauthorized' }); - } - - await deleteMessage(connection, message_id); - connection.end(); - - req.sockets.emit({ - type: 'delete_message', - channel_id: channel[0].id, - user_id: user.id, - }); - - res.send({ message: 'Message deleted' }); -}); - router.post('/add', speedLimiter, limiter, checkAuth, async (req, res) => { const { name, description } = req.body; const user = req.user; diff --git a/back/api/lastmessages.js b/back/api/lastmessages.js index 8803ec6..27807f2 100644 --- a/back/api/lastmessages.js +++ b/back/api/lastmessages.js @@ -14,6 +14,7 @@ router.get('/', async (req, res) => { } else { message.mentions = []; } + message.replies = []; } connection.end(); diff --git a/back/api/messages.js b/back/api/messages.js new file mode 100644 index 0000000..70bd6d9 --- /dev/null +++ b/back/api/messages.js @@ -0,0 +1,156 @@ +const express = require('express'); +const { + getConnection, + getChannel, + getMessage, + deleteMessage, + addMention, + getMentions, + getUserByUsername, + setHasReplies, + addReply, + getMessageReplies +} = require('../libs/mysql'); +const rateLimit = require("express-rate-limit"); +const slowDown = require("express-slow-down"); +const { checkAuth } = require('../libs/middlewares'); + +const limiter = rateLimit({ + windowMs: 1 * 1000, + max: 2, +}); + +const speedLimiter = slowDown({ + windowMs: 1 * 1000, + delayAfter: 2, + delayMs: () => 5000, +}); + +const router = express.Router(); + +async function getReplies(message, connection) { + const replies = await getMessageReplies(connection, message.id); + for (const reply of replies) { + if (reply.has_replies) { + const subReplies = await getReplies(reply, connection); + reply.replies = subReplies; + } + if (reply.content.includes('@')) { + const mentions = await getMentions(connection, reply.id); + reply.mentions = mentions; + } else { + reply.mentions = []; + } + } + return replies; +} + +router.get('/:message_id', async (req, res) => { + const message_id = req.params.message_id; + const connection = await getConnection(); + const message = await getMessage(connection, message_id); + if (!message[0]) { + connection.end(); + return res.send({ error: 'No message found' }); + } + + if (message[0].content.includes('@')) { + const mentions = await getMentions(connection, message.id); + message[0].mentions = mentions; + } else { + message[0].mentions = []; + } + + if (message[0].has_replies) { + const replies = await getReplies(message[0], connection); + message[0].replies = replies; + } else { + message[0].replies = []; + } + + connection.end(); + res.send(message[0]); +}); + +router.post('/:message_id/reply', speedLimiter, limiter, checkAuth, async (req, res) => { + const { message } = req.body; + const { message_id } = req.params; + const user = req.user; + + if (!message) { + return res.status(400).send({ error: 'Missing parameters' }); + } + + const connection = await getConnection(); + + const originalMessage = await getMessage(connection, message_id); + if (!originalMessage[0]) { + connection.end(); + return res.status(400).send({ error: 'No message found' }); + } + + const channel = await getChannel(connection, originalMessage[0].channel_name); + const sent_message = await addReply(connection, channel[0].id, user.id, message.replace("\"", "'"), message_id); + await setHasReplies(connection, message_id, true); + const new_message_id = sent_message.insertId; + + for (const word of message.split(' ')) { + if (word.startsWith('@')) { + const username = word.substring(1); + const mentionedUser = await getUserByUsername(connection, username); + if (mentionedUser[0]) { + await addMention(connection, new_message_id, mentionedUser[0].id); + } + } + } + + connection.end(); + + req.sockets.emit({ + type: 'new_message', + channel_id: channel[0].id, + user_id: user.id, + }); + + res.send({ message: 'Reply sent' }); +}); + +router.post('/:message_id/delete', checkAuth, async (req, res) => { + const { message_id } = req.params; + const user = req.user; + + const connection = await getConnection(); + + const message = await getMessage(connection, message_id); + if (!message[0]) { + connection.end(); + return res.status(400).send({ error: 'No message found' }); + } + + const channel = await getChannel(connection, message[0].channel_name); + + if (user.id !== channel[0].owner_id && user.id !== message[0].user_id && user.admin !== 1) { + connection.end(); + return res.status(401).send({ error: 'Unauthorized' }); + } + + if (message[0].reply_to_id) { + const replies = await getMessageReplies(connection, message[0].reply_to_id); + if (replies.length === 1) { + await setHasReplies(connection, replyToId, false); + } + } + + await deleteMessage(connection, message_id); + connection.end(); + + req.sockets.emit({ + type: 'delete_message', + channel_id: channel[0].id, + user_id: user.id, + }); + + res.send({ message: 'Message deleted' }); +}); + +module.exports = router; \ No newline at end of file diff --git a/back/api/users.js b/back/api/users.js index 457653e..f1ed9fd 100644 --- a/back/api/users.js +++ b/back/api/users.js @@ -37,6 +37,7 @@ router.get('/:username/lastmessages', async (req, res) => { } else { message.mentions = []; } + message.replies = []; } connection.end(); diff --git a/back/libs/mysql.js b/back/libs/mysql.js index 1fedcdf..ef277e9 100644 --- a/back/libs/mysql.js +++ b/back/libs/mysql.js @@ -9,6 +9,41 @@ function getConnection() { }); } +// +---------------------------+ +// | Users | +// +---------------------------+ + +function addUser(connection, username, password) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO users (username, password) VALUES (?, ?)`, + [username, password], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function deleteUser(connection, id) { + return new Promise((resolve, reject) => { + connection.query( + `DELETE FROM users WHERE id = ?`, + [id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + + function getUsers(connection) { return new Promise((resolve, reject) => { connection.query( @@ -27,22 +62,7 @@ function getUser(connection, id) { return new Promise((resolve, reject) => { connection.query( `SELECT * FROM users WHERE id = ?`, - [id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function searchUser(connection, search) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT * FROM users WHERE username LIKE ? LIMIT 5`, - [`%${search}%`], // Use parameterized query + [id], (error, result) => { if (error) { reject(new Error(error)); @@ -57,7 +77,7 @@ function getUserByUsername(connection, username) { return new Promise((resolve, reject) => { connection.query( `SELECT * FROM users WHERE username = ?`, - [username], // Use parameterized query + [username], (error, result) => { if (error) { reject(new Error(error)); @@ -68,11 +88,11 @@ function getUserByUsername(connection, username) { }); } -function addUser(connection, username, password) { +function searchUser(connection, search) { return new Promise((resolve, reject) => { connection.query( - `INSERT INTO users (username, password) VALUES (?, ?)`, - [username, password], // Use parameterized query + `SELECT * FROM users WHERE username LIKE ? LIMIT 5`, + [`%${search}%`], (error, result) => { if (error) { reject(new Error(error)); @@ -83,46 +103,11 @@ function addUser(connection, username, password) { }); } -function getUserLastMessages(connection, username) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name - FROM messages - JOIN users ON messages.user_id = users.id - JOIN channels ON messages.channel_id = channels.id - WHERE username = ? - ORDER BY date DESC LIMIT 5`, - [username], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function deleteUser(connection, id) { - return new Promise((resolve, reject) => { - connection.query( - `DELETE FROM users WHERE id = ?`, - [id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -}; - function setUserPfp(connection, id, pfp) { return new Promise((resolve, reject) => { connection.query( `UPDATE users SET pfp = ? WHERE id = ?`, - [pfp, id], // Use parameterized query + [pfp, id], (error, result) => { if (error) { reject(new Error(error)); @@ -137,7 +122,7 @@ function setUserUsername(connection, id, username) { return new Promise((resolve, reject) => { connection.query( `UPDATE users SET username = ? WHERE id = ?`, - [username, id], // Use parameterized query + [username, id], (error, result) => { if (error) { reject(new Error(error)); @@ -152,7 +137,41 @@ function setUserPassword(connection, id, password) { return new Promise((resolve, reject) => { connection.query( `UPDATE users SET password = ? WHERE id = ?`, - [password, id], // Use parameterized query + [password, id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +// +---------------------------+ +// | Channels | +// +---------------------------+ + +function addChannel(connection, name, description, owner_id) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO channels (name, description, owner_id) VALUES (?, ?, ?)`, + [name, description, owner_id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function deleteChannel(connection, channel_id) { + return new Promise((resolve, reject) => { + connection.query( + `DELETE FROM channels WHERE id = ?`, + [channel_id], (error, result) => { if (error) { reject(new Error(error)); @@ -177,6 +196,24 @@ function getChannels(connection) { }); } +function getChannel(connection, name) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT channels.id, name, description, owner_id, username AS owner_username + FROM channels + JOIN users ON channels.owner_id = users.id + WHERE name = ?`, + [name], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + function getActiveChannels(connection) { return new Promise((resolve, reject) => { connection.query( @@ -223,7 +260,7 @@ function searchChannels(connection, search) { JOIN users ON channels.owner_id = users.id WHERE name LIKE ? LIMIT 5`, - [`%${search}%`], // Use parameterized query + [`%${search}%`], (error, result) => { if (error) { reject(new Error(error)); @@ -234,116 +271,30 @@ function searchChannels(connection, search) { }); } -function getChannel(connection, name) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT channels.id, name, description, owner_id, username AS owner_username - FROM channels - JOIN users ON channels.owner_id = users.id - WHERE name = ?`, - [name], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function addChannel(connection, name, description, owner_id) { - return new Promise((resolve, reject) => { - connection.query( - `INSERT INTO channels (name, description, owner_id) VALUES (?, ?, ?)`, - [name, description, owner_id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function deleteChannel(connection, channel_id) { - return new Promise((resolve, reject) => { - connection.query( - `DELETE FROM channels WHERE id = ?`, - [channel_id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function getMessages(connection, channel_id) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name - FROM messages - JOIN users ON messages.user_id = users.id - JOIN channels ON messages.channel_id = channels.id - WHERE channel_id = ? - ORDER BY date DESC`, - [channel_id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function getLastMessages(connection) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name - FROM messages - JOIN users ON messages.user_id = users.id - JOIN channels ON messages.channel_id = channels.id - ORDER BY date DESC LIMIT 5`, - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function getMessage(connection, message_id) { - return new Promise((resolve, reject) => { - connection.query( - `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name - FROM messages - JOIN users ON messages.user_id = users.id - JOIN channels ON messages.channel_id = channels.id - WHERE messages.id = ?`, - [message_id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} +// +---------------------------+ +// | Messages | +// +---------------------------+ function addMessage(connection, channel_id, user_id, message) { return new Promise((resolve, reject) => { connection.query( `INSERT INTO messages (channel_id, user_id, content, date) VALUES (?, ?, ?, ?)`, - [channel_id, user_id, message, Math.floor(Date.now() / 1000)], // Use parameterized query + [channel_id, user_id, message, Math.floor(Date.now() / 1000)], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function addReply(connection, channel_id, user_id, message, message_id) { + return new Promise((resolve, reject) => { + connection.query( + `INSERT INTO messages (channel_id, user_id, content, date, reply_to_id) VALUES (?, ?, ?, ?, ?)`, + [channel_id, user_id, message, Math.floor(Date.now() / 1000), message_id], (error, result) => { if (error) { reject(new Error(error)); @@ -358,7 +309,7 @@ function deleteMessage(connection, message_id) { return new Promise((resolve, reject) => { connection.query( `DELETE FROM messages WHERE id = ?`, - [message_id], // Use parameterized query + [message_id], (error, result) => { if (error) { reject(new Error(error)); @@ -373,7 +324,7 @@ function deleteChannelMessages(connection, channel_id) { return new Promise((resolve, reject) => { connection.query( `DELETE FROM messages WHERE channel_id = ?`, - [channel_id], // Use parameterized query + [channel_id], (error, result) => { if (error) { reject(new Error(error)); @@ -384,11 +335,16 @@ function deleteChannelMessages(connection, channel_id) { }); } -function deleteUserMessages(connection, user_id) { +function getMessages(connection, channel_id) { return new Promise((resolve, reject) => { connection.query( - `DELETE FROM messages WHERE user_id = ?`, - [user_id], // Use parameterized query + `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies + FROM messages + JOIN users ON messages.user_id = users.id + JOIN channels ON messages.channel_id = channels.id + WHERE channel_id = ? AND reply_to_id IS NULL + ORDER BY date DESC`, + [channel_id], (error, result) => { if (error) { reject(new Error(error)); @@ -399,11 +355,106 @@ function deleteUserMessages(connection, user_id) { }); } +function getMessage(connection, message_id) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies + FROM messages + JOIN users ON messages.user_id = users.id + JOIN channels ON messages.channel_id = channels.id + WHERE messages.id = ?`, + [message_id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function getLastMessages(connection) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies + FROM messages + JOIN users ON messages.user_id = users.id + JOIN channels ON messages.channel_id = channels.id + ORDER BY date DESC LIMIT 5`, + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function getUserLastMessages(connection, username) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies + FROM messages + JOIN users ON messages.user_id = users.id + JOIN channels ON messages.channel_id = channels.id + WHERE username = ? + ORDER BY date DESC LIMIT 5`, + [username], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function getMessageReplies(connection, message_id) { + return new Promise((resolve, reject) => { + connection.query( + `SELECT messages.id, user_id, username, content, date, channels.name AS channel_name, has_replies + FROM messages + JOIN users ON messages.user_id = users.id + JOIN channels ON messages.channel_id = channels.id + WHERE reply_to_id = ?`, + [message_id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +function setHasReplies(connection, message_id, has_replies) { + return new Promise((resolve, reject) => { + connection.query( + `UPDATE messages SET has_replies = ? WHERE id = ?`, + [has_replies, message_id], + (error, result) => { + if (error) { + reject(new Error(error)); + } + resolve(result); + } + ); + }); +} + +// +---------------------------+ +// | Mentions | +// +---------------------------+ + function addMention(connection, message_id, user_id) { return new Promise((resolve, reject) => { connection.query( `INSERT INTO mentions (message_id, user_id) VALUES (?, ?)`, - [message_id, user_id], // Use parameterized query + [message_id, user_id], (error, result) => { if (error) { reject(new Error(error)); @@ -420,7 +471,7 @@ function getMentions(connection, message_id) { `SELECT users.username FROM mentions JOIN users ON mentions.user_id = users.id WHERE message_id = ?`, - [message_id], // Use parameterized query + [message_id], (error, result) => { if (error) { reject(new Error(error)); @@ -431,11 +482,15 @@ function getMentions(connection, message_id) { }); } -function deleMentions(connection, message_id) { +// +---------------------------+ +// | Emojis | +// +---------------------------+ + +function addEmoji(connection, name, file) { return new Promise((resolve, reject) => { connection.query( - `DELETE FROM mentions WHERE message_id = ?`, - [message_id], // Use parameterized query + `INSERT INTO emojis (name, file) VALUES (?, ?)`, + [name, file], (error, result) => { if (error) { reject(new Error(error)); @@ -446,27 +501,11 @@ function deleMentions(connection, message_id) { }); } -function deleteChannelMentions(connection, channel_id) { +function deleteEmoji(connection, id) { return new Promise((resolve, reject) => { connection.query( - `DELETE FROM mentions WHERE message_id IN - (SELECT id FROM messages WHERE channel_id = ?)`, - [channel_id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function deleteUserMentions(connection, user_id) { - return new Promise((resolve, reject) => { - connection.query( - `DELETE FROM mentions WHERE user_id = ?`, - [user_id], // Use parameterized query + `DELETE FROM emojis WHERE id = ?`, + [id], (error, result) => { if (error) { reject(new Error(error)); @@ -491,41 +530,11 @@ function getEmojis(connection) { }); } -function addEmoji(connection, name, file) { - return new Promise((resolve, reject) => { - connection.query( - `INSERT INTO emojis (name, file) VALUES (?, ?)`, - [name, file], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - -function deleteEmoji(connection, id) { - return new Promise((resolve, reject) => { - connection.query( - `DELETE FROM emojis WHERE id = ?`, - [id], // Use parameterized query - (error, result) => { - if (error) { - reject(new Error(error)); - } - resolve(result); - } - ); - }); -} - function getEmoji(connection, id) { return new Promise((resolve, reject) => { connection.query( `SELECT * FROM emojis WHERE id = ?`, - [id], // Use parameterized query + [id], (error, result) => { if (error) { reject(new Error(error)); @@ -540,7 +549,7 @@ function getEmojiByName(connection, name) { return new Promise((resolve, reject) => { connection.query( `SELECT * FROM emojis WHERE name = ?`, - [name], // Use parameterized query + [name], (error, result) => { if (error) { reject(new Error(error)); @@ -555,7 +564,7 @@ function searchEmojis(connection, search) { return new Promise((resolve, reject) => { connection.query( `SELECT * FROM emojis WHERE name LIKE ? LIMIT 5`, - [`%${search}%`], // Use parameterized query + [`%${search}%`], (error, result) => { if (error) { reject(new Error(error)); @@ -567,39 +576,43 @@ function searchEmojis(connection, search) { } module.exports = { - getUsers, getConnection, + + addUser, + deleteUser, + getUsers, getUser, searchUser, getUserByUsername, - addUser, - deleteUser, setUserPfp, setUserUsername, setUserPassword, - getUserLastMessages, + + addChannel, + deleteChannel, getChannels, + getChannel, getActiveChannels, getNewChannels, searchChannels, - getChannel, - addChannel, - deleteChannel, - getMessages, - getLastMessages, - getMessage, + addMessage, + addReply, deleteMessage, deleteChannelMessages, - deleteUserMessages, + getMessages, + getMessage, + getUserLastMessages, + getLastMessages, + getMessageReplies, + setHasReplies, + addMention, getMentions, - deleMentions, - deleteUserMentions, - deleteChannelMentions, - getEmojis, + addEmoji, deleteEmoji, + getEmojis, getEmoji, getEmojiByName, searchEmojis, diff --git a/front/src/components/MessageComponent.tsx b/front/src/components/MessageComponent.tsx index f80702a..74afe8d 100644 --- a/front/src/components/MessageComponent.tsx +++ b/front/src/components/MessageComponent.tsx @@ -1,14 +1,21 @@ import { Link } from "react-router-dom"; -import { Message, User, Channel } from "../types"; +import { Message, User, Channel, Users, Emojis } from "../types"; +import { useState, createRef, useEffect } from "react"; +import axios from "axios"; import "../styles/MessageComponent.css"; -export default function MessageComponent({ message, user, channel, deleteMessage }: { +export default function MessageComponent({ message, user, channel }: { message: Message; user: User | undefined; channel: Channel | undefined; - deleteMessage: ((messageId: number) => void) | undefined; }) { + const [reply, setReply] = useState(false); + const token = localStorage.getItem("token"); + const [replyContent, setReplyContent] = useState(""); + const [searchedUsers, setSearchedUsers] = useState([]); + const [searchedEmojis, setSearchedEmojis] = useState([]); + const ref = createRef(); function getMessageArray(message: Message) { const array = [] @@ -38,6 +45,70 @@ export default function MessageComponent({ message, user, channel, deleteMessage array.push(string) return array.filter((word) => word !== ''); } + + function handleReply(e: React.FormEvent, messageId: number) { + e.preventDefault(); + axios + .post(`/api/messages/${messageId}/reply`, { + token: token, + message: replyContent, + }) + .then(() => { + setReplyContent(""); + setReply(false); + setSearchedUsers([]); + setSearchedEmojis([]); + }) + .catch((err) => { + console.error(err.response.data); + }); + } + + function handleDelete() { + if (!window.confirm("Are you sure you want to delete this message?")) { + return; + } + axios + .post(`/api/messages/${message.id}/delete`, { + token: token, + }) + .catch((err) => { + console.error(err.response.data); + }); + } + + useEffect(() => { + const words = replyContent.toString().split(" "); + const lastWord = words[words.length - 1]; + + if (lastWord && lastWord.startsWith("@")) { + const username = lastWord.slice(1); + axios + .get(`/api/searchuser?search=${username}`) + .then((res) => { + setSearchedUsers(res.data); + }) + .catch((err) => { + console.error(err.response.data); + }); + } else { + setSearchedUsers([]); + } + + if (lastWord && lastWord.startsWith(":")) { + const emojiName = lastWord.slice(1); + axios + .get(`/api/searchemojis?search=${emojiName}`) + .then((res) => { + setSearchedEmojis(res.data); + }) + .catch((err) => { + console.error(err.response.data); + }); + } else { + setSearchedEmojis([]); + } + }, [replyContent]); return (
@@ -70,13 +141,81 @@ export default function MessageComponent({ message, user, channel, deleteMessage Le poisson steve )}
- {channel && deleteMessage ? ( + {channel?.owner_id == user?.id || user?.admin == 1 || user?.username == message.username ? ( user?.id === message.user_id || user?.id === channel?.owner_id || user?.admin === 1) && ( - - ) : ( -

In {message.channel_name}

- ) - } + + ) : ( +

In {message.channel_name}

+ )} + {channel && user && !reply && ( + + )} + {reply && ( +
handleReply(e, message.id)} className="message-form"> + setReplyContent(e.target.value)} + ref={ref} + /> + + {searchedUsers.length > 0 && ( +
+ {searchedUsers.map((user) => ( +
+ {user.username} + +
+ ))} +
+ )} + {searchedEmojis.length > 0 && ( +
+ {searchedEmojis.map((emoji) => ( +
+ {emoji.name} + :{emoji.name}: + +
+ ))} +
+ )} + +
+ )} + {message.has_replies == true && ( +
+ {message.replies.map((reply) => ( + + ))} +
+ )} + {!channel && ( +

in: {message.channel_name}

+ )} ); } \ No newline at end of file diff --git a/front/src/pages/ChannelPage.tsx b/front/src/pages/ChannelPage.tsx index c86c75a..374199c 100644 --- a/front/src/pages/ChannelPage.tsx +++ b/front/src/pages/ChannelPage.tsx @@ -34,14 +34,6 @@ export default function ChannelPage({socket}: {socket: WebSocket}) { }); } - function deleteMessage(message_id: number) { - axios - .post(`/api/channels/${name}/messages/delete`, { token, message_id }) - .catch((err) => { - console.error(err.response); - }); - } - function purgeChannel() { if (!window.confirm(`Are you sure you want to purge ${channel?.name}?`)) { return; @@ -261,7 +253,6 @@ export default function ChannelPage({socket}: {socket: WebSocket}) { message={message} user={user} channel={channel} - deleteMessage={deleteMessage} /> ))} {messages.length > maxMessageToShown && ( diff --git a/front/src/pages/Home.tsx b/front/src/pages/Home.tsx index 39486a2..12f565c 100644 --- a/front/src/pages/Home.tsx +++ b/front/src/pages/Home.tsx @@ -165,7 +165,6 @@ export default function Home({socket}: {socket: WebSocket}) { message={message} user={user} channel={undefined} - deleteMessage={undefined} /> ))} diff --git a/front/src/styles/MessageComponent.css b/front/src/styles/MessageComponent.css index 7f21acc..4a13b58 100644 --- a/front/src/styles/MessageComponent.css +++ b/front/src/styles/MessageComponent.css @@ -1,7 +1,12 @@ .message { - width: 95%; + width: 97%; border: 1px solid #270722; padding: 10px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: start; + gap: 5px; } .message-user-pfp { @@ -37,6 +42,15 @@ max-height: 1em; } +.message-replies { + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + gap: 10px; +} + @media (prefers-color-scheme: dark) { .message { border: 1px solid #a678af; diff --git a/front/src/types.tsx b/front/src/types.tsx index 75641ef..909f7a6 100644 --- a/front/src/types.tsx +++ b/front/src/types.tsx @@ -34,6 +34,8 @@ export type Message = { channel_id: number channel_name: string mentions: Mentions + replies: Messages + has_replies: boolean } export type Messages = Message[]