Переглянути джерело

feat: syntax highlighting of code section (#6236)

Co-authored-by: AdrieanKhisbe <adriean.khisbe@live.fr>
bubble
Tim Lai 4 роки тому
committed by GitHub
джерело
коміт
a73783b73d
Не вдалося знайти GPG ключ що відповідає даному підпису Ідентифікатор GPG ключа: 4AEE18F83AFDEB23
20 змінених файлів з 340 додано та 228 видалено
  1. BIN
     
  2. +3
    -0
      docs/usage/configuration.md
  3. +193
    -5
      package-lock.json
  4. +1
    -0
      package.json
  5. +25
    -9
      src/core/components/curl.jsx
  6. +18
    -20
      src/core/components/highlight-code.jsx
  7. +2
    -1
      src/core/components/live-response.jsx
  8. +3
    -1
      src/core/components/param-body.jsx
  9. +1
    -0
      src/core/components/parameter-row.jsx
  10. +7
    -6
      src/core/components/response-body.jsx
  11. +3
    -3
      src/core/components/response.jsx
  12. +5
    -0
      src/core/index.js
  13. +35
    -0
      src/core/syntax-highlighting.js
  14. +0
    -160
      src/core/utils.js
  15. +14
    -0
      src/style/_buttons.scss
  16. +9
    -4
      src/style/_layout.scss
  17. +5
    -3
      test/components/highlight-code.jsx
  18. +6
    -6
      test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js
  19. +8
    -8
      test/e2e-cypress/tests/features/oas3-request-body-required.js
  20. +2
    -2
      webpack/_config-builder.js

+ 3
- 0
docs/usage/configuration.md Переглянути файл

@@ -61,6 +61,9 @@ Parameter name | Docker variable | Description
<a name="tagSorter"></a>`tagsSorter` | _Unavailable_ | `Function=(a => a)`. Apply a sort to the tag list of each API. It can be 'alpha' (sort by paths alphanumerically) or a function (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to learn how to write a sort function). Two tag name strings are passed to the sorter for each pass. Default is the order determined by Swagger UI.
<a name="useUnsafeMarkdown"></a>`useUnsafeMarkdown` | `USE_UNSAFE_MARKDOWN` | `Boolean=false`. When enabled, sanitizer will leave `style`, `class` and `data-*` attributes untouched on all HTML Elements declared inside markdown strings. This parameter is **Deprecated** and will be removed in `4.0.0`.
<a name="onComplete"></a>`onComplete` | _Unavailable_ | `Function=NOOP`. Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition.
<a name="syntaxHighlight"></a>`syntaxHighlight` | _Unavailable_ | Set to false to deactivate syntax highlighting of payloads and curl command, can be otherwise an object with `activate` and `theme` properties.
<a name="syntaxHighlight.activate"></a>`syntaxHighlight.activate` | _Unavailable_ | `Boolean=true`. Whether the syntax highlight should be activated or not.
<a name="syntaxHighlight.theme"></a>`syntaxHighlight.theme` | _Unavailable_ | `String=["agate"*, "arta", "monokai", "nord", "obsidian", "tomorrow-night]`. [Highlight.js](https://highlightjs.org/static/demo/) syntax coloring theme to use. (only these 6 styles are available).

##### Network



+ 193
- 5
package-lock.json Переглянути файл

@@ -2317,7 +2317,6 @@
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
"integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
},
@@ -2325,8 +2324,7 @@
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==",
"dev": true
"integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
}
}
},
@@ -4970,6 +4968,21 @@
"integrity": "sha512-kpOfKlZ9x2UpeC4at6FAXHLKfi/JEUqUqkPCb1JUCa5FnNbJIzOHRM9RfeQ1QDcpj+Gxuc/UoHqASgmEeFDejQ==",
"dev": true
},
"character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="
},
"character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="
},
"character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
},
"chardet": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
@@ -5198,6 +5211,17 @@
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
"dev": true
},
"clipboard": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz",
"integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
@@ -5386,6 +5410,11 @@
"delayed-stream": "~1.0.0"
}
},
"comma-separated-tokens": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
"integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="
},
"commander": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
@@ -7743,6 +7772,12 @@
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@@ -9438,6 +9473,14 @@
"reusify": "^1.0.0"
}
},
"fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"requires": {
"format": "^0.2.0"
}
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@@ -9824,6 +9867,11 @@
"mime-types": "^2.1.12"
}
},
"format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -11201,6 +11249,15 @@
"minimatch": "~3.0.2"
}
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -11411,6 +11468,22 @@
"minimalistic-assert": "^1.0.1"
}
},
"hast-util-parse-selector": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.4.tgz",
"integrity": "sha512-gW3sxfynIvZApL4L07wryYF4+C9VvH3AUi7LAnVXV4MneGEgwOByXvFo18BgmTWnm7oHAe874jKbIB1YhHSIzA=="
},
"hastscript": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz",
"integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==",
"requires": {
"comma-separated-tokens": "^1.0.0",
"hast-util-parse-selector": "^2.0.0",
"property-information": "^5.0.0",
"space-separated-tokens": "^1.0.0"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -11423,6 +11496,11 @@
"integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==",
"dev": true
},
"highlight.js": {
"version": "9.15.10",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz",
"integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw=="
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@@ -12194,6 +12272,20 @@
}
}
},
"is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="
},
"is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"requires": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
}
},
"is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@@ -12285,6 +12377,11 @@
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
"dev": true
},
"is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
@@ -12385,6 +12482,11 @@
"is-extglob": "^2.1.1"
}
},
"is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
},
"is-installed-globally": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz",
@@ -14139,6 +14241,15 @@
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
"dev": true
},
"lowlight": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.12.1.tgz",
"integrity": "sha512-OqaVxMGIESnawn+TU/QMV5BJLbUghUfjDWPAtFqDYDmDtr4FnB+op8xM+pR7nKlauHNUHXGt0VgWatFB8voS5w==",
"requires": {
"fault": "^1.0.2",
"highlight.js": "~9.15.0"
}
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
@@ -16285,6 +16396,19 @@
"safe-buffer": "^5.1.1"
}
},
"parse-entities": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz",
"integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==",
"requires": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
}
},
"parse-github-repo-url": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz",
@@ -17933,6 +18057,14 @@
}
}
},
"prismjs": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.20.0.tgz",
"integrity": "sha512-AEDjSrVNkynnw6A+B1DsFkd6AVdTnp+/WoUixFRULlCLZVRZlVQMVWio/16jv7G1FscUxQxOQhWwApgbnxr6kQ==",
"requires": {
"clipboard": "^2.0.0"
}
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -17975,6 +18107,14 @@
"react-is": "^16.8.1"
}
},
"property-information": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-5.5.0.tgz",
"integrity": "sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA==",
"requires": {
"xtend": "^4.0.0"
}
},
"protocols": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/protocols/-/protocols-1.4.7.tgz",
@@ -18370,6 +18510,18 @@
"prop-types": "^15.7.2"
}
},
"react-syntax-highlighter": {
"version": "12.2.1",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-12.2.1.tgz",
"integrity": "sha512-CTsp0ZWijwKRYFg9xhkWD4DSpQqE4vb2NKVMdPAkomnILSmsNBHE0n5GuI5zB+PU3ySVvXvdt9jo+ViD9XibCA==",
"requires": {
"@babel/runtime": "^7.3.1",
"highlight.js": "~9.15.1",
"lowlight": "1.12.1",
"prismjs": "^1.8.4",
"refractor": "^2.4.1"
}
},
"react-test-renderer": {
"version": "15.6.2",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-15.6.2.tgz",
@@ -18505,6 +18657,26 @@
"immutable": "^3.8.1"
}
},
"refractor": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/refractor/-/refractor-2.10.1.tgz",
"integrity": "sha512-Xh9o7hQiQlDbxo5/XkOX6H+x/q8rmlmZKr97Ie1Q8ZM32IRRd3B/UxuA/yXDW79DBSXGWxm2yRTbcTVmAciJRw==",
"requires": {
"hastscript": "^5.0.0",
"parse-entities": "^1.1.2",
"prismjs": "~1.17.0"
},
"dependencies": {
"prismjs": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz",
"integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==",
"requires": {
"clipboard": "^2.0.0"
}
}
}
},
"regenerate": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz",
@@ -19450,6 +19622,12 @@
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=",
"dev": true
},
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -20086,6 +20264,11 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true
},
"space-separated-tokens": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="
},
"spdx-compare": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
@@ -21565,6 +21748,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true
},
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"tmatch": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tmatch/-/tmatch-2.0.1.tgz",
@@ -23046,8 +23235,7 @@
"xtend": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
"dev": true
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
},
"y18n": {
"version": "4.0.0",


+ 1
- 0
package.json Переглянути файл

@@ -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",


+ 25
- 9
src/core/components/curl.jsx Переглянути файл

@@ -1,26 +1,42 @@
import React from "react"
import PropTypes from "prop-types"
import curlify from "core/curlify"
import { CopyToClipboard } from "react-copy-to-clipboard"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get"

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

handleFocus(e) {
e.target.select()
document.execCommand("copy")
}

render() {
let { request } = this.props
let { request, getConfigs } = this.props
let curl = curlify(request)

const config = getConfigs()

const curlBlock = get(config, "syntaxHighlight.activated")
? <SyntaxHighlighter
language="bash"
className="curl microlight"
onWheel={this.preventYScrollingBeyondElement}
style={getStyle(get(config, "syntaxHighlight.theme"))}
>
{curl}
</SyntaxHighlighter>
:
<textarea readOnly={true} className="curl" value={curl}></textarea>

return (
<div>
<div className="curl-command">
<h4>Curl</h4>
<div className="copy-paste">
<textarea onFocus={this.handleFocus} readOnly={true} className="curl" value={curl}></textarea>
<div className="copy-to-clipboard">
<CopyToClipboard text={curl}><button/></CopyToClipboard>
</div>
<div>
{curlBlock}
</div>
</div>
)


+ 18
- 20
src/core/components/highlight-code.jsx Переглянути файл

@@ -1,30 +1,20 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import { highlight } from "core/utils"
import {SyntaxHighlighter, getStyle} from "core/syntax-highlighting"
import get from "lodash/get"
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,
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")
}
@@ -49,9 +39,22 @@ export default class HighlightCode extends Component {
}

render () {
let { value, className, downloadable, canCopy } = this.props
let { value, className, downloadable, getConfigs, canCopy } = this.props

const config = getConfigs ? getConfigs() : {syntaxHighlight: {activated: true, theme: "agate"}}

className = className || ""

const codeBlock = get(config, "syntaxHighlight.activated")
? <SyntaxHighlighter
className={className + " microlight"}
onWheel={this.preventYScrollingBeyondElement}
style={getStyle(get(config, "syntaxHighlight.theme"))}
>
{value}
</SyntaxHighlighter>
: <pre onWheel={this.preventYScrollingBeyondElement} className={className + " microlight"}>{value}</pre>

return (
<div className="highlight-code">
{ !downloadable ? null :
@@ -66,12 +69,7 @@ export default class HighlightCode extends Component {
</div>
}

<pre
ref={this.initializeComponent}
onWheel={this.preventYScrollingBeyondElement}
className={className + " microlight"}>
{value}
</pre>
{ codeBlock }
</div>
)
}


+ 2
- 1
src/core/components/live-response.jsx Переглянути файл

@@ -71,7 +71,7 @@ export default class LiveResponse extends React.Component {

return (
<div>
{ curlRequest && <Curl request={ curlRequest }/> }
{ curlRequest && <Curl request={ curlRequest } getConfigs={ getConfigs } /> }
{ url && <div>
<h4>Request URL</h4>
<div className="request-url">
@@ -110,6 +110,7 @@ export default class LiveResponse extends React.Component {
contentType={ contentType }
url={ url }
headers={ headers }
getConfigs={ getConfigs }
getComponent={ getComponent }/>
: null
}


+ 3
- 1
src/core/components/param-body.jsx Переглянути файл

@@ -14,6 +14,7 @@ export default class ParamBody extends PureComponent {
consumes: PropTypes.object,
consumesValue: PropTypes.string,
fn: PropTypes.object.isRequired,
getConfigs: PropTypes.func.isRequired,
getComponent: PropTypes.func.isRequired,
isExecute: PropTypes.bool,
specSelectors: PropTypes.object.isRequired,
@@ -96,7 +97,7 @@ export default class ParamBody extends PureComponent {
isExecute,
specSelectors,
pathMethod,
getConfigs,
getComponent,
} = this.props

@@ -118,6 +119,7 @@ export default class ParamBody extends PureComponent {
isEditBox && isExecute
? <TextArea className={ "body-param__text" + ( errors.count() ? " invalid" : "")} value={value} onChange={ this.handleOnChange }/>
: (value && <HighlightCode className="body-param__example"
getConfigs={ getConfigs }
value={ value }/>)
}
<div className="body-param-options">


+ 1
- 0
src/core/components/parameter-row.jsx Переглянути файл

@@ -191,6 +191,7 @@ export default class ParameterRow extends Component {
let inType = param.get("in")
let bodyParam = inType !== "body" ? null
: <ParamBody getComponent={getComponent}
getConfigs={ getConfigs }
fn={fn}
param={param}
consumes={ specSelectors.consumesOptionsFor(pathMethod) }


+ 7
- 6
src/core/components/response-body.jsx Переглянути файл

@@ -13,6 +13,7 @@ export default class ResponseBody extends React.PureComponent {
static propTypes = {
content: PropTypes.any.isRequired,
contentType: PropTypes.string,
getConfigs: PropTypes.func.isRequired,
getComponent: PropTypes.func.isRequired,
headers: PropTypes.object,
url: PropTypes.string
@@ -49,7 +50,7 @@ export default class ResponseBody extends React.PureComponent {
}

render() {
let { content, contentType, url, headers={}, getComponent } = this.props
let { content, contentType, url, headers={}, getConfigs, getComponent } = this.props
const { parsedContent } = this.state
const HighlightCode = getComponent("highlightCode")
const downloadName = "response_" + new Date().getTime()
@@ -99,7 +100,7 @@ export default class ResponseBody extends React.PureComponent {
body = "can't parse JSON. Raw result:\n\n" + content
}

bodyEl = <HighlightCode downloadable fileName={`${downloadName}.json`} value={ body } canCopy />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.json`} value={ body } getConfigs={ getConfigs } canCopy />

// XML
} else if (/xml/i.test(contentType)) {
@@ -107,11 +108,11 @@ export default class ResponseBody extends React.PureComponent {
textNodesOnSameLine: true,
indentor: " "
})
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.xml`} value={ body } canCopy />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.xml`} value={ body } getConfigs={ getConfigs } canCopy />

// HTML or Plain Text
} else if (toLower(contentType) === "text/html" || /text\/plain/.test(contentType)) {
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.html`} value={ content } canCopy />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.html`} value={ content } getConfigs={ getConfigs } canCopy />

// Image
} else if (/^image\//i.test(contentType)) {
@@ -125,7 +126,7 @@ export default class ResponseBody extends React.PureComponent {
} else if (/^audio\//i.test(contentType)) {
bodyEl = <pre className="microlight"><audio controls><source src={ url } type={ contentType } /></audio></pre>
} else if (typeof content === "string") {
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.txt`} value={ content } canCopy />
bodyEl = <HighlightCode downloadable fileName={`${downloadName}.txt`} value={ content } getConfigs={ getConfigs } canCopy />
} else if ( content.size > 0 ) {
// We don't know the contentType, but there was some content returned
if(parsedContent) {
@@ -135,7 +136,7 @@ export default class ResponseBody extends React.PureComponent {
<p className="i">
Unrecognized response type; displaying content as text.
</p>
<HighlightCode downloadable fileName={`${downloadName}.txt`} value={ parsedContent } canCopy />
<HighlightCode downloadable fileName={`${downloadName}.txt`} value={ parsedContent } getConfigs={ getConfigs } canCopy />
</div>

} else {


+ 3
- 3
src/core/components/response.jsx Переглянути файл

@@ -5,12 +5,12 @@ import cx from "classnames"
import { fromJS, Seq, Iterable, List, Map } from "immutable"
import { getSampleSchema, fromJSOrdered, stringify } from "core/utils"

const getExampleComponent = ( sampleResponse, HighlightCode ) => {
const getExampleComponent = ( sampleResponse, HighlightCode, getConfigs ) => {
if (
sampleResponse !== undefined &&
sampleResponse !== null
) { return <div>
<HighlightCode className="example" value={ stringify(sampleResponse) } />
<HighlightCode className="example" getConfigs={ getConfigs } value={ stringify(sampleResponse) } />
</div>
}
return null
@@ -150,7 +150,7 @@ export default class Response extends React.Component {
}
}

let example = getExampleComponent( sampleResponse, HighlightCode )
let example = getExampleComponent( sampleResponse, HighlightCode, getConfigs )

return (
<tr className={ "response " + ( className || "") } data-code={code}>


+ 5
- 0
src/core/index.js Переглянути файл

@@ -78,6 +78,11 @@ export default function SwaggerUI(opts) {
// Inline Plugin
fn: { },
components: { },

syntaxHighlight: {
activated: true,
theme: "agate"
}
}

let queryConfig = parseSearch()


+ 35
- 0
src/core/syntax-highlighting.js Переглянути файл

@@ -0,0 +1,35 @@
import { Light as SyntaxHighlighter } from "react-syntax-highlighter"

import js from "react-syntax-highlighter/dist/cjs/languages/hljs/javascript"
import json from "react-syntax-highlighter/dist/cjs/languages/hljs/json"
import xml from "react-syntax-highlighter/dist/cjs/languages/hljs/xml"
import bash from "react-syntax-highlighter/dist/cjs/languages/hljs/bash"
import yaml from "react-syntax-highlighter/dist/cjs/languages/hljs/yaml"
import http from "react-syntax-highlighter/dist/cjs/languages/hljs/http"

import agate from "react-syntax-highlighter/dist/cjs/styles/hljs/agate"
import arta from "react-syntax-highlighter/dist/cjs/styles/hljs/arta"
import monokai from "react-syntax-highlighter/dist/cjs/styles/hljs/monokai"
import nord from "react-syntax-highlighter/dist/cjs/styles/hljs/nord"
import obsidian from "react-syntax-highlighter/dist/cjs/styles/hljs/obsidian"
import tomorrowNight from "react-syntax-highlighter/dist/cjs/styles/hljs/tomorrow-night"

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, arta, monokai, nord, obsidian, "tomorrow-night": tomorrowNight}
export const availableStyles = Object.keys(styles)

export const getStyle = name => {
if (!availableStyles.includes(name)) {
console.warn(`Request style '${name}' is not available, returning default instead`)
return agate
}
return styles[name]
}

export {SyntaxHighlighter, styles}

+ 0
- 160
src/core/utils.js Переглянути файл

@@ -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


+ 14
- 0
src/style/_buttons.scss Переглянути файл

@@ -174,3 +174,17 @@ button
background: url("data:image/svg+xml, <svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' aria-hidden='true'><path fill='#ffffff' fill-rule='evenodd' d='M2 13h4v1H2v-1zm5-6H2v1h5V7zm2 3V8l-3 3 3 3v-2h5v-2H9zM4.5 9H2v1h2.5V9zM2 12h2.5v-1H2v1zm9 1h1v2c-.02.28-.11.52-.3.7-.19.18-.42.28-.7.3H1c-.55 0-1-.45-1-1V4c0-.55.45-1 1-1h3c0-1.11.89-2 2-2 1.11 0 2 .89 2 2h3c.55 0 1 .45 1 1v5h-1V6H1v9h10v-2zM2 5h8c0-.55-.45-1-1-1H8c-.55 0-1-.45-1-1s-.45-1-1-1-1 .45-1 1-.45 1-1 1H3c-.55 0-1 .45-1 1z'></path></svg>") center center no-repeat;
}
}

// overrides for smaller copy button for curl command
.curl-command .copy-to-clipboard
{
bottom: 5px;
right: 10px;
width: 20px;
height: 20px;
button
{
padding-left: 18px;
height: 18px
}
}

+ 9
- 4
src/style/_layout.scss Переглянути файл

@@ -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
{
@@ -662,6 +664,9 @@
min-height: 6em;
}
}
.curl-command {
position: relative;
}

.download-contents {
position: absolute;


+ 5
- 3
test/components/highlight-code.jsx Переглянути файл

@@ -3,22 +3,24 @@ import expect from "expect"
import { shallow } from "enzyme"
import HighlightCode from "components/highlight-code"

const fakeGetConfigs = () => ({syntaxHighlight: {activated: true, theme: "agate"}})

describe("<HighlightCode />", () => {
it("should render a Download button if downloadable", () => {
const props = {downloadable: true}
const props = {downloadable: true, getConfigs: fakeGetConfigs }
const wrapper = shallow(<HighlightCode {...props} />)
expect(wrapper.find(".download-contents").length).toEqual(1)
})

it("should render a Copy To Clipboard button if copyable", () => {
const props = {canCopy: true}
const props = {canCopy: true, getConfigs: fakeGetConfigs }
const wrapper = shallow(<HighlightCode {...props} />)
expect(wrapper.find("CopyToClipboard").length).toEqual(1)
})

it("should render values in a preformatted element", () => {
const value = "test text"
const props = {value: value}
const props = {value: value, getConfigs: fakeGetConfigs}
const wrapper = shallow(<HighlightCode {...props} />)
const preTag = wrapper.find("pre")



+ 6
- 6
test/e2e-cypress/tests/features/oas3-request-body-allow-empty-values.js Переглянути файл

@@ -74,9 +74,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
.get(".execute.opblock-control__btn")
.click()
// cURL component
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
.get(".responses-wrapper .copy-paste textarea")
.get(".responses-wrapper .curl-command span")
.should("contains.text", "tags=&status=")
})

@@ -101,9 +101,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
.get(".execute.opblock-control__btn")
.click()
// cURL component
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
.get(".responses-wrapper .copy-paste textarea")
.get(".responses-wrapper .curl-command span")
.should("contains.text", "&status=")
.should("not.contains.text", "tags=")
})
@@ -131,9 +131,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
.get(".execute.opblock-control__btn")
.click()
// cURL component
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
.get(".responses-wrapper .copy-paste textarea")
.get(".responses-wrapper .curl-command span")
.should("not.contains.text", "tags=")
.should("not.contains.text", "status=")
})


+ 8
- 8
test/e2e-cypress/tests/features/oas3-request-body-required.js Переглянути файл

@@ -23,7 +23,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input")
.should("have.class", "invalid")
// cURL component should not exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("not.exist")
})
it("on execute, if value exists, should NOT render class 'invalid' and SHOULD render cURL component", () => {
@@ -43,7 +43,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.click()
.should("not.have.class", "invalid")
// cURL component should exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
})
})
@@ -69,7 +69,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
.should("have.class", "invalid")
// cURL component should not exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("not.exist")
})
it("on execute, if value exists, even if just single space, should NOT render class 'invalid' and SHOULD render cURL component that contains the single space", () => {
@@ -91,10 +91,10 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
.should("not.have.class", "invalid")
// cURL component should exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
.get(".responses-wrapper .copy-paste textarea")
.should("contains.text", "-d \" \"")
.get(".responses-wrapper .curl-command span")
.should("contains.text", "\" \"")
})
})

@@ -125,7 +125,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
.should("have.class", "invalid")
// cURL component should not exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("not.exist")
})
it("on execute, if all values exist, even if array exists but is empty, should NOT render class 'invalid' and SHOULD render cURL component", () => {
@@ -152,7 +152,7 @@ describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fiel
.should("have.value", "")
.should("not.have.class", "invalid")
// cURL component should exist
.get(".responses-wrapper .copy-paste")
.get(".responses-wrapper .curl-command")
.should("exist")
})
})


+ 2
- 2
webpack/_config-builder.js Переглянути файл

@@ -133,8 +133,8 @@ export default function buildConfig(

performance: {
hints: "error",
maxEntrypointSize: 1048576, // 1024 KiB,
maxAssetSize: 1048576,
maxEntrypointSize: 1073152, // 1048 KiB,
maxAssetSize: 1073152,
},

optimization: {


Завантаження…
Відмінити
Зберегти