feat(business): implement block-based content editing and rich text rendering
Introduce a new `BlockEditor` component and upgrade the post/message rendering system to support rich text segments (images, @mentions, votes, and post references). - Implement `BlockEditor` for long-form post creation with image embedding support. - Upgrade `PostContentRenderer` and `SegmentRenderer` to handle complex segment types including inline images and block elements. - Refactor `PostCard` to use segment-based content and image signatures for optimized memoization. - Centralize segment partitioning logic in `segmentUtils.ts` to ensure consistent rendering across chat and post modules. - Update `PostRepository` and `Post` entity to support the new `segments` data structure.
This commit is contained in:
@@ -39,43 +39,7 @@ const IMAGE_MAX_WIDTH = 200;
|
||||
const IMAGE_MAX_HEIGHT = 260;
|
||||
const IMAGE_FALLBACK_SIZE = { width: 200, height: 150 };
|
||||
|
||||
/** 将非 reply 的 segments 分成:行内文案/@、连续图片组、其它块级(音视频文件等) */
|
||||
type ContentChunk =
|
||||
| { kind: 'inline'; parts: MessageSegment[] }
|
||||
| { kind: 'images'; parts: MessageSegment[] }
|
||||
| { kind: 'block'; segment: MessageSegment };
|
||||
|
||||
function partitionMessageSegments(segments: MessageSegment[]): ContentChunk[] {
|
||||
const out: ContentChunk[] = [];
|
||||
let inlineBuf: MessageSegment[] = [];
|
||||
const flushInline = () => {
|
||||
if (inlineBuf.length) {
|
||||
out.push({ kind: 'inline', parts: [...inlineBuf] });
|
||||
inlineBuf = [];
|
||||
}
|
||||
};
|
||||
|
||||
for (const s of segments) {
|
||||
if (s.type === 'image') {
|
||||
flushInline();
|
||||
const last = out[out.length - 1];
|
||||
if (last?.kind === 'images') {
|
||||
last.parts.push(s);
|
||||
} else {
|
||||
out.push({ kind: 'images', parts: [s] });
|
||||
}
|
||||
} else if (s.type === 'video' || s.type === 'file' || s.type === 'link' || s.type === 'voice') {
|
||||
flushInline();
|
||||
out.push({ kind: 'block', segment: s });
|
||||
} else if (s.type === 'text' || s.type === 'at' || s.type === 'face') {
|
||||
inlineBuf.push(s);
|
||||
} else {
|
||||
inlineBuf.push(s);
|
||||
}
|
||||
}
|
||||
flushInline();
|
||||
return out;
|
||||
}
|
||||
import { partitionSegments, type ContentChunk } from '../../../../types/dto';
|
||||
|
||||
// Segment 渲染器 Props
|
||||
export interface SegmentRendererProps {
|
||||
@@ -741,7 +705,7 @@ const MessageSegmentsRendererInner: React.FC<{
|
||||
|
||||
const replySegment = useMemo(() => segments.find(s => s.type === 'reply'), [segments]);
|
||||
const otherSegments = useMemo(() => segments.filter(s => s.type !== 'reply'), [segments]);
|
||||
const chunks = useMemo(() => partitionMessageSegments(otherSegments), [otherSegments]);
|
||||
const chunks = useMemo(() => partitionSegments(otherSegments), [otherSegments]);
|
||||
|
||||
const renderProps = useMemo(() => ({
|
||||
isMe,
|
||||
|
||||
@@ -296,11 +296,11 @@ export function createChatScreenStyles(colors: AppColors, dynamicStyles?: {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
senderName: {
|
||||
fontSize: 14,
|
||||
color: textPrimary,
|
||||
fontSize: 12,
|
||||
color: textSecondary,
|
||||
marginBottom: 4,
|
||||
marginLeft: 4,
|
||||
fontWeight: '600',
|
||||
fontWeight: '500',
|
||||
},
|
||||
mySenderName: {
|
||||
marginLeft: 0,
|
||||
|
||||
Reference in New Issue
Block a user