summaryrefslogtreecommitdiff
path: root/src/core/plugins/syntax-highlighting/components/HighlightCode.jsx
blob: f389f0b95ef610c9ffe52dec9e15c5c6f1d45eb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/**
 * @prettier
 */
import React, { useRef, useEffect } from "react"
import PropTypes from "prop-types"
import classNames from "classnames"
import saveAs from "js-file-download"
import { CopyToClipboard } from "react-copy-to-clipboard"

const HighlightCode = ({
  fileName = "response.txt",
  className,
  downloadable,
  getComponent,
  canCopy,
  language,
  children,
}) => {
  const rootRef = useRef(null)
  const SyntaxHighlighter = getComponent("SyntaxHighlighter", true)

  const handleDownload = () => {
    saveAs(children, 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

    if (isElementScrollable && (isScrollingPastTop || isScrollingPastBottom)) {
      e.preventDefault()
    }
  }

  useEffect(() => {
    const childNodes = Array.from(rootRef.current.childNodes).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
        )
      )
    }
  }, [children, className, language])

  return (
    <div className="highlight-code" ref={rootRef}>
      {canCopy && (
        <div className="copy-to-clipboard">
          <CopyToClipboard text={children}>
            <button />
          </CopyToClipboard>
        </div>
      )}

      {!downloadable ? null : (
        <button className="download-contents" onClick={handleDownload}>
          Download
        </button>
      )}

      <SyntaxHighlighter
        language={language}
        className={classNames(className, "microlight")}
        renderPlainText={({ children, PlainTextViewer }) => (
          <PlainTextViewer className={className}>{children}</PlainTextViewer>
        )}
      >
        {children}
      </SyntaxHighlighter>
    </div>
  )
}

HighlightCode.propTypes = {
  getComponent: PropTypes.func.isRequired,
  className: PropTypes.string,
  downloadable: PropTypes.bool,
  fileName: PropTypes.string,
  language: PropTypes.string,
  canCopy: PropTypes.bool,
  children: PropTypes.string.isRequired,
}

export default HighlightCode