dify/web/app/components/base/markdown-blocks/link.tsx
Stephen Zhou a84c2d36a3
style: format with vp fmt (#38803)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-12 15:57:46 +00:00

75 lines
2.5 KiB
TypeScript

/**
* @fileoverview Link component for rendering <a> tags in Markdown.
* Extracted from the main markdown renderer for modularity.
* Handles special rendering for "abbr:" type links for interactive chat actions.
*/
import * as React from 'react'
import { useChatContext } from '@/app/components/base/chat/chat/context'
import { isValidUrl } from './utils'
const filePreviewPathPattern = /\/files\/[^/?#]+\/file-preview(?:[?#]|$)/
const asAttachmentParamPattern = /[?&]as_attachment=/
const withFilePreviewAttachment = (href: string) => {
if (!filePreviewPathPattern.test(href) || asAttachmentParamPattern.test(href)) return href
const url = new URL(href, 'http://dify.local')
url.searchParams.set('as_attachment', 'true')
return href.startsWith('//') ? url.href.replace(url.protocol, '') : url.href
}
const Link = ({ node, children, ...props }: any) => {
const { onSend } = useChatContext()
const commonClassName = 'cursor-pointer underline decoration-primary-700! decoration-dashed'
if (node.properties?.href && node.properties.href?.toString().startsWith('abbr')) {
const hidden_text = decodeURIComponent(node.properties.href.toString().split('abbr:')[1])
return (
<abbr
className={commonClassName}
onClick={() => onSend?.(hidden_text)}
title={node.children[0]?.value || ''}
>
{node.children[0]?.value || ''}
</abbr>
)
} else {
const rawHref = props.href || node.properties?.href
const href = rawHref?.toString()
if (href && /^#[\w-]+$/.test(href)) {
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault()
// scroll to target element if exists within the answer container
const answerContainer = e.currentTarget.closest('.chat-answer-container')
if (answerContainer) {
const targetId = CSS.escape(href.toString().substring(1))
const targetElement = answerContainer.querySelector(`[id="${targetId}"]`)
if (targetElement) targetElement.scrollIntoView({ behavior: 'smooth' })
}
}
return (
<a href={href} onClick={handleClick} className={commonClassName}>
{children || 'ScrollView'}
</a>
)
}
if (!href || !isValidUrl(href)) return <span>{children}</span>
return (
<a
href={withFilePreviewAttachment(href)}
target="_blank"
rel="noopener noreferrer"
className={commonClassName}
>
{children || 'Download'}
</a>
)
}
}
export default Link