Skip to content

Commit b3b15c4

Browse files
authored
Use marked in conjunction with hljs instead of EuiMarkdownFormat (#2025)
* Use marked in conjunction with hljs instead of EuiMarkdownFormat * Run prettier and fix eslint errors * Add copy button to code block in AskAI chat * Fix eslint errors and run prettier * Remove tmp suggestion * Fix init * Optimize * Refactorings and re-render optimizations * Cleanup * Fix eslint and run prettier * Cleanup * Fix lint
1 parent d5f086c commit b3b15c4

File tree

10 files changed

+181
-179
lines changed

10 files changed

+181
-179
lines changed

src/Elastic.Documentation.Site/Assets/copybutton.ts

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
// Localization support
2-
import * as ClipboardJS from 'clipboard'
1+
import { $$ } from 'select-dom'
32

43
const DOCUMENTATION_OPTIONS = {
54
VERSION: '',
@@ -97,16 +96,7 @@ if (!iconCopy) {
9796
</svg>`
9897
}
9998

100-
const codeCellId = (index) => `codecell${index}`
101-
102-
// Clears selected text since ClipboardJS will select the text when copying
103-
const clearSelection = () => {
104-
if (window.getSelection) {
105-
window.getSelection().removeAllRanges()
106-
} else if ('selection' in document) {
107-
;(document.selection as Selection).empty()
108-
}
109-
}
99+
const codeCellId = (index: number, prefix: string) => `${prefix}${index}`
110100

111101
// Changes tooltip text for a moment, then changes it back
112102
// We want the timeout of our `success` class to be a bit shorter than the
@@ -131,30 +121,45 @@ const temporarilyChangeIcon = (el) => {
131121
}, timeoutIcon)
132122
}
133123

134-
const addCopyButtonToCodeCells = () => {
135-
// If ClipboardJS hasn't loaded, wait a bit and try again. This
136-
// happens because we load ClipboardJS asynchronously.
137-
124+
const addCopyButtonToCodeCells = (
125+
selector: string,
126+
baseElement: ParentNode,
127+
prefix: string
128+
) => {
138129
// Add copybuttons to all of our code cells
139-
const COPYBUTTON_SELECTOR = '.highlight pre'
140-
const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR)
130+
const codeCells = $$(selector, baseElement)
141131
codeCells.forEach((codeCell, index) => {
142132
if (codeCell.id) {
143133
return
144134
}
145-
146-
const id = codeCellId(index)
135+
const id = codeCellId(index, prefix)
147136
codeCell.setAttribute('id', id)
137+
const clipboardButton = document.createElement('button')
138+
clipboardButton.setAttribute('aria-label', 'Copy code to clipboard')
139+
clipboardButton.className = 'copybtn o-tooltip--left'
140+
clipboardButton.setAttribute('data-tooltip', messages[locale]['copy'])
141+
clipboardButton.setAttribute('data-clipboard-target', `#${id}`)
142+
clipboardButton.innerHTML = iconCopy
143+
clipboardButton.onclick = async () => {
144+
try {
145+
const text = copyTargetText(clipboardButton, baseElement)
146+
await navigator.clipboard.writeText(text)
147+
temporarilyChangeTooltip(
148+
clipboardButton,
149+
messages[locale]['copy'],
150+
messages[locale]['copy_success']
151+
)
152+
temporarilyChangeIcon(clipboardButton)
153+
} catch (error) {
154+
console.error(error)
155+
}
156+
}
148157

149-
const clipboardButton = (id) =>
150-
`<button aria-label="Copy code to clipboard" class="copybtn o-tooltip--left" data-tooltip="${messages[locale]['copy']}" data-clipboard-target="#${id}">
151-
${iconCopy}
152-
</button>`
153-
codeCell.insertAdjacentHTML('afterend', clipboardButton(id))
158+
codeCell.insertAdjacentElement('afterend', clipboardButton)
154159
})
155160

156-
function escapeRegExp(string) {
157-
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
161+
function escapeRegExp(str: string) {
162+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // $& means the whole matched string
158163
}
159164

160165
function filterText(target, excludes) {
@@ -228,8 +233,8 @@ const addCopyButtonToCodeCells = () => {
228233
return textContent
229234
}
230235

231-
const copyTargetText = (trigger) => {
232-
const target = document.querySelector(
236+
const copyTargetText = (trigger, searchElement: ParentNode = document) => {
237+
const target = searchElement.querySelector(
233238
trigger.attributes['data-clipboard-target'].value
234239
)
235240
// get filtered text
@@ -239,30 +244,12 @@ const addCopyButtonToCodeCells = () => {
239244
.join('\n')
240245
return formatCopyText(text, '', false, true, true, true, '', '')
241246
}
242-
243-
// Initialize with a callback so we can modify the text before copy
244-
const clipboard = new ClipboardJS('.copybtn', { text: copyTargetText })
245-
246-
// Update UI with error/success messages
247-
clipboard.on('success', (event) => {
248-
clearSelection()
249-
temporarilyChangeTooltip(
250-
event.trigger,
251-
messages[locale]['copy'],
252-
messages[locale]['copy_success']
253-
)
254-
temporarilyChangeIcon(event.trigger)
255-
})
256-
257-
clipboard.on('error', (event) => {
258-
temporarilyChangeTooltip(
259-
event.trigger,
260-
messages[locale]['copy'],
261-
messages[locale]['copy_failure']
262-
)
263-
})
264247
}
265248

266-
export function initCopyButton() {
267-
addCopyButtonToCodeCells()
249+
export function initCopyButton(
250+
selector: string = '.highlight pre',
251+
baseElement: ParentNode = document,
252+
prefix: string = 'markdown-content-codecell-'
253+
) {
254+
addCopyButtonToCodeCells(selector, baseElement, prefix)
268255
}

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/AskAiSuggestions.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { EuiButton, useEuiTheme } from '@elastic/eui'
44
import { css } from '@emotion/react'
55
import * as React from 'react'
66

7+
const buttonStyles = css`
8+
border: none;
9+
& > span {
10+
justify-content: flex-start;
11+
}
12+
`
13+
714
export interface AskAiSuggestion {
815
question: string
916
}
@@ -16,15 +23,14 @@ export const AskAiSuggestions = (props: Props) => {
1623
const { submitQuestion } = useChatActions()
1724
const { setModalMode } = useModalActions()
1825
const { euiTheme } = useEuiTheme()
19-
const buttonCss = css`
20-
border: none;
21-
& > span {
22-
justify-content: flex-start;
23-
}
26+
27+
const dynamicButtonStyles = css`
28+
${buttonStyles}
2429
svg {
2530
color: ${euiTheme.colors.textSubdued};
2631
}
2732
`
33+
2834
return (
2935
<ul>
3036
{Array.from(props.suggestions).map((suggestion) => (
@@ -34,7 +40,7 @@ export const AskAiSuggestions = (props: Props) => {
3440
color="text"
3541
fullWidth
3642
size="s"
37-
css={buttonCss}
43+
css={dynamicButtonStyles}
3844
onClick={() => {
3945
submitQuestion(suggestion.question)
4046
setModalMode('askAi')

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/Chat.tsx

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@ import { css } from '@emotion/react'
1717
import * as React from 'react'
1818
import { useCallback, useEffect, useRef } from 'react'
1919

20+
const containerStyles = css`
21+
height: 100%;
22+
max-height: 70vh;
23+
overflow: hidden;
24+
`
25+
26+
const scrollContainerStyles = css`
27+
position: relative;
28+
overflow: hidden;
29+
`
30+
31+
const scrollableStyles = css`
32+
height: 100%;
33+
overflow-y: auto;
34+
scrollbar-gutter: stable;
35+
padding: 1rem;
36+
`
37+
38+
const messagesStyles = css`
39+
max-width: 800px;
40+
margin: 0 auto;
41+
`
42+
2043
// Small helper for scroll behavior
2144
const scrollToBottom = (container: HTMLDivElement | null) => {
2245
if (!container) return
@@ -44,28 +67,9 @@ export const Chat = () => {
4467
const scrollRef = useRef<HTMLDivElement>(null)
4568
const lastMessageStatusRef = useRef<string | null>(null)
4669

47-
const containerStyles = css`
48-
height: 100%;
49-
max-height: 70vh;
50-
overflow: hidden;
51-
`
52-
53-
const scrollContainerStyles = css`
54-
position: relative;
55-
overflow: hidden;
56-
`
57-
58-
const scrollableStyles = css`
59-
height: 100%;
60-
overflow-y: auto;
61-
scrollbar-gutter: stable;
70+
const dynamicScrollableStyles = css`
71+
${scrollableStyles}
6272
${useEuiOverflowScroll('y', true)}
63-
padding: 1rem;
64-
`
65-
66-
const messagesStyles = css`
67-
max-width: 800px;
68-
margin: 0 auto;
6973
`
7074

7175
const handleSubmit = useCallback(
@@ -116,14 +120,14 @@ export const Chat = () => {
116120
gutterSize="none"
117121
css={containerStyles}
118122
>
119-
{/* Header - only show when there are messages */}
123+
<EuiSpacer size="m" />
124+
120125
{messages.length > 0 && (
121126
<NewConversationHeader onClick={clearChat} />
122127
)}
123128

124-
{/* Messages */}
125129
<EuiFlexItem grow={true} css={scrollContainerStyles}>
126-
<div ref={scrollRef} css={scrollableStyles}>
130+
<div ref={scrollRef} css={dynamicScrollableStyles}>
127131
{messages.length === 0 ? (
128132
<EuiEmptyPrompt
129133
iconType="logoElastic"

0 commit comments

Comments
 (0)