chore: update favorite icon in different sences
This commit is contained in:
@@ -1,252 +1,231 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { hideAll } from "tippy.js";
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import { hideAll } from 'tippy.js';
|
||||
// import toast from "react-hot-toast";
|
||||
import { updateSelectMessages } from "../../../app/slices/ui";
|
||||
import { addReplyingMessage } from "../../../app/slices/message";
|
||||
import StyledMenu from "../styled/Menu";
|
||||
import Tooltip from "../../component/Tooltip";
|
||||
import DeleteMessageConfirm from "../DeleteMessageConfirm";
|
||||
import useFavMessage from "../../hook/useFavMessage";
|
||||
import EmojiPicker from "./EmojiPicker";
|
||||
import replyIcon from "../../../assets/icons/reply.svg?url";
|
||||
import reactIcon from "../../../assets/icons/reaction.svg?url";
|
||||
import editIcon from "../../../assets/icons/edit.svg?url";
|
||||
import IconBookmark from "../../../assets/icons/bookmark.svg";
|
||||
import moreIcon from "../../../assets/icons/more.svg?url";
|
||||
import ForwardModal from "../ForwardModal";
|
||||
import PinMessageModal from "./PinMessageModal";
|
||||
import usePinMessage from "../../hook/usePinMessage";
|
||||
import { ContentTypes } from "../../../app/config";
|
||||
import toast from "react-hot-toast";
|
||||
import { updateSelectMessages } from '../../../app/slices/ui';
|
||||
import { addReplyingMessage } from '../../../app/slices/message';
|
||||
import StyledMenu from '../styled/Menu';
|
||||
import Tooltip from '../../component/Tooltip';
|
||||
import DeleteMessageConfirm from '../DeleteMessageConfirm';
|
||||
import useFavMessage from '../../hook/useFavMessage';
|
||||
import EmojiPicker from './EmojiPicker';
|
||||
import replyIcon from '../../../assets/icons/reply.svg?url';
|
||||
import reactIcon from '../../../assets/icons/reaction.svg?url';
|
||||
import editIcon from '../../../assets/icons/edit.svg?url';
|
||||
import IconBookmark from '../../../assets/icons/bookmark.add.svg';
|
||||
import moreIcon from '../../../assets/icons/more.svg?url';
|
||||
import ForwardModal from '../ForwardModal';
|
||||
import PinMessageModal from './PinMessageModal';
|
||||
import usePinMessage from '../../hook/usePinMessage';
|
||||
import { ContentTypes } from '../../../app/config';
|
||||
import toast from 'react-hot-toast';
|
||||
const StyledCmds = styled.ul`
|
||||
z-index: 9999;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
z-index: 9999;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
visibility: hidden;
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.cmd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
visibility: hidden;
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
.cmd {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
img,
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
&.fav {
|
||||
svg path {
|
||||
fill: #667085;
|
||||
}
|
||||
}
|
||||
img,
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
> .picker {
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
&.fav {
|
||||
svg path {
|
||||
fill: #667085;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .picker {
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
`;
|
||||
export default function Commands({
|
||||
content_type = ContentTypes.text,
|
||||
context = "user",
|
||||
contextId = 0,
|
||||
mid = 0,
|
||||
from_uid = 0,
|
||||
toggleEditMessage,
|
||||
content_type = ContentTypes.text,
|
||||
context = 'user',
|
||||
contextId = 0,
|
||||
mid = 0,
|
||||
from_uid = 0,
|
||||
toggleEditMessage
|
||||
}) {
|
||||
const { addFavorite, isFavorited } = useFavMessage(
|
||||
context == "channel" ? contextId : null
|
||||
);
|
||||
const [mids, setMids] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const [pinModalVisible, setPinModalVisible] = useState(false);
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
const [forwardModalVisible, setForwardModalVisible] = useState(false);
|
||||
const [tippyVisible, setTippyVisible] = useState(false);
|
||||
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
|
||||
context == "channel" ? contextId : undefined
|
||||
);
|
||||
const currUid = useSelector((store) => store.authData.uid);
|
||||
const cmdsRef = useRef(null);
|
||||
const handleReply = (fromMenu) => {
|
||||
if (contextId) {
|
||||
dispatch(addReplyingMessage({ id: contextId, mid }));
|
||||
}
|
||||
if (fromMenu) {
|
||||
hideAll();
|
||||
}
|
||||
};
|
||||
const toggleForwardModal = () => {
|
||||
hideAll();
|
||||
console.log("midss", mids);
|
||||
setForwardModalVisible((prev) => !prev);
|
||||
};
|
||||
const toggleDeleteModal = () => {
|
||||
hideAll();
|
||||
setDeleteModalVisible((prev) => !prev);
|
||||
};
|
||||
const togglePinModal = () => {
|
||||
hideAll();
|
||||
setPinModalVisible((prev) => !prev);
|
||||
};
|
||||
const handleTippyVisible = (visible = true) => {
|
||||
setTippyVisible(visible);
|
||||
};
|
||||
const handleSelect = (mid) => {
|
||||
dispatch(updateSelectMessages({ context, id: contextId, data: mid }));
|
||||
hideAll();
|
||||
};
|
||||
const handleUnpin = () => {
|
||||
hideAll();
|
||||
unpinMessage(mid);
|
||||
};
|
||||
const handleAddFav = async () => {
|
||||
hideAll();
|
||||
const faved = isFavorited(mid);
|
||||
if (faved) {
|
||||
toast.success("Favorited!");
|
||||
return;
|
||||
}
|
||||
await addFavorite(mid);
|
||||
toast.success("Added Favorites!");
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isUnpinSuccess) {
|
||||
toast.success("Unpin Message Successfully!");
|
||||
}
|
||||
}, [isUnpinSuccess]);
|
||||
const { addFavorite, isFavorited } = useFavMessage(context == 'channel' ? contextId : null);
|
||||
const [mids, setMids] = useState([]);
|
||||
const dispatch = useDispatch();
|
||||
const [pinModalVisible, setPinModalVisible] = useState(false);
|
||||
const [deleteModalVisible, setDeleteModalVisible] = useState(false);
|
||||
const [forwardModalVisible, setForwardModalVisible] = useState(false);
|
||||
const [tippyVisible, setTippyVisible] = useState(false);
|
||||
const { canPin, pins, unpinMessage, isUnpinSuccess } = usePinMessage(
|
||||
context == 'channel' ? contextId : undefined
|
||||
);
|
||||
const currUid = useSelector((store) => store.authData.uid);
|
||||
const cmdsRef = useRef(null);
|
||||
const handleReply = (fromMenu) => {
|
||||
if (contextId) {
|
||||
dispatch(addReplyingMessage({ id: contextId, mid }));
|
||||
}
|
||||
if (fromMenu) {
|
||||
hideAll();
|
||||
}
|
||||
};
|
||||
const toggleForwardModal = () => {
|
||||
hideAll();
|
||||
console.log('midss', mids);
|
||||
setForwardModalVisible((prev) => !prev);
|
||||
};
|
||||
const toggleDeleteModal = () => {
|
||||
hideAll();
|
||||
setDeleteModalVisible((prev) => !prev);
|
||||
};
|
||||
const togglePinModal = () => {
|
||||
hideAll();
|
||||
setPinModalVisible((prev) => !prev);
|
||||
};
|
||||
const handleTippyVisible = (visible = true) => {
|
||||
setTippyVisible(visible);
|
||||
};
|
||||
const handleSelect = (mid) => {
|
||||
dispatch(updateSelectMessages({ context, id: contextId, data: mid }));
|
||||
hideAll();
|
||||
};
|
||||
const handleUnpin = () => {
|
||||
hideAll();
|
||||
unpinMessage(mid);
|
||||
};
|
||||
const handleAddFav = async () => {
|
||||
hideAll();
|
||||
const faved = isFavorited(mid);
|
||||
if (faved) {
|
||||
toast.success('Favorited!');
|
||||
return;
|
||||
}
|
||||
await addFavorite(mid);
|
||||
toast.success('Added Favorites!');
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isUnpinSuccess) {
|
||||
toast.success('Unpin Message Successfully!');
|
||||
}
|
||||
}, [isUnpinSuccess]);
|
||||
|
||||
useEffect(() => {
|
||||
if (content_type == ContentTypes.archive) {
|
||||
// forward message
|
||||
const forwardEle = document.querySelector(
|
||||
`[data-msg-mid='${mid}'] .down [data-forwarded-mids]`
|
||||
);
|
||||
if (forwardEle) {
|
||||
const mids = forwardEle.dataset.forwardedMids.split(",");
|
||||
setMids(mids);
|
||||
}
|
||||
} else {
|
||||
setMids([mid]);
|
||||
}
|
||||
}, [mid, content_type]);
|
||||
useEffect(() => {
|
||||
if (content_type == ContentTypes.archive) {
|
||||
// forward message
|
||||
const forwardEle = document.querySelector(`[data-msg-mid='${mid}'] .down [data-forwarded-mids]`);
|
||||
if (forwardEle) {
|
||||
const mids = forwardEle.dataset.forwardedMids.split(',');
|
||||
setMids(mids);
|
||||
}
|
||||
} else {
|
||||
setMids([mid]);
|
||||
}
|
||||
}, [mid, content_type]);
|
||||
|
||||
const enablePin = context == "channel" && canPin;
|
||||
const enableEdit =
|
||||
currUid == from_uid &&
|
||||
[ContentTypes.text, ContentTypes.markdown].includes(content_type);
|
||||
const enableReply = currUid != from_uid;
|
||||
const pinned = enablePin ? pins.findIndex((p) => p.mid == mid) > -1 : false;
|
||||
return (
|
||||
<StyledCmds
|
||||
ref={cmdsRef}
|
||||
className={`cmds ${tippyVisible ? "visible" : ""}`}
|
||||
>
|
||||
<Tippy
|
||||
duration={0}
|
||||
delay={[0, 0]}
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
interactive
|
||||
placement="left-start"
|
||||
trigger="click"
|
||||
content={<EmojiPicker mid={mid} hidePicker={hideAll} />}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="Add Reaction">
|
||||
<img src={reactIcon} className="toggler" alt="icon emoji" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
{enableEdit && (
|
||||
<li className="cmd" onClick={toggleEditMessage}>
|
||||
<Tooltip placement="top" tip="Edit">
|
||||
<img src={editIcon} alt="icon edit" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
const enablePin = context == 'channel' && canPin;
|
||||
const enableEdit =
|
||||
currUid == from_uid && [ContentTypes.text, ContentTypes.markdown].includes(content_type);
|
||||
const enableReply = currUid != from_uid;
|
||||
const pinned = enablePin ? pins.findIndex((p) => p.mid == mid) > -1 : false;
|
||||
return (
|
||||
<StyledCmds ref={cmdsRef} className={`cmds ${tippyVisible ? 'visible' : ''}`}>
|
||||
<Tippy
|
||||
duration={0}
|
||||
delay={[0, 0]}
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
interactive
|
||||
placement="left-start"
|
||||
trigger="click"
|
||||
content={<EmojiPicker mid={mid} hidePicker={hideAll} />}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="Add Reaction">
|
||||
<img src={reactIcon} className="toggler" alt="icon emoji" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
{enableEdit && (
|
||||
<li className="cmd" onClick={toggleEditMessage}>
|
||||
<Tooltip placement="top" tip="Edit">
|
||||
<img src={editIcon} alt="icon edit" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
{enableReply && (
|
||||
<li className="cmd" onClick={handleReply}>
|
||||
<Tooltip placement="top" tip="Reply">
|
||||
<img src={replyIcon} alt="icon reply" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
<li className="cmd fav" onClick={handleAddFav}>
|
||||
<Tooltip placement="top" tip="Add to Favorites">
|
||||
<IconBookmark />
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Tippy
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
interactive
|
||||
popperOptions={{ strategy: 'fixed' }}
|
||||
placement="left-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<StyledMenu className="menu">
|
||||
{/* <li className="item">Edit Message</li> */}
|
||||
{enablePin && (
|
||||
<li className="item" onClick={pinned ? handleUnpin : togglePinModal}>
|
||||
{pinned ? `Unpin Message` : `Pin Message`}
|
||||
</li>
|
||||
)}
|
||||
{enableReply && (
|
||||
<li className="cmd" onClick={handleReply}>
|
||||
<Tooltip placement="top" tip="Reply">
|
||||
<img src={replyIcon} alt="icon reply" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
<li className="cmd fav" onClick={handleAddFav}>
|
||||
<Tooltip placement="top" tip="Add to Favorites">
|
||||
<IconBookmark />
|
||||
</Tooltip>
|
||||
<li className="item" onClick={toggleForwardModal}>
|
||||
Forward
|
||||
</li>
|
||||
<Tippy
|
||||
onShow={handleTippyVisible.bind(null, true)}
|
||||
onHide={handleTippyVisible.bind(null, false)}
|
||||
interactive
|
||||
popperOptions={{ strategy: "fixed" }}
|
||||
placement="left-start"
|
||||
trigger="click"
|
||||
content={
|
||||
<StyledMenu className="menu">
|
||||
{/* <li className="item">Edit Message</li> */}
|
||||
{enablePin && (
|
||||
<li
|
||||
className="item"
|
||||
onClick={pinned ? handleUnpin : togglePinModal}
|
||||
>
|
||||
{pinned ? `Unpin Message` : `Pin Message`}
|
||||
</li>
|
||||
)}
|
||||
<li className="item" onClick={toggleForwardModal}>
|
||||
Forward
|
||||
</li>
|
||||
<li className="item" onClick={handleReply.bind(null, true)}>
|
||||
Reply
|
||||
</li>
|
||||
<li className="item" onClick={handleSelect.bind(null, mid)}>
|
||||
Select
|
||||
</li>
|
||||
{currUid == from_uid && (
|
||||
<li className="item danger" onClick={toggleDeleteModal}>
|
||||
Delete Message
|
||||
</li>
|
||||
)}
|
||||
</StyledMenu>
|
||||
}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="More">
|
||||
<img src={moreIcon} alt="icon more" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
<li className="item" onClick={handleReply.bind(null, true)}>
|
||||
Reply
|
||||
</li>
|
||||
<li className="item" onClick={handleSelect.bind(null, mid)}>
|
||||
Select
|
||||
</li>
|
||||
{currUid == from_uid && (
|
||||
<li className="item danger" onClick={toggleDeleteModal}>
|
||||
Delete Message
|
||||
</li>
|
||||
)}
|
||||
</StyledMenu>
|
||||
}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="More">
|
||||
<img src={moreIcon} alt="icon more" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
|
||||
{deleteModalVisible && (
|
||||
<DeleteMessageConfirm closeModal={toggleDeleteModal} mids={mid} />
|
||||
)}
|
||||
{forwardModalVisible && (
|
||||
<ForwardModal mids={mids} closeModal={toggleForwardModal} />
|
||||
)}
|
||||
{pinModalVisible && (
|
||||
<PinMessageModal
|
||||
mid={mid}
|
||||
gid={contextId}
|
||||
closeModal={togglePinModal}
|
||||
/>
|
||||
)}
|
||||
</StyledCmds>
|
||||
);
|
||||
{deleteModalVisible && <DeleteMessageConfirm closeModal={toggleDeleteModal} mids={mid} />}
|
||||
{forwardModalVisible && <ForwardModal mids={mids} closeModal={toggleForwardModal} />}
|
||||
{pinModalVisible && <PinMessageModal mid={mid} gid={contextId} closeModal={togglePinModal} />}
|
||||
</StyledCmds>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,95 +1,94 @@
|
||||
import styled from "styled-components";
|
||||
import styled from 'styled-components';
|
||||
const StyledMsg = styled.div`
|
||||
position: relative;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 4px 8px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto 150px;
|
||||
&.in_view {
|
||||
content-visibility: visible;
|
||||
}
|
||||
&[data-highlight='true'] {
|
||||
background: #f5f6f7;
|
||||
}
|
||||
&:hover,
|
||||
&.preview {
|
||||
content-visibility: inherit;
|
||||
contain-intrinsic-size: inherit;
|
||||
background: #f5f6f7;
|
||||
.cmds {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
&.readonly:hover {
|
||||
background: none;
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
> .details {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 4px 8px;
|
||||
margin: 8px 0;
|
||||
border-radius: 8px;
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: auto 150px;
|
||||
&.in_view {
|
||||
content-visibility: visible;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
.up {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
.name {
|
||||
color: #06b6d4;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.time {
|
||||
color: #bfbfbf;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
&[data-highlight="true"] {
|
||||
background: #f5f6f7;
|
||||
}
|
||||
&:hover,
|
||||
&.preview {
|
||||
content-visibility: inherit;
|
||||
contain-intrinsic-size: inherit;
|
||||
background: #f5f6f7;
|
||||
.cmds {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
&.readonly:hover {
|
||||
background: none;
|
||||
}
|
||||
.avatar {
|
||||
flex-shrink: 0;
|
||||
.down {
|
||||
user-select: text;
|
||||
color: #374151;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
.edited {
|
||||
margin-left: 5px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
&.sending {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.img {
|
||||
max-width: 480px;
|
||||
cursor: pointer;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
> .details {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.up {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
.name {
|
||||
color: #06b6d4;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.time {
|
||||
color: #bfbfbf;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
.down {
|
||||
user-select: text;
|
||||
color: #374151;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
word-break: break-all;
|
||||
white-space: break-spaces;
|
||||
.edited {
|
||||
margin-left: 5px;
|
||||
color: #999;
|
||||
font-size: 10px;
|
||||
}
|
||||
&.sending {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.img {
|
||||
max-width: 480px;
|
||||
cursor: pointer;
|
||||
}
|
||||
> .link {
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
/* background-color: #f5feff; */
|
||||
padding: 2px;
|
||||
color: #1fe1f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .link {
|
||||
text-decoration: none;
|
||||
border-radius: 2px;
|
||||
/* background-color: #f5feff; */
|
||||
padding: 2px;
|
||||
color: #1fe1f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledMsg;
|
||||
|
||||
@@ -1,272 +1,266 @@
|
||||
import { useRef, useEffect, useState, useCallback } from "react";
|
||||
import { useKey } from "rooks";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Editor, Transforms } from "slate";
|
||||
import { useRef, useEffect, useState, useCallback } from 'react';
|
||||
import { useKey } from 'rooks';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Editor, Transforms } from 'slate';
|
||||
import {
|
||||
createPlateUI,
|
||||
Plate,
|
||||
createExitBreakPlugin,
|
||||
createTrailingBlockPlugin,
|
||||
createNodeIdPlugin,
|
||||
createParagraphPlugin,
|
||||
// createImagePlugin,
|
||||
createSoftBreakPlugin,
|
||||
createComboboxPlugin,
|
||||
createMentionPlugin,
|
||||
// comboboxSelectors,
|
||||
// getMentionOnSelectItem,
|
||||
// findMentionInput,
|
||||
// removeMentionInput,
|
||||
// isSelectionInMentionInput,
|
||||
createPlugins,
|
||||
ELEMENT_PARAGRAPH,
|
||||
getPlateEditorRef,
|
||||
// usePlateEditorRef,
|
||||
// ELEMENT_IMAGE,
|
||||
MentionCombobox,
|
||||
} from "@udecode/plate";
|
||||
import { ReactEditor } from "slate-react";
|
||||
import Styled from "./styled";
|
||||
import { CONFIG } from "./config";
|
||||
import Contact from "../Contact";
|
||||
import { updateUploadFiles } from "../../../app/slices/ui";
|
||||
export const TEXT_EDITOR_PREFIX = "rustchat_text_editor";
|
||||
createPlateUI,
|
||||
Plate,
|
||||
createExitBreakPlugin,
|
||||
createTrailingBlockPlugin,
|
||||
createNodeIdPlugin,
|
||||
createParagraphPlugin,
|
||||
// createImagePlugin,
|
||||
createSoftBreakPlugin,
|
||||
// createComboboxPlugin,
|
||||
createMentionPlugin,
|
||||
// comboboxSelectors,
|
||||
// getMentionOnSelectItem,
|
||||
// findMentionInput,
|
||||
// removeMentionInput,
|
||||
// isSelectionInMentionInput,
|
||||
createPlugins,
|
||||
ELEMENT_PARAGRAPH,
|
||||
getPlateEditorRef,
|
||||
// usePlateEditorRef,
|
||||
// ELEMENT_IMAGE,
|
||||
MentionCombobox
|
||||
} from '@udecode/plate';
|
||||
import { createComboboxPlugin } from '@udecode/plate-combobox';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import Styled from './styled';
|
||||
import { CONFIG } from './config';
|
||||
import Contact from '../Contact';
|
||||
import { updateUploadFiles } from '../../../app/slices/ui';
|
||||
export const TEXT_EDITOR_PREFIX = 'rustchat_text_editor';
|
||||
|
||||
let components = createPlateUI({
|
||||
// [ELEMENT_IMAGE]: ImageElement,
|
||||
// customize your components by plugin key
|
||||
// [ELEMENT_IMAGE]: ImageElement,
|
||||
// customize your components by plugin key
|
||||
});
|
||||
const initialValue = [{ type: ELEMENT_PARAGRAPH, children: [{ text: "" }] }];
|
||||
const initialValue = [{ type: ELEMENT_PARAGRAPH, children: [{ text: '' }] }];
|
||||
const Plugins = ({
|
||||
id = "",
|
||||
placeholder = "Write some markdown...",
|
||||
sendMessages,
|
||||
members = [],
|
||||
id = '',
|
||||
placeholder = 'Write some markdown...',
|
||||
sendMessages,
|
||||
members = []
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const enableMentions = members.length > 0;
|
||||
const filesRef = useRef([]);
|
||||
const contactData = useSelector((store) => store.contacts.byId);
|
||||
const [msgs, setMsgs] = useState([]);
|
||||
const [cmdKey, setCmdKey] = useState(false);
|
||||
const editableRef = useRef(null);
|
||||
const initialProps = {
|
||||
...CONFIG.editableProps,
|
||||
className: "box",
|
||||
placeholder,
|
||||
const dispatch = useDispatch();
|
||||
const enableMentions = members.length > 0;
|
||||
const filesRef = useRef([]);
|
||||
const contactData = useSelector((store) => store.contacts.byId);
|
||||
const [msgs, setMsgs] = useState([]);
|
||||
const [cmdKey, setCmdKey] = useState(false);
|
||||
const editableRef = useRef(null);
|
||||
const initialProps = {
|
||||
...CONFIG.editableProps,
|
||||
className: 'box',
|
||||
placeholder
|
||||
};
|
||||
useEffect(() => {
|
||||
const handlePasteEvent = (evt) => {
|
||||
const files = [...evt.clipboardData.files];
|
||||
if (files.length) {
|
||||
const filesData = files.map((file) => {
|
||||
const { size, type, name } = file;
|
||||
console.log('paste event name', name);
|
||||
const url = URL.createObjectURL(file);
|
||||
return { size, type, name, url };
|
||||
});
|
||||
const [context, to] = id.split('_');
|
||||
|
||||
console.log('paste event', context, to, files, evt);
|
||||
dispatch(updateUploadFiles({ context, id: to, data: filesData }));
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
const handlePasteEvent = (evt) => {
|
||||
const files = [...evt.clipboardData.files];
|
||||
if (files.length) {
|
||||
const filesData = files.map((file) => {
|
||||
const { size, type, name } = file;
|
||||
console.log("paste event name", name);
|
||||
const url = URL.createObjectURL(file);
|
||||
return { size, type, name, url };
|
||||
});
|
||||
const [context, to] = id.split("_");
|
||||
window.addEventListener('paste', handlePasteEvent);
|
||||
return () => {
|
||||
window.removeEventListener('paste', handlePasteEvent);
|
||||
};
|
||||
// window.addEventListener("paste")
|
||||
}, []);
|
||||
|
||||
console.log("paste event", context, to, files, evt);
|
||||
dispatch(updateUploadFiles({ context, id: to, data: filesData }));
|
||||
}
|
||||
};
|
||||
window.addEventListener("paste", handlePasteEvent);
|
||||
return () => {
|
||||
window.removeEventListener("paste", handlePasteEvent);
|
||||
};
|
||||
// window.addEventListener("paste")
|
||||
}, []);
|
||||
|
||||
useKey(
|
||||
"Enter",
|
||||
(evt) => {
|
||||
console.log("enter keypress", evt);
|
||||
if (evt.shiftKey || evt.ctrlKey || evt.altKey || evt.isComposing) {
|
||||
return true;
|
||||
}
|
||||
evt.preventDefault();
|
||||
sendMessages(msgs);
|
||||
// 清空
|
||||
const plateEditor = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`);
|
||||
Transforms.delete(plateEditor, {
|
||||
at: {
|
||||
anchor: Editor.start(plateEditor, []),
|
||||
focus: Editor.end(plateEditor, []),
|
||||
useKey(
|
||||
'Enter',
|
||||
(evt) => {
|
||||
console.log('enter keypress', evt);
|
||||
if (evt.shiftKey || evt.ctrlKey || evt.altKey || evt.isComposing) {
|
||||
return true;
|
||||
}
|
||||
evt.preventDefault();
|
||||
sendMessages(msgs);
|
||||
// 清空
|
||||
const plateEditor = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${id}`);
|
||||
Transforms.delete(plateEditor, {
|
||||
at: {
|
||||
anchor: Editor.start(plateEditor, []),
|
||||
focus: Editor.end(plateEditor, [])
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
// eventTypes: ["keydown"],
|
||||
target: editableRef,
|
||||
when: !cmdKey
|
||||
}
|
||||
);
|
||||
useKey(
|
||||
[91, 93],
|
||||
(evt) => {
|
||||
setCmdKey(evt.type == 'keydown');
|
||||
console.log('cmd', evt.type);
|
||||
},
|
||||
{
|
||||
eventTypes: ['keydown', 'keyup'],
|
||||
target: editableRef
|
||||
}
|
||||
);
|
||||
const pluginArr = [
|
||||
createParagraphPlugin(),
|
||||
createNodeIdPlugin(),
|
||||
createSoftBreakPlugin(CONFIG.softBreak),
|
||||
createTrailingBlockPlugin(CONFIG.trailingBlock),
|
||||
createExitBreakPlugin(CONFIG.exitBreak)
|
||||
];
|
||||
const plugins = createPlugins(
|
||||
enableMentions
|
||||
? pluginArr.concat([
|
||||
createComboboxPlugin(),
|
||||
createMentionPlugin({
|
||||
options: {
|
||||
createMentionNode: (item) => {
|
||||
console.log('mention', item);
|
||||
const {
|
||||
text,
|
||||
data: { uid }
|
||||
} = item;
|
||||
return { value: `@${text}`, uid };
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
// eventTypes: ["keydown"],
|
||||
target: editableRef,
|
||||
when: !cmdKey,
|
||||
}
|
||||
);
|
||||
useKey(
|
||||
[91, 93],
|
||||
(evt) => {
|
||||
setCmdKey(evt.type == "keydown");
|
||||
console.log("cmd", evt.type);
|
||||
},
|
||||
{
|
||||
eventTypes: ["keydown", "keyup"],
|
||||
target: editableRef,
|
||||
}
|
||||
);
|
||||
const pluginArr = [
|
||||
createParagraphPlugin(),
|
||||
createNodeIdPlugin(),
|
||||
createSoftBreakPlugin(CONFIG.softBreak),
|
||||
createTrailingBlockPlugin(CONFIG.trailingBlock),
|
||||
createExitBreakPlugin(CONFIG.exitBreak),
|
||||
];
|
||||
const plugins = createPlugins(
|
||||
enableMentions
|
||||
? pluginArr.concat([
|
||||
createComboboxPlugin(),
|
||||
createMentionPlugin({
|
||||
options: {
|
||||
createMentionNode: (item) => {
|
||||
console.log("mention", item);
|
||||
const {
|
||||
text,
|
||||
data: { uid },
|
||||
} = item;
|
||||
return { value: `@${text}`, uid };
|
||||
},
|
||||
insertSpaceAfterMention: true,
|
||||
},
|
||||
}),
|
||||
])
|
||||
: pluginArr,
|
||||
{
|
||||
components,
|
||||
}
|
||||
);
|
||||
insertSpaceAfterMention: true
|
||||
}
|
||||
})
|
||||
])
|
||||
: pluginArr,
|
||||
{
|
||||
components
|
||||
}
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
async (val) => {
|
||||
console.log("tmps changed", val);
|
||||
const tmps = [];
|
||||
const getMixedText = (children) => {
|
||||
const mentions = [];
|
||||
const arr = children.map(({ type, text, uid }) => {
|
||||
if (type == "mention") {
|
||||
mentions.push(uid);
|
||||
return ` @${uid} `;
|
||||
}
|
||||
return text;
|
||||
});
|
||||
return { value: arr.join(""), mentions };
|
||||
};
|
||||
for (const v of val) {
|
||||
if (v.type == "img") {
|
||||
// img
|
||||
const url = v.url;
|
||||
const file_path = decodeURIComponent(
|
||||
new URL(url).searchParams.get("file_path")
|
||||
);
|
||||
console.log("files", filesRef.current, file_path);
|
||||
const json = filesRef.current.find((f) => f.path == file_path) || {};
|
||||
const { name, size, hash, path, ...rest } = json;
|
||||
tmps.push({
|
||||
type: "file",
|
||||
content: { name, size, hash, path },
|
||||
properties: rest,
|
||||
});
|
||||
} else {
|
||||
// p
|
||||
const { value, mentions } = getMixedText(v.children);
|
||||
const prev = tmps[tmps.length - 1];
|
||||
if (!prev) {
|
||||
tmps.push([
|
||||
{ type: "text", content: value, properties: { mentions } },
|
||||
]);
|
||||
} else {
|
||||
if (Array.isArray(prev)) {
|
||||
tmps[tmps.length - 1].push({
|
||||
type: "text",
|
||||
content: value,
|
||||
properties: { mentions },
|
||||
});
|
||||
} else {
|
||||
tmps.push([
|
||||
{ type: "text", content: value, properties: { mentions } },
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleChange = useCallback(
|
||||
async (val) => {
|
||||
console.log('tmps changed', val);
|
||||
const tmps = [];
|
||||
const getMixedText = (children) => {
|
||||
const mentions = [];
|
||||
const arr = children.map(({ type, text, uid }) => {
|
||||
if (type == 'mention') {
|
||||
mentions.push(uid);
|
||||
return ` @${uid} `;
|
||||
}
|
||||
return text;
|
||||
});
|
||||
return { value: arr.join(''), mentions };
|
||||
};
|
||||
for (const v of val) {
|
||||
if (v.type == 'img') {
|
||||
// img
|
||||
const url = v.url;
|
||||
const file_path = decodeURIComponent(new URL(url).searchParams.get('file_path'));
|
||||
console.log('files', filesRef.current, file_path);
|
||||
const json = filesRef.current.find((f) => f.path == file_path) || {};
|
||||
const { name, size, hash, path, ...rest } = json;
|
||||
tmps.push({
|
||||
type: 'file',
|
||||
content: { name, size, hash, path },
|
||||
properties: rest
|
||||
});
|
||||
} else {
|
||||
// p
|
||||
const { value, mentions } = getMixedText(v.children);
|
||||
const prev = tmps[tmps.length - 1];
|
||||
if (!prev) {
|
||||
tmps.push([{ type: 'text', content: value, properties: { mentions } }]);
|
||||
} else {
|
||||
if (Array.isArray(prev)) {
|
||||
tmps[tmps.length - 1].push({
|
||||
type: 'text',
|
||||
content: value,
|
||||
properties: { mentions }
|
||||
});
|
||||
} else {
|
||||
tmps.push([{ type: 'text', content: value, properties: { mentions } }]);
|
||||
}
|
||||
const arr = tmps.map((tmp) => {
|
||||
return Array.isArray(tmp)
|
||||
? {
|
||||
type: "text",
|
||||
content: tmp.map((t) => t.content).join("\n"),
|
||||
properties: {
|
||||
mentions: tmp.map((t) => t.properties?.mentions || []).flat(),
|
||||
},
|
||||
}
|
||||
: tmp;
|
||||
});
|
||||
const msgs = arr.filter(({ content }) => !!content);
|
||||
setMsgs(msgs);
|
||||
console.log("tmps", tmps, arr, msgs);
|
||||
},
|
||||
[msgs]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const arr = tmps.map((tmp) => {
|
||||
return Array.isArray(tmp)
|
||||
? {
|
||||
type: 'text',
|
||||
content: tmp.map((t) => t.content).join('\n'),
|
||||
properties: {
|
||||
mentions: tmp.map((t) => t.properties?.mentions || []).flat()
|
||||
}
|
||||
}
|
||||
: tmp;
|
||||
});
|
||||
const msgs = arr.filter(({ content }) => !!content);
|
||||
setMsgs(msgs);
|
||||
console.log('tmps', tmps, arr, msgs);
|
||||
},
|
||||
[msgs]
|
||||
);
|
||||
|
||||
return (
|
||||
<Styled className="input" ref={editableRef}>
|
||||
<Plate
|
||||
edi
|
||||
onChange={handleChange}
|
||||
id={`${TEXT_EDITOR_PREFIX}_${id}`}
|
||||
editableProps={{ ...initialProps, style: { userSelect: "text" } }}
|
||||
initialValue={initialValue}
|
||||
plugins={plugins}
|
||||
>
|
||||
{enableMentions ? (
|
||||
<MentionCombobox
|
||||
// component={StyledCombobox}
|
||||
onRenderItem={({ item }) => {
|
||||
console.log("wtf", item);
|
||||
return <Contact uid={item.data.uid} interactive={false} />;
|
||||
}}
|
||||
items={members.map((id) => {
|
||||
const data = contactData[id];
|
||||
if (!data) return null;
|
||||
const { uid, name, ...rest } = data;
|
||||
return {
|
||||
key: uid,
|
||||
text: name,
|
||||
data: {
|
||||
uid,
|
||||
...rest,
|
||||
},
|
||||
};
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</Plate>
|
||||
</Styled>
|
||||
);
|
||||
return (
|
||||
<Styled className="input" ref={editableRef}>
|
||||
<Plate
|
||||
onChange={handleChange}
|
||||
id={`${TEXT_EDITOR_PREFIX}_${id}`}
|
||||
editableProps={{ ...initialProps, style: { userSelect: 'text' } }}
|
||||
initialValue={initialValue}
|
||||
plugins={plugins}
|
||||
>
|
||||
{enableMentions ? (
|
||||
<MentionCombobox
|
||||
// component={StyledCombobox}
|
||||
onRenderItem={({ item }) => {
|
||||
console.log('wtf', item);
|
||||
return <Contact uid={item.data.uid} interactive={false} />;
|
||||
}}
|
||||
items={members.map((id) => {
|
||||
const data = contactData[id];
|
||||
if (!data) return null;
|
||||
const { uid, name, ...rest } = data;
|
||||
return {
|
||||
key: uid,
|
||||
text: name,
|
||||
data: {
|
||||
uid,
|
||||
...rest
|
||||
}
|
||||
};
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</Plate>
|
||||
</Styled>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMixedEditor = (key) => {
|
||||
const editorRef = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${key}`);
|
||||
const focus = () => {
|
||||
if (editorRef) {
|
||||
ReactEditor.focus(editorRef);
|
||||
}
|
||||
};
|
||||
const insertText = (txt) => {
|
||||
if (editorRef) {
|
||||
editorRef.insertText(txt);
|
||||
}
|
||||
};
|
||||
return {
|
||||
focus,
|
||||
insertText,
|
||||
};
|
||||
const editorRef = getPlateEditorRef(`${TEXT_EDITOR_PREFIX}_${key}`);
|
||||
const focus = () => {
|
||||
if (editorRef) {
|
||||
ReactEditor.focus(editorRef);
|
||||
}
|
||||
};
|
||||
const insertText = (txt) => {
|
||||
if (editorRef) {
|
||||
editorRef.insertText(txt);
|
||||
}
|
||||
};
|
||||
return {
|
||||
focus,
|
||||
insertText
|
||||
};
|
||||
};
|
||||
export default Plugins;
|
||||
|
||||
Reference in New Issue
Block a user