diff --git a/src/assets/icons/lock.svg b/src/assets/icons/lock.svg
new file mode 100644
index 00000000..099c5910
--- /dev/null
+++ b/src/assets/icons/lock.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/routes/chat/SessionList/ContextMenu.js b/src/routes/chat/SessionList/ContextMenu.js
new file mode 100644
index 00000000..881d8b5d
--- /dev/null
+++ b/src/routes/chat/SessionList/ContextMenu.js
@@ -0,0 +1,103 @@
+// import React from 'react'
+import Tippy from "@tippyjs/react";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate, useLocation, useMatch } from "react-router-dom";
+import { useUpdateMuteSettingMutation } from "../../../app/services/contact";
+import { useReadMessageMutation } from "../../../app/services/message";
+import { removeUserSession } from "../../../app/slices/message.user";
+import ContextMenu from "../../../common/component/ContextMenu";
+
+export default function SessionContextMenu({
+ context,
+ id,
+ visible,
+ mid,
+ hide,
+ deleteChannel,
+ setInviteChannelId,
+ children
+}) {
+ const [muteChannel] = useUpdateMuteSettingMutation();
+ const [updateReadIndex] = useReadMessageMutation();
+ const pathMatched = useMatch(`/chat/dm/${id}`);
+ const dispatch = useDispatch();
+ const navigateTo = useNavigate();
+ const { pathname } = useLocation();
+ const { channelMuted } = useSelector((store) => {
+ return {
+ channelMuted: context == "channel" ? store.footprint.muteChannels[id] : false
+ };
+ });
+ const handleChannelSetting = () => {
+ navigateTo(`/setting/channel/${id}?f=${pathname}`);
+ };
+ const handleReadAll = () => {
+ // console.log("last mid", mids, lastMid);
+ if (mid) {
+ const param =
+ context == "dm" ? { users: [{ uid: +id, mid }] } : { groups: [{ gid: +id, mid }] };
+ updateReadIndex(param);
+ }
+ };
+ const handleRemoveSession = () => {
+ dispatch(removeUserSession(id));
+ if (pathMatched) {
+ navigateTo("/chat");
+ }
+ };
+ const handleChannelMute = () => {
+ const data = channelMuted ? { remove_groups: [id] } : { add_groups: [{ gid: id }] };
+ muteChannel(data);
+ };
+ const items =
+ context == "dm"
+ ? [
+ {
+ title: "Mark As Read",
+ handler: handleReadAll
+ },
+ {
+ title: "Hide Session",
+ danger: true,
+ handler: handleRemoveSession
+ }
+ ]
+ : [
+ {
+ title: "Settings",
+ underline: true,
+ handler: handleChannelSetting
+ },
+ {
+ title: "Mark As Read",
+ // underline: true
+ handler: handleReadAll
+ },
+ {
+ title: channelMuted ? "Unmute" : "Mute",
+ handler: handleChannelMute
+ },
+ {
+ title: "Invite People",
+ handler: setInviteChannelId.bind(null, id)
+ },
+ {
+ title: "Delete Channel",
+ danger: true,
+ handler: deleteChannel.bind(null, id)
+ }
+ ];
+ return (
+ }
+ >
+ {children}
+
+ );
+}
diff --git a/src/routes/chat/SessionList/Session.js b/src/routes/chat/SessionList/Session.js
new file mode 100644
index 00000000..580ff99e
--- /dev/null
+++ b/src/routes/chat/SessionList/Session.js
@@ -0,0 +1,108 @@
+import { useState, useEffect } from "react";
+import { useSelector } from "react-redux";
+import dayjs from "dayjs";
+import { useDrop } from "react-dnd";
+import { NativeTypes } from "react-dnd-html5-backend";
+import relativeTime from "dayjs/plugin/relativeTime";
+import ContextMenu from "./ContextMenu";
+import { renderPreviewMessage } from "../utils";
+import Contact from "../../../common/component/Contact";
+import iconChannel from "../../../assets/icons/channel.svg?url";
+import IconLock from "../../../assets/icons/lock.svg";
+import useContextMenu from "../../../common/hook/useContextMenu";
+import { useNavigate, NavLink } from "react-router-dom";
+dayjs.extend(relativeTime);
+export default function Session({
+ type = "dm",
+ id,
+ mid,
+ setFiles,
+ setDeleteChannelId,
+ setInviteChannelId
+}) {
+ const navigate = useNavigate();
+ const [{ isActive }, drop] = useDrop(() => ({
+ accept: [NativeTypes.FILE],
+ drop({ dataTransfer }) {
+ if (dataTransfer.files.length) {
+ // console.log(files, rest);
+ setFiles([...dataTransfer.files]);
+ navigate(type == "dm" ? `/chat/dm/${id}` : `/chat/channel/${id}`);
+ // 重置
+ setTimeout(() => {
+ setFiles([]);
+ }, 300);
+ }
+ },
+ collect: (monitor) => ({
+ isActive: monitor.canDrop() && monitor.isOver()
+ })
+ }));
+ const { visible: contextMenuVisible, handleContextMenuEvent, hideContextMenu } = useContextMenu();
+ const [data, setData] = useState(null);
+ const { messageData, contactData, channelData } = useSelector((store) => {
+ return {
+ messageData: store.message,
+ contactData: store.contacts.byId,
+ channelData: store.channels.byId
+ };
+ });
+ const handleImageError = (evt) => {
+ evt.target.classList.add("channel_default");
+ evt.target.src = iconChannel;
+ };
+ useEffect(() => {
+ const tmp = type == "dm" ? contactData[id] : channelData[id];
+ if (!tmp) return;
+ const { name, icon, avatar, is_public = true } = tmp;
+ const session =
+ type == "dm" ? { name, icon: avatar, mid, is_public } : { name, icon, mid, is_public };
+ setData(session);
+ }, [id, mid, type, contactData, channelData]);
+ if (!data) return null;
+ const previewMsg = messageData[mid] || {};
+ const { name, icon, is_public } = data;
+ return (
+
+
+
+
+ {type == "dm" ? (
+
+ ) : (
+

+ )}
+
+
+
+
+ {name} {!is_public && }
+
+ {dayjs(previewMsg.created_at).fromNow()}
+
+
+ {renderPreviewMessage(previewMsg)}
+
+
+
+
+
+ );
+}
diff --git a/src/routes/chat/SessionList/index.js b/src/routes/chat/SessionList/index.js
new file mode 100644
index 00000000..ea1aac49
--- /dev/null
+++ b/src/routes/chat/SessionList/index.js
@@ -0,0 +1,84 @@
+import { useState, useEffect } from "react";
+import { useSelector } from "react-redux";
+import Styled from "./styled";
+import Session from "./Session";
+import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal";
+import InviteModal from "../../../common/component/InviteModal";
+export default function SessionList() {
+ const [deleteId, setDeleteId] = useState(null);
+ const [inviteChannelId, setInviteChannelId] = useState(null);
+ const [sessions, setSessions] = useState([]);
+ const { channelIDs, DMs, readChannels, readUsers, channelMessage, userMessage, loginUid } =
+ useSelector((store) => {
+ return {
+ loginUid: store.authData.uid,
+ channelIDs: store.channels.ids,
+ DMs: store.userMessage.ids,
+ userMessage: store.userMessage.byId,
+ channelMessage: store.channelMessage,
+ readChannels: store.footprint.readChannels,
+ readUsers: store.footprint.readUsers
+ };
+ });
+ useEffect(() => {
+ const cSessions = channelIDs.map((id) => {
+ const mids = channelMessage[id];
+ if (!mids || mids.length == 0) {
+ return { mid: null, unreads: 0, id, type: "channel" };
+ }
+ const mid = [...mids].pop();
+ return { key: `channel_${id}`, id, mid, type: "channel" };
+ });
+ const uSessions = DMs.map((id) => {
+ const mids = userMessage[id];
+ if (!mids || mids.length == 0) {
+ return { mid: null, unreads: 0, id, type: "dm" };
+ }
+ const mid = [...mids].pop();
+ return { key: `dm_${id}`, type: "dm", id, mid };
+ });
+
+ setSessions(
+ [...cSessions, ...uSessions].sort((a, b) => {
+ return b.mid - a.mid;
+ })
+ );
+ }, [channelIDs, DMs, channelMessage, readChannels, readUsers, loginUid, userMessage]);
+ return (
+ <>
+
+ {sessions.map((s) => {
+ const { key, type, id, mid } = s;
+ return (
+
+ );
+ })}
+
+ {deleteId && (
+ {
+ setDeleteId(null);
+ }}
+ />
+ )}
+ {inviteChannelId && (
+ {
+ setInviteChannelId(null);
+ }}
+ />
+ )}
+ >
+ );
+}
diff --git a/src/routes/chat/SessionList/styled.js b/src/routes/chat/SessionList/styled.js
new file mode 100644
index 00000000..886dbb41
--- /dev/null
+++ b/src/routes/chat/SessionList/styled.js
@@ -0,0 +1,78 @@
+import styled from "styled-components";
+const Styled = styled.ul`
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ padding: 8px;
+ height: calc(100vh - 56px - 56px - 16px);
+ overflow: auto;
+ > .session {
+ > a {
+ display: flex;
+ gap: 8px;
+ border-radius: 8px;
+ padding: 8px;
+ width: 100%;
+ &.active,
+ &:hover {
+ background: rgba(116, 127, 141, 0.2);
+ }
+ .icon {
+ flex: 1;
+ background-color: #eee;
+ border-radius: 50%;
+ img {
+ width: 40px;
+ height: 40px;
+ &.channel_default {
+ padding: 5px;
+ /* height: 35px; */
+ }
+ }
+ }
+ .details {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ .up {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ .name {
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 20px;
+ color: #52525b;
+ max-width: 112px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ .time {
+ font-weight: 500;
+ font-size: 12px;
+ line-height: 18px;
+ color: #78787c;
+ }
+ }
+ .down {
+ display: flex;
+ align-items: center;
+ .msg {
+ font-weight: 400;
+ font-size: 12px;
+ line-height: 18px;
+ color: #78787c;
+ white-space: nowrap;
+ overflow: hidden;
+ width: 140px;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ }
+ }
+`;
+
+export default Styled;
diff --git a/src/routes/chat/index.js b/src/routes/chat/index.js
index 1eac1a96..71dc0e60 100644
--- a/src/routes/chat/index.js
+++ b/src/routes/chat/index.js
@@ -3,22 +3,17 @@ import { useState } from "react";
import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
-import { AiOutlineCaretDown } from "react-icons/ai";
-
import StyledWrapper from "./styled";
-import AddIcon from "../../assets/icons/add.svg";
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
import Server from "../../common/component/Server";
-import Tooltip from "../../common/component/Tooltip";
+// import Tooltip from "../../common/component/Tooltip";
// import Contact from "../../common/component/Contact";
import CurrentUser from "../../common/component/CurrentUser";
import ChannelChat from "./ChannelChat";
import DMChat from "./DMChat";
-import ChannelList from "./ChannelList";
import ContactsModal from "../../common/component/ContactsModal";
import ChannelModal from "../../common/component/ChannelModal";
-import DMList from "./DMList";
-
+import SessionList from "./SessionList";
export default function ChatPage() {
const [channelDropFiles, setChannelDropFiles] = useState([]);
const [userDropFiles, setUserDropFiles] = useState([]);
@@ -36,11 +31,6 @@ export default function ChatPage() {
const toggleChannelModalVisible = () => {
setChannelModalVisible((prev) => !prev);
};
- const handleToggleExpand = (evt) => {
- const { currentTarget } = evt;
- const listEle = currentTarget.parentElement.parentElement;
- listEle.classList.toggle("collapse");
- };
const tmpUid = sessionUids.findIndex((i) => i == user_id) > -1 ? null : user_id;
// console.log("temp uid", tmpUid);
const placeholderVisible = !channel_id && !user_id;
@@ -53,37 +43,7 @@ export default function ChatPage() {
-
-
-
-
- CHANNELS
-
-
-
-
-
-
-
-
-
-
-
- DIRECT MESSAGE
-
-
-
-
-
-
-
+
diff --git a/src/routes/chat/styled.js b/src/routes/chat/styled.js
index 86d682b2..3d46300a 100644
--- a/src/routes/chat/styled.js
+++ b/src/routes/chat/styled.js
@@ -107,8 +107,6 @@ const StyledWrapper = styled.div`
text-overflow: ellipsis;
}
> .badge {
- /* letter-spacing: -1px; */
- /* padding: 2px; */
color: #fff;
height: 20px;
min-width: 20px;
diff --git a/src/routes/chat/useSession.js b/src/routes/chat/useSession.js
new file mode 100644
index 00000000..1ad6dd01
--- /dev/null
+++ b/src/routes/chat/useSession.js
@@ -0,0 +1,25 @@
+import { useState } from "react";
+import { useSelector } from "react-redux";
+// import { useUpdateChannelMutation } from "../../app/services/channel";
+import { useUpdateMuteSettingMutation } from "../../app/services/contact";
+import { useReadMessageMutation } from "../../app/services/message";
+
+export default function useSession({ type, id }) {
+ const [inviteModalVisible, setInviteModalVisible] = useState(false);
+ const [muteChannel] = useUpdateMuteSettingMutation();
+ const [updateReadIndex] = useReadMessageMutation();
+ const { messageData, contactData, channelData } = useSelector((store) => {
+ return {
+ messageData: store.message,
+ contactData: store.contacts.byId,
+ channelData: store.channels.byId
+ };
+ });
+ const toggleInviteModal = () => {
+ setInviteModalVisible((prev) => !prev);
+ };
+ return {
+ inviteModalVisible,
+ toggleInviteModal
+ };
+}