feat: auto-linkify URLs in inline code blocks
This commit is contained in:
@@ -272,6 +272,10 @@ export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
|||||||
fontFamily: Fonts.mono,
|
fontFamily: Fonts.mono,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
},
|
},
|
||||||
|
markdownCodeInlineLink: {
|
||||||
|
color: theme.colors.primary,
|
||||||
|
textDecorationLine: "underline",
|
||||||
|
},
|
||||||
// Used in custom markdownRules for path chip styling
|
// Used in custom markdownRules for path chip styling
|
||||||
pathChip: {
|
pathChip: {
|
||||||
backgroundColor: theme.colors.surface2,
|
backgroundColor: theme.colors.surface2,
|
||||||
@@ -288,6 +292,30 @@ export const assistantMessageStylesheet = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function getInlineCodeAutoLinkUrl(
|
||||||
|
markdownParser: ReturnType<typeof MarkdownIt>,
|
||||||
|
content: string
|
||||||
|
): string | null {
|
||||||
|
const trimmed = content.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = markdownParser.linkify.match(trimmed) as
|
||||||
|
| Array<{ index: number; lastIndex: number; url: string }>
|
||||||
|
| null;
|
||||||
|
if (!matches || matches.length !== 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [match] = matches;
|
||||||
|
if (!match || match.index !== 0 || match.lastIndex !== trimmed.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match.url;
|
||||||
|
}
|
||||||
|
|
||||||
const turnCopyButtonStylesheet = StyleSheet.create((theme) => ({
|
const turnCopyButtonStylesheet = StyleSheet.create((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
@@ -584,13 +612,35 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
? parseInlinePathToken(content)
|
? parseInlinePathToken(content)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!parsed) {
|
if (parsed) {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
|
onPress={() => parsed && onInlinePathPress?.(parsed)}
|
||||||
|
selectable={false}
|
||||||
|
style={[
|
||||||
|
assistantMessageStylesheet.pathChip,
|
||||||
|
assistantMessageStylesheet.pathChipText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlineCodeLinkUrl = getInlineCodeAutoLinkUrl(markdownParser, content);
|
||||||
|
if (inlineCodeLinkUrl) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={node.key}
|
||||||
|
accessibilityRole="link"
|
||||||
|
onPress={() => {
|
||||||
|
handleLinkPress(inlineCodeLinkUrl);
|
||||||
|
}}
|
||||||
style={[
|
style={[
|
||||||
inheritedStyles,
|
inheritedStyles,
|
||||||
assistantMessageStylesheet.markdownCodeInline,
|
assistantMessageStylesheet.markdownCodeInline,
|
||||||
|
assistantMessageStylesheet.markdownCodeInlineLink,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
@@ -601,11 +651,9 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
key={node.key}
|
key={node.key}
|
||||||
onPress={() => parsed && onInlinePathPress?.(parsed)}
|
|
||||||
selectable={false}
|
|
||||||
style={[
|
style={[
|
||||||
assistantMessageStylesheet.pathChip,
|
inheritedStyles,
|
||||||
assistantMessageStylesheet.pathChipText,
|
assistantMessageStylesheet.markdownCodeInline,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
@@ -658,7 +706,7 @@ export const AssistantMessage = memo(function AssistantMessage({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [onInlinePathPress]);
|
}, [handleLinkPress, markdownParser, onInlinePathPress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|||||||
Reference in New Issue
Block a user