修复群公告发布通知消息正文完全不显示、聊天列表预览显示"undefined"

群公告发布后自动生成的通知消息,真实content是{text:"群公告\n<内容>"}这种
扁平对象(不是纯字符串,也不是标准Drafty的{txt,fmt,ent}结构),且
head['x-im-type']是"text"而不是已处理的"system_notice",落到Drafty处理
分支后既渲染不出气泡正文、列表预览也变成字面的"undefined"字符串。

抽出dispatch.ts里原本只服务system_notice的{text}扁平内容识别逻辑
(重命名为flatTextContentOf并导出),在renderDrafty和
formatMessagePreview的Drafty分支之前都接上——不改变已有的居中系统提示
样式(这条消息本来就该是普通气泡,发送者信息一直是正确的,只是正文
丢了),只是把"文本内容是扁平{text}对象"这一种情况也当作合法纯文本处理。

实测:真实发布一条群公告后,气泡正文和列表预览都正确显示"群公告
全功能测试公告内容",不再是空白或"undefined"。
This commit is contained in:
Your Name
2026-09-16 16:37:48 +08:00
parent 92892e3e7b
commit 823a47e182
3 changed files with 31 additions and 5 deletions
+20 -4
View File
@@ -18,12 +18,28 @@ export function isRecalled(msg: StoredMessage): boolean {
return !isServerDeleted(msg) && msg.content == null; return !isServerDeleted(msg) && msg.content == null;
} }
interface SystemNoticeContent { interface FlatTextContent {
text: string; text: string;
} }
function isSystemNoticeContent(content: unknown): content is SystemNoticeContent { function isFlatTextContent(content: unknown): content is FlatTextContent {
return !!content && typeof content === 'object' && typeof (content as SystemNoticeContent).text === 'string'; return !!content && typeof content === 'object' && typeof (content as FlatTextContent).text === 'string';
}
/**
* `{text: "..."}` -- a flat, non-Drafty content shape (no `txt`/`fmt`/`ent`) this backend uses for
* more than one kind of message, not just the centered system-event lines `systemNoticeText` below
* renders: a real, live-confirmed example is the auto-posted "群公告\n<announcement text>" message
* a group's announcement publish creates, which has an ordinary sender/avatar (NOT a centered system
* line) and `head['x-im-type']:"text"` (not `system_notice`) -- but the SAME flat `{text}` content
* shape regardless. `renderDrafty`/`formatMessagePreview` both need this recognized as plain text
* before falling through to real Drafty processing (`Drafty.format`/`Drafty.preview` on an object
* with none of the fields they expect renders empty/`undefined`, confirmed live: a group's own
* announcement-notice bubble showed no text at all, and the chat list's preview literally read "我:
* undefined").
*/
export function flatTextContentOf(content: unknown): string | null {
return isFlatTextContent(content) ? content.text : null;
} }
/** /**
@@ -35,7 +51,7 @@ function isSystemNoticeContent(content: unknown): content is SystemNoticeContent
export function systemNoticeText(msg: StoredMessage): string | null { export function systemNoticeText(msg: StoredMessage): string | null {
const headType = msg.head?.['x-im-type']; const headType = msg.head?.['x-im-type'];
if (headType !== 'system_notice' && headType !== 'system_action') return null; if (headType !== 'system_notice' && headType !== 'system_action') return null;
return isSystemNoticeContent(msg.content) ? msg.content.text : null; return flatTextContentOf(msg.content);
} }
export interface ContactCardContent { export interface ContactCardContent {
+3
View File
@@ -4,6 +4,7 @@ import { Drafty, type DraftyContent } from 'tinode-sdk';
import { AudioAttachment, FileAttachment, ImageAttachment, VideoAttachment } from '@/features/messages/AttachmentPreview'; import { AudioAttachment, FileAttachment, ImageAttachment, VideoAttachment } from '@/features/messages/AttachmentPreview';
import { SafeLink } from '@/features/messages/SafeLink'; import { SafeLink } from '@/features/messages/SafeLink';
import { flatTextContentOf } from '@/lib/messages/dispatch';
/** /**
* Bubble content renderer for the Drafty style tags this app currently sends/receives. Grown * Bubble content renderer for the Drafty style tags this app currently sends/receives. Grown
@@ -124,6 +125,8 @@ function makeFormatNode(highlightQuery: string | undefined) {
export function renderDrafty(content: DraftyContent | string | null | undefined, highlightQuery?: string): ReactNode { export function renderDrafty(content: DraftyContent | string | null | undefined, highlightQuery?: string): ReactNode {
if (content == null) return null; if (content == null) return null;
if (typeof content === 'string') return highlightQuery ? <>{highlightMatches(content, highlightQuery, 'plain')}</> : content; if (typeof content === 'string') return highlightQuery ? <>{highlightMatches(content, highlightQuery, 'plain')}</> : content;
const flatText = flatTextContentOf(content);
if (flatText != null) return highlightQuery ? <>{highlightMatches(flatText, highlightQuery, 'plain')}</> : flatText;
if (Drafty.isPlainText(content)) { if (Drafty.isPlainText(content)) {
const text = Drafty.toPlainText(content); const text = Drafty.toPlainText(content);
return highlightQuery ? <>{highlightMatches(text, highlightQuery, 'plain')}</> : text; return highlightQuery ? <>{highlightMatches(text, highlightQuery, 'plain')}</> : text;
+8 -1
View File
@@ -1,6 +1,6 @@
import { Drafty, type DraftyContent, type StoredMessage } from 'tinode-sdk'; import { Drafty, type DraftyContent, type StoredMessage } from 'tinode-sdk';
import { callResultDisplay, contactCardContentOf, isServerDeleted, systemNoticeText } from '@/lib/messages/dispatch'; import { callResultDisplay, contactCardContentOf, flatTextContentOf, isServerDeleted, systemNoticeText } from '@/lib/messages/dispatch';
/** /**
* One-line chat-list preview text (README §2.1): plain text renders as-is, everything else falls * One-line chat-list preview text (README §2.1): plain text renders as-is, everything else falls
@@ -26,6 +26,13 @@ export function formatMessagePreview(msg: StoredMessage | undefined, myUserId: s
if (contactCardContentOf(msg)) return `${prefix}[名片]`; if (contactCardContentOf(msg)) return `${prefix}[名片]`;
const callResult = callResultDisplay(msg, msg.from === myUserId); const callResult = callResultDisplay(msg, msg.from === myUserId);
if (callResult) return `${prefix}${callResult.text}`; if (callResult) return `${prefix}${callResult.text}`;
// Same flat `{text}` shape `systemNoticeText` above already knows about, but NOT gated to
// `system_notice`/`system_action` -- a real, live-confirmed example is a group's own
// announcement-publish notice (`head['x-im-type']:"text"`, ordinary sender, not a centered system
// line), which fell through everything above straight into the Drafty-entity branches below and
// produced the literal string "undefined" (confirmed live: chat list showed "我:undefined").
const flatText = flatTextContentOf(content);
if (flatText != null) return prefix + flatText;
if (typeof content === 'string' || Drafty.isPlainText(content)) { if (typeof content === 'string' || Drafty.isPlainText(content)) {
return prefix + Drafty.toPlainText(content); return prefix + Drafty.toPlainText(content);
} }