1- import React , { useEffect , useRef , useState , useCallback } from 'react' ;
1+ import React , { useEffect , useRef , useState , useCallback , ReactNode } from 'react' ;
22import styled from 'styled-components' ;
33import { Message , parseMessageContent } from '../types/types' ;
44import { appCacheDir } from '@tauri-apps/api/path' ;
@@ -7,9 +7,7 @@ import { path } from '@tauri-apps/api';
77import { invoke } from '@tauri-apps/api/core' ;
88import QuickEmojiReply from './UI/QuickEmojiReply' ;
99import { addReaction , loadMessages , removeReaction } from '../api/api' ;
10- import { BidirectionalInfiniteScroll , LoadItemsParams } from './bidirectional-infinite-scroll' ;
11- import { DataItem , useBidirectionalData } from '../hooks/use-bidirectional-data' ;
12- import { message } from '@tauri-apps/plugin-dialog' ;
10+ import { ScrollU } from 'scroll-u'
1311
1412const appCacheDirPath = await appCacheDir ( ) ;
1513
@@ -232,14 +230,17 @@ const ReactionIcon = styled.button<{ active?: boolean }>`
232230 }
233231` ;
234232
235- // ==================== Component Implementation ====================
236- const MessageItem = React . memo < {
233+ interface MessageNodeProps {
237234 message : Message ;
238235 currentUserId : string ;
239236 onReply : ( message : Message ) => void ;
240237 onReactionClick : ( message : Message , emoji : string ) => void ;
241- onScrollToMessage : ( messageId : number ) => void ;
242- } > ( ( { message, currentUserId, onReply, onReactionClick, onScrollToMessage } ) => {
238+ onScrollToMessage : ( data_message_id : string ) => void ;
239+ }
240+
241+
242+ // ==================== Component Implementation ====================
243+ const MessageItem = React . memo < MessageNodeProps > ( ( { message, currentUserId, onReply, onReactionClick, onScrollToMessage } ) => {
243244 const isOwn = message . user_id === currentUserId ;
244245 const parsedContent = parseMessageContent ( message . content , message . type_ ) ;
245246 const formatTime = ( timestamp : string ) => {
@@ -306,15 +307,14 @@ const MessageItem = React.memo<{
306307
307308 < MessageHeader >
308309 < Username > { message . id } </ Username >
309- { /* <Username>{message.user_id}</Username> */ }
310310 < Timestamp > { formatTime ( message . timestamp ) } </ Timestamp >
311311 </ MessageHeader >
312312
313313 { /* 新的回复引用显示 */ }
314314 { message . isReply && message . replyToMessage && (
315315 < ReplyQuote
316316 $isOwn = { isOwn }
317- onClick = { ( ) => onScrollToMessage ( message . replyToMessage ! . id ) }
317+ onClick = { ( ) => onScrollToMessage ( message . conversation_id + message . replyToMessage ! . id ) }
318318 >
319319 < ReplyQuoteHeader >
320320 < ReplyQuoteIcon >
@@ -391,71 +391,13 @@ const MessageItem = React.memo<{
391391 ) ;
392392} ) ;
393393
394- type MessageExt = DataItem < Message > ;
395394
396395const MessageList : React . FC < MessageListProps > = ( { currentUserId, conversationId, setReplyingTo } ) => {
397-
398- const loadItems = useCallback ( async ( params : LoadItemsParams ) => {
399- const { load_size = 100 } = params
400- let messageId : number = 0 ;
401- let direction : 'forward' | 'backward' = 'backward' ;
402- if ( params . forward ) {
403- messageId = params . forward . key as number ;
404- direction = 'forward' ;
405- } else if ( params . backward ) {
406- messageId = params . backward . key as number ;
407- direction = 'backward' ;
408- }
409-
410- //await new Promise((resolve) => setTimeout(resolve, 1000));
411-
412- const messages = await loadMessages ( conversationId , messageId , direction , load_size ) ;
413- if ( messages . length === 0 ) {
414- console . info ( 'no more message:' , conversationId , messageId , direction ) ;
415- return {
416- data : [ ] ,
417- hasNextPage : false ,
418- } ;
419- }
420- // 处理消息数据
421- const data : MessageExt [ ] = messages . map ( msg => ( {
422- ...msg ,
423- getKey ( item ) {
424- return item . id ;
425- } ,
426- } ) as MessageExt ) ;
427- return {
428- data,
429- hasNextPage : true ,
430- }
431-
432- } , [ conversationId ] ) ;
433-
434-
435- // 使用通用数据管理 hook
436- const {
437- loading,
438- items,
439- hasNextPage,
440- error,
441- loadMore,
442- trimItems,
443- trimForwardItems,
444- updateItem,
445- deleteItem,
446- } = useBidirectionalData ( {
447- loadItems,
448- initialLoadParams : { backward : { key : 1 << 60 } } ,
449- trimThreshold : 25 ,
450- enableDeduplication : true ,
451- } ) ;
452-
453-
454- const handleReply = ( message : Message ) => {
396+ const handleReply = useCallback ( ( message : Message ) => {
455397 setReplyingTo ( message ) ;
456- } ;
398+ } , [ setReplyingTo ] )
457399
458- const handleScrollToMessage = ( messageId : number ) => {
400+ const handleScrollToMessage = useCallback ( ( messageId : string ) => {
459401 // 滚动到被回复的消息
460402 const messageElement = document . querySelector ( `[data-message-id="${ messageId } "]` ) ;
461403 if ( messageElement ) {
@@ -466,7 +408,7 @@ const MessageList: React.FC<MessageListProps> = ({ currentUserId, conversationId
466408 messageElement . classList . remove ( 'highlight' ) ;
467409 } , 2000 ) ;
468410 }
469- } ;
411+ } , [ ] ) ;
470412
471413 // reaction 处理
472414 const handleReactionClick = ( msg : Message , emoji : string ) => {
@@ -485,57 +427,41 @@ const MessageList: React.FC<MessageListProps> = ({ currentUserId, conversationId
485427 }
486428 } ;
487429
488- const renderItem = useCallback ( ( item : MessageExt ) => {
489- return (
490- < MessageItem
491- key = { item . conversation_id + item . id }
492- message = { item }
493- currentUserId = { currentUserId }
494- onReply = { handleReply }
495- onReactionClick = { handleReactionClick }
496- onScrollToMessage = { handleScrollToMessage }
497- />
498- ) ;
499- } , [ currentUserId , handleReply , handleReactionClick , handleScrollToMessage ] ) ;
500430
431+ function getMessageProps ( node : ReactNode ) : MessageNodeProps | undefined {
432+ if ( node && React . isValidElement ( node ) ) {
433+ return node . props as MessageNodeProps ;
434+ }
435+ return undefined ;
436+ }
437+
438+
439+ const loadMore = useCallback ( async ( direction : 'pre' | 'next' , node : React . ReactNode ) => {
440+ const props = getMessageProps ( node ) ! ;
441+ const messageId = props . message . id ;
442+ const messages = await loadMessages ( conversationId , messageId , direction == 'pre' ? 'backward' : 'forward' , 10 ) ;
443+ if ( messages . length === 0 ) {
444+ console . info ( 'no more message:' , conversationId , messageId , direction ) ;
445+ return [ ] ;
446+ }
447+ messages . map ( item => {
448+ return (
449+ < MessageItem
450+ key = { item . conversation_id + item . id }
451+ message = { item }
452+ currentUserId = { currentUserId }
453+ onReply = { handleReply }
454+ onReactionClick = { handleReactionClick }
455+ onScrollToMessage = { handleScrollToMessage }
456+ />
457+ ) ;
458+ } ) ;
459+ } , [ currentUserId , handleReply , handleReactionClick , handleScrollToMessage ] ) ;
501460
502- const renderError = useCallback ( ( ) => {
503- return (
504- < div style = { { textAlign : 'center' , padding : '1rem' , color : 'red' } } >
505- 加载失败,请稍后重试。
506- </ div >
507- ) ;
508- } , [ ] ) ;
509461
510462 return (
511- < BidirectionalInfiniteScroll < Message >
512- items = { items }
513- loading = { loading }
514- hasNextPage = { hasNextPage }
515- error = { error }
516- onLoadMore = { ( params ) => loadMore ( params ) }
517- onTrimItems = { ( direction , count ) => {
518- if ( direction === 'backward' ) {
519- trimItems ( count ) ;
520- } else {
521- trimForwardItems ( count ) ;
522- }
523- } }
524- renderItem = { renderItem }
525- renderError = { renderError }
526- trimThreshold = { 100 }
527- scrollThresholdUp = { 0.15 }
528- scrollThresholdDown = { 0.75 }
529- bottomThreshold = { 100 }
530- enableTrimming = { true }
531- enableBottomLoading = { true }
532- enableScrollPositionPreservation = { true }
533- maxHeight = "2000px"
534- maxWidth = "2000px"
535- className = "mt-4"
536- containerClassName = "container"
537- renderDebugInfo = { true }
538- />
463+ < ScrollU renderItem = { loadMore } />
464+
539465 ) ;
540466} ;
541467
0 commit comments