fix(highlight-code): handle mousewheel events properly

SyntaxHighlighter component doesn't support ref. We had
to use different approach to finds it's DOM Node using
ref of the root Node of the render tree for HighlightCode
component.

Refs #7497
This commit is contained in:
Vladimir Gorej
2021-10-12 21:57:47 +03:00
parent c31cb3079a
commit 46b4e5cf3f

View File

@@ -1,47 +1,40 @@
import React, { Component } from "react" import React, { useRef, useEffect } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import cx from "classnames"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting" import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get" import get from "lodash/get"
import isFunction from "lodash/isFunction"
import saveAs from "js-file-download" import saveAs from "js-file-download"
import { CopyToClipboard } from "react-copy-to-clipboard" import { CopyToClipboard } from "react-copy-to-clipboard"
export default class HighlightCode extends Component { const HighlightCode = ({value, fileName, className, downloadable, getConfigs, canCopy, language}) => {
static propTypes = { const config = isFunction(getConfigs) ? getConfigs() : null
value: PropTypes.string.isRequired, const canSyntaxHighlight = get(config, "syntaxHighlight.activated", true)
getConfigs: PropTypes.func.isRequired, const highlighterStyle = getStyle(get(config, "syntaxHighlight.theme", "agate"))
className: PropTypes.string, const rootRef = useRef(null)
downloadable: PropTypes.bool,
fileName: PropTypes.string,
language: PropTypes.string,
canCopy: PropTypes.bool
}
#childNodes useEffect(() => {
const childNodes = Array
.from(rootRef.current.childNodes)
.filter(node => !!node.nodeType && node.classList.contains("microlight"))
downloadText = () => { // eslint-disable-next-line no-use-before-define
saveAs(this.props.value, this.props.fileName || "response.txt") childNodes.forEach(node => node.addEventListener("mousewheel", handlePreventYScrollingBeyondElement, { passive: false }))
}
handleRootRef = (node) => { return () => {
if (node === null) { // eslint-disable-next-line no-use-before-define
this.#childNodes = node childNodes.forEach(node => node.removeEventListener("mousewheel", handlePreventYScrollingBeyondElement))
} else {
this.#childNodes = Array
.from(node.childNodes)
.filter(node => !!node.nodeType && node.classList.contains("microlight"))
} }
}, [value, className, language])
const handleDownload = () => {
saveAs(value, fileName)
} }
preventYScrollingBeyondElement = (e) => { const handlePreventYScrollingBeyondElement = (e) => {
const target = e.target const { target, deltaY } = e
const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target
var deltaY = e.deltaY
var contentHeight = target.scrollHeight
var visibleHeight = target.offsetHeight
var scrollTop = target.scrollTop
const scrollOffset = visibleHeight + scrollTop const scrollOffset = visibleHeight + scrollTop
const isElementScrollable = contentHeight > visibleHeight const isElementScrollable = contentHeight > visibleHeight
const isScrollingPastTop = scrollTop === 0 && deltaY < 0 const isScrollingPastTop = scrollTop === 0 && deltaY < 0
const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0 const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0
@@ -51,49 +44,47 @@ export default class HighlightCode extends Component {
} }
} }
UNSAFE_componentDidMount() { return (
[this.#syntaxHighlighter, this.#pre] <div className="highlight-code" ref={rootRef}>
.map(element => element?.addEventListener("mousewheel", this.preventYScrollingBeyondElement, { passive: false })) {!downloadable ? null :
} <div className="download-contents" onClick={handleDownload}>
Download
</div>
}
UNSAFE_componentWillUnmount() { {canCopy && (
[this.#syntaxHighlighter, this.#pre] <div className="copy-to-clipboard">
.map(element => element?.removeEventListener("mousewheel", this.preventYScrollingBeyondElement)) <CopyToClipboard text={value}><button/></CopyToClipboard>
} </div>
)}
render () { {canSyntaxHighlight
let { value, className, downloadable, getConfigs, canCopy, language } = this.props ? <SyntaxHighlighter
const config = getConfigs ? getConfigs() : {syntaxHighlight: {activated: true, theme: "agate"}}
className = className || ""
const codeBlock = get(config, "syntaxHighlight.activated")
? <SyntaxHighlighter
language={language} language={language}
className={className + " microlight"} className={cx(className, "microlight")}
style={getStyle(get(config, "syntaxHighlight.theme"))} style={highlighterStyle}
> >
{value} {value}
</SyntaxHighlighter> </SyntaxHighlighter>
: <pre className={className + " microlight"}>{value}</pre> : <pre className={cx(className + "microlight")}>{value}</pre>
}
return ( </div>
<div className="highlight-code" ref={this.handleRootRef}> )
{ !downloadable ? null :
<div className="download-contents" onClick={this.downloadText}>
Download
</div>
}
{ !canCopy ? null :
<div className="copy-to-clipboard">
<CopyToClipboard text={value}><button/></CopyToClipboard>
</div>
}
{ codeBlock }
</div>
)
}
} }
HighlightCode.propTypes = {
value: PropTypes.string.isRequired,
getConfigs: PropTypes.func.isRequired,
className: PropTypes.string,
downloadable: PropTypes.bool,
fileName: PropTypes.string,
language: PropTypes.string,
canCopy: PropTypes.bool
}
HighlightCode.defaultProps = {
fileName: "response.txt"
}
export default HighlightCode