Skip to content

Commit a25aefc

Browse files
committed
Rework swagger-api#5259 syntax highlighting with react-syntax-highlight 🌈
1 parent 63f94db commit a25aefc

File tree

8 files changed

+412
-348
lines changed

8 files changed

+412
-348
lines changed

package-lock.json

Lines changed: 349 additions & 161 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"react-inspector": "^2.3.0",
7878
"react-motion": "^0.5.2",
7979
"react-redux": "^4.x.x",
80+
"react-syntax-highlighter": "=12.2.1",
8081
"redux": "^3.x.x",
8182
"redux-immutable": "3.1.0",
8283
"remarkable": "^2.0.1",

src/core/components/curl.jsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import React from "react"
22
import PropTypes from "prop-types"
33
import curlify from "core/curlify"
4+
import {copyToClipboard} from "core/copy-to-clipboard"
5+
import {SyntaxHighlighter, styles} from "core/syntax-highlighting"
46

57
export default class Curl extends React.Component {
68
static propTypes = {
79
request: PropTypes.object.isRequired
810
}
911

10-
handleFocus(e) {
11-
e.target.select()
12-
document.execCommand("copy")
12+
copy(curlCommand) {
13+
return () => copyToClipboard(curlCommand)
1314
}
1415

1516
render() {
@@ -18,9 +19,9 @@ export default class Curl extends React.Component {
1819

1920
return (
2021
<div>
21-
<h4>Curl</h4>
22-
<div className="copy-paste">
23-
<textarea onFocus={this.handleFocus} readOnly={true} className="curl" value={curl}></textarea>
22+
<h4>Curl <i onClick={this.copy(curl)}>(copyCommand)</i></h4>
23+
<div>
24+
<SyntaxHighlighter language="bash" className="curl" style={styles.agate}>{curl}</SyntaxHighlighter>
2425
</div>
2526
</div>
2627
)

src/core/components/highlight-code.jsx

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { Component } from "react"
22
import PropTypes from "prop-types"
3-
import { highlight } from "core/utils"
3+
import {SyntaxHighlighter, styles} from "core/syntax-highlighting"
44
import saveAs from "js-file-download"
55
import { CopyToClipboard } from "react-copy-to-clipboard"
66

@@ -13,18 +13,6 @@ export default class HighlightCode extends Component {
1313
canCopy: PropTypes.bool
1414
}
1515

16-
componentDidMount() {
17-
highlight(this.el)
18-
}
19-
20-
componentDidUpdate() {
21-
highlight(this.el)
22-
}
23-
24-
initializeComponent = (c) => {
25-
this.el = c
26-
}
27-
2816
downloadText = () => {
2917
saveAs(this.props.value, this.props.fileName || "response.txt")
3018
}
@@ -66,12 +54,13 @@ export default class HighlightCode extends Component {
6654
</div>
6755
}
6856

69-
<pre
70-
ref={this.initializeComponent}
57+
<SyntaxHighlighter
58+
className={className + " microlight"}
7159
onWheel={this.preventYScrollingBeyondElement}
72-
className={className + " microlight"}>
60+
style={styles.agate}
61+
>
7362
{value}
74-
</pre>
63+
</SyntaxHighlighter>
7564
</div>
7665
)
7766
}

src/core/copy-to-clipboard.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export const copyToClipboard = str => {
2+
// adapted from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
3+
const el = document.createElement("textarea")
4+
el.value = str
5+
el.setAttribute("readonly", "")
6+
el.style.position = "absolute"
7+
el.style.left = "-9999px"
8+
9+
document.body.appendChild(el)
10+
// preserve selection
11+
const selected =
12+
document.getSelection().rangeCount > 0
13+
? document.getSelection().getRangeAt(0)
14+
: false
15+
el.select()
16+
document.execCommand("copy")
17+
document.body.removeChild(el)
18+
if (selected) {
19+
document.getSelection().removeAllRanges()
20+
document.getSelection().addRange(selected)
21+
}
22+
}

src/core/syntax-highlighting.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Light as SyntaxHighlighter } from "react-syntax-highlighter"
2+
3+
import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"
4+
import json from "react-syntax-highlighter/dist/esm/languages/hljs/json"
5+
import xml from "react-syntax-highlighter/dist/esm/languages/hljs/xml"
6+
import bash from "react-syntax-highlighter/dist/esm/languages/hljs/bash"
7+
import yaml from "react-syntax-highlighter/dist/esm/languages/hljs/yaml"
8+
import http from "react-syntax-highlighter/dist/esm/languages/hljs/http"
9+
10+
import agate from "react-syntax-highlighter/dist/esm/styles/hljs/agate"
11+
12+
SyntaxHighlighter.registerLanguage("json", json)
13+
SyntaxHighlighter.registerLanguage("js", js)
14+
SyntaxHighlighter.registerLanguage("xml", xml)
15+
SyntaxHighlighter.registerLanguage("yaml", yaml)
16+
SyntaxHighlighter.registerLanguage("http", http)
17+
SyntaxHighlighter.registerLanguage("bash", bash)
18+
19+
const styles = {agate}
20+
21+
export {SyntaxHighlighter, styles}

src/core/utils.js

Lines changed: 0 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -220,166 +220,6 @@ export function getList(iterable, keys) {
220220
return Im.List.isList(val) ? val : Im.List()
221221
}
222222

223-
/**
224-
* Adapted from http://github.com/asvd/microlight
225-
* @copyright 2016 asvd <[email protected]>
226-
*/
227-
export function highlight (el) {
228-
const MAX_LENGTH = 5000
229-
var
230-
_document = document,
231-
appendChild = "appendChild",
232-
test = "test"
233-
234-
if (!el) return ""
235-
if (el.textContent.length > MAX_LENGTH) { return el.textContent }
236-
237-
var reset = function(el) {
238-
var text = el.textContent,
239-
pos = 0, // current position
240-
next1 = text[0], // next character
241-
chr = 1, // current character
242-
prev1, // previous character
243-
prev2, // the one before the previous
244-
token = // current token content
245-
el.innerHTML = "", // (and cleaning the node)
246-
247-
// current token type:
248-
// 0: anything else (whitespaces / newlines)
249-
// 1: operator or brace
250-
// 2: closing braces (after which '/' is division not regex)
251-
// 3: (key)word
252-
// 4: regex
253-
// 5: string starting with "
254-
// 6: string starting with '
255-
// 7: xml comment <!-- -->
256-
// 8: multiline comment /* */
257-
// 9: single-line comment starting with two slashes //
258-
// 10: single-line comment starting with hash #
259-
tokenType = 0,
260-
261-
// kept to determine between regex and division
262-
lastTokenType,
263-
// flag determining if token is multi-character
264-
multichar,
265-
node
266-
267-
// running through characters and highlighting
268-
while (prev2 = prev1,
269-
// escaping if needed (with except for comments)
270-
// previous character will not be therefore
271-
// recognized as a token finalize condition
272-
prev1 = tokenType < 7 && prev1 == "\\" ? 1 : chr
273-
) {
274-
chr = next1
275-
next1=text[++pos]
276-
multichar = token.length > 1
277-
278-
// checking if current token should be finalized
279-
if (!chr || // end of content
280-
// types 9-10 (single-line comments) end with a
281-
// newline
282-
(tokenType > 8 && chr == "\n") ||
283-
[ // finalize conditions for other token types
284-
// 0: whitespaces
285-
/\S/[test](chr), // merged together
286-
// 1: operators
287-
1, // consist of a single character
288-
// 2: braces
289-
1, // consist of a single character
290-
// 3: (key)word
291-
!/[$\w]/[test](chr),
292-
// 4: regex
293-
(prev1 == "/" || prev1 == "\n") && multichar,
294-
// 5: string with "
295-
prev1 == "\"" && multichar,
296-
// 6: string with '
297-
prev1 == "'" && multichar,
298-
// 7: xml comment
299-
text[pos-4]+prev2+prev1 == "-->",
300-
// 8: multiline comment
301-
prev2+prev1 == "*/"
302-
][tokenType]
303-
) {
304-
// appending the token to the result
305-
if (token) {
306-
// remapping token type into style
307-
// (some types are highlighted similarly)
308-
el[appendChild](
309-
node = _document.createElement("span")
310-
).setAttribute("class", [
311-
// 0: not formatted
312-
"token-not-formatted",
313-
// 1: keywords
314-
"",
315-
// 2: punctuation
316-
"",
317-
// 3: strings and regexps
318-
"token-string",
319-
// 4: comments
320-
""
321-
][
322-
// not formatted
323-
!tokenType ? 0 :
324-
// punctuation
325-
tokenType < 3 ? 2 :
326-
// comments
327-
tokenType > 6 ? 4 :
328-
// regex and strings
329-
tokenType > 3 ? 3 :
330-
// otherwise tokenType == 3, (key)word
331-
// (1 if regexp matches, 0 otherwise)
332-
+ /^(a(bstract|lias|nd|rguments|rray|s(m|sert)?|uto)|b(ase|egin|ool(ean)?|reak|yte)|c(ase|atch|har|hecked|lass|lone|ompl|onst|ontinue)|de(bugger|cimal|clare|f(ault|er)?|init|l(egate|ete)?)|do|double|e(cho|ls?if|lse(if)?|nd|nsure|num|vent|x(cept|ec|p(licit|ort)|te(nds|nsion|rn)))|f(allthrough|alse|inal(ly)?|ixed|loat|or(each)?|riend|rom|unc(tion)?)|global|goto|guard|i(f|mp(lements|licit|ort)|n(it|clude(_once)?|line|out|stanceof|t(erface|ernal)?)?|s)|l(ambda|et|ock|ong)|m(icrolight|odule|utable)|NaN|n(amespace|ative|ext|ew|il|ot|ull)|o(bject|perator|r|ut|verride)|p(ackage|arams|rivate|rotected|rotocol|ublic)|r(aise|e(adonly|do|f|gister|peat|quire(_once)?|scue|strict|try|turn))|s(byte|ealed|elf|hort|igned|izeof|tatic|tring|truct|ubscript|uper|ynchronized|witch)|t(emplate|hen|his|hrows?|ransient|rue|ry|ype(alias|def|id|name|of))|u(n(checked|def(ined)?|ion|less|signed|til)|se|sing)|v(ar|irtual|oid|olatile)|w(char_t|hen|here|hile|ith)|xor|yield)$/[test](token)
333-
])
334-
335-
node[appendChild](_document.createTextNode(token))
336-
}
337-
338-
// saving the previous token type
339-
// (skipping whitespaces and comments)
340-
lastTokenType =
341-
(tokenType && tokenType < 7) ?
342-
tokenType : lastTokenType
343-
344-
// initializing a new token
345-
token = ""
346-
347-
// determining the new token type (going up the
348-
// list until matching a token type start
349-
// condition)
350-
tokenType = 11
351-
while (![
352-
1, // 0: whitespace
353-
// 1: operator or braces
354-
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape
355-
/[\])]/[test](chr), // 2: closing brace
356-
/[$\w]/[test](chr), // 3: (key)word
357-
chr == "/" && // 4: regex
358-
// previous token was an
359-
// opening brace or an
360-
// operator (otherwise
361-
// division, not a regex)
362-
(lastTokenType < 2) &&
363-
// workaround for xml
364-
// closing tags
365-
prev1 != "<",
366-
chr == "\"", // 5: string with "
367-
chr == "'", // 6: string with '
368-
// 7: xml comment
369-
chr+next1+text[pos+1]+text[pos+2] == "<!--",
370-
chr+next1 == "/*", // 8: multiline comment
371-
chr+next1 == "//", // 9: single-line comment
372-
chr == "#" // 10: hash-style comment
373-
][--tokenType]);
374-
}
375-
376-
token += chr
377-
}
378-
}
379-
380-
return reset(el)
381-
}
382-
383223
/**
384224
* Take an immutable map, and convert to a list.
385225
* Where the keys are merged with the value objects

src/style/_layout.scss

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,10 +642,12 @@
642642

643643
overflow-wrap: break-word;
644644
@include text_code($opblock-body-font-color);
645-
span
646-
{
647-
color: $opblock-body-font-color !important;
648-
}
645+
646+
// disabled to have syntax highliting with react-syntax-highlight
647+
// span
648+
// {
649+
// color: $opblock-body-font-color !important;
650+
// }
649651

650652
.headerline
651653
{

0 commit comments

Comments
 (0)