Preserve assistant selection formatting when copying (#2808)
* fix(app): preserve formatting when copying assistant selections Browser selection serialized React Native Web layout nodes instead of the assistant message semantics. Recover semantic markup from renderer-owned metadata and reuse the rich clipboard generator used by Copy turn. * fix(app): preserve assistant copy semantics Carry source-only Markdown metadata through the selection copy boundary, unwrap generated code links, and cover ordered starts, hard breaks, and fence languages in Playwright. Refresh the Nix dependency hash for the added clipboard dependency. * fix(app): preserve partial selection context Wrap selected fragments with their shared semantic ancestor chain and enable GFM table and strikethrough conversion. Cover partial nested selections and GFM output through the browser clipboard. * fix(app): preserve copied list and autolink text Adjust partial ordered-list starts for omitted siblings, strip parser-generated anchors before Markdown reconstruction, and refresh the Nix dependency hash computed by CI. * fix(app): normalize partial selection context Normalize cloned ordered lists across block boundaries, degrade incomplete table fragments to selected text, and preserve table alignment provenance through Markdown reconstruction. * fix(app): escape copied table delimiters Rendered table cells no longer retain source escape characters. Escape literal pipes during DOM-to-Markdown conversion so copied tables preserve their column structure. * fix(app): preserve authored link semantics The rendered link boundary conflated parser-generated links with authored autolinks and discarded authored titles. Carry title semantics into the anchor and unwrap only links with explicit generated provenance. * fix(app): preserve nested copy structure Blank-line splitting separated loose nested lists before Markdown rendering, leaving no nesting for clipboard serialization to preserve. Derive protected separators from the Markdown block structure and keep copy metadata independent from monospace surface styling. * fix(app): preserve selected file links Assistant messages and rich clipboard HTML must apply the same link validation policy. A shared parser keeps file links semantic in both MIME representations while retaining markdown-it's unsafe-scheme rejection. * fix(app): preserve partial table content A selected body row wider than its retained header cannot be represented as GFM and is truncated when rich HTML is regenerated. Treat only those over-wide fragments, plus headerless fragments, as incomplete while preserving valid short rows.
This commit is contained in:
@@ -11,6 +11,10 @@ import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import { syntaxTokenStyleFor } from "@/styles/syntax-token-styles";
|
||||
import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
|
||||
import { highlightToKeyedLines, type KeyedLine } from "@/utils/highlight-cache";
|
||||
import {
|
||||
markdownCopyCodeBlockDataSet,
|
||||
markdownCopyDataSet,
|
||||
} from "@/assistant-selection-copy/markup";
|
||||
|
||||
interface HighlightedCodeBlockProps {
|
||||
code: string;
|
||||
@@ -63,6 +67,10 @@ export const HighlightedCodeBlock = React.memo(function HighlightedCodeBlock({
|
||||
[inheritedStyles, textStyle],
|
||||
);
|
||||
const renderedCode = useMemo(() => stripTerminalFenceNewline(code), [code]);
|
||||
const copyDataSet = useMemo(
|
||||
() => ({ ...CODE_SURFACE_DATASET, ...markdownCopyCodeBlockDataSet(language) }),
|
||||
[language],
|
||||
);
|
||||
|
||||
const keyedLines = useMemo<KeyedLine[] | null>(
|
||||
() => highlightToKeyedLines(renderedCode, fenceLanguageToExtension(language)),
|
||||
@@ -79,14 +87,18 @@ export const HighlightedCodeBlock = React.memo(function HighlightedCodeBlock({
|
||||
return (
|
||||
<View
|
||||
style={containerStyle}
|
||||
dataSet={CODE_SURFACE_DATASET}
|
||||
dataSet={copyDataSet}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
onPointerLeave={handlePointerLeave}
|
||||
>
|
||||
{keyedLines ? (
|
||||
<MarkdownTextSpan style={innerTextStyle}>{renderCodeSegments(keyedLines)}</MarkdownTextSpan>
|
||||
<MarkdownTextSpan style={innerTextStyle} copyTag="code">
|
||||
{renderCodeSegments(keyedLines)}
|
||||
</MarkdownTextSpan>
|
||||
) : (
|
||||
<MarkdownTextSpan style={innerTextStyle}>{renderedCode}</MarkdownTextSpan>
|
||||
<MarkdownTextSpan style={innerTextStyle} copyTag="code">
|
||||
{renderedCode}
|
||||
</MarkdownTextSpan>
|
||||
)}
|
||||
<CopyButton getCode={getCode} visible={controlsVisible} />
|
||||
</View>
|
||||
@@ -195,6 +207,7 @@ const CopyButton = React.memo(function CopyButton({ getCode, visible }: CopyButt
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={copied ? t("message.actions.copied") : t("message.actions.copyCode")}
|
||||
hitSlop={8}
|
||||
dataSet={markdownCopyDataSet.ignore}
|
||||
>
|
||||
{({ hovered }) => {
|
||||
const iconColor = hovered
|
||||
|
||||
@@ -7,10 +7,12 @@ import {
|
||||
type TextStyle,
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import type { MarkdownCopyInlineTag } from "@/assistant-selection-copy/markup";
|
||||
|
||||
interface MarkdownTextSpanProps {
|
||||
style?: StyleProp<TextStyle>;
|
||||
monoSurface?: boolean;
|
||||
copyTag?: MarkdownCopyInlineTag;
|
||||
children: ReactNode;
|
||||
onPress?: TextProps["onPress"];
|
||||
accessibilityRole?: TextProps["accessibilityRole"];
|
||||
|
||||
@@ -13,10 +13,12 @@ import {
|
||||
iosMarkdownTextIsSelectable,
|
||||
useMarkdownTextSurface,
|
||||
} from "@/components/markdown-text-selection";
|
||||
import type { MarkdownCopyInlineTag } from "@/assistant-selection-copy/markup";
|
||||
|
||||
interface MarkdownTextSpanProps {
|
||||
style?: StyleProp<TextStyle>;
|
||||
monoSurface?: boolean;
|
||||
copyTag?: MarkdownCopyInlineTag;
|
||||
children: ReactNode;
|
||||
// Links route through this span too (see assistant-file-links/link.tsx). A
|
||||
// plain <Text> nested in the paragraph UITextView is dropped, so the link
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import { CODE_SURFACE_DATASET } from "@/styles/code-surface";
|
||||
import { markdownCopyDataSet, type MarkdownCopyInlineTag } from "@/assistant-selection-copy/markup";
|
||||
|
||||
interface MarkdownTextSpanProps {
|
||||
style?: StyleProp<TextStyle>;
|
||||
monoSurface?: boolean;
|
||||
copyTag?: MarkdownCopyInlineTag;
|
||||
children: ReactNode;
|
||||
// Web links use the <a>/Pressable path in link.tsx, not this span, so these
|
||||
// are accepted for prop-shape parity with the native variants and forwarded
|
||||
@@ -28,17 +30,23 @@ interface MarkdownTextSpanProps {
|
||||
export function MarkdownTextSpan({
|
||||
style,
|
||||
monoSurface,
|
||||
copyTag,
|
||||
children,
|
||||
onPress,
|
||||
accessibilityRole,
|
||||
}: MarkdownTextSpanProps) {
|
||||
const dataSet = useMemo(() => {
|
||||
if (copyTag && (monoSurface || copyTag === "code")) {
|
||||
return { ...CODE_SURFACE_DATASET, ...markdownCopyDataSet[copyTag] };
|
||||
}
|
||||
if (copyTag) {
|
||||
return markdownCopyDataSet[copyTag];
|
||||
}
|
||||
return monoSurface ? CODE_SURFACE_DATASET : undefined;
|
||||
}, [copyTag, monoSurface]);
|
||||
|
||||
return (
|
||||
<Text
|
||||
dataSet={monoSurface ? CODE_SURFACE_DATASET : undefined}
|
||||
style={style}
|
||||
onPress={onPress}
|
||||
accessibilityRole={accessibilityRole}
|
||||
>
|
||||
<Text dataSet={dataSet} style={style} onPress={onPress} accessibilityRole={accessibilityRole}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
@@ -57,5 +65,9 @@ const MARKDOWN_PARAGRAPH_RESET: ViewStyle = {};
|
||||
// elements via CSS user-select, so no UITextView equivalent is needed.
|
||||
export function MarkdownParagraphView({ paragraphStyle, children }: MarkdownParagraphViewProps) {
|
||||
const style = useMemo(() => [paragraphStyle, MARKDOWN_PARAGRAPH_RESET], [paragraphStyle]);
|
||||
return <View style={style}>{children}</View>;
|
||||
return (
|
||||
<View style={style} dataSet={markdownCopyDataSet.p}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
cloneElement,
|
||||
} from "react";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
import { MarkdownIt, type ASTNode, type RenderRules } from "react-native-markdown-display";
|
||||
import type MarkdownIt from "markdown-it";
|
||||
import { type ASTNode, type RenderRules } from "react-native-markdown-display";
|
||||
import MaskedView from "@react-native-masked-view/masked-view";
|
||||
import {
|
||||
Circle,
|
||||
@@ -71,6 +72,7 @@ import { markdownNodeContainsType } from "@/utils/markdown-ast";
|
||||
import { useStableEvent } from "@/hooks/use-stable-event";
|
||||
import { HighlightedCodeBlock } from "@/components/highlighted-code-block";
|
||||
import { splitMarkdownBlocks } from "@/utils/split-markdown-blocks";
|
||||
import { createAssistantMarkdownParser } from "@/utils/assistant-markdown-parser";
|
||||
import { formatDuration, formatMessageTimestamp } from "@/utils/time";
|
||||
import { writeMarkdownToRichClipboard } from "@/utils/rich-clipboard";
|
||||
import { getDefaultMarkdownClipboardEnvironment } from "@/utils/rich-clipboard-default-environment";
|
||||
@@ -103,6 +105,12 @@ import { RewindMenu, type RewindMode } from "@/components/rewind/rewind-menu";
|
||||
import { useRewindAgentMutation } from "@/components/rewind/use-rewind-agent-mutation";
|
||||
import { AssistantForkMenu, type AssistantForkTarget } from "@/components/assistant-fork-menu";
|
||||
import { useRetainedPanelActive } from "@/components/retained-panel";
|
||||
import {
|
||||
markdownCopyDataSet,
|
||||
markdownCopyOrderedListDataSet,
|
||||
markdownCopyTableCellDataSet,
|
||||
type MarkdownCopyInlineTag,
|
||||
} from "@/assistant-selection-copy/markup";
|
||||
export type { InlinePathTarget } from "@/assistant-file-links";
|
||||
export type { AssistantForkTarget };
|
||||
|
||||
@@ -880,10 +888,7 @@ function AssistantMarkdownImage({
|
||||
);
|
||||
}
|
||||
|
||||
function getInlineCodeAutoLinkUrl(
|
||||
markdownParser: ReturnType<typeof MarkdownIt>,
|
||||
content: string,
|
||||
): string | null {
|
||||
function getInlineCodeAutoLinkUrl(markdownParser: MarkdownIt, content: string): string | null {
|
||||
const trimmed = content.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
@@ -928,6 +933,7 @@ function getMarkdownLinkSource(node: AssistantMarkdownAstNode): AssistantFileLin
|
||||
return {
|
||||
href: typeof node.attributes?.href === "string" ? node.attributes.href : "",
|
||||
text: getMarkdownNodeText(node),
|
||||
title: typeof node.attributes?.title === "string" ? node.attributes.title : undefined,
|
||||
markup: node.markup,
|
||||
sourceInfo: node.sourceInfo,
|
||||
sourceType: node.sourceType === "inline-code" ? "inline-code" : undefined,
|
||||
@@ -1364,6 +1370,7 @@ interface MarkdownInheritedTextProps {
|
||||
textStyle: TextStyle;
|
||||
style?: StyleProp<TextStyle>;
|
||||
monoSurface?: boolean;
|
||||
copyTag?: MarkdownCopyInlineTag;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
@@ -1372,6 +1379,7 @@ function MarkdownInheritedText({
|
||||
textStyle,
|
||||
style: overrideStyle,
|
||||
monoSurface,
|
||||
copyTag,
|
||||
children,
|
||||
}: MarkdownInheritedTextProps) {
|
||||
const style = useMemo(
|
||||
@@ -1387,6 +1395,7 @@ function MarkdownInheritedText({
|
||||
return (
|
||||
<MarkdownTextSpan
|
||||
monoSurface={monoSurface}
|
||||
copyTag={copyTag}
|
||||
style={style}
|
||||
onPress={linkPress?.onPress}
|
||||
accessibilityRole={linkPress?.accessibilityRole}
|
||||
@@ -1410,13 +1419,27 @@ function MarkdownListItemContent({ contentStyle, children }: MarkdownListItemCon
|
||||
|
||||
interface MarkdownListViewProps {
|
||||
baseStyle: ViewStyle;
|
||||
copyTag: "ol" | "ul";
|
||||
orderedStart?: unknown;
|
||||
spacing: { marginTop: number; marginBottom: number };
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function MarkdownListView({ baseStyle, spacing, children }: MarkdownListViewProps) {
|
||||
function MarkdownListView({
|
||||
baseStyle,
|
||||
copyTag,
|
||||
orderedStart,
|
||||
spacing,
|
||||
children,
|
||||
}: MarkdownListViewProps) {
|
||||
const style = useMemo(() => [baseStyle, spacing], [baseStyle, spacing]);
|
||||
return <View style={style}>{children}</View>;
|
||||
const copyDataSet =
|
||||
copyTag === "ol" ? markdownCopyOrderedListDataSet(orderedStart) : markdownCopyDataSet.ul;
|
||||
return (
|
||||
<View style={style} dataSet={copyDataSet}>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export const AssistantMessage = memo(function AssistantMessage({
|
||||
@@ -1428,18 +1451,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
client,
|
||||
spacing = "default",
|
||||
}: AssistantMessageProps) {
|
||||
const markdownParser = useMemo(() => {
|
||||
const parser = MarkdownIt({ typographer: true, linkify: true });
|
||||
const defaultValidateLink = parser.validateLink.bind(parser);
|
||||
parser.validateLink = (url: string) => {
|
||||
if (url.trim().toLowerCase().startsWith("file://")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return defaultValidateLink(url);
|
||||
};
|
||||
return parser;
|
||||
}, []);
|
||||
const markdownParser = useMemo(createAssistantMarkdownParser, []);
|
||||
|
||||
const fileLinkActions = useAssistantFileLinkActions();
|
||||
const handleMarkdownLinkPress = useStableEvent((url: string) => {
|
||||
@@ -1451,6 +1463,103 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
|
||||
const markdownRules = useMemo<RenderRules>(() => {
|
||||
return {
|
||||
heading1: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading1} dataSet={markdownCopyDataSet.h1}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
heading2: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading2} dataSet={markdownCopyDataSet.h2}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
heading3: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading3} dataSet={markdownCopyDataSet.h3}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
heading4: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading4} dataSet={markdownCopyDataSet.h4}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
heading5: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading5} dataSet={markdownCopyDataSet.h5}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
heading6: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_heading6} dataSet={markdownCopyDataSet.h6}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
blockquote: (
|
||||
node: ASTNode,
|
||||
children: ReactNode[],
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<View
|
||||
key={node.key}
|
||||
style={styles._VIEW_SAFE_blockquote}
|
||||
dataSet={markdownCopyDataSet.blockquote}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
hr: (node: ASTNode, _children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_hr} dataSet={markdownCopyDataSet.hr} />
|
||||
),
|
||||
table: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_table} dataSet={markdownCopyDataSet.table}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
thead: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_thead} dataSet={markdownCopyDataSet.thead}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
tbody: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_tbody} dataSet={markdownCopyDataSet.tbody}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
tr: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<View key={node.key} style={styles._VIEW_SAFE_tr} dataSet={markdownCopyDataSet.tr}>
|
||||
{children}
|
||||
</View>
|
||||
),
|
||||
text: (
|
||||
node: ASTNode,
|
||||
_children: ReactNode[],
|
||||
@@ -1497,6 +1606,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
) => (
|
||||
<MarkdownInheritedText
|
||||
key={node.key}
|
||||
copyTag="strong"
|
||||
inheritedStyles={inheritedStyles}
|
||||
textStyle={styles.strong}
|
||||
>
|
||||
@@ -1512,6 +1622,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
) => (
|
||||
<MarkdownInheritedText
|
||||
key={node.key}
|
||||
copyTag="em"
|
||||
inheritedStyles={inheritedStyles}
|
||||
textStyle={styles.em}
|
||||
>
|
||||
@@ -1527,6 +1638,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
) => (
|
||||
<MarkdownInheritedText
|
||||
key={node.key}
|
||||
copyTag="s"
|
||||
inheritedStyles={inheritedStyles}
|
||||
textStyle={styles.s}
|
||||
>
|
||||
@@ -1547,7 +1659,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
_parent: ASTNode[],
|
||||
styles: MarkdownStyles,
|
||||
) => (
|
||||
<MarkdownTextSpan key={node.key} style={styles.hardbreak}>
|
||||
<MarkdownTextSpan key={node.key} style={styles.hardbreak} copyTag="br">
|
||||
{"\n"}
|
||||
</MarkdownTextSpan>
|
||||
),
|
||||
@@ -1642,6 +1754,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
return (
|
||||
<MarkdownInheritedText
|
||||
key={node.key}
|
||||
copyTag="code"
|
||||
inheritedStyles={inheritedStyles}
|
||||
textStyle={styles.code_inline}
|
||||
monoSurface
|
||||
@@ -1659,6 +1772,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
<MarkdownListView
|
||||
key={node.key}
|
||||
baseStyle={styles.bullet_list}
|
||||
copyTag="ul"
|
||||
spacing={getMarkdownListSpacing(node, parent)}
|
||||
>
|
||||
{children}
|
||||
@@ -1673,6 +1787,8 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
<MarkdownListView
|
||||
key={node.key}
|
||||
baseStyle={styles.ordered_list}
|
||||
copyTag="ol"
|
||||
orderedStart={node.attributes?.start}
|
||||
spacing={getMarkdownListSpacing(node, parent)}
|
||||
>
|
||||
{children}
|
||||
@@ -1689,8 +1805,10 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
const contentStyle = isOrdered ? styles.ordered_list_content : styles.bullet_list_content;
|
||||
|
||||
return (
|
||||
<View key={node.key} style={styles.list_item}>
|
||||
<Text style={iconStyle}>{marker}</Text>
|
||||
<View key={node.key} style={styles.list_item} dataSet={markdownCopyDataSet.li}>
|
||||
<Text style={iconStyle} dataSet={markdownCopyDataSet.ignore}>
|
||||
{marker}
|
||||
</Text>
|
||||
<MarkdownListItemContent contentStyle={contentStyle}>
|
||||
{children}
|
||||
</MarkdownListItemContent>
|
||||
@@ -1699,12 +1817,22 @@ export const AssistantMessage = memo(function AssistantMessage({
|
||||
},
|
||||
th: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<MarkdownTableCellText key={node.key}>
|
||||
<View style={styles._VIEW_SAFE_th}>{children}</View>
|
||||
<View
|
||||
style={styles._VIEW_SAFE_th}
|
||||
dataSet={markdownCopyTableCellDataSet("th", node.attributes?.style)}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</MarkdownTableCellText>
|
||||
),
|
||||
td: (node: ASTNode, children: ReactNode[], _parent: ASTNode[], styles: MarkdownStyles) => (
|
||||
<MarkdownTableCellText key={node.key}>
|
||||
<View style={styles._VIEW_SAFE_td}>{children}</View>
|
||||
<View
|
||||
style={styles._VIEW_SAFE_td}
|
||||
dataSet={markdownCopyTableCellDataSet("td", node.attributes?.style)}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
</MarkdownTableCellText>
|
||||
),
|
||||
paragraph: (
|
||||
|
||||
Reference in New Issue
Block a user