diff --git a/src/app/listener.middleware/handler.footprint.ts b/src/app/listener.middleware/handler.footprint.ts index cad4f030..5d99719c 100644 --- a/src/app/listener.middleware/handler.footprint.ts +++ b/src/app/listener.middleware/handler.footprint.ts @@ -1,3 +1,4 @@ +import localforage from "localforage"; import clearTable from "./clear.handler"; interface Params { payload: any; @@ -5,7 +6,7 @@ interface Params { operation: string; } export default async function handler({ operation, data = {}, payload }: Params) { - const table = window.CACHE["footprint"]; + const table = window.CACHE["footprint"] as typeof localforage;; if (operation.startsWith("reset")) { clearTable("footprint"); return; @@ -20,7 +21,14 @@ export default async function handler({ operation, data = {}, payload }: Params) case "updateAfterMid": { const afterMid = payload; - await table?.setItem("afterMid", afterMid); + // console.log("local after mid", afterMid, data); + table.getItem("afterMid").then((val) => { + const storedNum = Number(val ?? 0); + if (storedNum < afterMid) { + table?.setItem("afterMid", afterMid); + } + }); + } break; case "updateMute": diff --git a/src/app/services/message.ts b/src/app/services/message.ts index aa087403..5110d80e 100644 --- a/src/app/services/message.ts +++ b/src/app/services/message.ts @@ -160,9 +160,10 @@ export const messageApi = createApi({ }, async onQueryStarted(params, { dispatch, getState, queryFulfilled }) { const { data: messages } = await queryFulfilled; + const fromHistory = true; if (messages?.length) { messages.forEach((msg) => { - handleChatMessage(msg, dispatch, getState() as RootState); + handleChatMessage(msg, dispatch, getState() as RootState, fromHistory); }); } } diff --git a/src/app/slices/footprint.ts b/src/app/slices/footprint.ts index 4fb19395..b7df4aff 100644 --- a/src/app/slices/footprint.ts +++ b/src/app/slices/footprint.ts @@ -57,7 +57,11 @@ const footprintSlice = createSlice({ state.usersVersion = action.payload; }, updateAfterMid(state, action: PayloadAction) { - state.afterMid = action.payload; + const newMid = action.payload; + // 如果新mid小于已有的afterMid,则不必更新 + if (state.afterMid < newMid) { + state.afterMid = action.payload; + } }, updateAutoDeleteSetting(state, action: PayloadAction) { const payload = action.payload; diff --git a/src/common/hook/useMessageFeed.ts b/src/common/hook/useMessageFeed.ts deleted file mode 100644 index a3c6c4f4..00000000 --- a/src/common/hook/useMessageFeed.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { useEffect, useState, useRef, useCallback } from "react"; -import { useLazyGetHistoryMessagesQuery } from "../../app/services/channel"; -import { useLazyGetHistoryMessagesQuery as useLazyGetDMHistoryMsg } from "../../app/services/user"; -import { useAppSelector } from "../../app/store"; -import { isElementVisible } from "../utils"; -export interface PageInfo { - isFirst: boolean; - isLast: boolean; - pageCount: number; - pageSize: number; - pageNumber: number; - ids: number[]; -} -interface Config extends Partial { - mids: number[]; -} -const getFeedWithPagination = (config: Config): PageInfo => { - const { pageNumber = 1, pageSize = 50, mids = [], isLast = false } = config || {}; - const shadowMids = mids.slice(0); - console.log("pagination", config, shadowMids); - - if (shadowMids.length == 0) - return { - isFirst: true, - isLast: true, - pageCount: 0, - pageSize, - pageNumber: 1, - ids: [] - }; - shadowMids.sort((a, b) => { - return Number(a) - Number(b); - }); - // console.log("message pagination", shadowMids); - const pageCount = Math.ceil(shadowMids.length / pageSize); - const computedPageNumber = isLast ? pageCount : pageNumber; - const _start = -(pageCount - computedPageNumber + 1) * pageSize; - const _tmp = _start + pageSize; - const _end = _tmp == 0 ? undefined : _tmp; - const ids = shadowMids.slice(_start, _end); - // console.log("start,end", _start, _end, ids); - return { - isFirst: computedPageNumber == 1, - isLast: computedPageNumber == pageCount, - pageCount, - pageSize, - pageNumber: computedPageNumber, - ids - }; -}; -let curScrollPos = 0; -let oldScroll = 0; -type Props = { - context?: "channel" | "user"; - id: number; -}; -export default function useMessageFeed({ context = "channel", id }: Props) { - const [loadMoreChannelMsgs] = useLazyGetHistoryMessagesQuery(); - const [loadMoreDmMsgs] = useLazyGetDMHistoryMsg(); - const listRef = useRef([]); - const pageRef = useRef(null); - const containerRef = useRef(null); - const [pulling, setPulling] = useState(false); - const [hasMore, setHasMore] = useState(true); - const [appends, setAppends] = useState([]); - const [items, setItems] = useState([]); - const loadMoreMsgsFromServer = context == "channel" ? loadMoreChannelMsgs : loadMoreDmMsgs; - const { mids, messageData, loginUid } = useAppSelector((store) => { - return { - loginUid: store.authData.user?.uid, - mids: - context == "channel" ? store.channelMessage[id] || [] : store.userMessage.byId[id] || [], - messageData: store.message - }; - }); - useEffect(() => { - // curScrollPos = 0; - // oldScroll = 0; - listRef.current = []; - pageRef.current = null; - setItems([]); - setHasMore(true); - setAppends([]); - setPulling(false); - }, [context, id]); - - useEffect(() => { - const currentItems = listRef.current; - // const [lastMid=Infinity]=currentItems.slice(-1) - //过滤掉本地(以及后来追加的消息?) - const serverMids = mids.filter((id: number) => { - // 如果是本地消息,id是时间戳 - const ts = +new Date(); - return Math.abs(ts - id) > 10 * 1000; - }); - if (serverMids.length > 0) { - if (currentItems.length == 0) { - //初次 - const pageInfo = getFeedWithPagination({ - mids: serverMids, - isLast: true - }); - pageRef.current = pageInfo; - listRef.current = pageInfo.ids; - setItems(pageInfo.ids); - // console.log("message pageInfo", serverMids, pageInfo); - } else { - const container = containerRef.current; - if (container) { - const loadMoreEle = container.querySelector("[data-load-more]"); - console.log("effected by pull server data", loadMoreEle, isElementVisible(loadMoreEle)); - if (isElementVisible(loadMoreEle)) { - // 有更新:来自于拉取历史消息,拉取下一页数据 - loadMore(); - } - } - } - } - }, [mids]); - - useEffect(() => { - //处理追加:来自于其它人的实时消息以及自己发的 - const [lastMid] = listRef.current.slice(-1); - const sorteds = mids.slice(0).sort((a: number, b: number) => { - return Number(a) - Number(b); - }); - const appends = sorteds.filter((s: number) => s > lastMid); - if (appends.length) { - setAppends(appends); - 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); - } - } - } - }, [mids, messageData, loginUid]); - useEffect(() => { - // 处理自动滚动 - if (items.length > 0) { - let wrapper = containerRef.current = document.querySelector(`#VOCECHAT_FEED_${context}_${id}`); - if (wrapper) { - const newScroll = wrapper.scrollHeight - wrapper.clientHeight; - wrapper.scrollTop = curScrollPos + (newScroll - oldScroll); - } - } - }, [items, context, id]); - - const loadMore = () => { - console.log("load more start", mids, listRef.current, pageRef.current); - const currPageInfo = pageRef.current; - let pageInfo: PageInfo; - if (!currPageInfo) { - // 初始化 - console.log("first pagination"); - - pageInfo = getFeedWithPagination({ - mids, - isLast: true - }); - } else { - const prevPageNumber = currPageInfo.pageNumber - 1; - pageInfo = getFeedWithPagination({ - mids, - pageNumber: prevPageNumber == 0 ? 1 : prevPageNumber - }); - console.log("continue to next page", currPageInfo, prevPageNumber, pageInfo); - } - pageRef.current = pageInfo; - listRef.current = [...new Set([...pageInfo.ids, ...listRef.current])].sort((a, b) => a > b ? 1 : -1); - setTimeout( - () => { - console.log("load more timeout", currPageInfo, mids, listRef.current); - setItems(listRef.current); - const container = containerRef.current; - if (container) { - curScrollPos = container.scrollTop; - oldScroll = container.scrollHeight - container.clientHeight; - } - setPulling(false); - }, - currPageInfo?.isLast ? 10 : 500 - ); - setPulling(false); - }; - const pullUp = useCallback( - async () => { - - setPulling(true); - const currPageInfo = pageRef.current; - console.log("pull up start", currPageInfo); - // 本地数据的第一页 - if (currPageInfo && currPageInfo.isFirst || (!currPageInfo && mids.length == 0)) { - const [firstMid] = currPageInfo ? currPageInfo.ids : [0]; - const { data: newList } = await loadMoreMsgsFromServer({ - mid: firstMid, - id - }); - if (newList?.length == 0) { - // 只有在这里,才可以把has more去掉 - setHasMore(false); - } - return; - } - loadMore(); - }, - [context, id], - ); - - return { - pulling, - mids, - appends, - hasMore, - pullUp, - list: items - }; -} diff --git a/src/common/hook/useStreaming/chat.handler.ts b/src/common/hook/useStreaming/chat.handler.ts index a775f6f5..ebb86cad 100644 --- a/src/common/hook/useStreaming/chat.handler.ts +++ b/src/common/hook/useStreaming/chat.handler.ts @@ -18,7 +18,7 @@ type CurrentState = { [key: number]: number; }; }; -const handler = (data: ChatEvent, dispatch: AppDispatch, currState: CurrentState) => { +const handler = (data: ChatEvent, dispatch: AppDispatch, currState: CurrentState, fromHistory = false) => { const { mid, from_uid, @@ -42,12 +42,15 @@ const handler = (data: ChatEvent, dispatch: AppDispatch, currState: CurrentState properties, expires_in }; - switch (type) { - case "normal": - case "reply": - // 更新after_mid - dispatch(updateAfterMid(mid)); - break; + if (!fromHistory) { + // 如果来自历史消息的拉取,则忽略更新after mid + switch (type) { + case "normal": + case "reply": + // 更新after_mid + dispatch(updateAfterMid(mid)); + break; + } } const { loginUid, readUsers = {}, readChannels = {} } = currState; const to = "gid" in target ? "channel" : "user"; diff --git a/src/routes/chat/Layout/MessageFeed.tsx b/src/routes/chat/Layout/MessageFeed.tsx deleted file mode 100644 index 6566ab8d..00000000 --- a/src/routes/chat/Layout/MessageFeed.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import { memo, useEffect, useRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { NavLink, useLocation } from 'react-router-dom'; -import { useDebounce, useLocalstorageState } from 'rooks'; -import { ViewportList, ViewportListRef } from 'react-viewport-list'; -import { Waveform } from '@uiball/loaders'; -import clsx from 'clsx'; - -import { useLazyLoadMoreMessagesQuery, useReadMessageMutation } from '../../../app/services/message'; -import { useAppSelector } from '../../../app/store'; -import EditIcon from "../../../assets/icons/edit.svg"; -import { renderMessageFragment } from '../utils'; -import { KEY_UID } from '../../../app/config'; -// import LoadMore from '../LoadMore'; - -const checkHistory = (key: string) => { - const currUid = localStorage.getItem(KEY_UID) ?? ""; - const _key = `${currUid}_${key}`; - const local = Number(localStorage.getItem(_key) ?? 0); - return local == 0; -}; - -const setHistory = (key: string) => { - const currUid = localStorage.getItem(KEY_UID) ?? ""; - const _key = `${currUid}_${key}`; - localStorage.setItem(_key, "1"); -}; -type Props = { - context: "user" | "channel", - id: number -} -const triggerScrollHeight = 400; -const MessageFeed = ({ context, id }: Props) => { - const { t } = useTranslation("chat"); - const [loadMoreMessage, { isLoading: loadingMore, isSuccess, data: historyData }] = useLazyLoadMoreMessagesQuery(); - const [historyMid, setHistoryMid] = useLocalstorageState(`history_mid_${context}_${id}`, ""); - const listRef = useRef(null); - const ref = useRef( - null, - ); - const { pathname } = useLocation(); - const [updateReadIndex] = useReadMessageMutation(); - const updateReadDebounced = useDebounce(updateReadIndex, 300); - const { - mids, - selects, - data, - messageData, - loginUser, - footprint - } = useAppSelector((store) => { - return { - mids: context == "user" ? store.userMessage.byId[id] : store.channelMessage[id], - selects: store.ui.selectMessages[`${context}_${id}`], - footprint: store.footprint, - loginUser: store.authData.user, - data: context == "channel" ? store.channels.byId[id] : undefined, - messageData: store.message || {} - }; - }); - const debouncedScrollHandler = useDebounce(() => { - const container = ref ? ref.current : null; - if (container && historyMid && !loadingMore) { - if (container.scrollTop <= 200) { - // pull up - console.log("start pul up"); - loadMoreMessage({ context, id, mid: +historyMid }); - } - } - }, 500); - useEffect(() => { - if (isSuccess && historyData) { - if (historyData.length == 0) { - // 到顶了 - setHistory(`history_${context}_${id}`); - } else { - // 记录最新的mid - const [{ mid }] = historyData; - setHistoryMid(String(mid)); - } - } - }, [isSuccess, historyData, context, id]); - - useEffect(() => { - // context changed, scroll to bottom - console.log("listRef", listRef); - if (listRef && listRef.current) { - const list = listRef.current; - list.scrollToIndex({ - prerender: 40, - index: Number.POSITIVE_INFINITY - }); - } - const hasHistory = checkHistory(`history_${context}_${id}`); - const container = ref ? ref.current : null; - if (hasHistory && container) { - container.addEventListener("scroll", debouncedScrollHandler); - } - return () => { - if (hasHistory && container) { - container.removeEventListener("scroll", debouncedScrollHandler); - } - }; - }, [context, id]); - useEffect(() => { - // check current scroll position first, scroll to bottom only when under the trigger number - if (ref && ref.current) { - const container = ref.current; - const { scrollHeight, scrollTop, offsetHeight } = container; - const deltaNum = scrollHeight - scrollTop - offsetHeight; - console.log("delta", deltaNum); - // sent by myself (tricky!) - const [lastMid] = mids ? mids.slice(-1) : [0]; - const ts = new Date().getTime(); - const isSentByMyself = ts - lastMid < 1000; - if (deltaNum < triggerScrollHeight) { - container.classList.add("scroll-smooth"); - // scroll to bottom - container.scrollTop = container.scrollHeight; - container.classList.remove("scroll-smooth"); - } else if (isSentByMyself) { - container.scrollTop = container.scrollHeight; - } - } - // 首次进入,就用mids - const lastMid = mids ? mids.slice(0, 1)[0] : ""; - if (!historyMid && lastMid) { - setHistoryMid(`${lastMid}`); - } - }, [mids, historyMid]); - - // const handleMessageListChange = (data: [number, number]) => { - // const [first, last] = data; - // // todo - // // console.log("index changed", data); - // }; - const readIndex = context == "channel" ? footprint.readChannels[id] : footprint.readUsers[id]; - const isEmptyList = !mids || mids.length == 0; - return ( -
- {context == "channel" &&
-

{t("welcome_channel", { name: data?.name })}

-

{t("welcome_desc", { name: data?.name })}

- {loginUser?.is_admin && ( - - - {t("edit_channel")} - - )} -
} -
- -
- {isEmptyList ? null : - {((mid, idx) => { - const curr = messageData[mid]; - if (!curr) return null; - const isFirst = idx == 0; - const prev = isFirst ? null : messageData[mids[idx - 1]]; - const read = curr?.from_uid == loginUser?.uid || mid <= readIndex; - return renderMessageFragment({ - selectMode: !!selects, - updateReadIndex: updateReadDebounced, - read, - prev, - curr, - contextId: id, - context - }); - })} - } -
- ); -}; - -export default memo(MessageFeed); \ No newline at end of file diff --git a/src/routes/chat/Layout/NewMessageBottomTip.tsx b/src/routes/chat/Layout/NewMessageBottomTip.tsx index 0fbbe286..71b4e228 100644 --- a/src/routes/chat/Layout/NewMessageBottomTip.tsx +++ b/src/routes/chat/Layout/NewMessageBottomTip.tsx @@ -1,17 +1,16 @@ import clsx from 'clsx'; -import { useState, useEffect } from 'react'; +// import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppSelector } from '../../../app/store'; import getUnreadCount from '../utils'; -import { triggerScrollHeight } from './MessageFeed'; type Props = { context: "channel" | "user", - id: number + id: number, + scrollToBottom?: () => void } // linear-gradient(135deg,_#3C8CE7_0%,_#00EAFF_100%) -const NewMessageBottomTip = ({ context, id }: Props) => { - const [visible, setVisible] = useState(false); +const NewMessageBottomTip = ({ context, id, scrollToBottom }: Props) => { const { t } = useTranslation("chat"); const { readIndex, @@ -34,33 +33,16 @@ const NewMessageBottomTip = ({ context, id }: Props) => { messageData, loginUid }); - useEffect(() => { - const container = document.querySelector(`#VOCECHAT_FEED_${context}_${id}`) as HTMLElement; - if (container) { - const { scrollHeight, scrollTop, offsetHeight } = container; - const deltaNum = scrollHeight - scrollTop - offsetHeight; - const showTheTip = deltaNum > triggerScrollHeight && unreads > 0; - console.log("show the tip", showTheTip); - setVisible(showTheTip); - } - }, [context, id, unreads]); - const handleMarkRead = () => { - const container = document.querySelector(`#VOCECHAT_FEED_${context}_${id}`) as HTMLElement; - if (container) { - // scroll to bottom - container.classList.add("scroll-smooth"); - container.scrollTop = container.scrollHeight; - container.classList.remove("scroll-smooth"); - setVisible(false); - } - }; + console.log("unreads", unreads); + + return ( - ); diff --git a/src/routes/chat/Layout/VirtualMessageFeed/CustomHeader.tsx b/src/routes/chat/Layout/VirtualMessageFeed/CustomHeader.tsx new file mode 100644 index 00000000..a18d4cea --- /dev/null +++ b/src/routes/chat/Layout/VirtualMessageFeed/CustomHeader.tsx @@ -0,0 +1,54 @@ +import { Waveform } from '@uiball/loaders'; +import clsx from 'clsx'; +// import React from 'react' +import { useTranslation } from 'react-i18next'; +import { NavLink, useLocation } from 'react-router-dom'; +import { useAppSelector } from '../../../../app/store'; +import EditIcon from "../../../../assets/icons/edit.svg"; + +type ChannelHeaderProps = { + cid: number +} +const ChannelHeader = ({ cid }: ChannelHeaderProps) => { + const { pathname } = useLocation(); + const { t } = useTranslation("chat"); + const { data, loginUser } = useAppSelector(store => { + return { + loginUser: store.authData.user, + data: store.channels.byId[cid] + + }; + }); + return ( +
+

{t("welcome_channel", { name: data?.name })}

+

{t("welcome_desc", { name: data?.name })}

+ {loginUser?.is_admin && ( + + + {t("edit_channel")} + + )} +
+ ); +}; + +type Props = { + context?: { + id: number, + isChannel: boolean, + loadingMore: boolean + } +} +const CustomHeader = ({ context }: Props) => { + if (!context) return null; + const { id, isChannel, loadingMore } = context; + return <> + {isChannel ? : null} +
+ +
+ ; +}; + +export default CustomHeader; \ No newline at end of file diff --git a/src/routes/chat/Layout/VirtualMessageFeed/CustomList.tsx b/src/routes/chat/Layout/VirtualMessageFeed/CustomList.tsx new file mode 100644 index 00000000..6e948aae --- /dev/null +++ b/src/routes/chat/Layout/VirtualMessageFeed/CustomList.tsx @@ -0,0 +1,10 @@ +import { forwardRef } from 'react'; + +// @ts-ignore +const CustomList = forwardRef(({ style, ...props }, ref) => { + // @ts-ignore + return
; +}); + + +export default CustomList; \ No newline at end of file diff --git a/src/routes/chat/Layout/VirtualMessageFeed/index.tsx b/src/routes/chat/Layout/VirtualMessageFeed/index.tsx new file mode 100644 index 00000000..3886e8fe --- /dev/null +++ b/src/routes/chat/Layout/VirtualMessageFeed/index.tsx @@ -0,0 +1,140 @@ +import { useEffect, useRef, useCallback, useState, useMemo } from 'react'; +// import clsx from 'clsx'; +// import { useTranslation } from 'react-i18next'; +import { useDebounce, useLocalstorageState } from 'rooks'; +import { Virtuoso, VirtuosoHandle } from 'react-virtuoso'; + +import { useLazyLoadMoreMessagesQuery, useReadMessageMutation } from '../../../../app/services/message'; +import { useAppSelector } from '../../../../app/store'; +import { renderMessageFragment } from '../../utils'; +import NewMessageBottomTip from "../NewMessageBottomTip"; +import CustomList from './CustomList'; +import CustomHeader from './CustomHeader'; +type Props = { + context: "user" | "channel", + id: number +} +// const firstMsgIndex = 10000; +// let prevMids: number[] = []; +const VirtualMessageFeed = ({ context, id }: Props) => { + // const { t } = useTranslation("chat"); + // const [firstItemIndex, setFirstItemIndex] = useState(firstMsgIndex); + const [atBottom, setAtBottom] = useState(false); + const [loadMoreMessage, { isLoading: loadingMore, isSuccess, data: historyData }] = useLazyLoadMoreMessagesQuery(); + const [historyMid, setHistoryMid] = useLocalstorageState<'reached' | string>(`history_mid_${context}_${id}`, ""); + const vList = useRef(null); + const [updateReadIndex] = useReadMessageMutation(); + const updateReadDebounced = useDebounce(updateReadIndex, 300); + const { + mids = [], + selects, + messageData, + loginUser, + footprint + } = useAppSelector((store) => { + return { + mids: context == "user" ? store.userMessage.byId[id] : store.channelMessage[id], + selects: store.ui.selectMessages[`${context}_${id}`], + footprint: store.footprint, + loginUser: store.authData.user, + messageData: store.message || {} + }; + }); + + useEffect(() => { + if (isSuccess && historyData) { + if (historyData.length == 0) { + // 到顶了 + setHistoryMid(`reached`); + } else { + // 记录最新的mid + const [{ mid }] = historyData; + setHistoryMid(`${mid}`); + } + } + }, [isSuccess, historyData, mids]); + // useEffect(() => { + // console.log("diff mids", prevMids, mids); + // const newCount = mids.length - prevMids.length; + // setFirstItemIndex((prev) => prev - newCount); + // }, [mids]); + + // 加载更多 + const handleLoadMore = useCallback(() => { + console.log("reach start "); + if (historyMid === "reached") return; + let lastMid = mids.slice(0, 1)[0]; + if (historyMid) { + lastMid = +historyMid; + } + // prevMids = mids; + loadMoreMessage({ context, id, mid: lastMid }); + // return false; + }, [mids, context, id]); + // 自动跟随 + const handleFollowOutput = (isAtBottom: boolean) => { + const [lastMid] = mids ? mids.slice(-1) : [0]; + const ts = new Date().getTime(); + // tricky + const isSentByMyself = ts - lastMid < 1000; + if (isAtBottom || isSentByMyself) { + return isAtBottom ? "smooth" : true; + } else { + return false; + } + }; + // 滚动到底部 + const handleScrollBottom = useCallback(() => { + const vl = vList!.current; + if (vl) { + vl.scrollToIndex(mids.length - 1); + } + }, [mids]); + const handleBottomStateChange = (bottom: boolean) => { + setAtBottom(bottom); + }; + const readIndex = context == "channel" ? footprint.readChannels[id] : footprint.readUsers[id]; + return <> + { + // 计算出真正的index + // const idx = index - firstItemIndex; + const curr = messageData[mid]; + if (!curr) return null; + const isFirst = idx == 0; + const prev = isFirst ? null : messageData[mids[idx - 1]]; + const read = curr?.from_uid == loginUser?.uid || mid <= readIndex; + return renderMessageFragment({ + selectMode: !!selects, + updateReadIndex: updateReadDebounced, + read, + prev, + curr, + contextId: id, + context + }); + }} /> + {!atBottom && } + + ; +}; + +export default VirtualMessageFeed; \ No newline at end of file diff --git a/src/routes/chat/Layout/index.tsx b/src/routes/chat/Layout/index.tsx index 27f9efa8..a5d20aad 100644 --- a/src/routes/chat/Layout/index.tsx +++ b/src/routes/chat/Layout/index.tsx @@ -15,7 +15,8 @@ import LicenseUpgradeTip from "./LicenseOutdatedTip"; import DnDTip from "./DnDTip"; import IconWarning from '../../../assets/icons/warning.svg'; import ImagePreview from "../../../common/component/ImagePreview"; -import MessageFeed from "./MessageFeed"; + +import VirtualMessageFeed from "./VirtualMessageFeed"; interface Props { readonly?: boolean; @@ -96,7 +97,7 @@ const Layout: FC = ({
{/* 消息流 */} - + {/* 发送框 */}
{readonly ? (