imRichContentHtml
IM 富文本 wire format(HTML 片段)的解析、序列化、消毒、@ 降级与待上传内联图替换。
Wire format 标记
| 语义 | HTML 形态 |
|---|---|
| @ 提及 | <span data-cw-mention data-user-id="…" data-label="…">@昵称</span> |
| 内联图 | <img data-cw-inline-image src="https:…|data:image/…" width height> |
| 文件 | <span data-cw-file data-path="…" data-label="…">…</span> |
| 表情 | <span data-emoji="😀"><img data-emoji="😀" …></span> |
常量:IM_RICH_MENTION_ATTR、IM_RICH_USER_ID_ATTR、IM_RICH_LABEL_ATTR、IM_RICH_INLINE_IMAGE_ATTR、IM_RICH_FILE_ATTR、IM_RICH_FILE_PATH_ATTR、IM_RICH_EMOJI_ATTR 等。
函数签名
typescript
type ImRichContentSegment =
| { type: 'text'; text: string }
| { type: 'mention'; userId: string; label: string }
| { type: 'file'; path: string; label: string }
| { type: 'image'; url: string; width?: number; height?: number; alt?: string }
| { type: 'emoji'; glyph: string; src?: string }
type MaterializeImRichHtmlOptions = {
resolveMentionLabel?: (userId: string) => string
mentionFallbackLabel?: (userId: string) => string
formatImage?: (url: string) => string
formatFile?: (path: string, label: string) => string
}
type DemoteOutOfScopeMentionsOptions = {
allowedUserIds: ReadonlySet<string> | readonly string[]
resolveMentionLabel?: (userId: string) => string
}
function isImRichHtmlContent(content: string): boolean
function hasImRichInlineClipboardMarkers(html: string): boolean
function contentHasPendingInlineImages(html: string): boolean
function parseImRichContentHtml(content: string): ImRichContentSegment[]
function extractImRichMentionUserIds(content: string): string[]
function materializeImRichHtmlToPlainText(content: string, options?: MaterializeImRichHtmlOptions): string
function serializeImRichContentSegments(segments: readonly ImRichContentSegment[]): string
function demoteOutOfScopeMentionsInImRichHtml(content: string, options: DemoteOutOfScopeMentionsOptions): string
function sanitizeImRichContentHtml(html: string): string
function normalizeImRichEditorClipboardHtml(html: string): string
function resolveImMentionDisplayLabel(userId: string, embeddedLabel?: string, resolvedLabel?: string): string
function replacePendingInlineImageSrcInHtml(
html: string,
upload: (dataUrl: string, alt: string) => Promise<string>,
): Promise<string>前置依赖
replacePendingInlineImageSrcInHtml 需注入上传回调:
| 参数名 | 类型 | 说明 |
|---|---|---|
upload | (dataUrl, alt) => Promise<string> | 将 data:image/* 上传为 HTTPS URL |
MaterializeImRichHtmlOptions / DemoteOutOfScopeMentionsOptions 为可选回调,无全局环境依赖。
主要函数参数
| 函数 | 关键参数 | 返回值 |
|---|---|---|
parseImRichContentHtml | content wire HTML | ImRichContentSegment[] |
serializeImRichContentSegments | segments | wire HTML 字符串 |
materializeImRichHtmlToPlainText | content, options? | 通知/引用用纯文本 |
demoteOutOfScopeMentionsInImRichHtml | content, allowedUserIds | 非成员 @ 降级为 @昵称 文本 |
sanitizeImRichContentHtml | html | 白名单消毒后的 wire HTML |
normalizeImRichEditorClipboardHtml | 剪贴板 HTML | 编辑器 wrap → wire 格式 |
replacePendingInlineImageSrcInHtml | html, upload | 替换 data URL 并 sanitize |
工作原理
- 识别:
isImRichHtmlContent检测是否含 IM 语义 data 属性;hasImRichInlineClipboardMarkers用于剪贴板 HTML 更严格判定。 - 解析:
parseImRichContentHtml按 mention / image / file / emoji 正则切分,块间文本经stripHtmlToText解码实体。 - 序列化:
serializeImRichContentSegments转义文本并输出受控 inline 标签;非 https/http/data 图片 URL 跳过。 - @ 降级:
demoteOutOfScopeMentionsInImRichHtml不在allowedUserIds的 mention 改为 text 段@昵称。 - 剪贴板:
normalizeImRichEditorClipboardHtml将 TipTap NodeView / 气泡 DOM 的 wrap 还原为 wire span/img。 - 上传:
replacePendingInlineImageSrcInHtml遍历data:内联图,调用upload后写回 HTTPS 并sanitize。