feat: message edit and reaction
This commit is contained in:
@@ -42,10 +42,12 @@ export default function Commands({
|
||||
message = null,
|
||||
mid = 0,
|
||||
uid = 0,
|
||||
reactions = [],
|
||||
menuVisible,
|
||||
toggleMenu,
|
||||
emojiPopVisible,
|
||||
toggleEmojiPopover,
|
||||
toggleEditMessage,
|
||||
}) {
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
|
||||
@@ -70,10 +72,14 @@ export default function Commands({
|
||||
/>
|
||||
</li>
|
||||
{emojiPopVisible && (
|
||||
<EmojiPicker mid={mid} hidePicker={toggleEmojiPopover} />
|
||||
<EmojiPicker
|
||||
reactions={reactions}
|
||||
mid={mid}
|
||||
hidePicker={toggleEmojiPopover}
|
||||
/>
|
||||
)}
|
||||
{currUid == uid ? (
|
||||
<li className="cmd" onClick={handleClick}>
|
||||
<li className="cmd" onClick={toggleEditMessage}>
|
||||
<img
|
||||
src="https://static.nicegoodthings.com/project/rustchat/icon.edit.svg"
|
||||
alt="icon edit"
|
||||
@@ -95,7 +101,7 @@ export default function Commands({
|
||||
</li>
|
||||
{menuVisible && (
|
||||
<StyledMenu className="menu" ref={menuRef}>
|
||||
<li className="item">Edit Message</li>
|
||||
{/* <li className="item">Edit Message</li> */}
|
||||
<li className="item underline">Pin Message</li>
|
||||
<li className="item">Reply</li>
|
||||
{currUid == uid && (
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import TextareaAutosize from "react-textarea-autosize";
|
||||
import { useKey } from "rooks";
|
||||
import { useEditMessageMutation } from "../../../app/services/message";
|
||||
const StyledWrapper = styled.div`
|
||||
width: 100%;
|
||||
.input {
|
||||
background: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
textarea {
|
||||
outline: none;
|
||||
width: 100%;
|
||||
background: none;
|
||||
resize: unset;
|
||||
user-select: text;
|
||||
color: #374151;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
}
|
||||
.opts {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
.opt {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
button {
|
||||
padding: 0 4px;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
color: #06b6d4;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default function EditMessage({ content, mid, cancelEdit }) {
|
||||
const inputRef = useRef();
|
||||
const [shift, setShift] = useState(false);
|
||||
const [enter, setEnter] = useState(false);
|
||||
const [currMsg, setCurrMsg] = useState(content);
|
||||
const [edit, { isLoading: isEditing, isSuccess }] = useEditMessageMutation();
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
cancelEdit();
|
||||
}
|
||||
}, [isSuccess]);
|
||||
|
||||
useKey(
|
||||
"Shift",
|
||||
(e) => {
|
||||
console.log("shift", e.type);
|
||||
setShift(e.type == "keydown");
|
||||
},
|
||||
{ eventTypes: ["keydown", "keyup"], target: inputRef }
|
||||
);
|
||||
// cancel by esc
|
||||
useKey(
|
||||
"Escape",
|
||||
() => {
|
||||
console.log("cancel edit");
|
||||
cancelEdit();
|
||||
},
|
||||
{ eventTypes: ["keydown", "keyup"], target: inputRef }
|
||||
);
|
||||
const handleMsgChange = (evt) => {
|
||||
if (enter && !shift) {
|
||||
handleSave();
|
||||
} else {
|
||||
setCurrMsg(evt.target.value);
|
||||
}
|
||||
};
|
||||
const handleInputKeydown = (e) => {
|
||||
console.log("keydown event", e);
|
||||
// if(e.key==="Esc")
|
||||
setEnter(e.key === "Enter");
|
||||
};
|
||||
const handleSave = () => {
|
||||
edit({ mid, content: currMsg });
|
||||
};
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="input">
|
||||
<TextareaAutosize
|
||||
autoFocus
|
||||
onFocus={(e) =>
|
||||
e.currentTarget.setSelectionRange(
|
||||
e.currentTarget.value.length,
|
||||
e.currentTarget.value.length
|
||||
)
|
||||
}
|
||||
ref={inputRef}
|
||||
className="content"
|
||||
maxRows={8}
|
||||
minRows={1}
|
||||
onKeyDown={handleInputKeydown}
|
||||
onChange={handleMsgChange}
|
||||
value={currMsg}
|
||||
placeholder={`Edit Message`}
|
||||
/>
|
||||
</div>
|
||||
<div className="opts">
|
||||
<span className="opt">
|
||||
esc to <button onClick={cancelEdit}>cancel</button>
|
||||
</span>
|
||||
<span className="opt">
|
||||
enter to{" "}
|
||||
<button onClick={handleSave}>{isEditing ? "saving" : `save`}</button>
|
||||
</span>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,8 @@ const StyledPicker = styled.div`
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
font-size: 30px;
|
||||
&:hover {
|
||||
&:hover,
|
||||
&.reacted {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +33,7 @@ const emojis = {
|
||||
ok: "👌",
|
||||
like: "❤️",
|
||||
};
|
||||
export default function EmojiPicker({ mid, hidePicker }) {
|
||||
export default function EmojiPicker({ mid, reactions = [], hidePicker }) {
|
||||
const wrapperRef = useRef(null);
|
||||
const [reactMessage, { isLoading }] = useLikeMessageMutation();
|
||||
useOutsideClick(wrapperRef, hidePicker);
|
||||
@@ -46,7 +47,7 @@ export default function EmojiPicker({ mid, hidePicker }) {
|
||||
{Object.entries(emojis).map(([key, emoji]) => {
|
||||
return (
|
||||
<li
|
||||
className="emoji"
|
||||
className={`emoji ${reactions.includes(key) ? "reacted" : ""}`}
|
||||
key={key}
|
||||
onClick={handleReact.bind(null, key)}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { setUserMsgRead } from "../../../app/slices/message.user";
|
||||
import StyledWrapper from "./styled";
|
||||
import Commands from "./Commands";
|
||||
import { emojis } from "./EmojiPicker";
|
||||
import EditMessage from "./EditMessage";
|
||||
import renderContent from "./renderContent";
|
||||
export default function Message({
|
||||
gid = "",
|
||||
@@ -22,9 +23,11 @@ export default function Message({
|
||||
unread = false,
|
||||
pending,
|
||||
removed = false,
|
||||
likes,
|
||||
edited = false,
|
||||
likes = {},
|
||||
}) {
|
||||
const [myRef, inView] = useInViewRef();
|
||||
const [edit, setEdit] = useState(false);
|
||||
const [emojiPopVisible, setEmojiPopVisible] = useState(false);
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const disptach = useDispatch();
|
||||
@@ -33,6 +36,9 @@ export default function Message({
|
||||
const toggleMenu = () => {
|
||||
setMenuVisible((prev) => !prev);
|
||||
};
|
||||
const toggleEditMessage = () => {
|
||||
setEdit((prev) => !prev);
|
||||
};
|
||||
const toggleEmojiPopover = () => {
|
||||
setEmojiPopVisible((prev) => !prev);
|
||||
};
|
||||
@@ -73,44 +79,56 @@ export default function Message({
|
||||
<i className="time">{dayjs(time).format("YYYY-MM-DD h:mm:ss A")}</i>
|
||||
{likes && (
|
||||
<span className="likes">
|
||||
{Object.entries(
|
||||
likes.reduce((acc, val) => {
|
||||
return { ...acc, [val]: (acc[val] || 0) + 1 };
|
||||
}, {})
|
||||
).map(([l, count]) => {
|
||||
return (
|
||||
{Object.entries(likes).map(([reaction, uids]) => {
|
||||
return uids.length > 0 ? (
|
||||
<i
|
||||
className="like"
|
||||
// data-count={count > 1 ? count : ""}
|
||||
key={l}
|
||||
key={reaction}
|
||||
>
|
||||
{emojis[l]}
|
||||
{emojis[reaction]}
|
||||
|
||||
{count > 1 ? <em>{`+${count}`} </em> : null}
|
||||
{uids.length > 1 ? <em>{`+${uids.length}`} </em> : null}
|
||||
</i>
|
||||
);
|
||||
) : null;
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={`down ${pending ? "pending" : ""}`}>
|
||||
{renderContent(content_type, content)}
|
||||
{edit ? (
|
||||
<EditMessage
|
||||
content={content}
|
||||
mid={mid}
|
||||
cancelEdit={toggleEditMessage}
|
||||
/>
|
||||
) : (
|
||||
renderContent(content_type, content, edited)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Commands
|
||||
message={{
|
||||
name: currUser.name,
|
||||
avatar: currUser.avatar,
|
||||
time,
|
||||
content: renderContent(content_type, content),
|
||||
}}
|
||||
mid={mid}
|
||||
uid={fromUid}
|
||||
toggleMenu={toggleMenu}
|
||||
menuVisible={menuVisible}
|
||||
emojiPopVisible={emojiPopVisible}
|
||||
toggleEmojiPopover={toggleEmojiPopover}
|
||||
/>
|
||||
{!edit && (
|
||||
<Commands
|
||||
message={{
|
||||
name: currUser.name,
|
||||
avatar: currUser.avatar,
|
||||
time,
|
||||
content: renderContent(content_type, content),
|
||||
}}
|
||||
reactions={Object.entries(likes ?? {})
|
||||
.filter(([reaction, uids = []]) => uids.includes(currUser.uid))
|
||||
.map(([reaction]) => {
|
||||
return reaction;
|
||||
})}
|
||||
mid={mid}
|
||||
uid={fromUid}
|
||||
toggleMenu={toggleMenu}
|
||||
menuVisible={menuVisible}
|
||||
emojiPopVisible={emojiPopVisible}
|
||||
toggleEmojiPopover={toggleEmojiPopover}
|
||||
toggleEditMessage={toggleEditMessage}
|
||||
/>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
import Linkify from "react-linkify";
|
||||
import dayjs from "dayjs";
|
||||
import BASE_URL from "../../../app/config";
|
||||
const renderContent = (type, content) => {
|
||||
const renderContent = (type, content, edited = false) => {
|
||||
let ctn = null;
|
||||
switch (type) {
|
||||
case "text/plain":
|
||||
ctn = (
|
||||
<Linkify
|
||||
componentDecorator={(decoratedHref, decoratedText, key) => (
|
||||
<a target="blank" href={decoratedHref} key={key}>
|
||||
{decoratedText}
|
||||
</a>
|
||||
<>
|
||||
<Linkify
|
||||
componentDecorator={(decoratedHref, decoratedText, key) => (
|
||||
<a target="blank" href={decoratedHref} key={key}>
|
||||
{decoratedText}
|
||||
</a>
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</Linkify>
|
||||
{edited && (
|
||||
<span
|
||||
className="edited"
|
||||
title={dayjs(edited).format("YYYY-MM-DD h:mm:ss A")}
|
||||
>
|
||||
(edited)
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</Linkify>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case "image/jpeg":
|
||||
|
||||
@@ -26,6 +26,7 @@ const StyledMsg = styled.div`
|
||||
}
|
||||
}
|
||||
.details {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
@@ -78,6 +79,11 @@ const StyledMsg = styled.div`
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
.edited {
|
||||
margin-left: 5px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
&.pending {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user