Skip to content

Commit

Permalink
feat: consolidate syntax highlighting code into standalone plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
char0n committed Apr 5, 2024
1 parent 086ffeb commit 9ea2cca
Show file tree
Hide file tree
Showing 16 changed files with 188 additions and 90 deletions.
1 change: 0 additions & 1 deletion src/core/components/curl.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from "react"
import PropTypes from "prop-types"
import { CopyToClipboard } from "react-copy-to-clipboard"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get"
import { requestSnippetGenerator_curl_bash } from "../plugins/request-snippets/fn"

Expand Down
9 changes: 6 additions & 3 deletions src/core/components/example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export default function Example(props) {
const { example, showValue, getComponent, getConfigs } = props

const Markdown = getComponent("Markdown", true)
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)

if(!example) return null
if (!example) return null

return (
<div className="example">
Expand All @@ -28,7 +28,10 @@ export default function Example(props) {
{showValue && example.has("value") ? (
<section className="example__section">
<div className="example__section-header">Example Value</div>
<HighlightCode getConfigs={ getConfigs } value={stringify(example.get("value"))} />
<HighlightCode
getConfigs={getConfigs}
value={stringify(example.get("value"))}
/>
</section>
) : null}
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/core/components/model-example.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ModelExample = ({
}) => {
const { defaultModelRendering, defaultModelExpandDepth } = getConfigs()
const ModelWrapper = getComponent("ModelWrapper")
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)
const exampleTabId = randomBytes(5).toString("base64")
const examplePanelId = randomBytes(5).toString("base64")
const modelTabId = randomBytes(5).toString("base64")
Expand Down
4 changes: 2 additions & 2 deletions src/core/components/param-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default class ParamBody extends PureComponent {

const Button = getComponent("Button")
const TextArea = getComponent("TextArea")
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)
const ContentType = getComponent("contentType")
// for domains where specSelectors not passed
let parameter = specSelectors ? specSelectors.parameterWithMetaByIdentity(pathMethod, param) : param
Expand Down Expand Up @@ -148,7 +148,7 @@ export default class ParamBody extends PureComponent {
contentTypes={ consumes }
onChange={onChangeConsumes}
className="body-param-content-type"
ariaLabel="Parameter content type"
ariaLabel="Parameter content type"
controlId={controlId}
/>
</label>
Expand Down
2 changes: 1 addition & 1 deletion src/core/components/response-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export default class ResponseBody extends React.PureComponent {
render() {
let { content, contentType, url, headers={}, getConfigs, getComponent } = this.props
const { parsedContent } = this.state
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)
const downloadName = "response_" + new Date().getTime()
let body, bodyEl
url = url || ""
Expand Down
2 changes: 1 addition & 1 deletion src/core/components/response.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default class Response extends React.Component {
let links = response.get("links")
const ResponseExtension = getComponent("ResponseExtension")
const Headers = getComponent("headers")
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)
const ModelExample = getComponent("modelExample")
const Markdown = getComponent("Markdown", true)
const OperationLink = getComponent("operationLink")
Expand Down
2 changes: 2 additions & 0 deletions src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import UtilPlugin from "./plugins/util"
import ViewPlugin from "./plugins/view"
import ViewLegacyPlugin from "core/plugins/view-legacy"
import DownloadUrlPlugin from "./plugins/download-url"
import SyntaxHighlightingPlugin from "core/plugins/syntax-highlighting"
import SafeRenderPlugin from "./plugins/safe-render"

import { parseSearch } from "./utils"
Expand Down Expand Up @@ -271,5 +272,6 @@ SwaggerUI.plugins = {
View: ViewPlugin,
ViewLegacy: ViewLegacyPlugin,
DownloadUrl: DownloadUrlPlugin,
SyntaxHighlighting: SyntaxHighlightingPlugin,
SafeRender: SafeRenderPlugin,
}
6 changes: 3 additions & 3 deletions src/core/plugins/oas3/components/request-body.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const RequestBody = ({
const Markdown = getComponent("Markdown", true)
const ModelExample = getComponent("modelExample")
const RequestBodyEditor = getComponent("RequestBodyEditor")
const HighlightCode = getComponent("highlightCode")
const HighlightCode = getComponent("HighlightCode", true)
const ExamplesSelectValueRetainer = getComponent("ExamplesSelectValueRetainer")
const Example = getComponent("Example")
const ParameterIncludeEmpty = getComponent("ParameterIncludeEmpty")
Expand Down Expand Up @@ -152,7 +152,7 @@ const RequestBody = ({
{
Map.isMap(bodyProperties) && bodyProperties.entrySeq().map(([key, schema]) => {
if (schema.get("readOnly")) return

const oneOf = schema.get("oneOf")?.get(0)?.toJS()
const anyOf = schema.get("anyOf")?.get(0)?.toJS()
schema = fromJS(fn.mergeJsonSchema(schema.toJS(), oneOf ?? anyOf ?? {}))
Expand All @@ -169,7 +169,7 @@ const RequestBody = ({
let initialValue = fn.getSampleSchema(schema, false, {
includeWriteOnly: true
})

if (initialValue === false) {
initialValue = "false"
}
Expand Down
12 changes: 6 additions & 6 deletions src/core/plugins/request-snippets/request-snippets.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import PropTypes from "prop-types"
import get from "lodash/get"
import isFunction from "lodash/isFunction"
import { CopyToClipboard } from "react-copy-to-clipboard"
import { SyntaxHighlighter, getStyle } from "core/syntax-highlighting"

const style = {
cursor: "pointer",
Expand Down Expand Up @@ -42,6 +41,7 @@ const RequestSnippets = ({ request, requestSnippetsSelectors, getConfigs, getCom

const ArrowIcon = getComponent("ArrowUpIcon")
const ArrowDownIcon = getComponent("ArrowDownIcon")
const SyntaxHighlighter = getComponent("SyntaxHighlighter", true)

const [activeLanguage, setActiveLanguage] = useState(requestSnippetsSelectors.getSnippetGenerators()?.keySeq().first())
const [isExpanded, setIsExpanded] = useState(requestSnippetsSelectors?.getDefaultExpanded())
Expand Down Expand Up @@ -99,16 +99,16 @@ const RequestSnippets = ({ request, requestSnippetsSelectors, getConfigs, getCom
}
}

const SnippetComponent = canSyntaxHighlight
? <SyntaxHighlighter
const SnippetComponent = canSyntaxHighlight ? (
<SyntaxHighlighter
language={activeGenerator.get("syntax")}
className="curl microlight"
style={getStyle(get(config, "syntaxHighlight.theme"))}
>
{snippet}
</SyntaxHighlighter>
:
) : (
<textarea readOnly={true} className="curl" value={snippet}></textarea>
)

return (
<div className="request-snippets" ref={rootRef}>
Expand Down Expand Up @@ -147,7 +147,7 @@ const RequestSnippets = ({ request, requestSnippetsSelectors, getConfigs, getCom
</div>
}
</div>
)
)
}

RequestSnippets.propTypes = {
Expand Down
25 changes: 25 additions & 0 deletions src/core/plugins/syntax-highlighting/after-load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @prettier
*/
import SyntaxHighlighter from "react-syntax-highlighter/dist/esm/light"
import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"
import json from "react-syntax-highlighter/dist/esm/languages/hljs/json"
import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml"
import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash"
import yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml"
import http from "react-syntax-highlighter/dist/esm/languages/hljs/http"
import powershell from "react-syntax-highlighter/dist/esm/languages/hljs/powershell"
import javascript from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"

const afterLoad = () => {
SyntaxHighlighter.registerLanguage("json", json)
SyntaxHighlighter.registerLanguage("js", js)
SyntaxHighlighter.registerLanguage("xml", xml)
SyntaxHighlighter.registerLanguage("yaml", yaml)
SyntaxHighlighter.registerLanguage("http", http)
SyntaxHighlighter.registerLanguage("bash", bash)
SyntaxHighlighter.registerLanguage("powershell", powershell)
SyntaxHighlighter.registerLanguage("javascript", javascript)
}

export default afterLoad
Original file line number Diff line number Diff line change
@@ -1,38 +1,42 @@
/**
* @prettier
*/
import React, { useRef, useEffect } from "react"
import PropTypes from "prop-types"
import cx from "classnames"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import classNames from "classnames"
import get from "lodash/get"
import isFunction from "lodash/isFunction"
import saveAs from "js-file-download"
import { CopyToClipboard } from "react-copy-to-clipboard"

const HighlightCode = ({value, fileName = "response.txt", className, downloadable, getConfigs, canCopy, language}) => {
const config = isFunction(getConfigs) ? getConfigs() : null
const canSyntaxHighlight = get(config, "syntaxHighlight") !== false && get(config, "syntaxHighlight.activated", true)
const HighlightCode = ({
value,
fileName = "response.txt",
className,
downloadable,
getConfigs,
getComponent,
canCopy,
language,
}) => {
const config = getConfigs()
const canSyntaxHighlight =
get(config, "syntaxHighlight") !== false &&
get(config, "syntaxHighlight.activated", true)
const rootRef = useRef(null)

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))
}
}, [value, className, language])
const SyntaxHighlighter = getComponent("SyntaxHighlighter", true)

const handleDownload = () => {
saveAs(value, fileName)
}

const handlePreventYScrollingBeyondElement = (e) => {
const { target, deltaY } = e
const { scrollHeight: contentHeight, offsetHeight: visibleHeight, scrollTop } = target
const {
scrollHeight: contentHeight,
offsetHeight: visibleHeight,
scrollTop,
} = target
const scrollOffset = visibleHeight + scrollTop
const isElementScrollable = contentHeight > visibleHeight
const isScrollingPastTop = scrollTop === 0 && deltaY < 0
Expand All @@ -43,43 +47,70 @@ const HighlightCode = ({value, fileName = "response.txt", className, downloadabl
}
}

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
)
)
}
}, [value, className, language])

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

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

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

) : (
<pre className={classNames(className, "microlight")}>{value}</pre>
)}
</div>
)
}

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

export default HighlightCode
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* @prettier
*/
import React from "react"
import PropTypes from "prop-types"
import ReactSyntaxHighlighter from "react-syntax-highlighter/dist/esm/light"
import get from "lodash/get"

const SyntaxHighlighter = ({
language,
className = "",
getSystem,
children = null,
}) => {
const system = getSystem()
const configs = system.getConfigs()
const theme = get(configs, "syntaxHighlight.theme")
const { styles, defaultStyle } = system.syntaxHighlighting
const style = styles[theme] ?? defaultStyle

return (
<ReactSyntaxHighlighter
language={language}
className={className}
style={style}
>
{children}
</ReactSyntaxHighlighter>
)
}

SyntaxHighlighter.propTypes = {
language: PropTypes.string.isRequired,
className: PropTypes.string,
getSystem: PropTypes.func.isRequired,
children: PropTypes.node,
}

export default SyntaxHighlighter
Loading

0 comments on commit 9ea2cca

Please sign in to comment.