import { memo, useMemo, type MouseEvent, type ReactNode } from 'react' import Markdown, { defaultUrlTransform } from 'react-markdown' import remarkGfm from 'remark-gfm' import rehypeHighlight from 'rehype-highlight' import { preprocessWikilinks, WIKILINK_SCHEME } from '../utils/chatWikilinks' import { supportsModernRegexFeatures } from '../utils/regexCapabilities' import { openExternalUrl } from '../utils/url' const MODERN_REGEX_AVAILABLE = supportsModernRegexFeatures() const REMARK_PLUGINS = MODERN_REGEX_AVAILABLE ? [remarkGfm] : [] const REHYPE_PLUGINS = MODERN_REGEX_AVAILABLE ? [rehypeHighlight] : [] function wikilinkUrlTransform(url: string): string { if (url.startsWith(WIKILINK_SCHEME)) return url return defaultUrlTransform(url) } function isExplicitWebUrl(href?: string): href is string { const lowerHref = href?.trim().toLowerCase() ?? '' return lowerHref.startsWith('http://') || lowerHref.startsWith('https://') } function openExplicitWebUrl(event: MouseEvent, href: string) { event.preventDefault() void openExternalUrl(href).catch((error) => { console.warn('[ai] Failed to open external link:', error) }) } function localNoteTarget(href?: string): string | null { const raw = href?.trim() if (!raw || raw.startsWith('#') || raw.startsWith('/') || raw.startsWith('\\')) return null if (/^[a-z][a-z0-9+.-]*:/i.test(raw)) return null try { return decodeURIComponent(raw) .split(/[?#]/, 1)[0] .replace(/^\.\//, '') .replace(/\.(?:md|hdlp)$/i, '') || null } catch { return raw.replace(/^\.\//, '').replace(/\.(?:md|hdlp)$/i, '') || null } } interface MarkdownContentProps { content: string onWikilinkClick?: (target: string) => void } export const MarkdownContent = memo(function MarkdownContent({ content, onWikilinkClick }: MarkdownContentProps) { const processedContent = useMemo( () => onWikilinkClick ? preprocessWikilinks(content) : content, [content, onWikilinkClick], ) const components = useMemo(() => { return { a: ({ href, children }: { href?: string; children?: ReactNode }) => { if (onWikilinkClick && href?.startsWith(WIKILINK_SCHEME)) { const target = decodeURIComponent(href.slice(WIKILINK_SCHEME.length)) return ( { node?.setAttribute('role', 'link') node?.setAttribute('tabindex', '0') }} href={href} className="chat-wikilink border-0 bg-transparent p-0" data-wikilink-target={target} onClick={(event) => { event.preventDefault() onWikilinkClick(target) }} > {children} ) } if (isExplicitWebUrl(href)) { return openExplicitWebUrl(event, href)}>{children} } const noteTarget = onWikilinkClick ? localNoteTarget(href) : null if (noteTarget) { return ( { event.preventDefault() onWikilinkClick?.(noteTarget) }} > {children} ) } return {children} }, table: ({ children }: { children?: ReactNode }) => (
{children}
), } }, [onWikilinkClick]) return (
{processedContent}
) })