Skip to content

Commit

Permalink
Rework swagger-api#5259 syntax highlighting with react-syntax-highlig…
Browse files Browse the repository at this point in the history
…ht 🌈
  • Loading branch information
AdrieanKhisbe committed Jun 17, 2020
1 parent 63f94db commit a25aefc
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 348 deletions.
510 changes: 349 additions & 161 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"react-inspector": "^2.3.0",
"react-motion": "^0.5.2",
"react-redux": "^4.x.x",
"react-syntax-highlighter": "=12.2.1",
"redux": "^3.x.x",
"redux-immutable": "3.1.0",
"remarkable": "^2.0.1",
Expand Down
13 changes: 7 additions & 6 deletions src/core/components/curl.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React from "react"
import PropTypes from "prop-types"
import curlify from "core/curlify"
import {copyToClipboard} from "core/copy-to-clipboard"
import {SyntaxHighlighter, styles} from "core/syntax-highlighting"

export default class Curl extends React.Component {
static propTypes = {
request: PropTypes.object.isRequired
}

handleFocus(e) {
e.target.select()
document.execCommand("copy")
copy(curlCommand) {
return () => copyToClipboard(curlCommand)
}

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

return (
<div>
<h4>Curl</h4>
<div className="copy-paste">
<textarea onFocus={this.handleFocus} readOnly={true} className="curl" value={curl}></textarea>
<h4>Curl <i onClick={this.copy(curl)}>(copyCommand)</i></h4>
<div>
<SyntaxHighlighter language="bash" className="curl" style={styles.agate}>{curl}</SyntaxHighlighter>
</div>
</div>
)
Expand Down
23 changes: 6 additions & 17 deletions src/core/components/highlight-code.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { highlight } from "core/utils"
import {SyntaxHighlighter, styles} from "core/syntax-highlighting"
import saveAs from "js-file-download"
import { CopyToClipboard } from "react-copy-to-clipboard"

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

componentDidMount() {
highlight(this.el)
}

componentDidUpdate() {
highlight(this.el)
}

initializeComponent = (c) => {
this.el = c
}

downloadText = () => {
saveAs(this.props.value, this.props.fileName || "response.txt")
}
Expand Down Expand Up @@ -66,12 +54,13 @@ export default class HighlightCode extends Component {
</div>
}

<pre
ref={this.initializeComponent}
<SyntaxHighlighter
className={className + " microlight"}
onWheel={this.preventYScrollingBeyondElement}
className={className + " microlight"}>
style={styles.agate}
>
{value}
</pre>
</SyntaxHighlighter>
</div>
)
}
Expand Down
22 changes: 22 additions & 0 deletions src/core/copy-to-clipboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const copyToClipboard = str => {
// adapted from https://hackernoon.com/copying-text-to-clipboard-with-javascript-df4d4988697f
const el = document.createElement("textarea")
el.value = str
el.setAttribute("readonly", "")
el.style.position = "absolute"
el.style.left = "-9999px"

document.body.appendChild(el)
// preserve selection
const selected =
document.getSelection().rangeCount > 0
? document.getSelection().getRangeAt(0)
: false
el.select()
document.execCommand("copy")
document.body.removeChild(el)
if (selected) {
document.getSelection().removeAllRanges()
document.getSelection().addRange(selected)
}
}
21 changes: 21 additions & 0 deletions src/core/syntax-highlighting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Light as SyntaxHighlighter } from "react-syntax-highlighter"

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 agate from "react-syntax-highlighter/dist/esm/styles/hljs/agate"

SyntaxHighlighter.registerLanguage("json", json)
SyntaxHighlighter.registerLanguage("js", js)
SyntaxHighlighter.registerLanguage("xml", xml)
SyntaxHighlighter.registerLanguage("yaml", yaml)
SyntaxHighlighter.registerLanguage("http", http)
SyntaxHighlighter.registerLanguage("bash", bash)

const styles = {agate}

export {SyntaxHighlighter, styles}
160 changes: 0 additions & 160 deletions src/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,166 +220,6 @@ export function getList(iterable, keys) {
return Im.List.isList(val) ? val : Im.List()
}

/**
* Adapted from http://github.com/asvd/microlight
* @copyright 2016 asvd <heliosframework@gmail.com>
*/
export function highlight (el) {
const MAX_LENGTH = 5000
var
_document = document,
appendChild = "appendChild",
test = "test"

if (!el) return ""
if (el.textContent.length > MAX_LENGTH) { return el.textContent }

var reset = function(el) {
var text = el.textContent,
pos = 0, // current position
next1 = text[0], // next character
chr = 1, // current character
prev1, // previous character
prev2, // the one before the previous
token = // current token content
el.innerHTML = "", // (and cleaning the node)

// current token type:
// 0: anything else (whitespaces / newlines)
// 1: operator or brace
// 2: closing braces (after which '/' is division not regex)
// 3: (key)word
// 4: regex
// 5: string starting with "
// 6: string starting with '
// 7: xml comment <!-- -->
// 8: multiline comment /* */
// 9: single-line comment starting with two slashes //
// 10: single-line comment starting with hash #
tokenType = 0,

// kept to determine between regex and division
lastTokenType,
// flag determining if token is multi-character
multichar,
node

// running through characters and highlighting
while (prev2 = prev1,
// escaping if needed (with except for comments)
// previous character will not be therefore
// recognized as a token finalize condition
prev1 = tokenType < 7 && prev1 == "\\" ? 1 : chr
) {
chr = next1
next1=text[++pos]
multichar = token.length > 1

// checking if current token should be finalized
if (!chr || // end of content
// types 9-10 (single-line comments) end with a
// newline
(tokenType > 8 && chr == "\n") ||
[ // finalize conditions for other token types
// 0: whitespaces
/\S/[test](chr), // merged together
// 1: operators
1, // consist of a single character
// 2: braces
1, // consist of a single character
// 3: (key)word
!/[$\w]/[test](chr),
// 4: regex
(prev1 == "/" || prev1 == "\n") && multichar,
// 5: string with "
prev1 == "\"" && multichar,
// 6: string with '
prev1 == "'" && multichar,
// 7: xml comment
text[pos-4]+prev2+prev1 == "-->",
// 8: multiline comment
prev2+prev1 == "*/"
][tokenType]
) {
// appending the token to the result
if (token) {
// remapping token type into style
// (some types are highlighted similarly)
el[appendChild](
node = _document.createElement("span")
).setAttribute("class", [
// 0: not formatted
"token-not-formatted",
// 1: keywords
"",
// 2: punctuation
"",
// 3: strings and regexps
"token-string",
// 4: comments
""
][
// not formatted
!tokenType ? 0 :
// punctuation
tokenType < 3 ? 2 :
// comments
tokenType > 6 ? 4 :
// regex and strings
tokenType > 3 ? 3 :
// otherwise tokenType == 3, (key)word
// (1 if regexp matches, 0 otherwise)
+ /^(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)
])

node[appendChild](_document.createTextNode(token))
}

// saving the previous token type
// (skipping whitespaces and comments)
lastTokenType =
(tokenType && tokenType < 7) ?
tokenType : lastTokenType

// initializing a new token
token = ""

// determining the new token type (going up the
// list until matching a token type start
// condition)
tokenType = 11
while (![
1, // 0: whitespace
// 1: operator or braces
/[\/{}[(\-+*=<>:;|\\.,?!&@~]/[test](chr), // eslint-disable-line no-useless-escape
/[\])]/[test](chr), // 2: closing brace
/[$\w]/[test](chr), // 3: (key)word
chr == "/" && // 4: regex
// previous token was an
// opening brace or an
// operator (otherwise
// division, not a regex)
(lastTokenType < 2) &&
// workaround for xml
// closing tags
prev1 != "<",
chr == "\"", // 5: string with "
chr == "'", // 6: string with '
// 7: xml comment
chr+next1+text[pos+1]+text[pos+2] == "<!--",
chr+next1 == "/*", // 8: multiline comment
chr+next1 == "//", // 9: single-line comment
chr == "#" // 10: hash-style comment
][--tokenType]);
}

token += chr
}
}

return reset(el)
}

/**
* Take an immutable map, and convert to a list.
* Where the keys are merged with the value objects
Expand Down
10 changes: 6 additions & 4 deletions src/style/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,12 @@

overflow-wrap: break-word;
@include text_code($opblock-body-font-color);
span
{
color: $opblock-body-font-color !important;
}

// disabled to have syntax highliting with react-syntax-highlight
// span
// {
// color: $opblock-body-font-color !important;
// }

.headerline
{
Expand Down

0 comments on commit a25aefc

Please sign in to comment.