feat: editing of messages
This commit is contained in:
parent
4d87f86ff7
commit
f27adc3360
7 changed files with 115 additions and 11 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { ChatMessage } from "../../../shared";
|
||||
|
||||
function ChatMessageDisplay({
|
||||
|
|
@ -10,14 +10,28 @@ function ChatMessageDisplay({
|
|||
message: ChatMessage;
|
||||
userId: string | undefined;
|
||||
onDelete: (messageId: string) => void;
|
||||
onEdit: (messageId: string) => void;
|
||||
onEdit: (messageId: string, newContent: string) => void;
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editContent, setEditContent] = useState(message.content);
|
||||
const isOwn = userId === message.author.userId;
|
||||
const timestamp = new Date(message.timestamp).toLocaleTimeString([], {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
if (editContent.trim() !== message.content) {
|
||||
onEdit(message.id, editContent);
|
||||
}
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setEditContent(message.content);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={message.id} className={`mb-4 ${isOwn ? "ml-auto" : "mr-auto"}`}>
|
||||
<div
|
||||
|
|
@ -33,19 +47,47 @@ function ChatMessageDisplay({
|
|||
)}
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`max-w-[80%] p-3 rounded-2xl shadow-sm bg-gray-200
|
||||
${isOwn ? "ml-auto rounded-tr-none" : "mr-auto rounded-tl-none"}`}
|
||||
>
|
||||
{message.content}
|
||||
</div>
|
||||
{isOwn && (
|
||||
{isEditing ? (
|
||||
<div className={`max-w-[80%] ${isOwn ? "ml-auto" : "mr-auto"}`}>
|
||||
<textarea
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
className="w-full p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
rows={2}
|
||||
autoFocus
|
||||
/>
|
||||
<div
|
||||
className={`flex gap-2 mt-2 ${isOwn ? "justify-end" : "justify-start"}`}
|
||||
>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
className="px-3 py-1 text-sm text-white bg-blue-600 rounded hover:bg-blue-700"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancel}
|
||||
className="px-3 py-1 text-sm text-gray-600 bg-gray-200 rounded hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`max-w-[80%] p-3 rounded-2xl shadow-sm bg-gray-200
|
||||
${isOwn ? "ml-auto rounded-tr-none" : "mr-auto rounded-tl-none"}`}
|
||||
>
|
||||
{message.content}
|
||||
</div>
|
||||
)}
|
||||
{isOwn && !isEditing && (
|
||||
<div
|
||||
className={`flex gap-2 mt-0.5 text-[10px]
|
||||
${isOwn ? "justify-end" : "justify-start"}`}
|
||||
>
|
||||
<button
|
||||
onClick={() => onEdit?.(message.id)}
|
||||
onClick={() => setIsEditing(true)}
|
||||
className="text-blue-600 hover:text-blue-800 active:text-blue-900"
|
||||
>
|
||||
Edit
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
|
|||
state,
|
||||
dispatch,
|
||||
actions: {
|
||||
editMessage: (messageId: string, newContent: string) =>
|
||||
dispatch({ type: "EDIT_MESSAGE", payload: { messageId, newContent } }),
|
||||
deleteMessage: (messageId: string) =>
|
||||
dispatch({ type: "DELETE_MESSAGE", payload: messageId }),
|
||||
addMessage: (message: Message) =>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
import {
|
||||
ClientDeleteMessage,
|
||||
ClientEditMessage,
|
||||
ClientMessage,
|
||||
ServerMessage,
|
||||
} from "../../../shared";
|
||||
|
|
@ -31,7 +32,17 @@ function ChatMessages({ sendMessage }: MessageDisplayProps) {
|
|||
sendMessage(deleteMessage);
|
||||
};
|
||||
|
||||
const onEdit = (messageId: string) => {};
|
||||
const onEdit = (messageId: string, newContent: string) => {
|
||||
const editMessage: ClientEditMessage = {
|
||||
type: "EDIT_MESSAGE",
|
||||
payload: {
|
||||
messageId,
|
||||
content: newContent,
|
||||
},
|
||||
};
|
||||
|
||||
sendMessage(editMessage);
|
||||
};
|
||||
|
||||
// scroll to bottom when messages change
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export type ChatState = {
|
|||
};
|
||||
|
||||
export type ChatAction =
|
||||
| { type: "EDIT_MESSAGE"; payload: { messageId: string; newContent: string } }
|
||||
| { type: "ADD_MESSAGE"; payload: Message }
|
||||
| { type: "DELETE_MESSAGE"; payload: string }
|
||||
| { type: "SET_USER"; payload: User }
|
||||
|
|
@ -25,6 +26,7 @@ export type ChatAction =
|
|||
| { type: "CLEAR_MESSAGES" };
|
||||
|
||||
export type ChatActions = {
|
||||
editMessage: (messageId: string, newContent: string) => void;
|
||||
addMessage: (message: Message) => void;
|
||||
deleteMessage: (messageId: string) => void;
|
||||
setUser: (user: User) => void;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,13 @@ export function useWebSocket(roomId: string) {
|
|||
actions.deleteMessage(message.payload.messageId);
|
||||
break;
|
||||
|
||||
case "MESSAGE_EDITED":
|
||||
actions.editMessage(
|
||||
message.payload.messageId,
|
||||
message.payload.content,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error("Unknown message type", message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,25 @@ import { ChatAction, ChatState } from "../contexts/ChatContext";
|
|||
|
||||
export function chatReducer(state: ChatState, action: ChatAction): ChatState {
|
||||
switch (action.type) {
|
||||
case "EDIT_MESSAGE":
|
||||
return {
|
||||
...state,
|
||||
messages: state.messages.map((message) => {
|
||||
if ("type" in message && message.type === "CHAT_MESSAGE") {
|
||||
if (message.payload.id === action.payload.messageId) {
|
||||
return {
|
||||
...message,
|
||||
payload: {
|
||||
...message.payload,
|
||||
content: action.payload.newContent,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}),
|
||||
};
|
||||
|
||||
case "DELETE_MESSAGE":
|
||||
return {
|
||||
...state,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue