mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Fix the context menu
This commit is contained in:
@@ -362,3 +362,91 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: $color-dark-on-surface;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
mdui-text-field {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-preview {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: $color-dark-surface-container;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid $color-dark-outline;
|
||||||
|
|
||||||
|
.reply-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.reply-username {
|
||||||
|
font-weight: 600;
|
||||||
|
color: $color-dark-on-surface;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-text {
|
||||||
|
color: $color-dark-on-surface-variant;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu {
|
||||||
|
position: fixed;
|
||||||
|
background: $color-dark-surface;
|
||||||
|
border: 1px solid $color-dark-outline;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
min-width: 160px;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.context-menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: $color-dark-on-surface;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-dark-surface-container;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-symbols {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: $color-dark-on-surface-variant;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ import { useAppState } from "../../state";
|
|||||||
import type { Message as MessageType } from "../../../core/types";
|
import type { Message as MessageType } from "../../../core/types";
|
||||||
import type { UserProfile } from "../../../core/types";
|
import type { UserProfile } from "../../../core/types";
|
||||||
import { UserProfileDialog } from "./UserProfileDialog";
|
import { UserProfileDialog } from "./UserProfileDialog";
|
||||||
|
import { MessageContextMenu, type ContextMenuState } from "./MessageContextMenu";
|
||||||
import { fetchUserProfile } from "../../api/profileApi";
|
import { fetchUserProfile } from "../../api/profileApi";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { delay } from "../../../utils/utils";
|
import { delay } from "../../../utils/utils";
|
||||||
|
import { request } from "../../../websocket";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function ChatMessages() {
|
export function ChatMessages() {
|
||||||
const { messages } = useChat();
|
const { messages } = useChat();
|
||||||
@@ -15,6 +19,13 @@ export function ChatMessages() {
|
|||||||
const [selectedUserProfile, setSelectedUserProfile] = useState<UserProfile | null>(null);
|
const [selectedUserProfile, setSelectedUserProfile] = useState<UserProfile | null>(null);
|
||||||
const [isLoadingProfile, setIsLoadingProfile] = useState(false);
|
const [isLoadingProfile, setIsLoadingProfile] = useState(false);
|
||||||
|
|
||||||
|
// Context menu state
|
||||||
|
const [contextMenu, setContextMenu] = useState<ContextMenuState>({
|
||||||
|
isOpen: false,
|
||||||
|
message: null,
|
||||||
|
position: { x: 0, y: 0 }
|
||||||
|
});
|
||||||
|
|
||||||
const handleProfileClick = async (username: string) => {
|
const handleProfileClick = async (username: string) => {
|
||||||
if (!user.authToken) return;
|
if (!user.authToken) return;
|
||||||
|
|
||||||
@@ -34,13 +45,94 @@ export function ChatMessages() {
|
|||||||
|
|
||||||
const handleContextMenu = (e: React.MouseEvent, message: MessageType) => {
|
const handleContextMenu = (e: React.MouseEvent, message: MessageType) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// TODO: Show context menu
|
console.log("Context menu triggered for message:", message.id, "at position:", e.clientX, e.clientY);
|
||||||
console.log("Show context menu for message:", message.id);
|
setContextMenu({
|
||||||
|
isOpen: true,
|
||||||
|
message,
|
||||||
|
position: { x: e.clientX, y: e.clientY }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleContextMenuClose = () => {
|
||||||
|
setContextMenu({
|
||||||
|
isOpen: false,
|
||||||
|
message: null,
|
||||||
|
position: { x: 0, y: 0 }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = async (message: MessageType) => {
|
||||||
|
// This will be called when the edit dialog is saved
|
||||||
|
if (!user.authToken) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request({
|
||||||
|
type: "editMessage",
|
||||||
|
data: {
|
||||||
|
message_id: message.id,
|
||||||
|
content: message.content // This should be updated content from the dialog
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
scheme: "Bearer",
|
||||||
|
credentials: user.authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to edit message:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReply = async (message: MessageType) => {
|
||||||
|
// This will be called when the reply dialog is sent
|
||||||
|
if (!user.authToken) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request({
|
||||||
|
type: "replyMessage",
|
||||||
|
data: {
|
||||||
|
content: message.content, // This should be the reply content from the dialog
|
||||||
|
reply_to_id: message.id
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
scheme: "Bearer",
|
||||||
|
credentials: user.authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send reply:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async (message: MessageType) => {
|
||||||
|
if (!user.authToken) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await request({
|
||||||
|
type: "deleteMessage",
|
||||||
|
data: { message_id: message.id },
|
||||||
|
credentials: {
|
||||||
|
scheme: "Bearer",
|
||||||
|
credentials: user.authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete message:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close context menu when clicking outside
|
||||||
|
const handleClickOutside = (e: React.MouseEvent) => {
|
||||||
|
if (contextMenu.isOpen) {
|
||||||
|
// Only close if clicking on the chat messages container, not on the context menu itself
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
handleContextMenuClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="chat-messages" id="chat-messages">
|
<div className="chat-messages" id="chat-messages" onClick={handleClickOutside}>
|
||||||
{messages.map((message) => (
|
{messages.map((message) => (
|
||||||
<Message
|
<Message
|
||||||
key={message.id}
|
key={message.id}
|
||||||
@@ -64,6 +156,19 @@ export function ChatMessages() {
|
|||||||
}}
|
}}
|
||||||
userProfile={selectedUserProfile}
|
userProfile={selectedUserProfile}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Context Menu */}
|
||||||
|
{contextMenu.isOpen && contextMenu.message && (
|
||||||
|
<MessageContextMenu
|
||||||
|
message={contextMenu.message}
|
||||||
|
isAuthor={contextMenu.message.username === user.currentUser?.username}
|
||||||
|
onEdit={handleEdit}
|
||||||
|
onReply={handleReply}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
position={contextMenu.position}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,54 @@
|
|||||||
export function EditMessageDialog() {
|
import { useState, useEffect } from "react";
|
||||||
|
import type { Message } from "../../../core/types";
|
||||||
|
import { MaterialDialog } from "../Dialog";
|
||||||
|
|
||||||
|
interface EditMessageDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (value: boolean) => void;
|
||||||
|
message: Message | null;
|
||||||
|
onSave: (messageId: number, newContent: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditMessageDialog({ isOpen, onOpenChange, message, onSave }: EditMessageDialogProps) {
|
||||||
|
const [editContent, setEditContent] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (message) {
|
||||||
|
setEditContent(message.content);
|
||||||
|
}
|
||||||
|
}, [message]);
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (message && editContent.trim()) {
|
||||||
|
onSave(message.id, editContent.trim());
|
||||||
|
onOpenChange(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
setEditContent("");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!message) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mdui-dialog id="edit-message-dialog" close-on-overlay-click close-on-esc>
|
<MaterialDialog open={isOpen} onOpenChange={onOpenChange} close-on-overlay-click close-on-esc>
|
||||||
<div className="dialog-content">
|
<div className="dialog-content">
|
||||||
<h3>Edit Message</h3>
|
<h3>Edit Message</h3>
|
||||||
<mdui-text-field
|
<mdui-text-field
|
||||||
id="edit-message-input"
|
value={editContent}
|
||||||
|
onInput={(e) => setEditContent((e.target as HTMLInputElement).value)}
|
||||||
label="Edit Message"
|
label="Edit Message"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
placeholder="Edit your message..."
|
placeholder="Edit your message..."
|
||||||
maxlength={1000}>
|
maxlength={1000}>
|
||||||
</mdui-text-field>
|
</mdui-text-field>
|
||||||
<div className="dialog-actions">
|
<div className="dialog-actions">
|
||||||
<mdui-button id="edit-cancel" variant="outlined">Cancel</mdui-button>
|
<mdui-button onClick={handleCancel} variant="outlined">Cancel</mdui-button>
|
||||||
<mdui-button id="edit-save">Save</mdui-button>
|
<mdui-button onClick={handleSave}>Save</mdui-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mdui-dialog>
|
</MaterialDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,18 @@ interface MessageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLoadingProfile = false }: MessageProps) {
|
export function Message({ message, isAuthor, onProfileClick, onContextMenu, isLoadingProfile = false }: MessageProps) {
|
||||||
|
const handleContextMenu = (e: React.MouseEvent) => {
|
||||||
|
console.log("Message context menu event triggered for message:", message.id);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
onContextMenu(e, message);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`message ${isAuthor ? "sent" : "received"}`}
|
className={`message ${isAuthor ? "sent" : "received"}`}
|
||||||
data-id={message.id}
|
data-id={message.id}
|
||||||
onContextMenu={(e) => onContextMenu(e, message)}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<div className="message-inner">
|
<div className="message-inner">
|
||||||
{/* Add profile picture for received messages */}
|
{/* Add profile picture for received messages */}
|
||||||
|
|||||||
@@ -1,18 +1,119 @@
|
|||||||
export function MessageContextMenu() {
|
import { useState } from "react";
|
||||||
|
import type { Message } from "../../../core/types";
|
||||||
|
import { EditMessageDialog } from "./EditMessageDialog";
|
||||||
|
import { ReplyMessageDialog } from "./ReplyMessageDialog";
|
||||||
|
|
||||||
|
interface MessageContextMenuProps {
|
||||||
|
message: Message;
|
||||||
|
isAuthor: boolean;
|
||||||
|
onEdit: (message: Message) => void;
|
||||||
|
onReply: (message: Message) => void;
|
||||||
|
onDelete: (message: Message) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContextMenuState {
|
||||||
|
isOpen: boolean;
|
||||||
|
message: Message | null;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MessageContextMenu({
|
||||||
|
message,
|
||||||
|
isAuthor,
|
||||||
|
onEdit,
|
||||||
|
onReply,
|
||||||
|
onDelete,
|
||||||
|
onClose,
|
||||||
|
position
|
||||||
|
}: MessageContextMenuProps) {
|
||||||
|
console.log("MessageContextMenu rendered with position:", position, "message:", message.id);
|
||||||
|
|
||||||
|
// Internal state for dialogs
|
||||||
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
|
const [replyDialogOpen, setReplyDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleAction = (action: string) => {
|
||||||
|
console.log("Context menu action triggered:", action);
|
||||||
|
switch (action) {
|
||||||
|
case "reply":
|
||||||
|
setReplyDialogOpen(true);
|
||||||
|
break;
|
||||||
|
case "edit":
|
||||||
|
if (isAuthor) {
|
||||||
|
setEditDialogOpen(true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
if (isAuthor) {
|
||||||
|
onDelete(message);
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditSave = (messageId: number, newContent: string) => {
|
||||||
|
// Create a temporary message object with the updated content
|
||||||
|
const updatedMessage = { ...message, content: newContent };
|
||||||
|
onEdit(updatedMessage);
|
||||||
|
setEditDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendReply = (content: string, replyToId: number) => {
|
||||||
|
// Create a temporary message object with the reply content
|
||||||
|
const replyMessage = { ...message, content, id: replyToId };
|
||||||
|
onReply(replyMessage);
|
||||||
|
setReplyDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="message-context-menu" className="context-menu">
|
<>
|
||||||
<div className="context-menu-item" data-action="reply">
|
<div
|
||||||
|
className="context-menu"
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
display: "block",
|
||||||
|
top: position.y,
|
||||||
|
left: position.x,
|
||||||
|
zIndex: 1000
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="context-menu-item" onClick={() => handleAction("reply")}>
|
||||||
<span className="material-symbols">reply</span>
|
<span className="material-symbols">reply</span>
|
||||||
Reply
|
Reply
|
||||||
</div>
|
</div>
|
||||||
<div className="context-menu-item" data-action="edit">
|
{isAuthor && (
|
||||||
|
<>
|
||||||
|
<div className="context-menu-item" onClick={() => handleAction("edit")}>
|
||||||
<span className="material-symbols">edit</span>
|
<span className="material-symbols">edit</span>
|
||||||
Edit
|
Edit
|
||||||
</div>
|
</div>
|
||||||
<div className="context-menu-item" data-action="delete">
|
<div className="context-menu-item" onClick={() => handleAction("delete")}>
|
||||||
<span className="material-symbols">delete</span>
|
<span className="material-symbols">delete</span>
|
||||||
Delete
|
Delete
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Edit Dialog */}
|
||||||
|
<EditMessageDialog
|
||||||
|
isOpen={editDialogOpen}
|
||||||
|
onOpenChange={setEditDialogOpen}
|
||||||
|
message={message}
|
||||||
|
onSave={handleEditSave}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Reply Dialog */}
|
||||||
|
<ReplyMessageDialog
|
||||||
|
isOpen={replyDialogOpen}
|
||||||
|
onOpenChange={setReplyDialogOpen}
|
||||||
|
replyToMessage={message}
|
||||||
|
onSendReply={handleSendReply}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,60 @@
|
|||||||
export function ReplyMessageDialog() {
|
import { useState, useEffect } from "react";
|
||||||
|
import type { Message } from "../../../core/types";
|
||||||
|
import { MaterialDialog } from "../Dialog";
|
||||||
|
|
||||||
|
interface ReplyMessageDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onOpenChange: (value: boolean) => void;
|
||||||
|
replyToMessage: Message | null;
|
||||||
|
onSendReply: (content: string, replyToId: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ReplyMessageDialog({ isOpen, onOpenChange, replyToMessage, onSendReply }: ReplyMessageDialogProps) {
|
||||||
|
const [replyContent, setReplyContent] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (replyToMessage) {
|
||||||
|
setReplyContent("");
|
||||||
|
}
|
||||||
|
}, [replyToMessage]);
|
||||||
|
|
||||||
|
const handleSendReply = () => {
|
||||||
|
if (replyToMessage && replyContent.trim()) {
|
||||||
|
onSendReply(replyContent.trim(), replyToMessage.id);
|
||||||
|
onOpenChange(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
onOpenChange(false);
|
||||||
|
setReplyContent("");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!replyToMessage) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mdui-dialog id="reply-message-dialog" close-on-overlay-click close-on-esc>
|
<MaterialDialog open={isOpen} onOpenChange={onOpenChange} close-on-overlay-click close-on-esc>
|
||||||
<div className="dialog-content">
|
<div className="dialog-content">
|
||||||
<h3>Reply to Message</h3>
|
<h3>Reply to Message</h3>
|
||||||
<div className="reply-preview" id="reply-preview"></div>
|
<div className="reply-preview">
|
||||||
|
<div className="reply-content">
|
||||||
|
<span className="reply-username">{replyToMessage.username}</span>
|
||||||
|
<span className="reply-text">{replyToMessage.content}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<mdui-text-field
|
<mdui-text-field
|
||||||
id="reply-message-input"
|
value={replyContent}
|
||||||
|
onInput={(e) => setReplyContent((e.target as HTMLInputElement).value)}
|
||||||
label="Reply"
|
label="Reply"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
placeholder="Type your reply..."
|
placeholder="Type your reply..."
|
||||||
maxlength={1000}>
|
maxlength={1000}>
|
||||||
</mdui-text-field>
|
</mdui-text-field>
|
||||||
<div className="dialog-actions">
|
<div className="dialog-actions">
|
||||||
<mdui-button id="reply-cancel" variant="outlined">Cancel</mdui-button>
|
<mdui-button onClick={handleCancel} variant="outlined">Cancel</mdui-button>
|
||||||
<mdui-button id="reply-send">Send Reply</mdui-button>
|
<mdui-button onClick={handleSendReply}>Send Reply</mdui-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mdui-dialog>
|
</MaterialDialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import { ChatInterface } from "../components/chat/ChatInterface";
|
import { ChatInterface } from "../components/chat/ChatInterface";
|
||||||
import { CropperDialog } from "../components/profile/CropperDialog";
|
import { CropperDialog } from "../components/profile/CropperDialog";
|
||||||
import { MessageContextMenu } from "../components/chat/MessageContextMenu";
|
|
||||||
import { EditMessageDialog } from "../components/chat/EditMessageDialog";
|
|
||||||
import { ReplyMessageDialog } from "../components/chat/ReplyMessageDialog";
|
|
||||||
|
|
||||||
export default function ChatScreen() {
|
export default function ChatScreen() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatInterface />
|
<ChatInterface />
|
||||||
<CropperDialog />
|
<CropperDialog />
|
||||||
<MessageContextMenu />
|
|
||||||
<EditMessageDialog />
|
|
||||||
<ReplyMessageDialog />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user