Browse Source

FEATURE | Edite message

merge-requests/184/merge
Vitalik 2 years ago
parent
commit
e88f02bd40
  1. 1
      .eslintrc.js
  2. 4
      src/api/chats-messages/request.interfaces.ts
  3. 5
      src/api/chats-messages/requests.ts
  4. 25
      src/containers/Chats/chats.screen.tsx
  5. 10
      src/containers/Chats/configs/chat-message-menu-options.config.ts
  6. 1
      src/containers/Chats/enums/chat-message-action.enum.ts
  7. 25
      src/containers/Chats/hooks/use-chat-messages.hook.ts
  8. 22
      src/containers/Chats/hooks/use-create-text-message.hook.ts
  9. 22
      src/containers/Chats/plugins/chat-bar.component.tsx
  10. 3
      src/containers/Chats/plugins/chat-messages.component.tsx
  11. 36
      src/containers/Chats/plugins/edted-bar-section.component.tsx
  12. 10
      src/services/domain/chat-messages.service.ts
  13. 9
      src/services/system/real-time.service.ts
  14. 6
      src/shared/events/index.ts

1
.eslintrc.js

@ -24,5 +24,6 @@ module.exports = {
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-this-alias": "off", "@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/ban-types": "off",
}, },
}; };

4
src/api/chats-messages/request.interfaces.ts

@ -11,6 +11,10 @@ export interface ISendTextMessage {
mentionsMessage?: string; mentionsMessage?: string;
replyToId?: number; replyToId?: number;
} }
export interface IEditTextMessage {
newMessage: string;
messageId: number;
}
export interface ISendStickerMessage { export interface ISendStickerMessage {
chatId: number; chatId: number;

5
src/api/chats-messages/requests.ts

@ -16,6 +16,10 @@ export const sendTextMessageReq = (
return http.post<void>(`app/chats-messages/text-message`, data); return http.post<void>(`app/chats-messages/text-message`, data);
}; };
export const editTextMessageReq = (data: Req.IEditTextMessage) => {
return http.patch<void>("chats-messages/text-message", data);
};
export const sendStickerMessageReq = (data: Req.ISendStickerMessage) => { export const sendStickerMessageReq = (data: Req.ISendStickerMessage) => {
return http.post<void>(`app/chats-messages/sticker-message`, data); return http.post<void>(`app/chats-messages/sticker-message`, data);
}; };
@ -70,4 +74,5 @@ export const chatMessagesApi = {
unpinMessageReq, unpinMessageReq,
clearChatReq, clearChatReq,
sendStickerMessageReq, sendStickerMessageReq,
editTextMessageReq,
}; };

25
src/containers/Chats/chats.screen.tsx

@ -74,16 +74,11 @@ const ChatsPage: FC = () => {
loadMore, loadMore,
loadPage, loadPage,
setSearchVal, setSearchVal,
// handleChatAction,
isLoading, isLoading,
onPin, onPin,
isPinned, isPinned,
// isLoadingNext,
// resetFlatList,
} = useChatList(); } = useChatList();
console.log("selected chat ID", selectedChatId);
const { headerChatInfo, chatDetails } = useChatDetails(selectedChatId); const { headerChatInfo, chatDetails } = useChatDetails(selectedChatId);
const { const {
@ -157,6 +152,8 @@ const ChatsPage: FC = () => {
setMessage: setNewMessage, setMessage: setNewMessage,
isSending, isSending,
sendMessage, sendMessage,
editedMessage,
clearEditedMessage,
} = useCreateTextMessage({ } = useCreateTextMessage({
chatMembers: chatDetails?.chatMembers, chatMembers: chatDetails?.chatMembers,
chatId: selectedChatId, chatId: selectedChatId,
@ -164,12 +161,15 @@ const ChatsPage: FC = () => {
replyToMessage, replyToMessage,
}); });
const { useEffect(() => {
imageMessages: newImage, if (editedMessage && replyToMessage) clearReplyTo();
setImages: setNewImages, }, [editedMessage]);
isSending: isFileSending,
sendImageMessage, useEffect(() => {
} = useSendFiles({ if (replyToMessage && editedMessage) clearEditedMessage();
}, [replyToMessage]);
useSendFiles({
// replyToMessage, // replyToMessage,
chatId: selectedChatId, chatId: selectedChatId,
onSend: onSendMessage, onSend: onSendMessage,
@ -306,6 +306,9 @@ const ChatsPage: FC = () => {
replyToMessage={replyToMessage} replyToMessage={replyToMessage}
clearReplyTo={clearReplyTo} clearReplyTo={clearReplyTo}
replyToName={replyToUserName} replyToName={replyToUserName}
editedMessage={editedMessage}
onPressClearEditedMessage={clearEditedMessage}
isSending={isSending}
/> />
</> </>
) : ( ) : (

10
src/containers/Chats/configs/chat-message-menu-options.config.ts

@ -7,6 +7,7 @@ interface IProps {
canPin?: boolean; canPin?: boolean;
canUnpin?: boolean; canUnpin?: boolean;
canDownload?: boolean; canDownload?: boolean;
canEdit?: boolean;
} }
export const getChatMessageMenuOptions = ({ export const getChatMessageMenuOptions = ({
@ -16,6 +17,7 @@ export const getChatMessageMenuOptions = ({
canPin, canPin,
canUnpin, canUnpin,
canDownload, canDownload,
canEdit,
}: IProps) => { }: IProps) => {
const menuOptions = { const menuOptions = {
copy: { copy: {
@ -23,6 +25,11 @@ export const getChatMessageMenuOptions = ({
label: "Копіювати", label: "Копіювати",
onClick: () => onClick(ChatMessageActionEnum.COPY), onClick: () => onClick(ChatMessageActionEnum.COPY),
}, },
edit: {
key: "edit",
label: "Редагувати",
onClick: () => onClick(ChatMessageActionEnum.EDIT),
},
forward: { forward: {
key: "forward", key: "forward",
label: "Переслати", label: "Переслати",
@ -68,6 +75,9 @@ export const getChatMessageMenuOptions = ({
if (canCopy) optionsKeys.push("copy"); if (canCopy) optionsKeys.push("copy");
optionsKeys.push("delete"); optionsKeys.push("delete");
if (canDeleteForAll) optionsKeys.push("deleteForAll"); if (canDeleteForAll) optionsKeys.push("deleteForAll");
if (canEdit) optionsKeys.push("edit");
console.log("edit", optionsKeys);
const options = optionsKeys.map((key) => menuOptions[key]); const options = optionsKeys.map((key) => menuOptions[key]);

1
src/containers/Chats/enums/chat-message-action.enum.ts

@ -7,4 +7,5 @@ export enum ChatMessageActionEnum {
DELETE, DELETE,
DELETE_FOR_ALL, DELETE_FOR_ALL,
DOWNLOAD, DOWNLOAD,
EDIT,
} }

25
src/containers/Chats/hooks/use-chat-messages.hook.ts

@ -19,6 +19,7 @@ import { appEvents } from "@/shared/events";
import { ChatMessageActionEnum } from "../enums"; import { ChatMessageActionEnum } from "../enums";
import { getChatMessageMenuOptions } from "../configs"; import { getChatMessageMenuOptions } from "../configs";
import { chatMessagesApi } from "@/api"; import { chatMessagesApi } from "@/api";
import store from "@/store";
export const useChatMessages = ( export const useChatMessages = (
chatId: number, chatId: number,
@ -122,6 +123,10 @@ export const useChatMessages = (
appEvents.emit("openForwardMessageModal", { message, isShow: true }); appEvents.emit("openForwardMessageModal", { message, isShow: true });
}; };
const onEdit = (message: IChatMessage) => {
appEvents.emit("onPressEditmessage", { message });
};
const actions = { const actions = {
[ChatMessageActionEnum.FORWARD]: onForward, [ChatMessageActionEnum.FORWARD]: onForward,
[ChatMessageActionEnum.DELETE]: onDelete, [ChatMessageActionEnum.DELETE]: onDelete,
@ -130,6 +135,7 @@ export const useChatMessages = (
[ChatMessageActionEnum.COPY]: onCopy, [ChatMessageActionEnum.COPY]: onCopy,
[ChatMessageActionEnum.PIN]: onPin, [ChatMessageActionEnum.PIN]: onPin,
[ChatMessageActionEnum.UNPIN]: onUnpin, [ChatMessageActionEnum.UNPIN]: onUnpin,
[ChatMessageActionEnum.EDIT]: onEdit,
}; };
const onMessageActions = (message: IChatMessage, role: ChatMemberRole) => { const onMessageActions = (message: IChatMessage, role: ChatMemberRole) => {
@ -138,6 +144,8 @@ export const useChatMessages = (
const canCopy = copyEnabledTypes.includes(message.type); const canCopy = copyEnabledTypes.includes(message.type);
const canPin = !message.isPined; const canPin = !message.isPined;
const canUnpin = message.isPined; const canUnpin = message.isPined;
const canEdit =
message.type === MessageType.Text && message.userId === account.id;
const options = getChatMessageMenuOptions({ const options = getChatMessageMenuOptions({
canDeleteForAll, canDeleteForAll,
@ -146,6 +154,7 @@ export const useChatMessages = (
canCopy, canCopy,
onClick: (actionType: ChatMessageActionEnum) => onClick: (actionType: ChatMessageActionEnum) =>
actions[actionType](message), actions[actionType](message),
canEdit,
}); });
appEvents.emit("openMessageMenuOptions", { items: options }); appEvents.emit("openMessageMenuOptions", { items: options });
@ -159,9 +168,7 @@ export const useChatMessages = (
}; };
const onLoadNew = async (limit?: number) => { const onLoadNew = async (limit?: number) => {
const id = messages[messages.length - 1]?.id;
await loadNew(limit); await loadNew(limit);
// setTimeout(() => setScrollToId(id), 80);
}; };
useEffect(() => { useEffect(() => {
@ -289,6 +296,20 @@ export const useChatMessages = (
_setItems(filteredItems); _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 // // APP EVENTS AND SOCKET LISTENERS //
useEventsListener("chat/new-media-message", (data) => { useEventsListener("chat/new-media-message", (data) => {

22
src/containers/Chats/hooks/use-create-text-message.hook.ts

@ -6,6 +6,7 @@ import {
hasImageUrl, hasImageUrl,
IChatMember, IChatMember,
IChatMessage, IChatMessage,
useEventsListener,
} from "@/shared"; } from "@/shared";
import _ from "lodash"; import _ from "lodash";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@ -28,6 +29,16 @@ export const useCreateTextMessage = ({
const [message, setMessage] = useState<string>(""); const [message, setMessage] = useState<string>("");
const [isSending, setSending] = useState<boolean>(false); const [isSending, setSending] = useState<boolean>(false);
const [editedMessage, setEditedMessage] = useState<IChatMessage>(null);
useEventsListener(
"onPressEditmessage",
({ message }) => {
setEditedMessage(message);
setMessage(message.content?.message);
},
[setEditedMessage, setMessage]
);
const sendMessage = async () => { const sendMessage = async () => {
if (!message || message.trim().length === 0) return; if (!message || message.trim().length === 0) return;
@ -35,16 +46,12 @@ export const useCreateTextMessage = ({
setSending(true); setSending(true);
try { try {
// const transformed = "";
const data: ISendTextMessage = { const data: ISendTextMessage = {
chatId, chatId,
message: message, message: message,
replyToId: replyToMessage?.id, replyToId: replyToMessage?.id,
}; };
// if (!_.isEqual(message, transformed)) data.mentionsMessage = message;
onSend(); onSend();
setMessage(null); setMessage(null);
await chatMessagesService.sendTextMessage(data); await chatMessagesService.sendTextMessage(data);
@ -70,11 +77,18 @@ export const useCreateTextMessage = ({
avatar: hasImageUrl(avatarUrl, name), avatar: hasImageUrl(avatarUrl, name),
}; };
}); });
const clearEditedMessage = () => {
setEditedMessage(null);
setMessage("");
};
return { return {
suggestionItems: allItems, suggestionItems: allItems,
message, message,
setMessage, setMessage,
isSending, isSending,
sendMessage, sendMessage,
editedMessage,
clearEditedMessage,
}; };
}; };

22
src/containers/Chats/plugins/chat-bar.component.tsx

@ -1,5 +1,5 @@
import { IconComponent } from "@/shared"; import { IconComponent, useEventsListener } from "@/shared";
import React, { FC, useCallback } from "react"; import React, { FC, useCallback, useRef } from "react";
import "./style.scss"; import "./style.scss";
import paperClipIcon from "@/assets/img/paperClip-icon.svg"; import paperClipIcon from "@/assets/img/paperClip-icon.svg";
import x1Icon from "@/assets/img/x-1-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 { Avatar } from "../atoms";
import { Button } from "antd"; import { Button } from "antd";
import { ReplyBarSection } from "./reply-bar-section.component"; import { ReplyBarSection } from "./reply-bar-section.component";
import { EditedBarSection } from "./edted-bar-section.component";
interface ChatBarProps { interface ChatBarProps {
onPressSend: () => void; onPressSend: () => void;
@ -26,9 +27,21 @@ interface ChatBarProps {
replyToMessage?: IChatMessage; replyToMessage?: IChatMessage;
replyToName?: string; replyToName?: string;
clearReplyTo?: () => void; clearReplyTo?: () => void;
editedMessage?: IChatMessage;
onPressClearEditedMessage?: () => void;
} }
export const ChatBar: FC<ChatBarProps> = (props) => { export const ChatBar: FC<ChatBarProps> = (props) => {
const inputRef = useRef<any>(null);
useEventsListener(
"onPressEditmessage",
() => {
if (inputRef.current) inputRef.current?.focus();
},
[inputRef.current]
);
const renderLeftPart = () => { const renderLeftPart = () => {
if (props?.withAttachmentsBtn) if (props?.withAttachmentsBtn)
return ( return (
@ -128,11 +141,16 @@ export const ChatBar: FC<ChatBarProps> = (props) => {
onPressClose={props.clearReplyTo} onPressClose={props.clearReplyTo}
name={props.replyToName} name={props.replyToName}
/> />
<EditedBarSection
message={props.editedMessage}
onPressClose={props.onPressClearEditedMessage}
/>
<div className="chat-bar-input-field"> <div className="chat-bar-input-field">
<div className="chat-bar-left"> <div className="chat-bar-left">
{renderLeftPart()} {renderLeftPart()}
<MentionsInput <MentionsInput
inputRef={inputRef}
className="message-input" className="message-input"
value={props.message} value={props.message}
onChange={(e) => { onChange={(e) => {

3
src/containers/Chats/plugins/chat-messages.component.tsx

@ -57,11 +57,8 @@ export const ChatMessages: FC<ChatMessagesProps> = ({
...props ...props
}) => { }) => {
const [activeAudioId, setActiveAudioId] = useState<number>(null); const [activeAudioId, setActiveAudioId] = useState<number>(null);
const [lastOffset, setOffset] = useState(0); const [lastOffset, setOffset] = useState(0);
// const [isScrolling, setScrolling] = useState(false);
const listRef = useRef(null); const listRef = useRef(null);
const ref = useRef(null); const ref = useRef(null);

36
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<IProps> = ({ message, onPressClose }) => {
if (!message) return null;
const text = useMemo(() => getMessagePreviewText(message), [message]);
return (
<div className="reply-bar-container">
<div className="reply-main-section">
<div className="reply-media-preview">
<MessageMediaPreview
type={message?.type}
uri={message?.content?.fileUrl}
/>
</div>
<div className="reply-text-section">
<span>{`Редагування повідомленя`}</span>
<span className="collapsed">{text}</span>
</div>
</div>
<div className="reply-close-section" onClick={onPressClose}>
<IconComponent name={x1Icon} />
</div>
</div>
);
};

10
src/services/domain/chat-messages.service.ts

@ -1,6 +1,7 @@
import { chatMessagesApi } from "@/api"; import { chatMessagesApi } from "@/api";
import { import {
IDeleteMessageParams, IDeleteMessageParams,
IEditTextMessage,
IFetchChatMessages, IFetchChatMessages,
ISendFileMessage, ISendFileMessage,
ISendStickerMessage, 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) => { const sendStickerMessage = async (data: ISendStickerMessage) => {
try { try {
await chatMessagesApi.sendStickerMessageReq(data); await chatMessagesApi.sendStickerMessageReq(data);
@ -93,4 +102,5 @@ export const chatMessagesService = {
deleteChatMessage, deleteChatMessage,
clearAllChatMessages, clearAllChatMessages,
sendStickerMessage, sendStickerMessage,
editTextMessage,
}; };

9
src/services/system/real-time.service.ts

@ -15,7 +15,7 @@ export class SocketIo {
this.socket = io(config.socketUrl, { this.socket = io(config.socketUrl, {
transports: ["websocket", "polling"], transports: ["websocket", "polling"],
reconnection: true, reconnection: true,
secure: true secure: true,
}); });
this._on("connect", () => { this._on("connect", () => {
this.emit("join-user", store().getState().account.account); this.emit("join-user", store().getState().account.account);
@ -28,16 +28,16 @@ export class SocketIo {
get header() { get header() {
return { return {
authorization: "Bearer " + store()?.getState()?.auth?.accessToken authorization: "Bearer " + store()?.getState()?.auth?.accessToken,
}; };
} }
_on(key, action) { _on(key, action) {
this.socket.on(key, data => action(data)); this.socket.on(key, (data) => action(data));
} }
_onSocketSendEvent(key) { _onSocketSendEvent(key) {
this.socket.on(key, data => { this.socket.on(key, (data) => {
socketEvents.emit(key, data); socketEvents.emit(key, data);
}); });
} }
@ -82,6 +82,7 @@ export class SocketIo {
this._onSocketSendEvent("user/disconnected"); this._onSocketSendEvent("user/disconnected");
this._onSocketSendEvent("user/deleted"); this._onSocketSendEvent("user/deleted");
this._onSocketSendEvent("user/change-permissions"); this._onSocketSendEvent("user/change-permissions");
this._onSocketSendEvent("chat/update-message");
this._onSocketSendEvent("stopSessions"); this._onSocketSendEvent("stopSessions");

6
src/shared/events/index.ts

@ -64,6 +64,10 @@ export type AppEvents = {
selectSticker: { selectSticker: {
onSelect: (sticker: string) => void; onSelect: (sticker: string) => void;
}; };
onPressEditmessage: {
message: IChatMessage;
};
}; };
export type SocketEvents = { export type SocketEvents = {
@ -93,6 +97,8 @@ export type SocketEvents = {
stopSessions: { sessionsIds: number[] }; stopSessions: { sessionsIds: number[] };
notification: { notification: IPushNotification }; notification: { notification: IPushNotification };
"chat/update-message": { message: IChatMessage };
}; };
export const appEvents = new Events<AppEvents>(); export const appEvents = new Events<AppEvents>();

Loading…
Cancel
Save