From e88f02bd4089fbb7cdc79ec65e01bc777b6f3865 Mon Sep 17 00:00:00 2001 From: Vitalik Date: Sun, 9 Oct 2022 20:07:37 +0300 Subject: [PATCH] FEATURE | Edite message --- .eslintrc.js | 1 + src/api/chats-messages/request.interfaces.ts | 4 +++ src/api/chats-messages/requests.ts | 5 +++ src/containers/Chats/chats.screen.tsx | 25 +++++++------ .../chat-message-menu-options.config.ts | 10 ++++++ .../Chats/enums/chat-message-action.enum.ts | 1 + .../Chats/hooks/use-chat-messages.hook.ts | 25 +++++++++++-- .../hooks/use-create-text-message.hook.ts | 22 +++++++++--- .../Chats/plugins/chat-bar.component.tsx | 22 ++++++++++-- .../Chats/plugins/chat-messages.component.tsx | 3 -- .../plugins/edted-bar-section.component.tsx | 36 +++++++++++++++++++ src/services/domain/chat-messages.service.ts | 10 ++++++ src/services/system/real-time.service.ts | 9 ++--- src/shared/events/index.ts | 6 ++++ 14 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 src/containers/Chats/plugins/edted-bar-section.component.tsx diff --git a/.eslintrc.js b/.eslintrc.js index b74934b..3bcac78 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,5 +24,6 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-this-alias": "off", "@typescript-eslint/no-empty-interface": "off", + "@typescript-eslint/ban-types": "off", }, }; diff --git a/src/api/chats-messages/request.interfaces.ts b/src/api/chats-messages/request.interfaces.ts index b5bd207..8a5ffa9 100644 --- a/src/api/chats-messages/request.interfaces.ts +++ b/src/api/chats-messages/request.interfaces.ts @@ -11,6 +11,10 @@ export interface ISendTextMessage { mentionsMessage?: string; replyToId?: number; } +export interface IEditTextMessage { + newMessage: string; + messageId: number; +} export interface ISendStickerMessage { chatId: number; diff --git a/src/api/chats-messages/requests.ts b/src/api/chats-messages/requests.ts index 5cfceea..a03c262 100644 --- a/src/api/chats-messages/requests.ts +++ b/src/api/chats-messages/requests.ts @@ -16,6 +16,10 @@ export const sendTextMessageReq = ( return http.post(`app/chats-messages/text-message`, data); }; +export const editTextMessageReq = (data: Req.IEditTextMessage) => { + return http.patch("chats-messages/text-message", data); +}; + export const sendStickerMessageReq = (data: Req.ISendStickerMessage) => { return http.post(`app/chats-messages/sticker-message`, data); }; @@ -70,4 +74,5 @@ export const chatMessagesApi = { unpinMessageReq, clearChatReq, sendStickerMessageReq, + editTextMessageReq, }; diff --git a/src/containers/Chats/chats.screen.tsx b/src/containers/Chats/chats.screen.tsx index e98907e..7048473 100644 --- a/src/containers/Chats/chats.screen.tsx +++ b/src/containers/Chats/chats.screen.tsx @@ -74,16 +74,11 @@ const ChatsPage: FC = () => { loadMore, loadPage, setSearchVal, - // handleChatAction, isLoading, onPin, isPinned, - // isLoadingNext, - // resetFlatList, } = useChatList(); - console.log("selected chat ID", selectedChatId); - const { headerChatInfo, chatDetails } = useChatDetails(selectedChatId); const { @@ -157,6 +152,8 @@ const ChatsPage: FC = () => { setMessage: setNewMessage, isSending, sendMessage, + editedMessage, + clearEditedMessage, } = useCreateTextMessage({ chatMembers: chatDetails?.chatMembers, chatId: selectedChatId, @@ -164,12 +161,15 @@ const ChatsPage: FC = () => { replyToMessage, }); - const { - imageMessages: newImage, - setImages: setNewImages, - isSending: isFileSending, - sendImageMessage, - } = useSendFiles({ + useEffect(() => { + if (editedMessage && replyToMessage) clearReplyTo(); + }, [editedMessage]); + + useEffect(() => { + if (replyToMessage && editedMessage) clearEditedMessage(); + }, [replyToMessage]); + + useSendFiles({ // replyToMessage, chatId: selectedChatId, onSend: onSendMessage, @@ -306,6 +306,9 @@ const ChatsPage: FC = () => { replyToMessage={replyToMessage} clearReplyTo={clearReplyTo} replyToName={replyToUserName} + editedMessage={editedMessage} + onPressClearEditedMessage={clearEditedMessage} + isSending={isSending} /> ) : ( diff --git a/src/containers/Chats/configs/chat-message-menu-options.config.ts b/src/containers/Chats/configs/chat-message-menu-options.config.ts index baba6a1..1f21010 100644 --- a/src/containers/Chats/configs/chat-message-menu-options.config.ts +++ b/src/containers/Chats/configs/chat-message-menu-options.config.ts @@ -7,6 +7,7 @@ interface IProps { canPin?: boolean; canUnpin?: boolean; canDownload?: boolean; + canEdit?: boolean; } export const getChatMessageMenuOptions = ({ @@ -16,6 +17,7 @@ export const getChatMessageMenuOptions = ({ canPin, canUnpin, canDownload, + canEdit, }: IProps) => { const menuOptions = { copy: { @@ -23,6 +25,11 @@ export const getChatMessageMenuOptions = ({ label: "Копіювати", onClick: () => onClick(ChatMessageActionEnum.COPY), }, + edit: { + key: "edit", + label: "Редагувати", + onClick: () => onClick(ChatMessageActionEnum.EDIT), + }, forward: { key: "forward", label: "Переслати", @@ -68,6 +75,9 @@ export const getChatMessageMenuOptions = ({ if (canCopy) optionsKeys.push("copy"); optionsKeys.push("delete"); if (canDeleteForAll) optionsKeys.push("deleteForAll"); + if (canEdit) optionsKeys.push("edit"); + + console.log("edit", optionsKeys); const options = optionsKeys.map((key) => menuOptions[key]); diff --git a/src/containers/Chats/enums/chat-message-action.enum.ts b/src/containers/Chats/enums/chat-message-action.enum.ts index 3c4aeae..c2f1f8f 100644 --- a/src/containers/Chats/enums/chat-message-action.enum.ts +++ b/src/containers/Chats/enums/chat-message-action.enum.ts @@ -7,4 +7,5 @@ export enum ChatMessageActionEnum { DELETE, DELETE_FOR_ALL, DOWNLOAD, + EDIT, } diff --git a/src/containers/Chats/hooks/use-chat-messages.hook.ts b/src/containers/Chats/hooks/use-chat-messages.hook.ts index 38af38f..a6e4413 100644 --- a/src/containers/Chats/hooks/use-chat-messages.hook.ts +++ b/src/containers/Chats/hooks/use-chat-messages.hook.ts @@ -19,6 +19,7 @@ import { appEvents } from "@/shared/events"; import { ChatMessageActionEnum } from "../enums"; import { getChatMessageMenuOptions } from "../configs"; import { chatMessagesApi } from "@/api"; +import store from "@/store"; export const useChatMessages = ( chatId: number, @@ -122,6 +123,10 @@ export const useChatMessages = ( appEvents.emit("openForwardMessageModal", { message, isShow: true }); }; + const onEdit = (message: IChatMessage) => { + appEvents.emit("onPressEditmessage", { message }); + }; + const actions = { [ChatMessageActionEnum.FORWARD]: onForward, [ChatMessageActionEnum.DELETE]: onDelete, @@ -130,6 +135,7 @@ export const useChatMessages = ( [ChatMessageActionEnum.COPY]: onCopy, [ChatMessageActionEnum.PIN]: onPin, [ChatMessageActionEnum.UNPIN]: onUnpin, + [ChatMessageActionEnum.EDIT]: onEdit, }; const onMessageActions = (message: IChatMessage, role: ChatMemberRole) => { @@ -138,6 +144,8 @@ export const useChatMessages = ( const canCopy = copyEnabledTypes.includes(message.type); const canPin = !message.isPined; const canUnpin = message.isPined; + const canEdit = + message.type === MessageType.Text && message.userId === account.id; const options = getChatMessageMenuOptions({ canDeleteForAll, @@ -146,6 +154,7 @@ export const useChatMessages = ( canCopy, onClick: (actionType: ChatMessageActionEnum) => actions[actionType](message), + canEdit, }); appEvents.emit("openMessageMenuOptions", { items: options }); @@ -159,9 +168,7 @@ export const useChatMessages = ( }; const onLoadNew = async (limit?: number) => { - const id = messages[messages.length - 1]?.id; await loadNew(limit); - // setTimeout(() => setScrollToId(id), 80); }; useEffect(() => { @@ -289,6 +296,20 @@ export const useChatMessages = ( _setItems(filteredItems); }; + const onUpdateMessage = ({ message }) => { + console.log("on update message"); + const newItems = messages.map((it) => { + if (it.id === message.id) { + return message; + } + return it; + }); + + _setItems(newItems); + }; + + useSocketListener("chat/update-message", onUpdateMessage, [messages]); + // APP EVENTS AND SOCKET LISTENERS // useEventsListener("chat/new-media-message", (data) => { diff --git a/src/containers/Chats/hooks/use-create-text-message.hook.ts b/src/containers/Chats/hooks/use-create-text-message.hook.ts index b41ab15..49ca48b 100644 --- a/src/containers/Chats/hooks/use-create-text-message.hook.ts +++ b/src/containers/Chats/hooks/use-create-text-message.hook.ts @@ -6,6 +6,7 @@ import { hasImageUrl, IChatMember, IChatMessage, + useEventsListener, } from "@/shared"; import _ from "lodash"; import { useSelector } from "react-redux"; @@ -28,6 +29,16 @@ export const useCreateTextMessage = ({ const [message, setMessage] = useState(""); const [isSending, setSending] = useState(false); + const [editedMessage, setEditedMessage] = useState(null); + + useEventsListener( + "onPressEditmessage", + ({ message }) => { + setEditedMessage(message); + setMessage(message.content?.message); + }, + [setEditedMessage, setMessage] + ); const sendMessage = async () => { if (!message || message.trim().length === 0) return; @@ -35,16 +46,12 @@ export const useCreateTextMessage = ({ setSending(true); try { - // const transformed = ""; - const data: ISendTextMessage = { chatId, message: message, replyToId: replyToMessage?.id, }; - // if (!_.isEqual(message, transformed)) data.mentionsMessage = message; - onSend(); setMessage(null); await chatMessagesService.sendTextMessage(data); @@ -70,11 +77,18 @@ export const useCreateTextMessage = ({ avatar: hasImageUrl(avatarUrl, name), }; }); + + const clearEditedMessage = () => { + setEditedMessage(null); + setMessage(""); + }; return { suggestionItems: allItems, message, setMessage, isSending, sendMessage, + editedMessage, + clearEditedMessage, }; }; diff --git a/src/containers/Chats/plugins/chat-bar.component.tsx b/src/containers/Chats/plugins/chat-bar.component.tsx index 05ef415..a2c4e28 100644 --- a/src/containers/Chats/plugins/chat-bar.component.tsx +++ b/src/containers/Chats/plugins/chat-bar.component.tsx @@ -1,5 +1,5 @@ -import { IconComponent } from "@/shared"; -import React, { FC, useCallback } from "react"; +import { IconComponent, useEventsListener } from "@/shared"; +import React, { FC, useCallback, useRef } from "react"; import "./style.scss"; import paperClipIcon from "@/assets/img/paperClip-icon.svg"; import x1Icon from "@/assets/img/x-1-icon.svg"; @@ -11,6 +11,7 @@ import { IChatMessage, ISuggestionUser } from "./interfaces"; import { Avatar } from "../atoms"; import { Button } from "antd"; import { ReplyBarSection } from "./reply-bar-section.component"; +import { EditedBarSection } from "./edted-bar-section.component"; interface ChatBarProps { onPressSend: () => void; @@ -26,9 +27,21 @@ interface ChatBarProps { replyToMessage?: IChatMessage; replyToName?: string; clearReplyTo?: () => void; + editedMessage?: IChatMessage; + onPressClearEditedMessage?: () => void; } export const ChatBar: FC = (props) => { + const inputRef = useRef(null); + + useEventsListener( + "onPressEditmessage", + () => { + if (inputRef.current) inputRef.current?.focus(); + }, + [inputRef.current] + ); + const renderLeftPart = () => { if (props?.withAttachmentsBtn) return ( @@ -128,11 +141,16 @@ export const ChatBar: FC = (props) => { onPressClose={props.clearReplyTo} name={props.replyToName} /> +
{renderLeftPart()} { diff --git a/src/containers/Chats/plugins/chat-messages.component.tsx b/src/containers/Chats/plugins/chat-messages.component.tsx index d869e39..dda2d38 100644 --- a/src/containers/Chats/plugins/chat-messages.component.tsx +++ b/src/containers/Chats/plugins/chat-messages.component.tsx @@ -57,11 +57,8 @@ export const ChatMessages: FC = ({ ...props }) => { const [activeAudioId, setActiveAudioId] = useState(null); - const [lastOffset, setOffset] = useState(0); - // const [isScrolling, setScrolling] = useState(false); - const listRef = useRef(null); const ref = useRef(null); diff --git a/src/containers/Chats/plugins/edted-bar-section.component.tsx b/src/containers/Chats/plugins/edted-bar-section.component.tsx new file mode 100644 index 0000000..9190b27 --- /dev/null +++ b/src/containers/Chats/plugins/edted-bar-section.component.tsx @@ -0,0 +1,36 @@ +import { getMessagePreviewText, IconComponent } from "@/shared"; +import React, { FC, useMemo } from "react"; +import { IChatMessage } from "./interfaces"; +import { MessageMediaPreview } from "./message-media-preview.component"; +import x1Icon from "@/assets/img/x-1-icon.svg"; + +interface IProps { + message: IChatMessage; + onPressClose: () => void; +} +export const EditedBarSection: FC = ({ message, onPressClose }) => { + if (!message) return null; + + const text = useMemo(() => getMessagePreviewText(message), [message]); + + return ( +
+
+
+ +
+
+ {`Редагування повідомленя`} + {text} +
+
+ +
+ +
+
+ ); +}; diff --git a/src/services/domain/chat-messages.service.ts b/src/services/domain/chat-messages.service.ts index 0e17e26..d1e44f8 100644 --- a/src/services/domain/chat-messages.service.ts +++ b/src/services/domain/chat-messages.service.ts @@ -1,6 +1,7 @@ import { chatMessagesApi } from "@/api"; import { IDeleteMessageParams, + IEditTextMessage, IFetchChatMessages, ISendFileMessage, ISendStickerMessage, @@ -33,6 +34,14 @@ const sendTextMessage = async (data: ISendTextMessage) => { } }; +const editTextMessage = async (data: IEditTextMessage) => { + try { + await chatMessagesApi.editTextMessageReq(data); + } catch (error) { + console.log("ERROR | SEND CHAT MESSAGES ERROR ", error); + } +}; + const sendStickerMessage = async (data: ISendStickerMessage) => { try { await chatMessagesApi.sendStickerMessageReq(data); @@ -93,4 +102,5 @@ export const chatMessagesService = { deleteChatMessage, clearAllChatMessages, sendStickerMessage, + editTextMessage, }; diff --git a/src/services/system/real-time.service.ts b/src/services/system/real-time.service.ts index 6fd081a..2a2deb8 100644 --- a/src/services/system/real-time.service.ts +++ b/src/services/system/real-time.service.ts @@ -15,7 +15,7 @@ export class SocketIo { this.socket = io(config.socketUrl, { transports: ["websocket", "polling"], reconnection: true, - secure: true + secure: true, }); this._on("connect", () => { this.emit("join-user", store().getState().account.account); @@ -28,16 +28,16 @@ export class SocketIo { get header() { return { - authorization: "Bearer " + store()?.getState()?.auth?.accessToken + authorization: "Bearer " + store()?.getState()?.auth?.accessToken, }; } _on(key, action) { - this.socket.on(key, data => action(data)); + this.socket.on(key, (data) => action(data)); } _onSocketSendEvent(key) { - this.socket.on(key, data => { + this.socket.on(key, (data) => { socketEvents.emit(key, data); }); } @@ -82,6 +82,7 @@ export class SocketIo { this._onSocketSendEvent("user/disconnected"); this._onSocketSendEvent("user/deleted"); this._onSocketSendEvent("user/change-permissions"); + this._onSocketSendEvent("chat/update-message"); this._onSocketSendEvent("stopSessions"); diff --git a/src/shared/events/index.ts b/src/shared/events/index.ts index 3085065..295aaaa 100644 --- a/src/shared/events/index.ts +++ b/src/shared/events/index.ts @@ -64,6 +64,10 @@ export type AppEvents = { selectSticker: { onSelect: (sticker: string) => void; }; + + onPressEditmessage: { + message: IChatMessage; + }; }; export type SocketEvents = { @@ -93,6 +97,8 @@ export type SocketEvents = { stopSessions: { sessionsIds: number[] }; notification: { notification: IPushNotification }; + + "chat/update-message": { message: IChatMessage }; }; export const appEvents = new Events();