refactor: message feed pagination
This commit is contained in:
@@ -59,10 +59,12 @@ export const onMessageSendStarted = async (
|
||||
console.log("message server mid", server_mid);
|
||||
batch(() => {
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
dispatch(removeMessage(ts));
|
||||
dispatch(addMessage({ mid: server_mid, ...tmpMsg }));
|
||||
dispatch(addContextMessage({ id, mid: server_mid }));
|
||||
});
|
||||
setTimeout(() => {
|
||||
dispatch(removeMessage(ts));
|
||||
}, 300);
|
||||
// dispatch(removePendingMessage({ id, mid:ts, type: from }));
|
||||
} catch {
|
||||
console.log("message send failed");
|
||||
|
||||
@@ -27,6 +27,7 @@ export default function MessageContextMenu({
|
||||
// isMarkdown,
|
||||
canEdit,
|
||||
canPin,
|
||||
canDelete,
|
||||
canCopy,
|
||||
canReply,
|
||||
pinned,
|
||||
@@ -97,7 +98,7 @@ export default function MessageContextMenu({
|
||||
icon: <IconSelect className="icon" />,
|
||||
handler: handleSelect,
|
||||
},
|
||||
{
|
||||
canDelete && {
|
||||
title: "Delete",
|
||||
danger: true,
|
||||
icon: <IconDelete className="icon" />,
|
||||
|
||||
@@ -103,7 +103,7 @@ function Message({
|
||||
popperOptions={{ strategy: "fixed" }}
|
||||
disabled={readOnly}
|
||||
interactive
|
||||
placement="left"
|
||||
placement="right"
|
||||
trigger="click"
|
||||
content={<Profile uid={fromUid} type="card" cid={contextId} />}
|
||||
>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useLazyGetHistoryMessagesQuery } from "../../app/services/channel";
|
||||
|
||||
const getFeedWithPagination = (config) => {
|
||||
const { pageNumber = 1, pageSize = 20, mids = [] } = config || {};
|
||||
const { pageNumber = 1, pageSize = 30, mids = [], isLast = false } =
|
||||
config || {};
|
||||
const shadowMids = mids.slice(0);
|
||||
|
||||
if (shadowMids.length == 0)
|
||||
@@ -15,119 +17,112 @@ const getFeedWithPagination = (config) => {
|
||||
ids: [],
|
||||
};
|
||||
shadowMids.sort((a, b) => {
|
||||
return Number(b) - Number(a);
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
console.log("message pagination", shadowMids);
|
||||
const pageCount = Math.ceil(shadowMids.length / pageSize);
|
||||
const computedPageNumber = isLast ? pageCount : pageNumber;
|
||||
const ids = shadowMids.slice(
|
||||
(pageNumber - 1) * pageSize,
|
||||
pageNumber * pageSize
|
||||
(computedPageNumber - 1) * pageSize,
|
||||
computedPageNumber * pageSize
|
||||
);
|
||||
const info = {
|
||||
isFirst: pageNumber == 1,
|
||||
isLast: pageNumber == pageCount,
|
||||
return {
|
||||
isFirst: computedPageNumber == 1,
|
||||
isLast: computedPageNumber == pageCount,
|
||||
pageCount,
|
||||
pageSize,
|
||||
pageNumber,
|
||||
pageNumber: computedPageNumber,
|
||||
ids,
|
||||
};
|
||||
console.log("get page Info", info);
|
||||
return info;
|
||||
};
|
||||
let curScrollPos = 0;
|
||||
let oldScroll = 0;
|
||||
export default function useMessageFeed({ context = "channel", id = null }) {
|
||||
const [loadMoreFromServer] = useLazyGetHistoryMessagesQuery();
|
||||
const listRef = useRef([]);
|
||||
const pageRef = useRef(null);
|
||||
const containerRef = useRef(null);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const [prepends, setPrepends] = useState([]);
|
||||
// const [appends, setAppends] = useState([]);
|
||||
const [appends, setAppends] = useState([]);
|
||||
const [items, setItems] = useState([]);
|
||||
const { mids } = useSelector((store) => {
|
||||
const { mids, messageData, loginUid } = useSelector((store) => {
|
||||
return {
|
||||
loginUid: store.authData.uid,
|
||||
mids:
|
||||
context == "channel"
|
||||
? store.channelMessage[id] || []
|
||||
: store.userMessage.byId[id] || [],
|
||||
// messageData: store.message,
|
||||
messageData: store.message,
|
||||
};
|
||||
});
|
||||
useEffect(() => {
|
||||
listRef.current = [];
|
||||
pageRef.current = [];
|
||||
setItems([]);
|
||||
setPrepends([]);
|
||||
setHasMore(true);
|
||||
// setAppends([]);
|
||||
setAppends([]);
|
||||
}, [context, id]);
|
||||
// useEffect(() => {
|
||||
// if (prepends.length) {
|
||||
// const feedsWrapperEle = document.querySelector(
|
||||
// `#RUSTCHAT_FEED_${context}_${id}`
|
||||
// );
|
||||
// const [newestId]=prepends;
|
||||
|
||||
// if (feedsWrapperEle) {
|
||||
// feedsWrapperEle.scrollTop = feedsWrapperEle.scrollHeight;
|
||||
// }
|
||||
// }
|
||||
// }, [prepends, context, id,messageData]);
|
||||
useEffect(() => {
|
||||
const container = document.querySelector(`#RUSTCHAT_FEED_${context}_${id}`);
|
||||
const handler = (e) => {
|
||||
e.preventDefault();
|
||||
var n = 0;
|
||||
if ("deltaY" in e) n = 1 === e.deltaMode ? 20 * -e.deltaY : -e.deltaY;
|
||||
else if ("wheelDeltaY" in e) n = (e.wheelDeltaY / 120) * 20;
|
||||
else if ("wheelDelta" in e) n = (e.wheelDelta / 120) * 20;
|
||||
else {
|
||||
n = (-e.detail / 3) * 20;
|
||||
if (items.length) {
|
||||
containerRef.current = document.querySelector(
|
||||
`#RUSTCHAT_FEED_${context}_${id}`
|
||||
);
|
||||
if (containerRef.current) {
|
||||
const newScroll =
|
||||
containerRef.current.scrollHeight - containerRef.current.clientHeight;
|
||||
containerRef.current.scrollTop = curScrollPos + (newScroll - oldScroll);
|
||||
}
|
||||
container.scrollTop += n;
|
||||
};
|
||||
if (container) {
|
||||
container.addEventListener("wheel", handler);
|
||||
}
|
||||
return () => {
|
||||
if (container) {
|
||||
container.removeEventListener("wheel", handler);
|
||||
}
|
||||
};
|
||||
}, [context, id]);
|
||||
|
||||
}, [items, context, id]);
|
||||
useEffect(() => {
|
||||
const fetchMore = () => {
|
||||
if (listRef.current.length == 0 && mids.length) {
|
||||
// 初次
|
||||
const pageInfo = getFeedWithPagination({ mids });
|
||||
console.log("pull down 2", pageInfo);
|
||||
pageRef.current = pageInfo;
|
||||
listRef.current = pageInfo.ids;
|
||||
setItems(listRef.current);
|
||||
console.log("message pageInfo", mids, pageInfo);
|
||||
} else {
|
||||
// 追加
|
||||
const lastMid = listRef.current[0];
|
||||
const sorteds = mids.slice(0).sort((a, b) => {
|
||||
return Number(b) - Number(a);
|
||||
});
|
||||
const prepends = sorteds.filter((s) => s > lastMid);
|
||||
if (prepends.length) {
|
||||
setPrepends(prepends);
|
||||
if (listRef.current.length == 0 && mids.length) {
|
||||
// 初次
|
||||
const pageInfo = getFeedWithPagination({ mids, isLast: true });
|
||||
console.log("pull up 2", pageInfo);
|
||||
pageRef.current = pageInfo;
|
||||
listRef.current = pageInfo.ids;
|
||||
setItems(listRef.current);
|
||||
console.log("message pageInfo", mids, pageInfo);
|
||||
} else {
|
||||
// 追加
|
||||
const lastMid = listRef.current.slice(-1);
|
||||
const sorteds = mids.slice(0).sort((a, b) => {
|
||||
return Number(a) - Number(b);
|
||||
});
|
||||
const appends = sorteds.filter((s) => s > lastMid);
|
||||
if (appends.length) {
|
||||
const [newestMsgId] = appends.slice(-1);
|
||||
// 自己发的消息
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
const msgFromSelf = loginUid == messageData[newestMsgId]?.from_uid;
|
||||
const scrollDistance =
|
||||
container.scrollHeight -
|
||||
(container.offsetHeight + container.scrollTop);
|
||||
console.log("scrollDistance", msgFromSelf, scrollDistance);
|
||||
if (msgFromSelf) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
} else if (scrollDistance <= 100) {
|
||||
setTimeout(() => {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
console.log("prepends", prepends, items);
|
||||
setAppends(appends);
|
||||
}
|
||||
};
|
||||
fetchMore();
|
||||
}, [mids]);
|
||||
|
||||
const pullDown = async () => {
|
||||
// 向下加载
|
||||
console.log("appends", appends, listRef.current);
|
||||
}
|
||||
}, [mids, messageData, loginUid]);
|
||||
const pullUp = async () => {
|
||||
const currPageInfo = pageRef.current;
|
||||
console.log("pull down", currPageInfo);
|
||||
// 最后一页
|
||||
if (currPageInfo && currPageInfo.isLast) {
|
||||
const [mid] = currPageInfo.ids.slice(-1);
|
||||
const { data: newList } = await loadMoreFromServer({ mid, gid: id });
|
||||
console.log("pull up", currPageInfo);
|
||||
// 第一页
|
||||
if (currPageInfo && currPageInfo.isFirst) {
|
||||
const [firstMid] = currPageInfo.ids;
|
||||
const { data: newList } = await loadMoreFromServer({
|
||||
mid: firstMid,
|
||||
gid: id,
|
||||
});
|
||||
if (newList.length == 0) {
|
||||
setHasMore(false);
|
||||
return;
|
||||
@@ -138,29 +133,37 @@ export default function useMessageFeed({ context = "channel", id = null }) {
|
||||
// 初始化
|
||||
pageInfo = getFeedWithPagination({
|
||||
mids,
|
||||
isLast: true,
|
||||
});
|
||||
} else {
|
||||
const nextPageNumber = currPageInfo.pageNumber + 1;
|
||||
const prevPageNumber = currPageInfo.pageNumber - 1;
|
||||
pageInfo = getFeedWithPagination({
|
||||
mids,
|
||||
pageNumber: nextPageNumber,
|
||||
pageNumber: prevPageNumber,
|
||||
});
|
||||
}
|
||||
pageRef.current = pageInfo;
|
||||
listRef.current = [...listRef.current, ...pageInfo.ids];
|
||||
listRef.current = [...pageInfo.ids, ...listRef.current];
|
||||
setTimeout(() => {
|
||||
setHasMore(!pageInfo.isLast);
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
curScrollPos = container.scrollTop;
|
||||
oldScroll = container.scrollHeight - container.clientHeight;
|
||||
}
|
||||
setItems(listRef.current);
|
||||
console.log("pull down update", currPageInfo, listRef.current);
|
||||
}, 1000);
|
||||
console.log("pull up", currPageInfo, listRef.current);
|
||||
setHasMore(pageInfo.pageNumber !== 1);
|
||||
}, 800);
|
||||
};
|
||||
const pullDown = () => {
|
||||
// 向下加载
|
||||
};
|
||||
|
||||
return {
|
||||
mids,
|
||||
prepends,
|
||||
// appends,
|
||||
appends,
|
||||
hasMore,
|
||||
// pullUp,
|
||||
pullUp,
|
||||
pullDown,
|
||||
list: items,
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ const Styled = styled.div`
|
||||
padding: 30px 0;
|
||||
/* background-color: #eee; */
|
||||
`;
|
||||
export default function LoadMore({ pullDown = null }) {
|
||||
export default function LoadMore({ pullUp = null }) {
|
||||
const ref = useRef(undefined);
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
@@ -19,10 +19,10 @@ export default function LoadMore({ pullDown = null }) {
|
||||
entries.forEach((entry) => {
|
||||
const intersecting = entry.isIntersecting;
|
||||
// const currEle = entry.target;
|
||||
if (intersecting && pullDown) {
|
||||
if (intersecting && pullUp) {
|
||||
// load more
|
||||
console.log("inview");
|
||||
pullDown();
|
||||
pullUp();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -37,7 +37,7 @@ export default function LoadMore({ pullDown = null }) {
|
||||
observer.unobserve(currEle);
|
||||
}
|
||||
};
|
||||
}, [ref, pullDown]);
|
||||
}, [ref, pullUp]);
|
||||
return (
|
||||
<Styled ref={ref}>
|
||||
<Waveform
|
||||
|
||||
@@ -3,12 +3,10 @@ import { useDebounce } from "rooks";
|
||||
import { NavLink, useLocation } from "react-router-dom";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
// import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import PinList from "./PinList";
|
||||
import FavList from "../FavList";
|
||||
import { useReadMessageMutation } from "../../../app/services/message";
|
||||
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
||||
// import useChatScroll from "../../../common/hook/useChatScroll";
|
||||
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||
|
||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||
@@ -36,7 +34,7 @@ import InviteModal from "../../../common/component/InviteModal";
|
||||
import LoadMore from "./LoadMore";
|
||||
|
||||
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
|
||||
const { list: msgIds, appends, hasMore, pullUp } = useMessageFeed({
|
||||
context: "channel",
|
||||
id: cid,
|
||||
});
|
||||
@@ -94,7 +92,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
console.log("channel message list", msgIds);
|
||||
const readIndex = footprint.readChannels[cid];
|
||||
const pinCount = data?.pinned_messages?.length || 0;
|
||||
const feeds = [...prepends, ...msgIds];
|
||||
const feeds = [...msgIds, ...appends];
|
||||
return (
|
||||
<>
|
||||
{addMemberModalVisible && (
|
||||
@@ -233,27 +231,9 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
}
|
||||
>
|
||||
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
|
||||
{/* <div className="feed"> */}
|
||||
{feeds.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
if (!curr) return null;
|
||||
const isFirst = idx == 0;
|
||||
const prev =
|
||||
idx == feeds.length - 1 ? null : messageData[feeds[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 className="anchor"></div> */}
|
||||
{hasMore ? (
|
||||
<LoadMore pullDown={pullDown} />
|
||||
<LoadMore pullUp={pullUp} />
|
||||
) : (
|
||||
<div className="info">
|
||||
<h2 className="title">Welcome to #{name} !</h2>
|
||||
@@ -267,6 +247,25 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
</NavLink>
|
||||
</div>
|
||||
)}
|
||||
{/* <div className="feed"> */}
|
||||
{feeds.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
if (!curr) return null;
|
||||
const isFirst = idx == 0;
|
||||
const prev = isFirst ? null : messageData[feeds[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 && (
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useDebounce } from "rooks";
|
||||
import { NavLink, useLocation } from "react-router-dom";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
// import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import PinList from "./PinList";
|
||||
import FavList from "../FavList";
|
||||
import { useReadMessageMutation } from "../../../app/services/message";
|
||||
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
||||
// import useChatScroll from "../../../common/hook/useChatScroll";
|
||||
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||
|
||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||
import Tooltip from "../../../common/component/Tooltip";
|
||||
import Contact from "../../../common/component/Contact";
|
||||
import Layout from "../Layout";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
import EditIcon from "../../../assets/icons/edit.svg";
|
||||
// import alertIcon from "../../../assets/icons/alert.svg?url";
|
||||
import IconFav from "../../../assets/icons/bookmark.svg";
|
||||
import IconPeople from "../../../assets/icons/people.svg";
|
||||
import IconPin from "../../../assets/icons/pin.svg";
|
||||
// import searchIcon from "../../../assets/icons/search.svg?url";
|
||||
import IconHeadphone from "../../../assets/icons/headphone.svg";
|
||||
import boardosIcon from "../../../assets/icons/app.boardos.svg?url";
|
||||
import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url";
|
||||
import addIcon from "../../../assets/icons/add.svg?url";
|
||||
import {
|
||||
// StyledNotification,
|
||||
StyledContacts,
|
||||
StyledChannelChat,
|
||||
StyledHeader,
|
||||
} from "./styled";
|
||||
import InviteModal from "../../../common/component/InviteModal";
|
||||
import LoadMore from "./LoadMore";
|
||||
|
||||
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
|
||||
context: "channel",
|
||||
id: cid,
|
||||
});
|
||||
const [toolVisible, setToolVisible] = useState("");
|
||||
const { pathname } = useLocation();
|
||||
const dispatch = useDispatch();
|
||||
const [updateReadIndex] = useReadMessageMutation();
|
||||
const updateReadDebounced = useDebounce(updateReadIndex, 300);
|
||||
const [membersVisible, setMembersVisible] = useState(true);
|
||||
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
||||
const {
|
||||
selects,
|
||||
// msgIds,
|
||||
userIds,
|
||||
data,
|
||||
messageData,
|
||||
loginUid,
|
||||
loginUser,
|
||||
footprint,
|
||||
} = useSelector((store) => {
|
||||
return {
|
||||
selects: store.ui.selectMessages[`channel_${cid}`],
|
||||
footprint: store.footprint,
|
||||
loginUser: store.contacts.byId[store.authData.uid],
|
||||
loginUid: store.authData.uid,
|
||||
// msgIds: store.channelMessage[cid] || [],
|
||||
userIds: store.contacts.ids,
|
||||
data: store.channels.byId[cid] || {},
|
||||
messageData: store.message || {},
|
||||
};
|
||||
});
|
||||
// const ref = useChatScroll(msgIds);
|
||||
// const handleClearUnreads = () => {
|
||||
// dispatch(readMessage(msgIds));
|
||||
// };
|
||||
useEffect(() => {
|
||||
dispatch(updateRemeberedNavs());
|
||||
return () => {
|
||||
dispatch(updateRemeberedNavs({ path: pathname }));
|
||||
};
|
||||
}, [pathname]);
|
||||
|
||||
const toggleMembersVisible = () => {
|
||||
setMembersVisible((prev) => !prev);
|
||||
};
|
||||
const toggleAddVisible = () => {
|
||||
setAddMemberModalVisible((prev) => !prev);
|
||||
};
|
||||
if (!data) return null;
|
||||
const { name, description, is_public, members = [], owner } = data;
|
||||
const memberIds = is_public
|
||||
? userIds
|
||||
: members.slice(0).sort((n) => (n == owner ? -1 : 0));
|
||||
const addVisible = loginUser?.is_admin || owner == loginUid;
|
||||
console.log("channel message list", msgIds);
|
||||
const readIndex = footprint.readChannels[cid];
|
||||
const pinCount = data?.pinned_messages?.length || 0;
|
||||
const feeds = [...prepends, ...msgIds];
|
||||
return (
|
||||
<>
|
||||
{addMemberModalVisible && (
|
||||
<InviteModal cid={cid} closeModal={toggleAddVisible} />
|
||||
)}
|
||||
<Layout
|
||||
to={cid}
|
||||
context="channel"
|
||||
dropFiles={dropFiles}
|
||||
// ref={containerRef}
|
||||
aside={
|
||||
<>
|
||||
<ul className="tools">
|
||||
<li className="tool">
|
||||
<Tooltip tip="Voice/Video Chat" placement="left">
|
||||
<IconHeadphone />
|
||||
</Tooltip>
|
||||
</li>
|
||||
<Tooltip
|
||||
tip="Pin"
|
||||
placement="left"
|
||||
disabled={toolVisible == "pin"}
|
||||
>
|
||||
<Tippy
|
||||
onShow={() => {
|
||||
setToolVisible("pin");
|
||||
}}
|
||||
onHide={() => {
|
||||
setToolVisible("");
|
||||
}}
|
||||
placement="left-start"
|
||||
popperOptions={{ strategy: "fixed" }}
|
||||
offset={[0, 80]}
|
||||
interactive
|
||||
trigger="click"
|
||||
content={<PinList id={cid} />}
|
||||
>
|
||||
<li
|
||||
className={`tool ${pinCount > 0 ? "badge" : ""} ${
|
||||
toolVisible == "pin" ? "active" : ""
|
||||
} `}
|
||||
data-count={pinCount}
|
||||
>
|
||||
<IconPin />
|
||||
</li>
|
||||
</Tippy>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
tip="Favorite"
|
||||
placement="left"
|
||||
disabled={toolVisible == "favorite"}
|
||||
>
|
||||
<Tippy
|
||||
onShow={() => {
|
||||
setToolVisible("favorite");
|
||||
}}
|
||||
onHide={() => {
|
||||
setToolVisible("");
|
||||
}}
|
||||
placement="left-start"
|
||||
popperOptions={{ strategy: "fixed" }}
|
||||
offset={[0, 180]}
|
||||
interactive
|
||||
trigger="click"
|
||||
content={<FavList cid={cid} />}
|
||||
>
|
||||
<li
|
||||
className={`tool fav ${
|
||||
toolVisible == "favorite" ? "active" : ""
|
||||
} `}
|
||||
data-count={pinCount}
|
||||
>
|
||||
<IconFav />
|
||||
</li>
|
||||
</Tippy>
|
||||
</Tooltip>
|
||||
<li
|
||||
className={`tool ${membersVisible ? "active" : ""}`}
|
||||
onClick={toggleMembersVisible}
|
||||
>
|
||||
<Tooltip tip="Channel Members" placement="left">
|
||||
<IconPeople />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<hr className="divider" />
|
||||
<ul className="apps">
|
||||
<li className="app">
|
||||
<Tooltip tip="Webrowse" placement="left">
|
||||
<img src={webrowseIcon} alt="app icon" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
<li className="app">
|
||||
<Tooltip tip="BoardOS" placement="left">
|
||||
<img src={boardosIcon} alt="app icon" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
}
|
||||
header={
|
||||
<StyledHeader className="head">
|
||||
<div className="txt">
|
||||
<ChannelIcon personal={!is_public} />
|
||||
<span className="title">{name}</span>
|
||||
<span className="desc">{description}</span>
|
||||
</div>
|
||||
</StyledHeader>
|
||||
}
|
||||
contacts={
|
||||
membersVisible ? (
|
||||
<>
|
||||
<StyledContacts>
|
||||
{addVisible && (
|
||||
<div className="add" onClick={toggleAddVisible}>
|
||||
<img className="icon" src={addIcon} />
|
||||
<div className="txt">Add members</div>
|
||||
</div>
|
||||
)}
|
||||
{memberIds.map((uid) => {
|
||||
return (
|
||||
<Contact
|
||||
enableContextMenu={true}
|
||||
cid={cid}
|
||||
owner={owner == uid}
|
||||
key={uid}
|
||||
uid={uid}
|
||||
dm
|
||||
popover
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledContacts>
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
|
||||
{/* <div className="feed"> */}
|
||||
{feeds.map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
if (!curr) return null;
|
||||
const isFirst = idx == 0;
|
||||
const prev =
|
||||
idx == feeds.length - 1 ? null : messageData[feeds[idx + 1]];
|
||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||
return renderMessageFragment({
|
||||
selectMode: !!selects,
|
||||
updateReadIndex: updateReadDebounced,
|
||||
read,
|
||||
isFirst,
|
||||
prev,
|
||||
curr,
|
||||
contextId: cid,
|
||||
context: "channel",
|
||||
});
|
||||
})}
|
||||
{hasMore ? (
|
||||
<LoadMore pullDown={pullDown} />
|
||||
) : (
|
||||
<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> */}
|
||||
</StyledChannelChat>
|
||||
{/* {unreads != 0 && (
|
||||
<StyledNotification>
|
||||
<div className="content">
|
||||
{unreads} new messages
|
||||
{msgs.lastAccess
|
||||
? `since ${dayjs(msgs.lastAccess).format("YYYY-MM-DD h:mm:ss A")}`
|
||||
: ""}
|
||||
</div>
|
||||
<button onClick={handleClearUnreads} className="clear">
|
||||
Mark As Read
|
||||
</button>
|
||||
</StyledNotification>
|
||||
)} */}
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -88,14 +88,15 @@ export const StyledChannelChat = styled.article`
|
||||
height: -webkit-fill-available;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* overflow-anchor: none; */
|
||||
/* pagination start */
|
||||
transform: rotate(180deg);
|
||||
/* transform: rotate(180deg);
|
||||
direction: rtl;
|
||||
> div,
|
||||
> hr {
|
||||
direction: ltr;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
} */
|
||||
/* pagination end */
|
||||
> .info {
|
||||
padding-top: 62px;
|
||||
|
||||
@@ -182,6 +182,7 @@ export const renderMessageFragment = ({
|
||||
console.log("_key", _key, local_id, mid);
|
||||
return (
|
||||
<React.Fragment key={_key}>
|
||||
{divider && <Divider content={divider}></Divider>}
|
||||
<MessageWrapper
|
||||
key={_key}
|
||||
data-key={_key}
|
||||
@@ -201,7 +202,6 @@ export const renderMessageFragment = ({
|
||||
contextId={contextId}
|
||||
/>
|
||||
</MessageWrapper>
|
||||
{divider && <Divider content={divider}></Divider>}
|
||||
</React.Fragment>
|
||||
);
|
||||
// React.memo(
|
||||
|
||||
Reference in New Issue
Block a user