Skip to content

Commit

Permalink
fix(highlight-code): handle mousewheel events properly
Browse files Browse the repository at this point in the history
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
  • Loading branch information
char0n committed Oct 12, 2021
1 parent 70730cc commit da4f70b
Showing 1 changed file with 61 additions and 70 deletions.
131 changes: 61 additions & 70 deletions src/core/components/highlight-code.jsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,40 @@
import React, { Component } from "react"
import React, { useRef, useEffect } from "react"
import PropTypes from "prop-types"
import cx from "classnames"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get"
import isFunction from "lodash/isFunction"
import saveAs from "js-file-download"
import { CopyToClipboard } from "react-copy-to-clipboard"

export default class HighlightCode extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
getConfigs: PropTypes.func.isRequired,
className: PropTypes.string,
downloadable: PropTypes.bool,
fileName: PropTypes.string,
language: PropTypes.string,
canCopy: PropTypes.bool
}
const HighlightCode = ({value, fileName, className, downloadable, getConfigs, canCopy, language}) => {
const config = isFunction(getConfigs) ? getConfigs() : null
const canSyntaxHighlight = get(config, "syntaxHighlight.activated", true)
const highlighterStyle = getStyle(get(config, "syntaxHighlight.theme", "agate"))
const rootRef = useRef(null)

#childNodes
useEffect(() => {
const childNodes = Array
.from(rootRef.current.childNodes)
.filter(node => !!node.nodeType && node.classList.contains("microlight"))

downloadText = () => {
saveAs(this.props.value, this.props.fileName || "response.txt")
}
// eslint-disable-next-line no-use-before-define
childNodes.forEach(node => node.addEventListener("mousewheel", handlePreventYScrollingBeyondElement, { passive: false }))

handleRootRef = (node) => {
if (node === null) {
this.#childNodes = node
} else {
this.#childNodes = Array
.from(node.childNodes)
.filter(node => !!node.nodeType && node.classList.contains("microlight"))
return () => {
// eslint-disable-next-line no-use-before-define
childNodes.forEach(node => node.removeEventListener("mousewheel", handlePreventYScrollingBeyondElement))
}
}
}, [value, className, language])

preventYScrollingBeyondElement = (e) => {
const target = e.target

var deltaY = e.deltaY
var contentHeight = target.scrollHeight
var visibleHeight = target.offsetHeight
var scrollTop = target.scrollTop
const handleDownload = () => {
saveAs(value, fileName)
}

const handlePreventYScrollingBeyondElement = (e) => {
const { target, deltaY } = e
const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target
const scrollOffset = visibleHeight + scrollTop

const isElementScrollable = contentHeight > visibleHeight
const isScrollingPastTop = scrollTop === 0 && deltaY < 0
const isScrollingPastBottom = scrollOffset >= contentHeight && deltaY > 0
Expand All @@ -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
return (
<div className="highlight-code" ref={rootRef}>
{!downloadable ? null :
<div className="download-contents" onClick={handleDownload}>
Download
</div>
}

{canCopy && (
<div className="copy-to-clipboard">
<CopyToClipboard text={value}><button/></CopyToClipboard>
</div>
)}

{canSyntaxHighlight
? <SyntaxHighlighter
language={language}
className={className + " microlight"}
style={getStyle(get(config, "syntaxHighlight.theme"))}
>
className={cx(className, "microlight")}
style={highlighterStyle}
>
{value}
</SyntaxHighlighter>
: <pre className={className + " microlight"}>{value}</pre>
: <pre className={cx(className + "microlight")}>{value}</pre>
}

return (
<div className="highlight-code" ref={this.handleRootRef}>
{ !downloadable ? null :
<div className="download-contents" onClick={this.downloadText}>
Download
</div>
}
</div>
)
}

{ !canCopy ? null :
<div className="copy-to-clipboard">
<CopyToClipboard text={value}><button/></CopyToClipboard>
</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
}

{ codeBlock }
</div>
)
}
HighlightCode.defaultProps = {
fileName: "response.txt"
}

export default HighlightCode

0 comments on commit da4f70b

Please sign in to comment.