feat: select multiple messages and forward
This commit is contained in:
@@ -5,7 +5,6 @@ import { useReadMessageMutation } from "../../../app/services/message";
|
||||
import useChatScroll from "../../../common/hook/useChatScroll";
|
||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||
import Tooltip from "../../../common/component/Tooltip";
|
||||
import Send from "../../../common/component/Send";
|
||||
import Contact from "../../../common/component/Contact";
|
||||
import Layout from "../Layout";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
@@ -13,7 +12,7 @@ import EditIcon from "../../../assets/icons/edit.svg";
|
||||
import alertIcon from "../../../assets/icons/alert.svg?url";
|
||||
import peopleIcon from "../../../assets/icons/people.svg?url";
|
||||
import pinIcon from "../../../assets/icons/pin.svg?url";
|
||||
import searchIcon from "../../../assets/icons/search.svg?url";
|
||||
// import searchIcon from "../../../assets/icons/search.svg?url";
|
||||
import headphoneIcon from "../../../assets/icons/headphone.svg?url";
|
||||
import boardosIcon from "../../../assets/icons/app.boardos.svg?url";
|
||||
import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url";
|
||||
@@ -35,6 +34,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
||||
// const dispatch = useDispatch();
|
||||
const {
|
||||
selects,
|
||||
msgIds,
|
||||
userIds,
|
||||
data,
|
||||
@@ -44,6 +44,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
footprint,
|
||||
} = useSelector((store) => {
|
||||
return {
|
||||
selects: store.ui.selectMessages[`channel_${cid}`],
|
||||
footprint: store.footprint,
|
||||
loginUser: store.contacts.byId[store.authData.uid],
|
||||
loginUid: store.authData.uid,
|
||||
@@ -150,52 +151,40 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<StyledChannelChat>
|
||||
<div className="wrapper">
|
||||
<div className="chat" ref={ref}>
|
||||
<div className="info">
|
||||
<h2 className="title">Welcome to #{name} !</h2>
|
||||
<p className="desc">
|
||||
This is the start of the #{name} channel.{" "}
|
||||
</p>
|
||||
<NavLink
|
||||
to={`/setting/channel/${cid}?f=${pathname}`}
|
||||
className="edit"
|
||||
>
|
||||
<EditIcon className="icon" />
|
||||
Edit Channel
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="feed">
|
||||
{[...msgIds]
|
||||
.sort((a, b) => {
|
||||
return Number(a) - Number(b);
|
||||
})
|
||||
.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
if (!curr) return null;
|
||||
const isFirst = idx == 0;
|
||||
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||
return renderMessageFragment({
|
||||
updateReadIndex: updateReadDebounced,
|
||||
read,
|
||||
isFirst,
|
||||
prev,
|
||||
curr,
|
||||
contextId: cid,
|
||||
context: "channel",
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Send
|
||||
key={cid}
|
||||
id={cid}
|
||||
context="channel"
|
||||
name={name}
|
||||
members={memberIds}
|
||||
/>
|
||||
<StyledChannelChat ref={ref}>
|
||||
<div className="info">
|
||||
<h2 className="title">Welcome to #{name} !</h2>
|
||||
<p className="desc">This is the start of the #{name} channel. </p>
|
||||
<NavLink
|
||||
to={`/setting/channel/${cid}?f=${pathname}`}
|
||||
className="edit"
|
||||
>
|
||||
<EditIcon className="icon" />
|
||||
Edit Channel
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="feed">
|
||||
{[...msgIds]
|
||||
.sort((a, b) => {
|
||||
return Number(a) - Number(b);
|
||||
})
|
||||
.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
if (!curr) return null;
|
||||
const isFirst = idx == 0;
|
||||
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||
return renderMessageFragment({
|
||||
selectMode: !!selects,
|
||||
updateReadIndex: updateReadDebounced,
|
||||
read,
|
||||
isFirst,
|
||||
prev,
|
||||
curr,
|
||||
contextId: cid,
|
||||
context: "channel",
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
</StyledChannelChat>
|
||||
{/* {unreads != 0 && (
|
||||
|
||||
@@ -82,68 +82,53 @@ export const StyledContacts = styled.div`
|
||||
}
|
||||
`;
|
||||
export const StyledChannelChat = styled.article`
|
||||
position: relative;
|
||||
padding: 18px 16px;
|
||||
width: 100%;
|
||||
/* margin-bottom: 120px; */
|
||||
> .wrapper {
|
||||
background-color: #fff;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
overflow: auto;
|
||||
> .info {
|
||||
padding-top: 62px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
padding-bottom: 16px;
|
||||
height: calc(100vh - 56px - 22px);
|
||||
.chat {
|
||||
padding: 18px 16px;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
overflow: auto;
|
||||
> .info {
|
||||
padding-top: 62px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
}
|
||||
.desc {
|
||||
color: #78787c;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
path {
|
||||
fill: #3c8ce7;
|
||||
}
|
||||
}
|
||||
padding: 0;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
> .feed {
|
||||
/* display: flex;
|
||||
flex-direction: column-reverse; */
|
||||
}
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
}
|
||||
> .send {
|
||||
margin: 0 16px;
|
||||
.desc {
|
||||
color: #78787c;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.edit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
path {
|
||||
fill: #3c8ce7;
|
||||
}
|
||||
}
|
||||
padding: 0;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
> .feed {
|
||||
/* display: flex;
|
||||
flex-direction: column-reverse; */
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -4,11 +4,10 @@ import { useDebounce } from "rooks";
|
||||
import Tooltip from "../../../common/component/Tooltip";
|
||||
import alertIcon from "../../../assets/icons/alert.svg?url";
|
||||
import pinIcon from "../../../assets/icons/pin.svg?url";
|
||||
import searchIcon from "../../../assets/icons/search.svg?url";
|
||||
// import searchIcon from "../../../assets/icons/search.svg?url";
|
||||
import boardosIcon from "../../../assets/icons/app.boardos.svg?url";
|
||||
import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url";
|
||||
import useChatScroll from "../../../common/hook/useChatScroll";
|
||||
import Send from "../../../common/component/Send";
|
||||
import { useReadMessageMutation } from "../../../app/services/message";
|
||||
import Contact from "../../../common/component/Contact";
|
||||
import Layout from "../Layout";
|
||||
@@ -19,17 +18,23 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
|
||||
const updateReadDebounced = useDebounce(updateReadIndex, 300);
|
||||
console.log("dm files", dropFiles);
|
||||
// const [mids, setMids] = useState([]);
|
||||
const { msgIds, currUser, messageData, footprint, loginUid } = useSelector(
|
||||
(store) => {
|
||||
return {
|
||||
loginUid: store.authData.uid,
|
||||
footprint: store.footprint,
|
||||
currUser: store.contacts.byId[uid],
|
||||
msgIds: store.userMessage.byId[uid] || [],
|
||||
messageData: store.message,
|
||||
};
|
||||
}
|
||||
);
|
||||
const {
|
||||
msgIds,
|
||||
currUser,
|
||||
messageData,
|
||||
footprint,
|
||||
loginUid,
|
||||
selects,
|
||||
} = useSelector((store) => {
|
||||
return {
|
||||
selects: store.ui.selectMessages[`user_${uid}`],
|
||||
loginUid: store.authData.uid,
|
||||
footprint: store.footprint,
|
||||
currUser: store.contacts.byId[uid],
|
||||
msgIds: store.userMessage.byId[uid] || [],
|
||||
messageData: store.message,
|
||||
};
|
||||
});
|
||||
const ref = useChatScroll(msgIds);
|
||||
|
||||
if (!currUser) return null;
|
||||
@@ -74,32 +79,25 @@ export default function DMChat({ uid = "", dropFiles = [] }) {
|
||||
</StyledHeader>
|
||||
}
|
||||
>
|
||||
<StyledDMChat>
|
||||
<div className="chat" ref={ref}>
|
||||
{[...msgIds]
|
||||
.sort((a, b) => {
|
||||
return Number(a) - Number(b);
|
||||
})
|
||||
.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||
return renderMessageFragment({
|
||||
updateReadIndex: updateReadDebounced,
|
||||
read,
|
||||
prev,
|
||||
curr,
|
||||
contextId: uid,
|
||||
context: "user",
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
<Send
|
||||
key={currUser?.uid}
|
||||
context="user"
|
||||
name={currUser?.name}
|
||||
id={currUser?.uid}
|
||||
/>
|
||||
<StyledDMChat ref={ref}>
|
||||
{[...msgIds]
|
||||
.sort((a, b) => {
|
||||
return Number(a) - Number(b);
|
||||
})
|
||||
.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||
return renderMessageFragment({
|
||||
selectMode: !!selects,
|
||||
updateReadIndex: updateReadDebounced,
|
||||
read,
|
||||
prev,
|
||||
curr,
|
||||
contextId: uid,
|
||||
context: "user",
|
||||
});
|
||||
})}
|
||||
</StyledDMChat>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
@@ -28,20 +28,9 @@ export const StyledHeader = styled.header`
|
||||
}
|
||||
`;
|
||||
export const StyledDMChat = styled.article`
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
padding-bottom: 16px;
|
||||
height: calc(100vh - 56px - 22px);
|
||||
> .chat {
|
||||
padding: 18px 16px;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
overflow: auto;
|
||||
}
|
||||
> .send {
|
||||
margin: 0 16px;
|
||||
}
|
||||
padding: 18px 16px;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useKey } from "rooks";
|
||||
import { updateSelectMessages } from "../../../app/slices/ui";
|
||||
import IconForward from "../../../assets/icons/forward.svg";
|
||||
import IconBookmark from "../../../assets/icons/bookmark.svg";
|
||||
import IconDelete from "../../../assets/icons/delete.svg";
|
||||
import IconClose from "../../../assets/icons/close.circle.svg";
|
||||
import ForwardModal from "../../../common/component/ForwardModal";
|
||||
const Styled = styled.div`
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
/* padding-bottom: 0; */
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0px -1px 0px rgba(0, 0, 0, 0.05);
|
||||
.opt {
|
||||
padding: 8px;
|
||||
background: #f2f4f7;
|
||||
border-radius: var(--br);
|
||||
}
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
`;
|
||||
export default function Operations({ context, id }) {
|
||||
const mids = useSelector(
|
||||
(store) => store.ui.selectMessages[`${context}_${id}`]
|
||||
);
|
||||
const [forwardModalVisible, setForwardModalVisible] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const handleClose = () => {
|
||||
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
|
||||
};
|
||||
const toggleForwardModal = () => {
|
||||
setForwardModalVisible((prev) => !prev);
|
||||
};
|
||||
useKey("Escape", (evt) => {
|
||||
console.log("Escape keypress", evt);
|
||||
dispatch(updateSelectMessages({ context, id, operation: "reset" }));
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<Styled>
|
||||
<button className="opt" onClick={toggleForwardModal}>
|
||||
<IconForward />
|
||||
</button>
|
||||
<button className="opt">
|
||||
<IconBookmark />
|
||||
</button>
|
||||
<button className="opt">
|
||||
<IconDelete />
|
||||
</button>
|
||||
<IconClose className="close" onClick={handleClose} />
|
||||
</Styled>
|
||||
{forwardModalVisible && (
|
||||
<ForwardModal mids={mids} closeModal={toggleForwardModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +1,20 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { NativeTypes } from "react-dnd-html5-backend";
|
||||
import styled from "styled-components";
|
||||
import { updateUploadFiles } from "../../app/slices/ui";
|
||||
import ImagePreviewModal from "../../common/component/ImagePreviewModal";
|
||||
const StyledWrapper = styled.article`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-top-right-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
> .head {
|
||||
box-sizing: content-box;
|
||||
height: 56px;
|
||||
padding: 0 20px;
|
||||
/* box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1); */
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
> .main {
|
||||
height: calc(100vh - 56px - 22px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
.members {
|
||||
box-shadow: inset 0px 10px 2px -10px rgba(0, 0, 0, 0.1);
|
||||
/* margin-top: 1px; */
|
||||
/* border-top: 1px solid transparent; */
|
||||
}
|
||||
> .aside {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -56px;
|
||||
transform: translateX(100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.divider {
|
||||
border: none;
|
||||
background-color: #d4d4d4;
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
margin: 18px auto;
|
||||
}
|
||||
.tools,
|
||||
.apps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tools {
|
||||
gap: 24px;
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.apps {
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drag_tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
visibility: hidden;
|
||||
/* pointer-events: none; */
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.box {
|
||||
padding: 16px;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
border-radius: 8px;
|
||||
background: #52edff;
|
||||
.inner {
|
||||
padding: 16px;
|
||||
padding-top: 64px;
|
||||
border: 2px dashed #a5f3fc;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
.head {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.intro {
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { updateUploadFiles } from "../../../app/slices/ui";
|
||||
import ImagePreviewModal from "../../../common/component/ImagePreviewModal";
|
||||
import Send from "../../../common/component/Send";
|
||||
import Styled from "./styled";
|
||||
import Operations from "./Operations";
|
||||
|
||||
export default function Layout({
|
||||
children,
|
||||
header,
|
||||
aside = null,
|
||||
contacts = null,
|
||||
// dropFiles = [],
|
||||
dropFiles = [],
|
||||
context = "channel",
|
||||
to = null,
|
||||
}) {
|
||||
@@ -119,6 +22,9 @@ export default function Layout({
|
||||
|
||||
const messagesContainer = useRef(null);
|
||||
const [previewImage, setPreviewImage] = useState(null);
|
||||
const selects = useSelector(
|
||||
(store) => store.ui.selectMessages[`${context}_${to}`]
|
||||
);
|
||||
const [{ isActive }, drop] = useDrop(
|
||||
() => ({
|
||||
accept: [NativeTypes.FILE],
|
||||
@@ -139,11 +45,16 @@ export default function Layout({
|
||||
}),
|
||||
[context, to]
|
||||
);
|
||||
// useEffect(() => {
|
||||
// if (dropFiles?.length) {
|
||||
// setFiles((prevs) => [...prevs, ...dropFiles]);
|
||||
// }
|
||||
// }, [dropFiles]);
|
||||
useEffect(() => {
|
||||
if (dropFiles?.length) {
|
||||
const filesData = dropFiles.map((file) => {
|
||||
const { size, type, name } = file;
|
||||
const url = URL.createObjectURL(file);
|
||||
return { size, type, name, url };
|
||||
});
|
||||
dispatch(updateUploadFiles({ context, id: to, data: filesData }));
|
||||
}
|
||||
}, [dropFiles]);
|
||||
|
||||
const closePreviewModal = () => {
|
||||
setPreviewImage(null);
|
||||
@@ -177,10 +88,16 @@ export default function Layout({
|
||||
{previewImage && (
|
||||
<ImagePreviewModal data={previewImage} closeModal={closePreviewModal} />
|
||||
)}
|
||||
<StyledWrapper ref={drop}>
|
||||
<Styled ref={drop}>
|
||||
{header}
|
||||
<main className="main" ref={messagesContainer}>
|
||||
{children}
|
||||
<div className="chat">
|
||||
{children}
|
||||
<div className={`send ${selects ? "selecting" : ""}`}>
|
||||
<Send key={to} id={to} context={context} />
|
||||
{selects && <Operations context={context} id={to} />}
|
||||
</div>
|
||||
</div>
|
||||
{contacts && <div className="members">{contacts}</div>}
|
||||
{aside && <div className="aside">{aside}</div>}
|
||||
</main>
|
||||
@@ -202,7 +119,7 @@ export default function Layout({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</Styled>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import styled from "styled-components";
|
||||
const Styled = styled.article`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-top-right-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
> .head {
|
||||
box-sizing: content-box;
|
||||
height: 56px;
|
||||
padding: 0 20px;
|
||||
/* box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1); */
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
> .main {
|
||||
height: calc(100vh - 56px - 22px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
> .chat {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
/* padding-bottom: 16px; */
|
||||
height: calc(100vh - 56px - 22px);
|
||||
> .send {
|
||||
padding: 0 16px 16px 16px;
|
||||
&.selecting {
|
||||
padding: 0;
|
||||
> .send {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.members {
|
||||
box-shadow: inset 0px 10px 2px -10px rgba(0, 0, 0, 0.1);
|
||||
/* margin-top: 1px; */
|
||||
/* border-top: 1px solid transparent; */
|
||||
}
|
||||
> .aside {
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -56px;
|
||||
transform: translateX(100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.divider {
|
||||
border: none;
|
||||
background-color: #d4d4d4;
|
||||
width: 16px;
|
||||
height: 1px;
|
||||
margin: 18px auto;
|
||||
}
|
||||
.tools,
|
||||
.apps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tools {
|
||||
gap: 24px;
|
||||
.tool {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.apps {
|
||||
gap: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.drag_tip {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
visibility: hidden;
|
||||
/* pointer-events: none; */
|
||||
&.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
.box {
|
||||
padding: 16px;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
border-radius: 8px;
|
||||
background: #52edff;
|
||||
.inner {
|
||||
padding: 16px;
|
||||
padding-top: 64px;
|
||||
border: 2px dashed #a5f3fc;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
.head {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.intro {
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default Styled;
|
||||
@@ -1,9 +1,13 @@
|
||||
import React from "react";
|
||||
import dayjs from "dayjs";
|
||||
import styled from "styled-components";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { isImage } from "../../common/utils";
|
||||
import { ContentTypes } from "../../app/config";
|
||||
import Checkbox from "../../common/component/styled/Checkbox";
|
||||
import Divider from "../../common/component/Divider";
|
||||
import Message from "../../common/component/Message";
|
||||
import { updateSelectMessages } from "../../app/slices/ui";
|
||||
// function debounce(callback, wait = 2000, immediate = false) {
|
||||
// let timeout = null;
|
||||
// return function () {
|
||||
@@ -86,7 +90,66 @@ export const renderPreviewMessage = (message = null) => {
|
||||
}
|
||||
return res;
|
||||
};
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
> .overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
> .check {
|
||||
display: none;
|
||||
margin-top: 18px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
> .message {
|
||||
flex: 1;
|
||||
}
|
||||
&.select {
|
||||
/* cursor: pointer; */
|
||||
&:hover {
|
||||
border-radius: var(--br);
|
||||
background: #f5f6f7;
|
||||
}
|
||||
> .check {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const MessageWrapper = ({ selectMode = false, context, id, mid, children }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const selects = useSelector(
|
||||
(store) => store.ui.selectMessages[`${context}_${id}`]
|
||||
);
|
||||
const selected = !!(selects && selects.find((s) => s == mid));
|
||||
const toggleSelect = () => {
|
||||
const operation = selected ? "remove" : "add";
|
||||
dispatch(updateSelectMessages({ context, id, operation, data: mid }));
|
||||
};
|
||||
return (
|
||||
<StyledWrapper className={selectMode ? "select" : ""}>
|
||||
<Checkbox className="check" checked={selected} />
|
||||
{/* {React.cloneElement(children, { readOnly: selected })} */}
|
||||
{children}
|
||||
{selectMode && (
|
||||
<div
|
||||
className="overlay"
|
||||
onClick={selectMode ? toggleSelect : null}
|
||||
></div>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
export const renderMessageFragment = ({
|
||||
selectMode = false,
|
||||
isFirst = false,
|
||||
read = true,
|
||||
updateReadIndex,
|
||||
@@ -111,15 +174,23 @@ export const renderMessageFragment = ({
|
||||
return (
|
||||
<React.Fragment key={mid}>
|
||||
{divider && <Divider content={divider}></Divider>}
|
||||
<Message
|
||||
isFirst={isFirst}
|
||||
updateReadIndex={updateReadIndex}
|
||||
read={read}
|
||||
<MessageWrapper
|
||||
context={context}
|
||||
id={contextId}
|
||||
mid={mid}
|
||||
key={mid}
|
||||
contextId={contextId}
|
||||
/>
|
||||
selectMode={selectMode}
|
||||
>
|
||||
<Message
|
||||
readOnly={selectMode}
|
||||
isFirst={isFirst}
|
||||
updateReadIndex={updateReadIndex}
|
||||
read={read}
|
||||
context={context}
|
||||
mid={mid}
|
||||
key={mid}
|
||||
contextId={contextId}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user