| 1 | import { useMemo } from "react"; |
| 2 | import { Image } from "react-native"; |
| 3 | import type { |
| 4 | LinkPillContent, |
| 5 | MarkdownDocumentAsset, |
| 6 | MarkdownStyle, |
| 7 | } from "react-native-enriched-markdown"; |
| 8 | import { markdownFileIconSource } from "../lib/markdownFileIcons"; |
| 9 | import { markdownLinkIconSource } from "../lib/markdownLinkIcons"; |
| 10 | import { resolveMarkdownLinkIcon, resolveMarkdownLinkPresentation } from "../lib/markdownLinks"; |
| 11 | import type { SelectableMarkdownTextProps } from "./SelectableMarkdownText.types"; |
| 12 | import { |
| 13 | enrichedContextLinkPresentation, |
| 14 | enrichedLinkVariantPattern, |
| 15 | enrichedSkillDisplayName, |
| 16 | enrichedSkillReference, |
| 17 | } from "../lib/enrichedLinkPresentation"; |
| 18 | |
| 19 | function presentation( |
| 20 | url: string, |
| 21 | props: Pick<SelectableMarkdownTextProps, "linkCustomization" | "skills" | "textStyle">, |
| 22 | ) { |
| 23 | const custom = props.linkCustomization?.(url); |
| 24 | if (custom) return custom; |
| 25 | const context = enrichedContextLinkPresentation(url); |
| 26 | if (context) return { color: context.color, icon: markdownFileIconSource(context.icon) }; |
| 27 | const skill = enrichedSkillReference(url, props.skills); |
| 28 | if (skill) |
| 29 | return { |
| 30 | color: props.textStyle.skillTextColor, |
| 31 | label: enrichedSkillDisplayName(skill), |
| 32 | icon: markdownFileIconSource("mcp"), |
| 33 | }; |
| 34 | const link = resolveMarkdownLinkPresentation(url); |
| 35 | if (link.kind === "file") |
| 36 | return { |
| 37 | color: props.textStyle.fileTextColor, |
| 38 | label: link.label, |
| 39 | icon: markdownFileIconSource(link.icon), |
| 40 | }; |
| 41 | const icon = link.kind === "external" ? resolveMarkdownLinkIcon(link.host) : null; |
| 42 | return icon |
| 43 | ? { |
| 44 | color: props.textStyle.linkColor, |
| 45 | icon: markdownLinkIconSource(icon), |
| 46 | iconTintColor: props.textStyle.linkColor, |
| 47 | } |
| 48 | : undefined; |
| 49 | } |
| 50 | |
| 51 | export function useEnrichedLinkVariants( |
| 52 | assets: ReadonlyArray<MarkdownDocumentAsset>, |
| 53 | props: SelectableMarkdownTextProps, |
| 54 | ) { |
| 55 | const { linkCustomization, skills, textStyle } = props; |
| 56 | const links = useMemo(() => { |
| 57 | const byUrl = new Map<string, NonNullable<ReturnType<typeof presentation>>>(); |
| 58 | for (const asset of assets) { |
| 59 | if (asset.kind !== "link" || byUrl.has(asset.url)) continue; |
| 60 | const value = presentation(asset.url, { linkCustomization, skills, textStyle }); |
| 61 | if (value) byUrl.set(asset.url, value); |
| 62 | } |
| 63 | return byUrl; |
| 64 | }, [assets, linkCustomization, skills, textStyle]); |
| 65 | const linkVariants = useMemo<MarkdownStyle["linkVariants"]>( |
| 66 | () => |
| 67 | Object.fromEntries( |
| 68 | [...links].map(([url, link]) => { |
| 69 | const color = link.color ?? textStyle.fileTextColor; |
| 70 | return [ |
| 71 | enrichedLinkVariantPattern(url), |
| 72 | { |
| 73 | pill: { |
| 74 | borderColor: "transparent", |
| 75 | borderWidth: 0, |
| 76 | borderRadius: 6, |
| 77 | paddingHorizontal: 5, |
| 78 | paddingVertical: 2, |
| 79 | }, |
| 80 | fontFamily: textStyle.fontFamily, |
| 81 | color, |
| 82 | underline: false, |
| 83 | backgroundColor: "transparent", |
| 84 | }, |
| 85 | ]; |
| 86 | }), |
| 87 | ), |
| 88 | [links, textStyle], |
| 89 | ); |
| 90 | const linkPillContent = useMemo<Record<string, LinkPillContent>>( |
| 91 | () => |
| 92 | Object.fromEntries( |
| 93 | [...links].map(([url, link]) => [ |
| 94 | url, |
| 95 | { |
| 96 | label: link.label, |
| 97 | iconUri: link.icon ? Image.resolveAssetSource(link.icon)?.uri : undefined, |
| 98 | iconTintColor: link.iconTintColor, |
| 99 | }, |
| 100 | ]), |
| 101 | ), |
| 102 | [links], |
| 103 | ); |
| 104 | return { linkVariants, linkPillContent }; |
| 105 | } |