Skip to content

Commit

Permalink
chore: re-implement without portals #2025
Browse files Browse the repository at this point in the history
  • Loading branch information
marek-mihok authored and mturoci committed Sep 6, 2023
1 parent 9955eae commit d26a5ef
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 45 deletions.
82 changes: 42 additions & 40 deletions ui/src/copyable_text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import { B, S, U } from 'h2o-wave'
import React from 'react'
import { stylesheet } from 'typestyle'
import { clas, cssVar, pc } from './theme'
import ReactDOM from 'react-dom'

const
BUTTON_HEIGHT = 24,
BUTTON_WIDTH = 34,
CORNER_OFFSET = 3
BUTTON_WIDTH = 34

const
css = stylesheet({
Expand All @@ -20,15 +18,16 @@ const
opacity: 1,
},
btn: {
minWidth: 'initial',
position: 'absolute',
top: CORNER_OFFSET,
right: CORNER_OFFSET,
minWidth: 'initial',
width: BUTTON_WIDTH,
height: BUTTON_HEIGHT,
right: 0,
transform: 'translate(-4px, 4px)',
outlineWidth: 1,
outlineStyle: 'solid',
outlineColor: cssVar('$white')
outlineColor: cssVar('$white'),
zIndex: 1,
},
copiedBtn: {
background: cssVar('$green'),
Expand All @@ -38,6 +37,9 @@ const
}
}
},
labelContainer: {
position: 'relative'
}
}),
fullHeightStyle = {
display: 'flex',
Expand Down Expand Up @@ -68,12 +70,10 @@ type ClipboardCopyButton = {
/** The element to which the copy button is attached. */
anchorElement: HTMLElement | undefined,
/** Show copy button only on hover over anchor element. */
showOnHoverOnly?: B,
/** Use portal if ClipboardCopyButton is not direct child of the anchor element. */
portal?: B
showOnHoverOnly?: B
}

export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = false, portal = false }: ClipboardCopyButton) => {
export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = false }: ClipboardCopyButton) => {
const
timeoutRef = React.useRef<U>(),
[copied, setCopied] = React.useState(false),
Expand All @@ -92,13 +92,7 @@ export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = fa

setCopied(true)
timeoutRef.current = window.setTimeout(() => setCopied(false), 2000)
}, [anchorElement, value]),
CopyButton = React.useMemo(() => <Fluent.PrimaryButton
title='Copy to clipboard'
onClick={onClick}
iconProps={{ iconName: copied ? 'CheckMark' : 'Copy' }}
className={clas(css.btn, copied ? css.copiedBtn : '', showOnHoverOnly ? css.animate : '', visible ? css.visible : '')}
/>, [copied, onClick, showOnHoverOnly, visible])
}, [anchorElement, value])

React.useEffect(() => {
if (anchorElement && showOnHoverOnly) {
Expand All @@ -112,7 +106,13 @@ export const ClipboardCopyButton = ({ value, anchorElement, showOnHoverOnly = fa

React.useEffect(() => () => window.clearTimeout(timeoutRef.current), [])

return portal && anchorElement ? ReactDOM.createPortal(CopyButton, anchorElement) : CopyButton
return <Fluent.PrimaryButton
id='copybutton'
title='Copy to clipboard'
onClick={onClick}
iconProps={{ iconName: copied ? 'CheckMark' : 'Copy' }}
className={clas(css.btn, copied ? css.copiedBtn : '', showOnHoverOnly ? css.animate : '', visible ? css.visible : '')}
/>
}

export const XCopyableText = ({ model }: { model: CopyableText }) => {
Expand All @@ -126,26 +126,28 @@ export const XCopyableText = ({ model }: { model: CopyableText }) => {
}, [label])

return (
<>
<Fluent.TextField
data-test={name}
// Temporary solution which will be replaced with 'ref' once Fluent.TextField is converted to a function component.
elementRef={domRef}
value={value}
multiline={multiline}
label={label}
styles={{
root: {
...heightStyle,
textFieldRoot: { position: 'relative', width: pc(100) },
},
wrapper: heightStyle,
fieldGroup: heightStyle || { minHeight: height },
field: { ...heightStyle, height, resize: multiline ? 'vertical' : 'none', },
}}
readOnly
/>
<ClipboardCopyButton value={value} anchorElement={inputEl} showOnHoverOnly={!!multiline} portal />
</>
<Fluent.TextField
data-test={name}
// Temporary solution which will be replaced with 'ref' once Fluent.TextField is converted to a function component.
elementRef={domRef}
value={value}
multiline={multiline}
onRenderLabel={() =>
<div className={css.labelContainer}>
<Fluent.Label>{label}</Fluent.Label>
<ClipboardCopyButton value={value} anchorElement={inputEl} showOnHoverOnly={!!multiline} />
</div>
}
styles={{
root: {
...heightStyle,
textFieldRoot: { position: 'relative', width: pc(100) },
},
wrapper: heightStyle,
fieldGroup: heightStyle || { minHeight: height },
field: { ...heightStyle, height, resize: multiline ? 'vertical' : 'none', },
}}
readOnly
/>
)
}
16 changes: 11 additions & 5 deletions ui/src/markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const highlightSyntax = async (str: S, language: S, codeBlockId: S) => {
? hljs.highlight(str, { language, ignoreIllegals: true }).value
: hljs.highlightAuto(str).value

codeBlock.innerHTML += highlightedCode
codeBlock.innerHTML = highlightedCode
return highlightedCode
}

Expand All @@ -103,19 +103,25 @@ export const Markdown = ({ source }: { source: S }) => {
markdown = React.useMemo(() => MarkdownIt({
html: true, linkify: true, typographer: true, highlight: (str, lang) => {
const codeBlockId = codeBlockIdx.current.toString()
const buttonContainerId = `cpb-${codeBlockId}`
if (prevHighlights.current.length === codeBlockIdx.current) prevHighlights.current.push('')

// HACK: MarkdownIt does not support async rules.
// https://github.com/markdown-it/markdown-it/blob/master/docs/development.md#i-need-async-rule-how-to-do-it
setTimeout(async () => prevHighlights.current[+codeBlockId] = await highlightSyntax(str, lang, codeBlockId).finally(() => {
// Add copy button once code block is higlighted.
const codeBlock = document.getElementById(codeBlockId)
if (codeBlock) ReactDOM.render(<ClipboardCopyButton value={str} anchorElement={codeBlock} showOnHoverOnly />, document.getElementById(buttonContainerId))
const codeBlockContainer = document.getElementById(codeBlockId)?.parentElement
if (codeBlockContainer) {
const buttonContainer = document.createElement('span')
ReactDOM.render(<ClipboardCopyButton value={str} anchorElement={codeBlockContainer} showOnHoverOnly />, buttonContainer,
() => {
codeBlockContainer.style.position = 'relative'
codeBlockContainer.insertAdjacentElement('afterbegin', buttonContainer)
})
}
}), 0)

// TODO: Sanitize the HTML.
const ret = `<code id='${codeBlockId}' class="hljs" style="position:relative;"><span id="${buttonContainerId}"/>${prevHighlights.current[codeBlockIdx.current] || str}</code>`
const ret = `<code id='${codeBlockId}' class="hljs">${prevHighlights.current[codeBlockIdx.current] || str}</code>`
codeBlockIdx.current++
return ret
}
Expand Down

0 comments on commit d26a5ef

Please sign in to comment.