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:
@@ -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
|
||||||
downloadText = () => {
|
.from(rootRef.current.childNodes)
|
||||||
saveAs(this.props.value, this.props.fileName || "response.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRootRef = (node) => {
|
|
||||||
if (node === null) {
|
|
||||||
this.#childNodes = node
|
|
||||||
} else {
|
|
||||||
this.#childNodes = Array
|
|
||||||
.from(node.childNodes)
|
|
||||||
.filter(node => !!node.nodeType && node.classList.contains("microlight"))
|
.filter(node => !!node.nodeType && node.classList.contains("microlight"))
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
childNodes.forEach(node => node.addEventListener("mousewheel", handlePreventYScrollingBeyondElement, { passive: false }))
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
childNodes.forEach(node => node.removeEventListener("mousewheel", handlePreventYScrollingBeyondElement))
|
||||||
}
|
}
|
||||||
|
}, [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() {
|
|
||||||
[this.#syntaxHighlighter, this.#pre]
|
|
||||||
.map(element => element?.addEventListener("mousewheel", this.preventYScrollingBeyondElement, { passive: false }))
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillUnmount() {
|
|
||||||
[this.#syntaxHighlighter, this.#pre]
|
|
||||||
.map(element => element?.removeEventListener("mousewheel", this.preventYScrollingBeyondElement))
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { value, className, downloadable, getConfigs, canCopy, language } = this.props
|
|
||||||
|
|
||||||
const config = getConfigs ? getConfigs() : {syntaxHighlight: {activated: true, theme: "agate"}}
|
|
||||||
|
|
||||||
className = className || ""
|
|
||||||
|
|
||||||
const codeBlock = get(config, "syntaxHighlight.activated")
|
|
||||||
? <SyntaxHighlighter
|
|
||||||
language={language}
|
|
||||||
className={className + " microlight"}
|
|
||||||
style={getStyle(get(config, "syntaxHighlight.theme"))}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</SyntaxHighlighter>
|
|
||||||
: <pre className={className + " microlight"}>{value}</pre>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="highlight-code" ref={this.handleRootRef}>
|
<div className="highlight-code" ref={rootRef}>
|
||||||
{ !downloadable ? null :
|
{!downloadable ? null :
|
||||||
<div className="download-contents" onClick={this.downloadText}>
|
<div className="download-contents" onClick={handleDownload}>
|
||||||
Download
|
Download
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ !canCopy ? null :
|
{canCopy && (
|
||||||
<div className="copy-to-clipboard">
|
<div className="copy-to-clipboard">
|
||||||
<CopyToClipboard text={value}><button/></CopyToClipboard>
|
<CopyToClipboard text={value}><button/></CopyToClipboard>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{canSyntaxHighlight
|
||||||
|
? <SyntaxHighlighter
|
||||||
|
language={language}
|
||||||
|
className={cx(className, "microlight")}
|
||||||
|
style={highlighterStyle}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
: <pre className={cx(className + "microlight")}>{value}</pre>
|
||||||
}
|
}
|
||||||
|
|
||||||
{ codeBlock }
|
|
||||||
</div>
|
</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
|
||||||
|
|||||||
Reference in New Issue
Block a user