Skip to content

Commit 18d0271

Browse files
committed
Fix file mentions rendering as block elements in submitted messages
1 parent 8aeeb52 commit 18d0271

File tree

2 files changed

+59
-55
lines changed

2 files changed

+59
-55
lines changed

apps/twig/src/renderer/features/editor/components/MarkdownRenderer.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const HeadingText = ({ children }: { children: React.ReactNode }) => (
3131
</Text>
3232
);
3333

34-
const components: Components = {
34+
export const baseComponents: Components = {
3535
h1: ({ children }) => <HeadingText>{children}</HeadingText>,
3636
h2: ({ children }) => <HeadingText>{children}</HeadingText>,
3737
h3: ({ children }) => <HeadingText>{children}</HeadingText>,
@@ -151,7 +151,7 @@ const components: Components = {
151151
),
152152
};
153153

154-
const remarkPlugins = [remarkGfm];
154+
export const remarkPlugins = [remarkGfm];
155155

156156
export const MarkdownRenderer = memo(function MarkdownRenderer({
157157
content,
@@ -161,7 +161,7 @@ export const MarkdownRenderer = memo(function MarkdownRenderer({
161161
[content],
162162
);
163163
return (
164-
<ReactMarkdown remarkPlugins={remarkPlugins} components={components}>
164+
<ReactMarkdown remarkPlugins={remarkPlugins} components={baseComponents}>
165165
{processedContent}
166166
</ReactMarkdown>
167167
);

apps/twig/src/renderer/features/sessions/components/session-update/UserMessage.tsx

Lines changed: 56 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,77 @@
1-
import { MarkdownRenderer } from "@features/editor/components/MarkdownRenderer";
2-
import { File } from "@phosphor-icons/react";
3-
import { Box, Code } from "@radix-ui/themes";
4-
import type { ReactNode } from "react";
1+
import { FileIcon } from "@components/ui/FileIcon";
2+
import {
3+
baseComponents,
4+
remarkPlugins,
5+
} from "@features/editor/components/MarkdownRenderer";
6+
import { Box, Code, Link } from "@radix-ui/themes";
7+
import { useMemo } from "react";
8+
import type { Components } from "react-markdown";
9+
import ReactMarkdown from "react-markdown";
510

611
interface UserMessageProps {
712
content: string;
813
}
914

10-
function parseFileMentions(content: string): ReactNode[] {
11-
const fileTagRegex = /<file\s+path="([^"]+)"\s*\/>/g;
12-
const parts: ReactNode[] = [];
13-
let lastIndex = 0;
15+
/**
16+
* Convert `<file path="..." />` XML tags into markdown links with a custom
17+
* `twig-file:` protocol so they are parsed as inline elements within paragraphs.
18+
*/
19+
function preprocessFileMentions(content: string): string {
20+
return content.replace(/<file\s+path="([^"]+)"\s*\/>/g, (_match, path) => {
21+
const fileName = path.split("/").pop() ?? path;
22+
const safeFileName = fileName.replace(/[[\]]/g, "\\$&");
23+
return `[${safeFileName}](twig-file:${encodeURIComponent(path)})`;
24+
});
25+
}
1426

15-
for (const match of content.matchAll(fileTagRegex)) {
16-
if (match.index !== undefined && match.index > lastIndex) {
17-
const textBefore = content.slice(lastIndex, match.index);
18-
parts.push(
19-
<MarkdownRenderer key={`text-${lastIndex}`} content={textBefore} />,
27+
const userMessageComponents: Components = {
28+
...baseComponents,
29+
a: ({ href, children }) => {
30+
if (href?.startsWith("twig-file:")) {
31+
const filePath = decodeURIComponent(href.slice("twig-file:".length));
32+
const fileName = filePath.split("/").pop() ?? filePath;
33+
return (
34+
<Code
35+
size="1"
36+
variant="soft"
37+
style={{
38+
display: "inline-flex",
39+
alignItems: "center",
40+
gap: "4px",
41+
verticalAlign: "middle",
42+
}}
43+
>
44+
<FileIcon filename={fileName} size={12} />
45+
{children}
46+
</Code>
2047
);
2148
}
22-
23-
const filePath = match[1];
24-
const fileName = filePath.split("/").pop() ?? filePath;
25-
parts.push(
26-
<Code
27-
key={`file-${match.index}`}
28-
size="1"
29-
variant="soft"
30-
style={{
31-
display: "inline-flex",
32-
alignItems: "center",
33-
gap: "4px",
34-
verticalAlign: "middle",
35-
}}
36-
>
37-
<File size={12} />
38-
{fileName}
39-
</Code>,
40-
);
41-
42-
lastIndex = (match.index ?? 0) + match[0].length;
43-
}
44-
45-
if (lastIndex < content.length) {
46-
parts.push(
47-
<MarkdownRenderer
48-
key={`text-${lastIndex}`}
49-
content={content.slice(lastIndex)}
50-
/>,
49+
return (
50+
<Link href={href} target="_blank" rel="noopener noreferrer" size="1">
51+
{children}
52+
</Link>
5153
);
52-
}
53-
54-
return parts;
55-
}
54+
},
55+
};
5656

5757
export function UserMessage({ content }: UserMessageProps) {
58-
const hasFileMentions = /<file\s+path="[^"]+"\s*\/>/.test(content);
58+
const processedContent = useMemo(
59+
() => preprocessFileMentions(content),
60+
[content],
61+
);
5962

6063
return (
6164
<Box
6265
className="border-l-2 bg-gray-2 py-2 pl-3"
6366
style={{ borderColor: "var(--accent-9)" }}
6467
>
6568
<Box className="font-medium [&>*:last-child]:mb-0">
66-
{hasFileMentions ? (
67-
parseFileMentions(content)
68-
) : (
69-
<MarkdownRenderer content={content} />
70-
)}
69+
<ReactMarkdown
70+
remarkPlugins={remarkPlugins}
71+
components={userMessageComponents}
72+
>
73+
{processedContent}
74+
</ReactMarkdown>
7175
</Box>
7276
</Box>
7377
);

0 commit comments

Comments
 (0)