React Native中react-native-markdown-display组件的原生文本选择与弹窗交互实现方案咨询
React Native中react-native-markdown-display组件的原生文本选择与弹窗交互实现方案咨询
我目前正在用Expo + React Native开发一款AI聊天应用,用react-native-markdown-display来渲染LLM API返回的流式响应内容,但遇到了个头疼的问题——原生文本选择(比如复制、高亮、选择)在这个Markdown渲染组件上完全用不了。
我的核心需求是:让用户能直接在聊天界面选中响应里的任意文本,选完后触发一个弹窗来分析选中的内容。
目前的临时解决方案(体验不佳)
现在我用了个折中的办法:给Markdown组件套了个TouchableOpacity,长按的时候跳转到专门的分析屏,把完整的消息文本传到新屏的TextInput里(因为TextInput支持原生选择),再在那个屏弹出分析弹窗。但这种来回切屏的体验太割裂了,我想直接在主聊天屏完成所有交互。
我需要的帮助
有没有办法实现以下目标:
- 直接在
react-native-markdown-display或者类似的Markdown渲染组件中开启原生文本选择功能? - 或者有没有更优的方案,能在主聊天屏直接实现「文本选择+弹窗触发」的交互,彻底去掉额外的跳转步骤?
任何相关的库、技巧或者可行的临时方案我都非常欢迎!
我的当前代码片段
HomeChatScreen.tsx
<TouchableOpacity activeOpacity={1} delayLongPress={300} onLongPress={() => { // @ts-ignore navigation.navigate('AnalyzeScreen', { messageText: msg.text, messageId: msg.messageId, sessionId: sessionId }); }} > <ChatMarkdown text={msg.text} theme={theme} msgId={msg.id} highlights={messageHighlights} /> </TouchableOpacity>
ChatMarkdown.tsx
import React from 'react'; import { StyleSheet, Text } from 'react-native'; import Markdown, { MarkdownIt } from 'react-native-markdown-display'; import CustomCodeBlock from './CustomCodeBlock'; import { getColorByName } from '../utils/highlightChatUtils'; import AnalyzeModal from './AnalyzeModal'; import type { Highlight } from '../app/api/Api'; const applyHighlights = (text: string, highlights: Highlight[] = [], theme: any) => { if (!highlights?.length) return text; const sortedHighlights = [...highlights].sort((a, b) => b.start - a.start); let highlightedText = text; sortedHighlights.forEach((highlight) => { const { start, end, color } = highlight; if (start < 0 || end > highlightedText.length || start >= end) { return; } const beforeText = highlightedText.substring(0, start); const highlightedPart = highlightedText.substring(start, end); const afterText = highlightedText.substring(end); const colorValue = getColorByName(color); highlightedText = `${beforeText}[[HIGHLIGHT_START:${colorValue}]]${highlightedPart}[[HIGHLIGHT_END]]${afterText}`; }); return highlightedText; }; export default function ChatMarkdown({ text, theme, msgId, highlights }: { text: string; theme: any; msgId: string; highlights?: Highlight[]; }) { if (!text) return null; const highlightedText = applyHighlights(text, highlights || [], theme); const rules = { code_block: (node: any, _children: any, _parent: any, styles: any) => ( <CustomCodeBlock key={node.key} node={node} styles={styles} theme={theme} highlights={highlights} /> ), fence: (node: any, _children: any, _parent: any, styles: any) => ( <CustomCodeBlock key={node.key} node={node} styles={styles} theme={theme} highlights={highlights} /> ), // 自定义文本处理器处理高亮 text: (node: any, _children: any, _parent: any, styles: any) => { if (!node.content) return null; const content = node.content; if (content.includes('[[HIGHLIGHT_START:') && content.includes('[[HIGHLIGHT_END]]')) { return processHighlightedText(content, node.key, styles, theme); } return ( <Text selectable={true} key={node.key} style={styles.body}> {content} </Text> ); }, // 让标题支持选择 heading1: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={[styles.heading1]}> {children} </Text> ), heading2: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={[styles.heading2]}> {children} </Text> ), heading3: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={[styles.heading3]}> {children} </Text> ), // 让列表项支持选择 list_item: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={styles.list_item}> • {children} </Text> ), // 让链接支持选择(同时保留点击功能) link: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={styles.link} onPress={() => { // 处理链接点击(如果需要) // Linking.openURL(node.attributes.href); }} > {children} </Text> ), // 让粗体文本支持选择 strong: (node: any, children: any, _parent: any, styles: any) => ( <Text selectable={true} key={node.key} style={styles.strong}> {children} </Text> ), }; return ( <Markdown rules={rules} style={styles.markdown} theme={theme} > {highlightedText} </Markdown> ); } const styles = StyleSheet.create({ markdown: { // 自定义样式 }, });
(注:我注意到在ChatMarkdown里已经给部分Text组件加了selectable={true},但因为外层套了TouchableOpacity,可能阻止了原生的选择交互?这也是我疑惑的点之一)
内容来源于stack exchange




