fix(markdown): classify web links explicitly and complete renderer types

This commit is contained in:
Mohamed Boudra
2026-10-15 16:42:14 +02:00
parent e014113792
commit e7589cbc86
2 changed files with 76 additions and 53 deletions
@@ -36,7 +36,7 @@ test("chat and file preview share the library renderer and resolve file links",
}, testInfo) => {
const { writeFile, mkdir } = await import("node:fs/promises");
const source =
"First paragraph.\n\nSecond paragraph.\n\n```ts\nconst answer = 42;\n```\n\n> A quote\n\n| Key || Value |\n| --- | --- | --- |\n| Answer || 42 |\n\nOpen `README.md` or `src/说明.md:2-3`.\n\n[Space file](my%20file.ts) and `my%20file.ts`.";
"First paragraph.\n\nSecond paragraph.\n\n```ts\nconst answer = 42;\n```\n\n> A quote\n\n| Key || Value |\n| --- | --- | --- |\n| Answer || 42 |\n\nOpen `README.md` or `src/说明.md:2-3`, or packages/app/src/说明.md:2-3.\n\n[Space file](my%20file.ts) and `my%20file.ts`.";
const session = await seedMockAgentWorkspace({
repoPrefix: "shared-markdown-",
title: "Shared Markdown",
@@ -73,10 +73,13 @@ test("chat and file preview share the library renderer and resolve file links",
await page.getByRole("button", { name: "Shared Markdown", exact: true }).click();
await expectFileLinkTooltip(
page,
message.getByRole("link", { name: "src/说明.md:2-3" }),
message.getByRole("link", { name: "src/说明.md:2-3", exact: true }),
"packages/app/src/说明.md:2-3",
);
await message.getByRole("link", { name: "src/说明.md:2-3" }).click();
await message.getByRole("link", { name: "src/说明.md:2-3", exact: true }).click();
await expect(page.getByText("Unicode suffix lookup succeeded.", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Shared Markdown", exact: true }).click();
await message.getByRole("link", { name: "packages/app/src/说明.md:2-3", exact: true }).click();
await expect(page.getByText("Unicode suffix lookup succeeded.", { exact: true })).toBeVisible();
await page.getByRole("button", { name: "Shared Markdown", exact: true }).click();
await message.getByRole("link", { name: "Space file", exact: true }).click();
@@ -1597,15 +1597,6 @@ index aaeab3f..6c2cbe8 100644
\ No newline at end of file
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,YAAY,EACV,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC7E,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,YAAY,EACV,mBAAmB,EACnB,eAAe,GAChB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAChF,YAAY,EACV,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,SAAS,EACT,eAAe,IAAI,mBAAmB,EACtC,mBAAmB,IAAI,uBAAuB,EAC9C,yBAAyB,IAAI,6BAA6B,GAC3D,MAAM,+BAA+B,CAAC;AACvC,YAAY,EACV,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,eAAe,EACf,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,YAAY,EACV,8BAA8B,EAC9B,iCAAiC,EACjC,sBAAsB,EACtB,UAAU,EACV,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,uBAAuB,EACvB,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,YAAY,GACb,MAAM,6BAA6B,CAAC"}
\ No newline at end of file
diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
index 17def30..eae0a3a 100644
--- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
+++ b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
@@ -1,3 +1,4 @@
+export type { MarkdownRenderers, CodeBlockRendererProps, } from './types/MarkdownRenderers';
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web';
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map
index ad425ad..c3403c8 100644
--- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts.map
@@ -1838,18 +1829,6 @@ index 6f31ccc..7b08b62 100644
}
export interface LinkLongPressEvent {
diff --git a/node_modules/react-native-enriched-markdown/src/index.web.tsx b/node_modules/react-native-enriched-markdown/src/index.web.tsx
index 8628b37..de167b7 100644
--- a/node_modules/react-native-enriched-markdown/src/index.web.tsx
+++ b/node_modules/react-native-enriched-markdown/src/index.web.tsx
@@ -1,3 +1,7 @@
+export type {
+ MarkdownRenderers,
+ CodeBlockRendererProps,
+} from './types/MarkdownRenderers';
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web';
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
diff --git a/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx b/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx
index ca7157e..40581c1 100644
--- a/node_modules/react-native-enriched-markdown/src/native/EnrichedMarkdownText.tsx
@@ -2251,10 +2230,47 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/inde
export type { LinkVariantStyle, LinkPillStyle } from './types/MarkdownStyle';
export type { LinkPillContent } from './types/MarkdownTextProps';
export type { LinkContextMenuItem, LinkRecognition, } from './types/MarkdownTextProps';
diff --git a/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts b/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts
--- a/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts
+++ b/node_modules/react-native-enriched-markdown/src/types/MarkdownTextProps.ts
@@ -1,3 +1,4 @@
+import type { MarkdownRenderers } from './MarkdownRenderers';
import type { ColorValue, ViewProps, ViewStyle, TextStyle } from 'react-native';
import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle';
import type { AccessibilityLabels } from './AccessibilityLabels';
@@ -154,6 +155,8 @@
* @platform ios, android, web
*/
markdown: string;
+ /** Custom block components. Ignored on native. @platform web */
+ renderers?: MarkdownRenderers;
/**
* Style configuration for markdown elements.
* @platform ios, android, web
diff --git a/node_modules/react-native-enriched-markdown/src/index.web.tsx b/node_modules/react-native-enriched-markdown/src/index.web.tsx
--- a/node_modules/react-native-enriched-markdown/src/index.web.tsx
+++ b/node_modules/react-native-enriched-markdown/src/index.web.tsx
@@ -1,3 +1,8 @@
+export type {
+ MarkdownRenderers,
+ CodeBlockRendererProps,
+ LinkRendererProps,
+} from './types/MarkdownRenderers';
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web';
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
diff --git a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
--- a/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
+++ b/node_modules/react-native-enriched-markdown/lib/typescript/src/index.web.d.ts
@@ -1,3 +1,4 @@
+export type { MarkdownRenderers, CodeBlockRendererProps, LinkRendererProps, } from './types/MarkdownRenderers';
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web';
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx b/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx
--- a/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx
+++ b/node_modules/react-native-enriched-markdown/src/web/renderers/InlineRenderers.tsx
@@ -58,6 +58,7 @@
@@ -58,16 +58,25 @@
node,
style,
callbacks,
@@ -2262,24 +2278,26 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin
renderChildren,
}: RendererProps) {
const url = node.attributes?.url;
@@ -67,7 +68,15 @@
if (!url) return <>{renderChildren(node)}</>;
+ const isRecognizedLink = node.attributes?.recognizedLink === 'true';
+ const containsInlineCode = node.children?.some((child) => child.type === 'Code');
+ let sourceType: 'markdown' | 'text' | 'inline-code' = 'markdown';
+ if (isRecognizedLink) {
+ sourceType = containsInlineCode ? 'inline-code' : 'text';
+ }
+ const link = { url, sourceType };
+
const handleClick = (event: MouseEvent) => {
if (callbacks.onLinkPress) {
event.preventDefault();
- callbacks.onLinkPress({ url });
+ callbacks.onLinkPress({
+ url,
+ sourceType:
+ node.attributes?.recognizedLink === 'true'
+ ? node.children?.some((child) => child.type === 'Code')
+ ? 'inline-code'
+ : 'text'
+ : 'markdown',
+ });
+ callbacks.onLinkPress(link);
}
};
@@ -78,18 +87,32 @@
@@ -78,18 +87,27 @@
}
};
@@ -2308,12 +2326,7 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin
+ if (CustomLink) {
+ return (
+ <CustomLink
+ link={{
+ url,
+ sourceType: node.attributes?.recognizedLink === 'true'
+ ? node.children?.some((child) => child.type === 'Code') ? 'inline-code' : 'text'
+ : 'markdown',
+ }}
+ link={link}
+ anchorProps={anchorProps}
+ >
+ {renderChildren(node)}
@@ -2327,7 +2340,7 @@ diff --git a/node_modules/react-native-enriched-markdown/src/web/renderers/Inlin
diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js b/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js
--- a/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js
+++ b/node_modules/react-native-enriched-markdown/lib/module/web/renderers/InlineRenderers.js
@@ -103,6 +103,7 @@
@@ -103,18 +103,24 @@
node,
style,
callbacks,
@@ -2335,17 +2348,27 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderer
renderChildren
}) {
const url = node.attributes?.url;
@@ -113,7 +114,8 @@
if (!url) return /*#__PURE__*/_jsx(_Fragment, {
children: renderChildren(node)
});
+ const isRecognizedLink = node.attributes?.recognizedLink === 'true';
+ const containsInlineCode = node.children?.some(child => child.type === 'Code');
+ let sourceType = 'markdown';
+ if (isRecognizedLink) {
+ sourceType = containsInlineCode ? 'inline-code' : 'text';
+ }
+ const link = { url, sourceType };
const handleClick = event => {
if (callbacks.onLinkPress) {
event.preventDefault();
callbacks.onLinkPress({
- callbacks.onLinkPress({
- url
+ url,
+ sourceType: node.attributes?.recognizedLink === 'true' ? node.children?.some(child => child.type === 'Code') ? 'inline-code' : 'text' : 'markdown'
});
- });
+ callbacks.onLinkPress(link);
}
};
@@ -125,15 +127,27 @@
const handleContextMenu = event => {
@@ -125,15 +131,24 @@
});
}
};
@@ -2367,10 +2390,7 @@ diff --git a/node_modules/react-native-enriched-markdown/lib/module/web/renderer
+ const CustomLink = capabilities.renderers?.link;
+ if (CustomLink) {
+ return /*#__PURE__*/_jsx(CustomLink, {
+ link: {
+ url,
+ sourceType: node.attributes?.recognizedLink === 'true' ? node.children?.some(child => child.type === 'Code') ? 'inline-code' : 'text' : 'markdown'
+ },
+ link,
+ anchorProps,
+ children: renderChildren(node)
+ });