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:
@@ -1,10 +1,65 @@
|
||||
/**
|
||||
* Segment text extraction utilities
|
||||
* Segment utilities
|
||||
*/
|
||||
|
||||
import type { MessageSegment, TextSegmentData, AtSegmentData, FileSegmentData, FaceSegmentData, LinkSegmentData, PostRefSegmentData } from './message';
|
||||
import type { UserDTO } from './user';
|
||||
|
||||
// ==================== Chunk Types ====================
|
||||
|
||||
export type ContentChunk =
|
||||
| { kind: 'inline'; parts: MessageSegment[] }
|
||||
| { kind: 'images'; parts: MessageSegment[] }
|
||||
| { kind: 'block'; segment: MessageSegment };
|
||||
|
||||
/**
|
||||
* 将 segments 分成三种 chunk:
|
||||
* - inline: text / at / face 等行内元素
|
||||
* - images: 连续的图片 segment 组合
|
||||
* - block: vote / post_ref / video / file / link / voice 等块级元素
|
||||
*
|
||||
* 与聊天和帖子详情共用同一套分组逻辑。
|
||||
*/
|
||||
export function partitionSegments(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 === 'vote' ||
|
||||
s.type === 'post_ref' ||
|
||||
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 {
|
||||
// 未知类型也当作 inline 处理,避免丢失内容
|
||||
inlineBuf.push(s);
|
||||
}
|
||||
}
|
||||
flushInline();
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract plain text from message segments for display
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user