2026-02-25 15:02:05 +08:00
|
|
|
/**
|
|
|
|
|
* CommentList Component
|
|
|
|
|
* Feature: personal-user-enhancements
|
|
|
|
|
*
|
|
|
|
|
* Displays a list of comments with virtualization for long lists
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import React, { useMemo } from 'react';
|
|
|
|
|
import { Empty, Divider, Spin } from 'antd';
|
|
|
|
|
import { FixedSizeList as List } from 'react-window';
|
|
|
|
|
import CommentItem from './CommentItem';
|
|
|
|
|
|
|
|
|
|
export interface CommentListProps {
|
|
|
|
|
/** Array of comments to display */
|
|
|
|
|
comments: API.Comment[];
|
|
|
|
|
/** Loading state */
|
|
|
|
|
loading?: boolean;
|
|
|
|
|
/** Callback when comment is edited */
|
|
|
|
|
onEdit?: (id: string, text: string) => void | Promise<void>;
|
|
|
|
|
/** Callback when comment is deleted */
|
|
|
|
|
onDelete?: (id: string) => void | Promise<void>;
|
|
|
|
|
/** Loading state for edit operation */
|
|
|
|
|
editLoading?: boolean;
|
|
|
|
|
/** Loading state for delete operation */
|
|
|
|
|
deleteLoading?: boolean;
|
|
|
|
|
/** Enable virtualization (default: true for >20 comments) */
|
|
|
|
|
enableVirtualization?: boolean;
|
|
|
|
|
/** Height of the list container (for virtualization) */
|
|
|
|
|
height?: number;
|
|
|
|
|
/** Item height estimate (for virtualization) */
|
|
|
|
|
itemHeight?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const VIRTUALIZATION_THRESHOLD = 20;
|
|
|
|
|
const DEFAULT_ITEM_HEIGHT = 100;
|
|
|
|
|
const DEFAULT_LIST_HEIGHT = 600;
|
|
|
|
|
|
|
|
|
|
const CommentList: React.FC<CommentListProps> = ({
|
|
|
|
|
comments,
|
|
|
|
|
loading = false,
|
|
|
|
|
onEdit,
|
|
|
|
|
onDelete,
|
|
|
|
|
editLoading = false,
|
|
|
|
|
deleteLoading = false,
|
|
|
|
|
enableVirtualization,
|
|
|
|
|
height = DEFAULT_LIST_HEIGHT,
|
|
|
|
|
itemHeight = DEFAULT_ITEM_HEIGHT,
|
|
|
|
|
}) => {
|
|
|
|
|
// Sort comments chronologically (oldest first)
|
|
|
|
|
const sortedComments = useMemo(() => {
|
2026-02-27 10:07:03 +08:00
|
|
|
if (!comments || !Array.isArray(comments)) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2026-02-25 15:02:05 +08:00
|
|
|
return [...comments].sort((a, b) => {
|
|
|
|
|
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
|
|
|
});
|
|
|
|
|
}, [comments]);
|
|
|
|
|
|
|
|
|
|
// Determine if virtualization should be used
|
|
|
|
|
const shouldVirtualize = useMemo(() => {
|
|
|
|
|
if (enableVirtualization !== undefined) {
|
|
|
|
|
return enableVirtualization;
|
|
|
|
|
}
|
|
|
|
|
return sortedComments.length > VIRTUALIZATION_THRESHOLD;
|
|
|
|
|
}, [enableVirtualization, sortedComments.length]);
|
|
|
|
|
|
|
|
|
|
// Show loading state
|
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<div style={{ textAlign: 'center', padding: '40px 0' }}>
|
|
|
|
|
<Spin size="large" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show empty state
|
|
|
|
|
if (sortedComments.length === 0) {
|
|
|
|
|
return (
|
|
|
|
|
<Empty
|
|
|
|
|
description="No comments yet"
|
|
|
|
|
style={{ padding: '40px 0' }}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render a single comment item
|
|
|
|
|
const renderCommentItem = (comment: API.Comment, index: number) => (
|
|
|
|
|
<div key={comment.id}>
|
|
|
|
|
<CommentItem
|
|
|
|
|
comment={comment}
|
|
|
|
|
onEdit={onEdit}
|
|
|
|
|
onDelete={onDelete}
|
|
|
|
|
editLoading={editLoading}
|
|
|
|
|
deleteLoading={deleteLoading}
|
|
|
|
|
/>
|
|
|
|
|
{index < sortedComments.length - 1 && <Divider style={{ margin: '8px 0' }} />}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Render with virtualization for long lists
|
|
|
|
|
if (shouldVirtualize) {
|
|
|
|
|
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
|
|
|
|
|
const comment = sortedComments[index];
|
|
|
|
|
return (
|
|
|
|
|
<div style={style}>
|
|
|
|
|
{renderCommentItem(comment, index)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<List
|
|
|
|
|
height={height}
|
|
|
|
|
itemCount={sortedComments.length}
|
|
|
|
|
itemSize={itemHeight}
|
|
|
|
|
width="100%"
|
|
|
|
|
overscanCount={5}
|
|
|
|
|
>
|
|
|
|
|
{Row}
|
|
|
|
|
</List>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render without virtualization for short lists
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
{sortedComments.map((comment, index) => renderCommentItem(comment, index))}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default CommentList;
|