-
Notifications
You must be signed in to change notification settings - Fork 66.9k
Expand file tree
/
Copy pathUnrenderedMarkdownContent.tsx
More file actions
153 lines (144 loc) · 4.94 KB
/
UnrenderedMarkdownContent.tsx
File metadata and controls
153 lines (144 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import React from 'react'
import ReactMarkdown from 'react-markdown'
import type { Components } from 'react-markdown'
import remarkGfm from 'remark-gfm'
import cx from 'classnames'
import { IconButton } from '@primer/react'
import { CopyIcon, CheckIcon } from '@primer/octicons-react'
import { announce } from '@primer/live-region-element'
import { useTranslation } from '@/languages/components/useTranslation'
import useCopyClipboard from '@/rest/components/useClipboard'
import { EventType } from '@/events/types'
import { sendEvent } from '@/events/components/events'
export type MarkdownContentPropsT = {
children: string
className?: string
openLinksInNewTab?: boolean
includeQueryParams?: boolean
codeBlocksCopyable?: boolean
eventGroupKey?: string
eventGroupId?: string
as?: keyof JSX.IntrinsicElements
tabIndex?: number
}
export const UnrenderedMarkdownContent = ({
children,
className,
openLinksInNewTab = true,
includeQueryParams = true,
codeBlocksCopyable = false,
eventGroupKey = '',
eventGroupId = '',
...restProps
}: MarkdownContentPropsT) => {
const { t } = useTranslation('search')
// Overrides for ReactMarkdown components
const components = {} as Components
if (codeBlocksCopyable) {
// Override the default code block to make multiline code blocks copyable
components.code = ({ ...props }) => {
// get the literal text of the code block
let text = String(props.children)
// If the codeblock is not multiline, return inline code block without copy functionality
if (!text.includes('\n')) {
return <code {...props}>{props.children}</code>
} else {
// Otherwise it's multiline and we want to make it copyable
text = text.replace(/\n$/, '')
}
// Extract language from className for better accessibility
const language = props.className?.startsWith('language-')
? props.className.replace('language-', '')
: ''
const [isCopied, copyToClipboard] = useCopyClipboard(text, {
successDuration: 2000,
})
// Create more descriptive aria-label
const getAriaLabel = () => {
if (isCopied) {
return t('search.ai.response.copied_code')
}
return t('search.ai.response.copy_code_lang').replace(
'{language}',
language ? `${language} ` : '',
)
}
return (
<div style={{ position: 'relative' }}>
<IconButton
size="small"
icon={isCopied ? CheckIcon : CopyIcon}
className="btn-octicon"
aria-label={getAriaLabel()}
onClick={async () => {
await copyToClipboard()
announce(t('search.ai.response.copied_code'))
sendEvent({
type: EventType.clipboard,
clipboard_operation: 'copy',
eventGroupKey: eventGroupKey,
eventGroupId: eventGroupId,
})
}}
sx={{
position: 'absolute',
right: '-.7rem',
top: '-.7rem',
zIndex: 1,
}}
></IconButton>
<code {...props}>{props.children}</code>
</div>
)
}
}
// Override the default anchor tag to open links in a new tab and include specific query parameters
components.a = ({ ...props }) => {
let href = props.href || ''
let existingAnchorParams = ''
// When we want to include specific query parameters in the URL
if (includeQueryParams) {
if (href.includes('?')) {
href = href.split('?')[0]
existingAnchorParams = href.split('?')[1]
}
// Include feature, search-overlay-ask-ai, and search-overlay-input query parameters if they exist in the current URL
const existingURLParams = new URLSearchParams(window.location.search)
const newParams = new URLSearchParams()
if (existingURLParams.get('feature')) {
newParams.set('feature', existingURLParams.get('feature') || '')
}
// Combine new and existing query parameters
if (newParams.toString()) {
href = `${href}?${existingAnchorParams}&${newParams.toString()}`
}
}
return (
<a
{...props}
href={href}
target={openLinksInNewTab ? '_blank' : undefined}
rel={openLinksInNewTab ? 'noopener noreferrer' : undefined}
onClick={(e) => {
// For some reason we need to override the default onClick to get these links to open in a new tab
if (openLinksInNewTab) {
e.stopPropagation()
e.preventDefault()
window.open(href, '_blank')
}
}}
data-group-key={eventGroupKey}
data-group-id={eventGroupId}
>
<u>{props.children}</u>
</a>
)
}
return (
<div className={cx('markdown-body', className)}>
<ReactMarkdown remarkPlugins={[remarkGfm]} {...restProps} components={components}>
{children}
</ReactMarkdown>
</div>
)
}