Skip to content

Commit dc8aa07

Browse files
committed
feat: Enhance markdown rendering for lists, bold text, and code blocks, and add developer warnings for unhandled tags.
1 parent 095bfaa commit dc8aa07

File tree

3 files changed

+63
-11
lines changed

3 files changed

+63
-11
lines changed

apps/mobile/components/ai-chat/markdown-text.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,23 @@ const defaultComponents = {
112112
{children}
113113
</Link>
114114
),
115+
strong: ({ children }) => (
116+
<Text className={cn("font-bold")}>{children}</Text>
117+
),
115118
blockquote: ({ children }) => (
116119
<Text className={cn("border-l-2 pl-6 italic")}>{children}</Text>
117120
),
118121
ul: ({ children }) => (
119-
<Text className={cn("my-5 ml-6 list-disc [&>li]:mt-2")}>{children}</Text>
122+
<View className={cn("my-5 flex gap-4 flex-col")}>{children}</View>
123+
),
124+
li: ({ children }) => (
125+
<View className="flex flex-row items-start gap-4">
126+
<View className="bg-foreground mt-2.5 h-2 w-2 rounded-full" />
127+
<Text className="leading-7">{children}</Text>
128+
</View>
120129
),
121130
ol: ({ children }) => (
122-
<Text className={cn("my-5 ml-6 list-decimal [&>li]:mt-2")}>{children}</Text>
131+
<Text className={cn("my-5 flex gap-4 flex-col")}>{children}</Text>
123132
),
124133
hr: () => <View className={cn("my-5 border-b")} />,
125134
table: ({ children }) => (
@@ -160,11 +169,13 @@ const defaultComponents = {
160169
code: ({ children, node }) => {
161170
const isCodeBlock = (node as any)?.__parent?.tagName === "pre";
162171
return (
163-
<Text
164-
className={cn(!isCodeBlock && "rounded border bg-muted font-semibold")}
165-
>
166-
{children}
167-
</Text>
172+
<View className="translate-y-[15px]">
173+
<Text
174+
className={cn(!isCodeBlock && "rounded-lg border px-1 border-input leading-6 bg-muted font-semibold")}
175+
>
176+
{children}
177+
</Text>
178+
</View>
168179
);
169180
},
170181
CodeHeader: ({ language, code }: CodeHeaderProps) => {

packages-test-3/ai-react-native/src/markdown/renderer/Markdown.tsx

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,61 @@
1-
import React, { useMemo } from "react";
1+
import React, { useEffect, useMemo } from "react";
22
import { View } from "react-native";
33
import { parseMarkdown } from "./parser";
44
import { renderNode } from "./renderNode";
55
import { createRenderers, type RendererColors } from "./renderers";
66
import type { HastNode, RenderersMap } from "./types";
77
import { useRuntime } from "@creatorem/ai-chat/runtime";
8+
import type { RootContent, Element } from "hast";
89

910
type Props = {
1011
content: string;
1112
colors: RendererColors;
1213
renderers?: Partial<RenderersMap>;
1314
};
1415

16+
function collectElementTags(nodes: RootContent[], tags: Set<string>): void {
17+
for (const node of nodes) {
18+
if (node.type !== "element") continue;
19+
const element = node as Element;
20+
tags.add(element.tagName);
21+
if (element.children?.length) {
22+
collectElementTags(element.children as RootContent[], tags);
23+
}
24+
}
25+
}
26+
1527
export function Markdown({ content, colors, renderers: customRenderers }: Props) {
1628
const components = useRuntime().components
1729

18-
const renderers = useMemo(() => {
30+
const renderers = useMemo<RenderersMap>(() => {
1931
const defaults = createRenderers(colors, components);
2032
if (!customRenderers) return defaults;
21-
return { ...defaults, ...customRenderers };
22-
}, [colors, customRenderers]);
33+
return { ...defaults, ...customRenderers } as RenderersMap;
34+
}, [colors, customRenderers, components]);
2335

2436
const tree = useMemo(() => parseMarkdown(content), [content]);
37+
const customRendererKeys = useMemo(
38+
() => new Set(Object.keys(customRenderers ?? {})),
39+
[customRenderers],
40+
);
41+
42+
useEffect(() => {
43+
if (!__DEV__) return;
44+
45+
const usedTags = new Set<string>();
46+
collectElementTags(tree.children as RootContent[], usedTags);
47+
48+
const nonCustomizedTags = Array.from(usedTags)
49+
.filter((tag) => !customRendererKeys.has(tag))
50+
.sort();
51+
52+
if (nonCustomizedTags.length > 0) {
53+
console.log(
54+
"[MarkdownRenderer] Tags using default renderer:",
55+
nonCustomizedTags,
56+
);
57+
}
58+
}, [tree, customRendererKeys]);
2559

2660
return (
2761
<View>

packages-test-3/ai-react-native/src/markdown/renderer/renderNode.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Element, Text as HastText } from "hast";
66
const BLOCK_TAGS = new Set([
77
"div",
88
"ul",
9+
"strong",
910
"ol",
1011
"li",
1112
"blockquote",
@@ -131,6 +132,12 @@ export function renderNode(
131132
);
132133
}
133134

135+
if (__DEV__) {
136+
console.warn(
137+
`[MarkdownRenderer] Missing renderer for tag <${node.tagName}>. Falling back to Text wrapper.`,
138+
);
139+
}
140+
134141
// Fallback for unknown tags: wrap in <Text>
135142
return <Text key={key}>{children}</Text>;
136143
}

0 commit comments

Comments
 (0)