mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-06 15:08:20 +00:00
实现 #580 引用消息支持部分引用显示
This commit is contained in:
@@ -4486,15 +4486,16 @@ class ChatService {
|
|||||||
*/
|
*/
|
||||||
private parseQuoteMessage(content: string): { content?: string; sender?: string } {
|
private parseQuoteMessage(content: string): { content?: string; sender?: string } {
|
||||||
try {
|
try {
|
||||||
|
const normalizedContent = this.decodeHtmlEntities(content || '')
|
||||||
// 提取 refermsg 部分
|
// 提取 refermsg 部分
|
||||||
const referMsgStart = content.indexOf('<refermsg>')
|
const referMsgStart = normalizedContent.indexOf('<refermsg>')
|
||||||
const referMsgEnd = content.indexOf('</refermsg>')
|
const referMsgEnd = normalizedContent.indexOf('</refermsg>')
|
||||||
|
|
||||||
if (referMsgStart === -1 || referMsgEnd === -1) {
|
if (referMsgStart === -1 || referMsgEnd === -1) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const referMsgXml = content.substring(referMsgStart, referMsgEnd + 11)
|
const referMsgXml = normalizedContent.substring(referMsgStart, referMsgEnd + 11)
|
||||||
|
|
||||||
// 提取发送者名称
|
// 提取发送者名称
|
||||||
let displayName = this.extractXmlValue(referMsgXml, 'displayname')
|
let displayName = this.extractXmlValue(referMsgXml, 'displayname')
|
||||||
@@ -4511,8 +4512,8 @@ class ChatService {
|
|||||||
let displayContent = referContent
|
let displayContent = referContent
|
||||||
switch (referType) {
|
switch (referType) {
|
||||||
case '1':
|
case '1':
|
||||||
// 文本消息,清理可能的 wxid
|
// 文本消息优先取“部分引用”字段,缺失时再回退到完整 content
|
||||||
displayContent = this.sanitizeQuotedContent(referContent)
|
displayContent = this.extractPreferredQuotedText(referMsgXml)
|
||||||
break
|
break
|
||||||
case '3':
|
case '3':
|
||||||
displayContent = '[图片]'
|
displayContent = '[图片]'
|
||||||
@@ -4552,6 +4553,76 @@ class ChatService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractPreferredQuotedText(referMsgXml: string): string {
|
||||||
|
if (!referMsgXml) return ''
|
||||||
|
|
||||||
|
const sources = [this.decodeHtmlEntities(referMsgXml)]
|
||||||
|
const rawMsgSource = this.extractXmlValue(referMsgXml, 'msgsource')
|
||||||
|
if (rawMsgSource) {
|
||||||
|
const decodedMsgSource = this.decodeHtmlEntities(rawMsgSource)
|
||||||
|
if (decodedMsgSource) {
|
||||||
|
sources.push(decodedMsgSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullContent = this.sanitizeQuotedContent(this.extractXmlValue(sources[0] || referMsgXml, 'content'))
|
||||||
|
const partialText = this.extractPartialQuotedText(sources[0] || referMsgXml, fullContent)
|
||||||
|
if (partialText) return partialText
|
||||||
|
|
||||||
|
const candidateTags = [
|
||||||
|
'selectedcontent',
|
||||||
|
'selectedtext',
|
||||||
|
'selectcontent',
|
||||||
|
'selecttext',
|
||||||
|
'quotecontent',
|
||||||
|
'quotetext',
|
||||||
|
'partcontent',
|
||||||
|
'parttext',
|
||||||
|
'excerpt',
|
||||||
|
'summary',
|
||||||
|
'preview'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const source of sources) {
|
||||||
|
for (const tag of candidateTags) {
|
||||||
|
const value = this.sanitizeQuotedContent(this.extractXmlValue(source, tag))
|
||||||
|
if (value) return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullContent
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractPartialQuotedText(xml: string, fullContent: string): string {
|
||||||
|
if (!xml || !fullContent) return ''
|
||||||
|
|
||||||
|
const startChar = this.extractXmlValue(xml, 'start')
|
||||||
|
const endChar = this.extractXmlValue(xml, 'end')
|
||||||
|
const startIndexRaw = this.extractXmlValue(xml, 'startindex')
|
||||||
|
const endIndexRaw = this.extractXmlValue(xml, 'endindex')
|
||||||
|
const startIndex = Number.parseInt(startIndexRaw, 10)
|
||||||
|
const endIndex = Number.parseInt(endIndexRaw, 10)
|
||||||
|
|
||||||
|
if (startChar && endChar) {
|
||||||
|
const startPos = fullContent.indexOf(startChar)
|
||||||
|
if (startPos !== -1) {
|
||||||
|
const endPos = fullContent.indexOf(endChar, startPos + startChar.length - 1)
|
||||||
|
if (endPos !== -1 && endPos >= startPos) {
|
||||||
|
const sliced = fullContent.slice(startPos, endPos + endChar.length).trim()
|
||||||
|
if (sliced) return sliced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isFinite(startIndex) && Number.isFinite(endIndex) && endIndex >= startIndex) {
|
||||||
|
const chars = Array.from(fullContent)
|
||||||
|
const sliced = chars.slice(startIndex, endIndex + 1).join('').trim()
|
||||||
|
if (sliced) return sliced
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析名片消息
|
* 解析名片消息
|
||||||
* 格式: <msg username="wxid_xxx" nickname="昵称" ... />
|
* 格式: <msg username="wxid_xxx" nickname="昵称" ... />
|
||||||
|
|||||||
@@ -2254,7 +2254,7 @@ class ExportService {
|
|||||||
const referMsgXml = normalized.substring(referMsgStart, referMsgEnd + 11)
|
const referMsgXml = normalized.substring(referMsgStart, referMsgEnd + 11)
|
||||||
const quoteInfo = this.parseQuoteMessage(normalized)
|
const quoteInfo = this.parseQuoteMessage(normalized)
|
||||||
const replyText = this.stripSenderPrefix(this.extractXmlValue(normalized, 'title') || '')
|
const replyText = this.stripSenderPrefix(this.extractXmlValue(normalized, 'title') || '')
|
||||||
const quotedPreview = this.formatQuotedReferencePreview(
|
const quotedPreview = quoteInfo.content || this.formatQuotedReferencePreview(
|
||||||
this.extractXmlValue(referMsgXml, 'content'),
|
this.extractXmlValue(referMsgXml, 'content'),
|
||||||
this.extractXmlValue(referMsgXml, 'type')
|
this.extractXmlValue(referMsgXml, 'type')
|
||||||
)
|
)
|
||||||
@@ -2960,7 +2960,7 @@ class ExportService {
|
|||||||
|
|
||||||
switch (referType) {
|
switch (referType) {
|
||||||
case '1':
|
case '1':
|
||||||
displayContent = this.sanitizeQuotedContent(referContent)
|
displayContent = this.extractPreferredQuotedText(referMsgXml)
|
||||||
break
|
break
|
||||||
case '3':
|
case '3':
|
||||||
displayContent = '[图片]'
|
displayContent = '[图片]'
|
||||||
@@ -3001,6 +3001,76 @@ class ExportService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractPreferredQuotedText(referMsgXml: string): string {
|
||||||
|
if (!referMsgXml) return ''
|
||||||
|
|
||||||
|
const sources = [this.decodeHtmlEntities(referMsgXml)]
|
||||||
|
const rawMsgSource = this.extractXmlValue(referMsgXml, 'msgsource')
|
||||||
|
if (rawMsgSource) {
|
||||||
|
const decodedMsgSource = this.decodeHtmlEntities(rawMsgSource)
|
||||||
|
if (decodedMsgSource) {
|
||||||
|
sources.push(decodedMsgSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullContent = this.sanitizeQuotedContent(this.extractXmlValue(sources[0] || referMsgXml, 'content'))
|
||||||
|
const partialText = this.extractPartialQuotedText(sources[0] || referMsgXml, fullContent)
|
||||||
|
if (partialText) return partialText
|
||||||
|
|
||||||
|
const candidateTags = [
|
||||||
|
'selectedcontent',
|
||||||
|
'selectedtext',
|
||||||
|
'selectcontent',
|
||||||
|
'selecttext',
|
||||||
|
'quotecontent',
|
||||||
|
'quotetext',
|
||||||
|
'partcontent',
|
||||||
|
'parttext',
|
||||||
|
'excerpt',
|
||||||
|
'summary',
|
||||||
|
'preview'
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const source of sources) {
|
||||||
|
for (const tag of candidateTags) {
|
||||||
|
const value = this.sanitizeQuotedContent(this.extractXmlValue(source, tag))
|
||||||
|
if (value) return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullContent
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractPartialQuotedText(xml: string, fullContent: string): string {
|
||||||
|
if (!xml || !fullContent) return ''
|
||||||
|
|
||||||
|
const startChar = this.extractXmlValue(xml, 'start')
|
||||||
|
const endChar = this.extractXmlValue(xml, 'end')
|
||||||
|
const startIndexRaw = this.extractXmlValue(xml, 'startindex')
|
||||||
|
const endIndexRaw = this.extractXmlValue(xml, 'endindex')
|
||||||
|
const startIndex = Number.parseInt(startIndexRaw, 10)
|
||||||
|
const endIndex = Number.parseInt(endIndexRaw, 10)
|
||||||
|
|
||||||
|
if (startChar && endChar) {
|
||||||
|
const startPos = fullContent.indexOf(startChar)
|
||||||
|
if (startPos !== -1) {
|
||||||
|
const endPos = fullContent.indexOf(endChar, startPos + startChar.length - 1)
|
||||||
|
if (endPos !== -1 && endPos >= startPos) {
|
||||||
|
const sliced = fullContent.slice(startPos, endPos + endChar.length).trim()
|
||||||
|
if (sliced) return sliced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isFinite(startIndex) && Number.isFinite(endIndex) && endIndex >= startIndex) {
|
||||||
|
const chars = Array.from(fullContent)
|
||||||
|
const sliced = chars.slice(startIndex, endIndex + 1).join('').trim()
|
||||||
|
if (sliced) return sliced
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
private extractChatLabReplyToMessageId(content: string): string | undefined {
|
private extractChatLabReplyToMessageId(content: string): string | undefined {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeAppMessageContent(content || '')
|
const normalized = this.normalizeAppMessageContent(content || '')
|
||||||
|
|||||||
@@ -8695,6 +8695,28 @@ function MessageBubble({
|
|||||||
appMsgTextCache.set(selector, value)
|
appMsgTextCache.set(selector, value)
|
||||||
return value
|
return value
|
||||||
}, [appMsgDoc, appMsgTextCache])
|
}, [appMsgDoc, appMsgTextCache])
|
||||||
|
const queryPreferredQuotedContent = useCallback((): string => {
|
||||||
|
if (message.quotedContent) return message.quotedContent
|
||||||
|
const candidates = [
|
||||||
|
'refermsg > selectedcontent',
|
||||||
|
'refermsg > selectedtext',
|
||||||
|
'refermsg > selectcontent',
|
||||||
|
'refermsg > selecttext',
|
||||||
|
'refermsg > quotecontent',
|
||||||
|
'refermsg > quotetext',
|
||||||
|
'refermsg > partcontent',
|
||||||
|
'refermsg > parttext',
|
||||||
|
'refermsg > excerpt',
|
||||||
|
'refermsg > summary',
|
||||||
|
'refermsg > preview',
|
||||||
|
'refermsg > content'
|
||||||
|
]
|
||||||
|
for (const selector of candidates) {
|
||||||
|
const value = queryAppMsgText(selector)
|
||||||
|
if (value) return value
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}, [message.quotedContent, queryAppMsgText])
|
||||||
const appMsgThumbRawCandidate = useMemo(() => (
|
const appMsgThumbRawCandidate = useMemo(() => (
|
||||||
message.linkThumb ||
|
message.linkThumb ||
|
||||||
message.appMsgThumbUrl ||
|
message.appMsgThumbUrl ||
|
||||||
@@ -8712,7 +8734,7 @@ function MessageBubble({
|
|||||||
queryAppMsgText('refermsg > fromusr'),
|
queryAppMsgText('refermsg > fromusr'),
|
||||||
queryAppMsgText('refermsg > chatusr')
|
queryAppMsgText('refermsg > chatusr')
|
||||||
)
|
)
|
||||||
const quotedContent = message.quotedContent || queryAppMsgText('refermsg > content') || ''
|
const quotedContent = queryPreferredQuotedContent()
|
||||||
const quotedSenderFallbackName = useMemo(
|
const quotedSenderFallbackName = useMemo(
|
||||||
() => resolveQuotedSenderFallbackDisplayName(
|
() => resolveQuotedSenderFallbackDisplayName(
|
||||||
session.username,
|
session.username,
|
||||||
@@ -9262,7 +9284,7 @@ function MessageBubble({
|
|||||||
// type 57: 引用回复消息,解析 refermsg 渲染为引用样式
|
// type 57: 引用回复消息,解析 refermsg 渲染为引用样式
|
||||||
if (xmlType === '57') {
|
if (xmlType === '57') {
|
||||||
const replyText = q('title') || cleanedParsedContent || ''
|
const replyText = q('title') || cleanedParsedContent || ''
|
||||||
const referContent = q('refermsg > content') || ''
|
const referContent = queryPreferredQuotedContent()
|
||||||
const referType = q('refermsg > type') || ''
|
const referType = q('refermsg > type') || ''
|
||||||
|
|
||||||
// 根据被引用消息类型渲染对应内容
|
// 根据被引用消息类型渲染对应内容
|
||||||
@@ -9385,7 +9407,7 @@ function MessageBubble({
|
|||||||
if (kind === 'quote') {
|
if (kind === 'quote') {
|
||||||
// 引用回复消息(appMsgKind='quote',xmlType=57)
|
// 引用回复消息(appMsgKind='quote',xmlType=57)
|
||||||
const replyText = message.linkTitle || q('title') || cleanedParsedContent || ''
|
const replyText = message.linkTitle || q('title') || cleanedParsedContent || ''
|
||||||
const referContent = message.quotedContent || q('refermsg > content') || ''
|
const referContent = queryPreferredQuotedContent()
|
||||||
return (
|
return (
|
||||||
renderBubbleWithQuote(
|
renderBubbleWithQuote(
|
||||||
renderQuotedMessageBlock(renderTextWithEmoji(cleanMessageContent(referContent))),
|
renderQuotedMessageBlock(renderTextWithEmoji(cleanMessageContent(referContent))),
|
||||||
@@ -9576,7 +9598,7 @@ function MessageBubble({
|
|||||||
// 引用回复消息 (type=57),防止被误判为链接
|
// 引用回复消息 (type=57),防止被误判为链接
|
||||||
if (appMsgType === '57') {
|
if (appMsgType === '57') {
|
||||||
const replyText = parsedDoc?.querySelector('title')?.textContent?.trim() || cleanedParsedContent || ''
|
const replyText = parsedDoc?.querySelector('title')?.textContent?.trim() || cleanedParsedContent || ''
|
||||||
const referContent = parsedDoc?.querySelector('refermsg > content')?.textContent?.trim() || ''
|
const referContent = queryPreferredQuotedContent()
|
||||||
const referType = parsedDoc?.querySelector('refermsg > type')?.textContent?.trim() || ''
|
const referType = parsedDoc?.querySelector('refermsg > type')?.textContent?.trim() || ''
|
||||||
|
|
||||||
const renderReferContent2 = () => {
|
const renderReferContent2 = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user