diff --git a/ui/src/copyable_text.tsx b/ui/src/copyable_text.tsx index b160bc0147..7532aed1f5 100644 --- a/ui/src/copyable_text.tsx +++ b/ui/src/copyable_text.tsx @@ -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({ @@ -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'), @@ -38,6 +37,9 @@ const } } }, + labelContainer: { + position: 'relative' + } }), fullHeightStyle = { display: 'flex', @@ -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(), [copied, setCopied] = React.useState(false), @@ -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(() => , [copied, onClick, showOnHoverOnly, visible]) + }, [anchorElement, value]) React.useEffect(() => { if (anchorElement && showOnHoverOnly) { @@ -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 } export const XCopyableText = ({ model }: { model: CopyableText }) => { @@ -126,26 +126,28 @@ export const XCopyableText = ({ model }: { model: CopyableText }) => { }, [label]) return ( - <> - - - + +
+ {label} + +
+ } + styles={{ + root: { + ...heightStyle, + textFieldRoot: { position: 'relative', width: pc(100) }, + }, + wrapper: heightStyle, + fieldGroup: heightStyle || { minHeight: height }, + field: { ...heightStyle, height, resize: multiline ? 'vertical' : 'none', }, + }} + readOnly + /> ) } \ No newline at end of file diff --git a/ui/src/markdown.tsx b/ui/src/markdown.tsx index 585ef5382c..95fbd57f3e 100644 --- a/ui/src/markdown.tsx +++ b/ui/src/markdown.tsx @@ -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 } @@ -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(, document.getElementById(buttonContainerId)) + const codeBlockContainer = document.getElementById(codeBlockId)?.parentElement + if (codeBlockContainer) { + const buttonContainer = document.createElement('span') + ReactDOM.render(, buttonContainer, + () => { + codeBlockContainer.style.position = 'relative' + codeBlockContainer.insertAdjacentElement('afterbegin', buttonContainer) + }) + } }), 0) // TODO: Sanitize the HTML. - const ret = `${prevHighlights.current[codeBlockIdx.current] || str}` + const ret = `${prevHighlights.current[codeBlockIdx.current] || str}` codeBlockIdx.current++ return ret }