refactor: stash long list updates
This commit is contained in:
+13
-24
@@ -8,6 +8,7 @@ import { removeMessage } from "../slices/message";
|
|||||||
import { removeChannelSession } from "../slices/message.channel";
|
import { removeChannelSession } from "../slices/message.channel";
|
||||||
import { removeReactionMessage } from "../slices/message.reaction";
|
import { removeReactionMessage } from "../slices/message.reaction";
|
||||||
import { onMessageSendStarted } from "./handlers";
|
import { onMessageSendStarted } from "./handlers";
|
||||||
|
import handleChatMessage from "../../common/hook/useStreaming/chat.handler";
|
||||||
export const channelApi = createApi({
|
export const channelApi = createApi({
|
||||||
reducerPath: "channelApi",
|
reducerPath: "channelApi",
|
||||||
baseQuery,
|
baseQuery,
|
||||||
@@ -58,31 +59,19 @@ export const channelApi = createApi({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getHistoryMessages: builder.query({
|
getHistoryMessages: builder.query({
|
||||||
query: ({ gid, mid = 0, limit = 50 }) => ({
|
query: ({ gid, mid = null, limit = 100 }) => ({
|
||||||
url: `/group/${gid}/history?before=${mid}&limit=${limit}`,
|
url: mid
|
||||||
|
? `/group/${gid}/history?before=${mid}&limit=${limit}`
|
||||||
|
: `/group/${gid}/history?limit=${limit}`,
|
||||||
}),
|
}),
|
||||||
// async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
|
async onQueryStarted(id, { dispatch, getState, queryFulfilled }) {
|
||||||
// const {
|
const { data: messages } = await queryFulfilled;
|
||||||
// ui: { channelSetting },
|
if (messages?.length) {
|
||||||
// channelMessage,
|
messages.forEach((msg) => {
|
||||||
// } = getState();
|
handleChatMessage(msg, dispatch, getState());
|
||||||
// try {
|
});
|
||||||
// await queryFulfilled;
|
}
|
||||||
// dispatch(removeChannel(id));
|
},
|
||||||
// if (id == channelSetting) {
|
|
||||||
// dispatch(toggleChannelSetting());
|
|
||||||
// }
|
|
||||||
// // 删掉该channel下的所有消息&reaction
|
|
||||||
// const mids = channelMessage[id];
|
|
||||||
// if (mids) {
|
|
||||||
// dispatch(removeChannelSession(id));
|
|
||||||
// dispatch(removeMessage(mids));
|
|
||||||
// dispatch(removeReactionMessage(mids));
|
|
||||||
// }
|
|
||||||
// } catch {
|
|
||||||
// console.log("remove channel error");
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
}),
|
}),
|
||||||
createInviteLink: builder.query({
|
createInviteLink: builder.query({
|
||||||
query: (gid) => ({
|
query: (gid) => ({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// import { useRef } from "react";
|
// import { useRef } from "react";
|
||||||
// import styled from "styled-components";
|
// import styled from "styled-components";
|
||||||
import StyledMenu from "./styled/Menu";
|
import StyledMenu from "./styled/Menu";
|
||||||
export default function ContextMenu({ items = [], hideMenu }) {
|
export default function ContextMenu({ items = [], hideMenu = null }) {
|
||||||
return (
|
return (
|
||||||
<StyledMenu>
|
<StyledMenu>
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
@@ -11,7 +11,9 @@ export default function ContextMenu({ items = [], hideMenu }) {
|
|||||||
icon = null,
|
icon = null,
|
||||||
handler = (evt) => {
|
handler = (evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
hideMenu();
|
if (hideMenu) {
|
||||||
|
hideMenu();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
underline = false,
|
underline = false,
|
||||||
danger = false,
|
danger = false,
|
||||||
@@ -24,7 +26,9 @@ export default function ContextMenu({ items = [], hideMenu }) {
|
|||||||
key={title}
|
key={title}
|
||||||
onClick={(evt) => {
|
onClick={(evt) => {
|
||||||
handler(evt);
|
handler(evt);
|
||||||
hideMenu();
|
if (hideMenu) {
|
||||||
|
hideMenu();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
|||||||
@@ -87,8 +87,10 @@ function Message({
|
|||||||
|
|
||||||
const pinInfo = getPinInfo(mid);
|
const pinInfo = getPinInfo(mid);
|
||||||
// return null;
|
// return null;
|
||||||
|
const _key = properties?.local_id || mid;
|
||||||
return (
|
return (
|
||||||
<StyledWrapper
|
<StyledWrapper
|
||||||
|
key={_key}
|
||||||
onContextMenu={handleContextMenuEvent}
|
onContextMenu={handleContextMenuEvent}
|
||||||
data-msg-mid={mid}
|
data-msg-mid={mid}
|
||||||
ref={inviewRef}
|
ref={inviewRef}
|
||||||
@@ -97,6 +99,8 @@ function Message({
|
|||||||
} ${contextMenuVisible ? "contextVisible" : ""} `}
|
} ${contextMenuVisible ? "contextVisible" : ""} `}
|
||||||
>
|
>
|
||||||
<Tippy
|
<Tippy
|
||||||
|
key={_key}
|
||||||
|
popperOptions={{ strategy: "fixed" }}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
interactive
|
interactive
|
||||||
placement="left"
|
placement="left"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
z-index: 9999;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,127 +1,166 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import { useLazyGetHistoryMessagesQuery } from "../../app/services/channel";
|
||||||
const getFeedWithPagination = (config) => {
|
const getFeedWithPagination = (config) => {
|
||||||
const { pageNumber = 1, pageSize = 10, mids = [], isLast = false } =
|
const { pageNumber = 1, pageSize = 20, mids = [] } = config || {};
|
||||||
config || {};
|
|
||||||
const shadowMids = mids.slice(0);
|
const shadowMids = mids.slice(0);
|
||||||
|
|
||||||
if (shadowMids.length == 0)
|
if (shadowMids.length == 0)
|
||||||
return {
|
return {
|
||||||
|
isFirst: true,
|
||||||
|
isLast: true,
|
||||||
pageCount: 0,
|
pageCount: 0,
|
||||||
pageSize,
|
pageSize,
|
||||||
pageNumber: 1,
|
pageNumber: 1,
|
||||||
ids: [],
|
ids: [],
|
||||||
};
|
};
|
||||||
shadowMids.sort((a, b) => {
|
shadowMids.sort((a, b) => {
|
||||||
return Number(a) - Number(b);
|
return Number(b) - Number(a);
|
||||||
});
|
});
|
||||||
console.log("message pagination", shadowMids);
|
console.log("message pagination", shadowMids);
|
||||||
const pageCount = Math.ceil(shadowMids.length / pageSize);
|
const pageCount = Math.ceil(shadowMids.length / pageSize);
|
||||||
const computedPageNumber = isLast ? pageCount : pageNumber;
|
|
||||||
const ids = shadowMids.slice(
|
const ids = shadowMids.slice(
|
||||||
(computedPageNumber - 1) * pageSize,
|
(pageNumber - 1) * pageSize,
|
||||||
computedPageNumber * pageSize
|
pageNumber * pageSize
|
||||||
);
|
);
|
||||||
return {
|
const info = {
|
||||||
|
isFirst: pageNumber == 1,
|
||||||
|
isLast: pageNumber == pageCount,
|
||||||
pageCount,
|
pageCount,
|
||||||
pageSize,
|
pageSize,
|
||||||
pageNumber: computedPageNumber,
|
pageNumber,
|
||||||
ids,
|
ids,
|
||||||
};
|
};
|
||||||
|
console.log("get page Info", info);
|
||||||
|
return info;
|
||||||
};
|
};
|
||||||
export default function useMessageFeed({ context = "channel", id = null }) {
|
export default function useMessageFeed({ context = "channel", id = null }) {
|
||||||
|
const [loadMoreFromServer] = useLazyGetHistoryMessagesQuery();
|
||||||
const listRef = useRef([]);
|
const listRef = useRef([]);
|
||||||
const pageRef = useRef(null);
|
const pageRef = useRef(null);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const [appends, setAppends] = useState([]);
|
const [prepends, setPrepends] = useState([]);
|
||||||
|
// const [appends, setAppends] = useState([]);
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const { mids, messageData } = useSelector((store) => {
|
const { mids } = useSelector((store) => {
|
||||||
return {
|
return {
|
||||||
mids:
|
mids:
|
||||||
context == "channel"
|
context == "channel"
|
||||||
? store.channelMessage[id] || []
|
? store.channelMessage[id] || []
|
||||||
: store.userMessage.byId[id] || [],
|
: store.userMessage.byId[id] || [],
|
||||||
messageData: store.message,
|
// messageData: store.message,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listRef.current = [];
|
listRef.current = [];
|
||||||
pageRef.current = [];
|
pageRef.current = [];
|
||||||
setItems([]);
|
setItems([]);
|
||||||
|
setPrepends([]);
|
||||||
setHasMore(true);
|
setHasMore(true);
|
||||||
setAppends([]);
|
// setAppends([]);
|
||||||
}, [context, id]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
if (appends.length) {
|
const container = document.querySelector(`#RUSTCHAT_FEED_${context}_${id}`);
|
||||||
const feedsWrapperEle = document.querySelector(
|
const handler = (e) => {
|
||||||
`#RUSTCHAT_FEED_${context}_${id}`
|
e.preventDefault();
|
||||||
);
|
var n = 0;
|
||||||
if (feedsWrapperEle) {
|
if ("deltaY" in e) n = 1 === e.deltaMode ? 20 * -e.deltaY : -e.deltaY;
|
||||||
feedsWrapperEle.scrollTop = feedsWrapperEle.scrollHeight;
|
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;
|
||||||
}
|
}
|
||||||
|
container.scrollTop += n;
|
||||||
|
};
|
||||||
|
if (container) {
|
||||||
|
container.addEventListener("wheel", handler);
|
||||||
}
|
}
|
||||||
}, [appends, context, id]);
|
return () => {
|
||||||
|
if (container) {
|
||||||
|
container.removeEventListener("wheel", handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [context, id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listRef.current.length == 0) {
|
const fetchMore = () => {
|
||||||
// 初次
|
if (listRef.current.length == 0 && mids.length) {
|
||||||
const pageInfo = getFeedWithPagination({ mids, isLast: true });
|
// 初次
|
||||||
console.log("pull up 2", pageInfo);
|
const pageInfo = getFeedWithPagination({ mids });
|
||||||
pageRef.current = pageInfo;
|
console.log("pull down 2", pageInfo);
|
||||||
listRef.current = pageInfo.ids;
|
pageRef.current = pageInfo;
|
||||||
setItems(listRef.current);
|
listRef.current = pageInfo.ids;
|
||||||
console.log("message pageInfo", mids, pageInfo);
|
setItems(listRef.current);
|
||||||
} else {
|
console.log("message pageInfo", mids, pageInfo);
|
||||||
// 追加
|
} else {
|
||||||
const lastMid = listRef.current.slice(-1);
|
// 追加
|
||||||
const sorteds = mids.slice(0).sort((a, b) => {
|
const lastMid = listRef.current[0];
|
||||||
return Number(a) - Number(b);
|
const sorteds = mids.slice(0).sort((a, b) => {
|
||||||
});
|
return Number(b) - Number(a);
|
||||||
const appends = sorteds.filter((s) => s > lastMid);
|
});
|
||||||
if (appends.length) {
|
const prepends = sorteds.filter((s) => s > lastMid);
|
||||||
setAppends(appends);
|
if (prepends.length) {
|
||||||
|
setPrepends(prepends);
|
||||||
|
}
|
||||||
|
console.log("prepends", prepends, items);
|
||||||
}
|
}
|
||||||
console.log("appends", appends);
|
};
|
||||||
}
|
fetchMore();
|
||||||
}, [mids]);
|
}, [mids]);
|
||||||
const pullUp = () => {
|
|
||||||
|
const pullDown = async () => {
|
||||||
|
// 向下加载
|
||||||
const currPageInfo = pageRef.current;
|
const currPageInfo = pageRef.current;
|
||||||
console.log("pull up", currPageInfo);
|
console.log("pull down", currPageInfo);
|
||||||
// 第一页
|
// 最后一页
|
||||||
if (currPageInfo && currPageInfo.pageNumber == 1) {
|
if (currPageInfo && currPageInfo.isLast) {
|
||||||
setHasMore(false);
|
const [mid] = currPageInfo.ids.slice(-1);
|
||||||
return;
|
const { data: newList } = await loadMoreFromServer({ mid, gid: id });
|
||||||
|
if (newList.length == 0) {
|
||||||
|
setHasMore(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let pageInfo = null;
|
let pageInfo = null;
|
||||||
if (!currPageInfo) {
|
if (!currPageInfo) {
|
||||||
// 初始化
|
// 初始化
|
||||||
pageInfo = getFeedWithPagination({
|
pageInfo = getFeedWithPagination({
|
||||||
mids,
|
mids,
|
||||||
isLast: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const prevPageNumber = currPageInfo.pageNumber - 1;
|
const nextPageNumber = currPageInfo.pageNumber + 1;
|
||||||
pageInfo = getFeedWithPagination({
|
pageInfo = getFeedWithPagination({
|
||||||
mids,
|
mids,
|
||||||
pageNumber: prevPageNumber,
|
pageNumber: nextPageNumber,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
pageRef.current = pageInfo;
|
pageRef.current = pageInfo;
|
||||||
listRef.current = [...pageInfo.ids, ...listRef.current];
|
listRef.current = [...listRef.current, ...pageInfo.ids];
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
setHasMore(!pageInfo.isLast);
|
||||||
setItems(listRef.current);
|
setItems(listRef.current);
|
||||||
console.log("pull up", currPageInfo, listRef.current);
|
console.log("pull down update", currPageInfo, listRef.current);
|
||||||
setHasMore(pageInfo.pageNumber !== 1);
|
}, 1000);
|
||||||
}, 800);
|
|
||||||
};
|
|
||||||
const pullDown = () => {
|
|
||||||
// 向下加载
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mids,
|
mids,
|
||||||
appends,
|
prepends,
|
||||||
|
// appends,
|
||||||
hasMore,
|
hasMore,
|
||||||
pullUp,
|
// pullUp,
|
||||||
pullDown,
|
pullDown,
|
||||||
list: items,
|
list: items,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { Waveform } from "@uiball/loaders";
|
import { Waveform } from "@uiball/loaders";
|
||||||
|
|
||||||
const Styled = styled.div`
|
const Styled = styled.div`
|
||||||
|
margin-top: 80px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -10,35 +11,35 @@ const Styled = styled.div`
|
|||||||
padding: 30px 0;
|
padding: 30px 0;
|
||||||
/* background-color: #eee; */
|
/* background-color: #eee; */
|
||||||
`;
|
`;
|
||||||
export default function LoadMore() {
|
export default function LoadMore({ pullDown = null }) {
|
||||||
// const ref = useRef(undefined);
|
const ref = useRef(undefined);
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// const observer = new IntersectionObserver(
|
const observer = new IntersectionObserver(
|
||||||
// (entries) => {
|
(entries) => {
|
||||||
// entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
// const intersecting = entry.isIntersecting;
|
const intersecting = entry.isIntersecting;
|
||||||
// // const currEle = entry.target;
|
// const currEle = entry.target;
|
||||||
// if (intersecting && loadMore) {
|
if (intersecting && pullDown) {
|
||||||
// // load more
|
// load more
|
||||||
// console.log("inview");
|
console.log("inview");
|
||||||
// loadMore();
|
pullDown();
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// },
|
},
|
||||||
// { threshold: 0 }
|
{ threshold: 0 }
|
||||||
// );
|
);
|
||||||
// const currEle = ref?.current;
|
const currEle = ref?.current;
|
||||||
// if (currEle) {
|
if (currEle) {
|
||||||
// observer.observe(ref.current);
|
observer.observe(ref.current);
|
||||||
// }
|
}
|
||||||
// return () => {
|
return () => {
|
||||||
// if (currEle) {
|
if (currEle) {
|
||||||
// observer.unobserve(currEle);
|
observer.unobserve(currEle);
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
// }, [ref]);
|
}, [ref, pullDown]);
|
||||||
return (
|
return (
|
||||||
<Styled>
|
<Styled ref={ref}>
|
||||||
<Waveform
|
<Waveform
|
||||||
className="loading"
|
className="loading"
|
||||||
size={24}
|
size={24}
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import { useDebounce } from "rooks";
|
|||||||
import { NavLink, useLocation } from "react-router-dom";
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
import Tippy from "@tippyjs/react";
|
import Tippy from "@tippyjs/react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
// import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import PinList from "./PinList";
|
import PinList from "./PinList";
|
||||||
import FavList from "../FavList";
|
import FavList from "../FavList";
|
||||||
import { useReadMessageMutation } from "../../../app/services/message";
|
import { useReadMessageMutation } from "../../../app/services/message";
|
||||||
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
||||||
import useChatScroll from "../../../common/hook/useChatScroll";
|
// import useChatScroll from "../../../common/hook/useChatScroll";
|
||||||
|
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||||
|
|
||||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||||
import Tooltip from "../../../common/component/Tooltip";
|
import Tooltip from "../../../common/component/Tooltip";
|
||||||
import Contact from "../../../common/component/Contact";
|
import Contact from "../../../common/component/Contact";
|
||||||
@@ -30,8 +33,13 @@ import {
|
|||||||
StyledHeader,
|
StyledHeader,
|
||||||
} from "./styled";
|
} from "./styled";
|
||||||
import InviteModal from "../../../common/component/InviteModal";
|
import InviteModal from "../../../common/component/InviteModal";
|
||||||
|
import LoadMore from "./LoadMore";
|
||||||
|
|
||||||
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||||
|
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
|
||||||
|
context: "channel",
|
||||||
|
id: cid,
|
||||||
|
});
|
||||||
const [toolVisible, setToolVisible] = useState("");
|
const [toolVisible, setToolVisible] = useState("");
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -41,7 +49,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
||||||
const {
|
const {
|
||||||
selects,
|
selects,
|
||||||
msgIds,
|
// msgIds,
|
||||||
userIds,
|
userIds,
|
||||||
data,
|
data,
|
||||||
messageData,
|
messageData,
|
||||||
@@ -54,13 +62,13 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
footprint: store.footprint,
|
footprint: store.footprint,
|
||||||
loginUser: store.contacts.byId[store.authData.uid],
|
loginUser: store.contacts.byId[store.authData.uid],
|
||||||
loginUid: store.authData.uid,
|
loginUid: store.authData.uid,
|
||||||
msgIds: store.channelMessage[cid] || [],
|
// msgIds: store.channelMessage[cid] || [],
|
||||||
userIds: store.contacts.ids,
|
userIds: store.contacts.ids,
|
||||||
data: store.channels.byId[cid] || {},
|
data: store.channels.byId[cid] || {},
|
||||||
messageData: store.message || {},
|
messageData: store.message || {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const ref = useChatScroll(msgIds);
|
// const ref = useChatScroll(msgIds);
|
||||||
// const handleClearUnreads = () => {
|
// const handleClearUnreads = () => {
|
||||||
// dispatch(readMessage(msgIds));
|
// dispatch(readMessage(msgIds));
|
||||||
// };
|
// };
|
||||||
@@ -86,6 +94,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
console.log("channel message list", msgIds);
|
console.log("channel message list", msgIds);
|
||||||
const readIndex = footprint.readChannels[cid];
|
const readIndex = footprint.readChannels[cid];
|
||||||
const pinCount = data?.pinned_messages?.length || 0;
|
const pinCount = data?.pinned_messages?.length || 0;
|
||||||
|
const feeds = [...prepends, ...msgIds];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{addMemberModalVisible && (
|
{addMemberModalVisible && (
|
||||||
@@ -223,41 +232,42 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledChannelChat ref={ref}>
|
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
|
||||||
<div className="info">
|
{/* <div className="feed"> */}
|
||||||
<h2 className="title">Welcome to #{name} !</h2>
|
{feeds.map((mid, idx) => {
|
||||||
<p className="desc">This is the start of the #{name} channel. </p>
|
const curr = messageData[mid];
|
||||||
<NavLink
|
if (!curr) return null;
|
||||||
to={`/setting/channel/${cid}?f=${pathname}`}
|
const isFirst = idx == 0;
|
||||||
className="edit"
|
const prev =
|
||||||
>
|
idx == feeds.length - 1 ? null : messageData[feeds[idx + 1]];
|
||||||
<EditIcon className="icon" />
|
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||||
Edit Channel
|
return renderMessageFragment({
|
||||||
</NavLink>
|
selectMode: !!selects,
|
||||||
</div>
|
updateReadIndex: updateReadDebounced,
|
||||||
<div className="feed">
|
read,
|
||||||
{[...msgIds]
|
isFirst,
|
||||||
.sort((a, b) => {
|
prev,
|
||||||
return Number(a) - Number(b);
|
curr,
|
||||||
})
|
contextId: cid,
|
||||||
.map((mid, idx) => {
|
context: "channel",
|
||||||
const curr = messageData[mid];
|
});
|
||||||
if (!curr) return null;
|
})}
|
||||||
const isFirst = idx == 0;
|
{hasMore ? (
|
||||||
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
<LoadMore pullDown={pullDown} />
|
||||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
) : (
|
||||||
return renderMessageFragment({
|
<div className="info">
|
||||||
selectMode: !!selects,
|
<h2 className="title">Welcome to #{name} !</h2>
|
||||||
updateReadIndex: updateReadDebounced,
|
<p className="desc">This is the start of the #{name} channel. </p>
|
||||||
read,
|
<NavLink
|
||||||
isFirst,
|
to={`/setting/channel/${cid}?f=${pathname}`}
|
||||||
prev,
|
className="edit"
|
||||||
curr,
|
>
|
||||||
contextId: cid,
|
<EditIcon className="icon" />
|
||||||
context: "channel",
|
Edit Channel
|
||||||
});
|
</NavLink>
|
||||||
})}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
{/* </div> */}
|
||||||
</StyledChannelChat>
|
</StyledChannelChat>
|
||||||
{/* {unreads != 0 && (
|
{/* {unreads != 0 && (
|
||||||
<StyledNotification>
|
<StyledNotification>
|
||||||
|
|||||||
+47
-56
@@ -2,15 +2,12 @@ import { useState, useEffect } from "react";
|
|||||||
import { useDebounce } from "rooks";
|
import { useDebounce } from "rooks";
|
||||||
import { NavLink, useLocation } from "react-router-dom";
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
import Tippy from "@tippyjs/react";
|
import Tippy from "@tippyjs/react";
|
||||||
import InfiniteScroll from "react-infinite-scroller";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import PinList from "./PinList";
|
import PinList from "./PinList";
|
||||||
import LoadMore from "./LoadMore";
|
|
||||||
import FavList from "../FavList";
|
import FavList from "../FavList";
|
||||||
import { useReadMessageMutation } from "../../../app/services/message";
|
import { useReadMessageMutation } from "../../../app/services/message";
|
||||||
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
import { updateRemeberedNavs } from "../../../app/slices/ui";
|
||||||
// import useChatScroll from "../../../common/hook/useChatScroll";
|
import useChatScroll from "../../../common/hook/useChatScroll";
|
||||||
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
|
||||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||||
import Tooltip from "../../../common/component/Tooltip";
|
import Tooltip from "../../../common/component/Tooltip";
|
||||||
import Contact from "../../../common/component/Contact";
|
import Contact from "../../../common/component/Contact";
|
||||||
@@ -26,14 +23,15 @@ import IconHeadphone from "../../../assets/icons/headphone.svg";
|
|||||||
import boardosIcon from "../../../assets/icons/app.boardos.svg?url";
|
import boardosIcon from "../../../assets/icons/app.boardos.svg?url";
|
||||||
import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url";
|
import webrowseIcon from "../../../assets/icons/app.webrowse.svg?url";
|
||||||
import addIcon from "../../../assets/icons/add.svg?url";
|
import addIcon from "../../../assets/icons/add.svg?url";
|
||||||
import { StyledContacts, StyledChannelChat, StyledHeader } from "./styled";
|
import {
|
||||||
|
// StyledNotification,
|
||||||
|
StyledContacts,
|
||||||
|
StyledChannelChat,
|
||||||
|
StyledHeader,
|
||||||
|
} from "./styled";
|
||||||
import InviteModal from "../../../common/component/InviteModal";
|
import InviteModal from "../../../common/component/InviteModal";
|
||||||
|
|
||||||
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||||
const { list: msgIdList, appends, hasMore, pullUp } = useMessageFeed({
|
|
||||||
context: "channel",
|
|
||||||
id: cid,
|
|
||||||
});
|
|
||||||
const [toolVisible, setToolVisible] = useState("");
|
const [toolVisible, setToolVisible] = useState("");
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -43,6 +41,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
const [addMemberModalVisible, setAddMemberModalVisible] = useState(false);
|
||||||
const {
|
const {
|
||||||
selects,
|
selects,
|
||||||
|
msgIds,
|
||||||
userIds,
|
userIds,
|
||||||
data,
|
data,
|
||||||
messageData,
|
messageData,
|
||||||
@@ -55,16 +54,16 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
footprint: store.footprint,
|
footprint: store.footprint,
|
||||||
loginUser: store.contacts.byId[store.authData.uid],
|
loginUser: store.contacts.byId[store.authData.uid],
|
||||||
loginUid: store.authData.uid,
|
loginUid: store.authData.uid,
|
||||||
|
msgIds: store.channelMessage[cid] || [],
|
||||||
userIds: store.contacts.ids,
|
userIds: store.contacts.ids,
|
||||||
data: store.channels.byId[cid] || {},
|
data: store.channels.byId[cid] || {},
|
||||||
messageData: store.message || {},
|
messageData: store.message || {},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
// const ref = useChatScroll(mids);
|
const ref = useChatScroll(msgIds);
|
||||||
// const handleClearUnreads = () => {
|
// const handleClearUnreads = () => {
|
||||||
// dispatch(readMessage(msgIds));
|
// dispatch(readMessage(msgIds));
|
||||||
// };
|
// };
|
||||||
// remember the route while switching out
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(updateRemeberedNavs());
|
dispatch(updateRemeberedNavs());
|
||||||
return () => {
|
return () => {
|
||||||
@@ -84,10 +83,9 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
? userIds
|
? userIds
|
||||||
: members.slice(0).sort((n) => (n == owner ? -1 : 0));
|
: members.slice(0).sort((n) => (n == owner ? -1 : 0));
|
||||||
const addVisible = loginUser?.is_admin || owner == loginUid;
|
const addVisible = loginUser?.is_admin || owner == loginUid;
|
||||||
|
console.log("channel message list", msgIds);
|
||||||
const readIndex = footprint.readChannels[cid];
|
const readIndex = footprint.readChannels[cid];
|
||||||
const pinCount = data?.pinned_messages?.length || 0;
|
const pinCount = data?.pinned_messages?.length || 0;
|
||||||
// const finalHasMore = selects ? false : hasMore;
|
|
||||||
const msgs = [...msgIdList, ...appends];
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{addMemberModalVisible && (
|
{addMemberModalVisible && (
|
||||||
@@ -211,10 +209,10 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
return (
|
return (
|
||||||
<Contact
|
<Contact
|
||||||
enableContextMenu={true}
|
enableContextMenu={true}
|
||||||
|
cid={cid}
|
||||||
owner={owner == uid}
|
owner={owner == uid}
|
||||||
key={uid}
|
key={uid}
|
||||||
uid={uid}
|
uid={uid}
|
||||||
cid={cid}
|
|
||||||
dm
|
dm
|
||||||
popover
|
popover
|
||||||
/>
|
/>
|
||||||
@@ -225,48 +223,41 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
|
<StyledChannelChat ref={ref}>
|
||||||
{!hasMore && (
|
<div className="info">
|
||||||
<div className="info">
|
<h2 className="title">Welcome to #{name} !</h2>
|
||||||
<h2 className="title">Welcome to #{name} !</h2>
|
<p className="desc">This is the start of the #{name} channel. </p>
|
||||||
<p className="desc">This is the start of the #{name} channel. </p>
|
<NavLink
|
||||||
<NavLink
|
to={`/setting/channel/${cid}?f=${pathname}`}
|
||||||
to={`/setting/channel/${cid}?f=${pathname}`}
|
className="edit"
|
||||||
className="edit"
|
>
|
||||||
>
|
<EditIcon className="icon" />
|
||||||
<EditIcon className="icon" />
|
Edit Channel
|
||||||
Edit Channel
|
</NavLink>
|
||||||
</NavLink>
|
</div>
|
||||||
</div>
|
<div className="feed">
|
||||||
)}
|
{[...msgIds]
|
||||||
{/* <div className="feed"> */}
|
.sort((a, b) => {
|
||||||
<InfiniteScroll
|
return Number(a) - Number(b);
|
||||||
threshold={100}
|
})
|
||||||
hasMore={hasMore}
|
.map((mid, idx) => {
|
||||||
useWindow={false}
|
const curr = messageData[mid];
|
||||||
loader={<LoadMore />}
|
if (!curr) return null;
|
||||||
isReverse={true}
|
const isFirst = idx == 0;
|
||||||
loadMore={pullUp}
|
const prev = idx == 0 ? null : messageData[msgIds[idx - 1]];
|
||||||
>
|
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||||
{msgs.map((mid, idx) => {
|
return renderMessageFragment({
|
||||||
const curr = messageData[mid];
|
selectMode: !!selects,
|
||||||
if (!curr) return null;
|
updateReadIndex: updateReadDebounced,
|
||||||
const isFirst = idx == 0;
|
read,
|
||||||
const prev = idx == 0 ? null : messageData[msgs[idx - 1]];
|
isFirst,
|
||||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
prev,
|
||||||
return renderMessageFragment({
|
curr,
|
||||||
selectMode: !!selects,
|
contextId: cid,
|
||||||
updateReadIndex: updateReadDebounced,
|
context: "channel",
|
||||||
read,
|
});
|
||||||
isFirst,
|
})}
|
||||||
prev,
|
</div>
|
||||||
curr,
|
|
||||||
contextId: cid,
|
|
||||||
context: "channel",
|
|
||||||
});
|
|
||||||
})}
|
|
||||||
</InfiniteScroll>
|
|
||||||
{/* </div> */}
|
|
||||||
</StyledChannelChat>
|
</StyledChannelChat>
|
||||||
{/* {unreads != 0 && (
|
{/* {unreads != 0 && (
|
||||||
<StyledNotification>
|
<StyledNotification>
|
||||||
+5
-4
@@ -36,7 +36,7 @@ import InviteModal from "../../../common/component/InviteModal";
|
|||||||
import LoadMore from "./LoadMore";
|
import LoadMore from "./LoadMore";
|
||||||
|
|
||||||
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
||||||
const { list: msgIds, appends, hasMore, pullDown } = useMessageFeed({
|
const { list: msgIds, prepends, hasMore, pullDown } = useMessageFeed({
|
||||||
context: "channel",
|
context: "channel",
|
||||||
id: cid,
|
id: cid,
|
||||||
});
|
});
|
||||||
@@ -94,6 +94,7 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
console.log("channel message list", msgIds);
|
console.log("channel message list", msgIds);
|
||||||
const readIndex = footprint.readChannels[cid];
|
const readIndex = footprint.readChannels[cid];
|
||||||
const pinCount = data?.pinned_messages?.length || 0;
|
const pinCount = data?.pinned_messages?.length || 0;
|
||||||
|
const feeds = [...prepends, ...msgIds];
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{addMemberModalVisible && (
|
{addMemberModalVisible && (
|
||||||
@@ -231,14 +232,14 @@ export default function ChannelChat({ cid = "", dropFiles = [] }) {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<StyledChannelChat id="ScrollFeedWrapper">
|
<StyledChannelChat id={`RUSTCHAT_FEED_channel_${cid}`}>
|
||||||
{/* <div className="feed"> */}
|
{/* <div className="feed"> */}
|
||||||
{[...msgIds].map((mid, idx) => {
|
{feeds.map((mid, idx) => {
|
||||||
const curr = messageData[mid];
|
const curr = messageData[mid];
|
||||||
if (!curr) return null;
|
if (!curr) return null;
|
||||||
const isFirst = idx == 0;
|
const isFirst = idx == 0;
|
||||||
const prev =
|
const prev =
|
||||||
idx == msgIds.length - 1 ? null : messageData[msgIds[idx + 1]];
|
idx == feeds.length - 1 ? null : messageData[feeds[idx + 1]];
|
||||||
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
const read = curr?.from_uid == loginUid || mid <= readIndex;
|
||||||
return renderMessageFragment({
|
return renderMessageFragment({
|
||||||
selectMode: !!selects,
|
selectMode: !!selects,
|
||||||
@@ -88,6 +88,15 @@ export const StyledChannelChat = styled.article`
|
|||||||
height: -webkit-fill-available;
|
height: -webkit-fill-available;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
/* pagination start */
|
||||||
|
transform: rotate(180deg);
|
||||||
|
direction: rtl;
|
||||||
|
> div,
|
||||||
|
> hr {
|
||||||
|
direction: ltr;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
/* pagination end */
|
||||||
> .info {
|
> .info {
|
||||||
padding-top: 62px;
|
padding-top: 62px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -129,9 +138,5 @@ export const StyledChannelChat = styled.article`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* > .feed {
|
/* > .feed {
|
||||||
overflow-anchor: none;
|
|
||||||
> div {
|
|
||||||
overflow-anchor: auto;
|
|
||||||
}
|
|
||||||
} */
|
} */
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ const NavItem = ({ id, setFiles, toggleRemoveConfirm }) => {
|
|||||||
title: muted ? "Unmute" : "Mute",
|
title: muted ? "Unmute" : "Mute",
|
||||||
handler: handleMute,
|
handler: handleMute,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title: "Notification Settings",
|
// title: "Notification Settings",
|
||||||
underline: true,
|
// underline: true,
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
title: "Invite People",
|
title: "Invite People",
|
||||||
handler: toggleInviteModalVisible,
|
handler: toggleInviteModalVisible,
|
||||||
|
|||||||
@@ -179,10 +179,9 @@ export const renderMessageFragment = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const _key = local_id || mid;
|
const _key = local_id || mid;
|
||||||
// console.log("_key", _key, local_id, mid);
|
console.log("_key", _key, local_id, mid);
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={_key}>
|
<React.Fragment key={_key}>
|
||||||
{divider && <Divider content={divider}></Divider>}
|
|
||||||
<MessageWrapper
|
<MessageWrapper
|
||||||
key={_key}
|
key={_key}
|
||||||
data-key={_key}
|
data-key={_key}
|
||||||
@@ -202,8 +201,17 @@ export const renderMessageFragment = ({
|
|||||||
contextId={contextId}
|
contextId={contextId}
|
||||||
/>
|
/>
|
||||||
</MessageWrapper>
|
</MessageWrapper>
|
||||||
|
{divider && <Divider content={divider}></Divider>}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
// React.memo(
|
||||||
|
// (prevs, nexts) => {
|
||||||
|
// // curr.properties?.local_id
|
||||||
|
// const prevObj = prevs.curr || undefined;
|
||||||
|
// const nextObj = nexts.curr || undefined;
|
||||||
|
// return prevObj?.properties?.local_id === nextObj?.properties?.local_id;
|
||||||
|
// }
|
||||||
|
// );
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getUnreadCount;
|
export default getUnreadCount;
|
||||||
|
|||||||
Reference in New Issue
Block a user