├── .babelrc ├── .eslintrc.json ├── .gitignore ├── README.md ├── package.json ├── src ├── actions │ └── OpenAPIActions.js ├── constants │ └── index.js ├── data │ └── openapi.yml ├── favicon.ico ├── index.ejs ├── lib │ └── components │ │ ├── common │ │ ├── CommonMark │ │ │ └── CommonMark.jsx │ │ ├── FontAwesome │ │ │ └── FontAwesome.jsx │ │ ├── Heading │ │ │ ├── Heading.css │ │ │ └── Heading.jsx │ │ ├── LabelValueListItem │ │ │ ├── LabelValueListItem.css │ │ │ └── LabelValueListItem.jsx │ │ ├── List │ │ │ ├── List.css │ │ │ └── List.jsx │ │ └── ToolTip │ │ │ ├── ToolTip.css │ │ │ ├── ToolTip.jsx │ │ │ └── components │ │ │ └── ToolTipBubble │ │ │ ├── ToolTipBubble.css │ │ │ └── ToolTipBubble.jsx │ │ └── openapi │ │ ├── Contact │ │ ├── Contact.css │ │ └── Contact.jsx │ │ ├── Content │ │ ├── Content.css │ │ └── Content.jsx │ │ ├── EncodingProperty │ │ ├── EncodingProperty.css │ │ └── EncodingProperty.jsx │ │ ├── ExternalDocumentation │ │ ├── ExternalDocumentation.css │ │ └── ExternalDocumentation.jsx │ │ ├── Info │ │ ├── Info.css │ │ └── Info.jsx │ │ ├── License │ │ ├── License.css │ │ └── License.jsx │ │ ├── MediaType │ │ ├── MediaType.css │ │ └── MediaType.jsx │ │ ├── OAuthFlow │ │ ├── OAuthFlow.css │ │ └── OAuthFlow.jsx │ │ ├── OAuthFlows │ │ ├── OAuthFlows.css │ │ └── OAuthFlows.jsx │ │ ├── OpenAPI │ │ ├── OpenAPI.css │ │ └── OpenAPI.jsx │ │ ├── Operation │ │ ├── Operation.css │ │ └── Operation.jsx │ │ ├── Parameter │ │ ├── Parameter.css │ │ └── Parameter.jsx │ │ ├── Parameters │ │ ├── Parameters.css │ │ └── Parameters.jsx │ │ ├── PathItem │ │ ├── PathItem.css │ │ └── PathItem.jsx │ │ ├── Paths │ │ ├── Paths.css │ │ └── Paths.jsx │ │ ├── RequestBodies │ │ ├── RequestBodies.css │ │ └── RequestBodies.jsx │ │ ├── RequestBody │ │ ├── RequestBody.css │ │ └── RequestBody.jsx │ │ ├── Security │ │ ├── Security.css │ │ └── Security.jsx │ │ ├── SecurityRequirement │ │ ├── SecurityRequirement.css │ │ └── SecurityRequirement.jsx │ │ ├── SecurityScheme │ │ ├── SecurityScheme.css │ │ └── SecurityScheme.jsx │ │ ├── Server │ │ ├── Server.css │ │ └── Server.jsx │ │ ├── ServerVariable │ │ ├── ServerVariable.css │ │ └── ServerVariable.jsx │ │ ├── ServerVariables │ │ ├── ServerVariables.css │ │ └── ServerVariables.jsx │ │ └── Servers │ │ ├── Servers.css │ │ └── Servers.jsx ├── main.css ├── main.jsx ├── reducers │ └── OpenAPIReducer.js ├── screens │ └── App │ │ ├── App.css │ │ └── App.jsx ├── selectors │ └── OpenAPISelectors.js ├── settings │ └── production.js ├── store │ ├── index.js │ └── middleware.js └── styles │ └── variables.css └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "phantomjs": true, 7 | "node": true, 8 | "mocha": true 9 | }, 10 | "extends": ["eslint:recommended", "plugin:react/recommended"], 11 | "parser": "babel-eslint", 12 | "parserOptions": { 13 | "ecmaFeatures": { 14 | "experimentalObjectRestSpread": true, 15 | "jsx": true 16 | }, 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "react" 21 | ], 22 | "rules": { 23 | "indent": [ 24 | "error", 25 | 2 26 | ], 27 | "linebreak-style": [ 28 | "error", 29 | "unix" 30 | ], 31 | "quotes": [ 32 | "error", 33 | "single" 34 | ], 35 | "semi": [ 36 | "error", 37 | "always" 38 | ], 39 | "no-multi-spaces": [ 40 | "error" 41 | ], 42 | "space-before-blocks": [ 43 | "error" 44 | ], 45 | "arrow-spacing": [ 46 | "error" 47 | ], 48 | "eqeqeq": [ 49 | "error" 50 | ], 51 | "jsx-quotes": [ 52 | "error" 53 | ], 54 | "react/prop-types": [ 55 | "error", { 56 | "ignore": ["children", "className", "params", "router", "route", "location"] 57 | } 58 | ], 59 | "react/no-string-refs": [ 60 | "error" 61 | ], 62 | "react/self-closing-comp": [ 63 | "error", { 64 | "component": true, 65 | "html": false 66 | } 67 | ], 68 | "react/style-prop-object": [ 69 | "error" 70 | ], 71 | "react/jsx-boolean-value": [ 72 | "error" 73 | ], 74 | "react/jsx-closing-bracket-location": [ 75 | "error" 76 | ], 77 | "react/jsx-curly-spacing": [ 78 | "error" 79 | ], 80 | "react/jsx-equals-spacing": [ 81 | "error" 82 | ], 83 | "react/jsx-filename-extension": [ 84 | "error" 85 | ], 86 | "react/jsx-first-prop-new-line": [ 87 | "error", 88 | "multiline" 89 | ], 90 | "react/jsx-no-bind": [ 91 | "error", { 92 | "ignoreRefs": true, 93 | "allowArrowFunctions": true, 94 | "allowBind": false 95 | } 96 | ], 97 | "react/jsx-pascal-case": [ 98 | "error" 99 | ], 100 | "react/jsx-space-before-closing": [ 101 | "error" 102 | ], 103 | "react/jsx-wrap-multilines": [ 104 | "error" 105 | ], 106 | "react/no-multi-comp": [ 107 | "error", { 108 | "ignoreStateless": true 109 | } 110 | ] 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .tmp 3 | dist 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI UI 2 | 3 | This is a React based single page app which renders documentation from a valid OpenAPI 3.0.0-RC0 document. 4 | 5 | # NOTE 6 | 7 | This project was experimental and is not being actively maintained. 8 | 9 | ## Getting started 10 | 11 | #### Project setup 12 | 13 | Install project dependencies: 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | #### Run development server 20 | 21 | ``` 22 | npm start 23 | ``` 24 | 25 | #### Run linter 26 | 27 | ``` 28 | npm run lint 29 | ``` 30 | 31 | #### Create production build 32 | 33 | ``` 34 | npm run build 35 | ``` 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contentjet-openapi-ui", 3 | "version": "0.0.0", 4 | "description": "", 5 | "repository": "https://github.com/contentjet/openapi-ui", 6 | "private": true, 7 | "src": "src", 8 | "test": "test", 9 | "dist": "dist", 10 | "mainInput": "main", 11 | "mainOutput": "main", 12 | "dependencies": { 13 | "commonmark": "^0.27.0", 14 | "font-awesome": "^4.6.3", 15 | "history": "^4.6.0", 16 | "immutable": "^3.7.6", 17 | "json-schema-ref-parser": "^3.1.2", 18 | "lodash": "^4.17.4", 19 | "normalize.css": "^5.0.0", 20 | "react": "^15.3.1", 21 | "react-dom": "^15.3.1", 22 | "react-redux": "^5.0.3", 23 | "redux": "^3.3.1", 24 | "redux-actions": "^2.0.1", 25 | "redux-immutable": "^3.0.6", 26 | "redux-thunk": "^2.1.0", 27 | "reselect": "^2.5.4" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^6.3.6", 31 | "babel": "^6.23.0", 32 | "babel-core": "^6.7.7", 33 | "babel-eslint": "*", 34 | "babel-loader": "^6.4.0", 35 | "babel-polyfill": "^6.8.0", 36 | "babel-preset-es2015": "^6.6.0", 37 | "babel-preset-react": "^6.5.0", 38 | "classnames": "^2.1.2", 39 | "clean-webpack-plugin": "^0.1.9", 40 | "css-loader": "^0.26.2", 41 | "directory-named-webpack-plugin": "^2.1.0", 42 | "eslint": "^3.3.1", 43 | "eslint-plugin-react": "^6.4.1", 44 | "extract-text-webpack-plugin": "^2.1.0", 45 | "file-loader": "^0.10.1", 46 | "html-webpack-plugin": "^2.28.0", 47 | "lost": "^8.0.0", 48 | "postcss": "^5.1.2", 49 | "postcss-advanced-variables": "^1.2.2", 50 | "postcss-atroot": "^0.1.3", 51 | "postcss-color-function": "^3.0.0", 52 | "postcss-custom-media": "^5.0.1", 53 | "postcss-custom-properties": "^5.0.1", 54 | "postcss-custom-selectors": "^3.0.0", 55 | "postcss-extend": "^1.0.5", 56 | "postcss-loader": "^1.3.3", 57 | "postcss-media-minmax": "^2.1.2", 58 | "postcss-mixins": "^5.2.0", 59 | "postcss-nested": "^1.0.0", 60 | "postcss-nesting": "^2.3.1", 61 | "postcss-partial-import": "^3.1.1", 62 | "postcss-property-lookup": "^1.2.1", 63 | "postcss-selector-matches": "^2.0.1", 64 | "postcss-selector-not": "^2.0.0", 65 | "style-loader": "^0.13.1", 66 | "url-loader": "~0.5.5", 67 | "webpack": "^2.2.1", 68 | "webpack-custom-directory-default-file-plugin": "^1.1.0", 69 | "webpack-dev-server": "^2.4.1" 70 | }, 71 | "scripts": { 72 | "start": "webpack-dev-server --progress --inline --open --content-base dist/", 73 | "lint": "eslint -c .eslintrc.json --ext .js --ext .jsx src", 74 | "build": "webpack --progress --config webpack.config.js" 75 | }, 76 | "engines": { 77 | "node": ">=6.10.0" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/actions/OpenAPIActions.js: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions'; 2 | import _ from 'lodash'; 3 | import refParser from 'json-schema-ref-parser'; 4 | import createHistory from 'history/createBrowserHistory'; 5 | import { operationMethods } from 'constants'; 6 | 7 | 8 | const history = createHistory(); 9 | 10 | 11 | let load = createAction('LOAD_SPEC', url => { 12 | let hash = window.location.hash; 13 | history.replace({ hash: '' }); 14 | return refParser.dereference(url).then(result => { 15 | // We give each operation a uniqueId if not defined. 16 | _.forEach(result.paths, (pathItem) => { 17 | operationMethods.forEach(method => { 18 | let operation = _.get(pathItem, method); 19 | if (operation && _.isUndefined(operation.operationId)) { 20 | operation.operationId = _.uniqueId('operation-'); 21 | } 22 | }); 23 | }); 24 | // Replace the hash removed above to trigger the browser to jump to the hash. 25 | _.delay(() => window.location.hash = hash, 200); 26 | return result; 27 | }); 28 | }); 29 | 30 | 31 | export default { 32 | load 33 | }; 34 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | 2 | export const operationMethods = [ 3 | 'get', 4 | 'put', 5 | 'post', 6 | 'delete', 7 | 'options', 8 | 'head', 9 | 'patch', 10 | 'trace' 11 | ]; 12 | -------------------------------------------------------------------------------- /src/data/openapi.yml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | 3 | info: 4 | title: contentjet 5 | version: 1.0.0 6 | description: REST API for contentjet.io 7 | contact: 8 | name: Joe Blogs 9 | url: http://example.com 10 | email: will@example.com 11 | license: 12 | name: MIT 13 | url: https://opensource.org/licenses/MIT 14 | 15 | externalDocs: 16 | description: Further documentation can be found at the following link. 17 | url: http://example.com/ 18 | 19 | servers: 20 | - url: https://api.contentjet.io/{version} 21 | description: The production API server 22 | variables: 23 | version: 24 | default: v1 25 | enum: 26 | - v1 27 | description: API version number 28 | 29 | tags: 30 | - name: Project 31 | - name: EntryType 32 | 33 | paths: 34 | 35 | # Invite 36 | 37 | /project/{projectUUID}/invite/: 38 | get: 39 | tags: 40 | - Invite 41 | description: '' 42 | responses: 43 | '200': 44 | description: Returns a list of ProjectInvites. 45 | schema: 46 | type: array 47 | items: 48 | $ref: '#/components/definitions/ProjectInvite' 49 | externalDocs: 50 | description: Further documentation can be found at the following link. 51 | url: http://example.com/ 52 | 53 | post: 54 | tags: 55 | - Invite 56 | description: '' 57 | requestBody: 58 | - content: 59 | application/json: 60 | schema: 61 | type: object 62 | properties: 63 | name: 64 | type: string 65 | email: 66 | type: string 67 | responses: 68 | '201': 69 | description: Returns a list of ProjectInvites. 70 | schema: 71 | $ref: '#/components/definitions/ProjectInvite' 72 | parameters: 73 | - $ref: '#/components/parameters/projectUUID' 74 | 75 | /project/{projectUUID}/invite/{inviteUUID}/: 76 | get: 77 | tags: 78 | - Invite 79 | description: '' 80 | responses: 81 | '200': 82 | description: Returns a single ProjectInvite. 83 | schema: 84 | type: array 85 | items: 86 | $ref: '#/components/definitions/ProjectInvite' 87 | parameters: 88 | - $ref: '#/components/parameters/projectUUID' 89 | - name: inviteUUID 90 | required: true 91 | in: path 92 | type: string 93 | 94 | # Project 95 | 96 | /project/: 97 | get: 98 | tags: 99 | - Project 100 | description: '' 101 | responses: 102 | '200': 103 | description: Returns a list of Projects. 104 | schema: 105 | type: array 106 | items: 107 | $ref: '#/components/definitions/Project' 108 | post: 109 | description: '' 110 | responses: 111 | '200': 112 | description: Create Project. 113 | schema: 114 | type: array 115 | items: 116 | $ref: '#/components/definitions/Project' 117 | 118 | /project/{projectUUID}/: 119 | get: 120 | tags: 121 | - Project 122 | description: '' 123 | responses: 124 | '200': 125 | description: Returns a single Project. 126 | schema: 127 | type: array 128 | items: 129 | $ref: '#/components/definitions/Project' 130 | put: 131 | description: '' 132 | responses: 133 | '200': 134 | description: The updated project. 135 | schema: 136 | $ref: '#/components/definitions/Project' 137 | delete: 138 | tags: 139 | - Project 140 | description: '' 141 | responses: 142 | '204': 143 | description: Returns nothing. 144 | options: 145 | tags: 146 | - Project 147 | description: '' 148 | responses: 149 | '200': 150 | description: TODO. 151 | trace: 152 | tags: 153 | - Project 154 | description: '' 155 | responses: 156 | '200': 157 | description: TODO. 158 | head: 159 | tags: 160 | - Project 161 | description: '' 162 | responses: 163 | '200': 164 | description: TODO. 165 | patch: 166 | tags: 167 | - Project 168 | description: '' 169 | responses: 170 | '200': 171 | description: TODO. 172 | parameters: 173 | - $ref: '#/components/parameters/projectUUID' 174 | 175 | # EntryType 176 | 177 | /project/{projectUUID}/entry-type/: 178 | get: 179 | tags: 180 | - EntryType 181 | description: '' 182 | responses: 183 | '200': 184 | description: A list of EntryTypes. 185 | schema: 186 | type: array 187 | items: 188 | $ref: '#/components/definitions/EntryType' 189 | parameters: 190 | - $ref: '#/components/parameters/projectUUID' 191 | 192 | /project/{projectUUID}/entry-type/{entryTypeUUID}/: 193 | get: 194 | tags: 195 | - EntryType 196 | description: '' 197 | responses: 198 | '200': 199 | description: Returns a single EntryType. 200 | schema: 201 | type: array 202 | items: 203 | $ref: '#/components/definitions/EntryType' 204 | parameters: 205 | - $ref: '#/components/parameters/projectUUID' 206 | - name: entryTypeUUID 207 | required: true 208 | in: path 209 | type: string 210 | 211 | # EntryTag 212 | 213 | /project/{projectUUID}/entry-tag/: 214 | get: 215 | tags: 216 | - EntryTag 217 | summary: 'List Entry Tags' 218 | responses: 219 | '200': 220 | description: A list of EntryTags. 221 | schema: 222 | type: array 223 | items: 224 | $ref: '#/components/definitions/EntryTag' 225 | parameters: 226 | - $ref: '#/components/parameters/projectUUID' 227 | 228 | /project/{projectUUID}/entry-tag/{entryTypeUUID}/: 229 | get: 230 | tags: 231 | - EntryTag 232 | description: '' 233 | responses: 234 | '200': 235 | description: Returns a single EntryTag. 236 | schema: 237 | type: array 238 | items: 239 | $ref: '#/components/definitions/EntryType' 240 | parameters: 241 | - $ref: '#/components/parameters/projectUUID' 242 | - name: entryTypeUUID 243 | required: true 244 | in: path 245 | type: string 246 | 247 | # Entry 248 | 249 | /project/{projectUUID}/entry/: 250 | get: 251 | tags: 252 | - Entry 253 | description: '' 254 | responses: 255 | '200': 256 | description: A list of Entries. 257 | schema: 258 | type: array 259 | items: 260 | $ref: '#/components/definitions/Entry' 261 | parameters: 262 | - $ref: '#/components/parameters/projectUUID' 263 | 264 | /project/{projectUUID}/entry/{entryUUID}/: 265 | get: 266 | tags: 267 | - Entry 268 | description: '' 269 | responses: 270 | '200': 271 | description: Returns a single Entry. 272 | schema: 273 | type: array 274 | items: 275 | $ref: '#/components/definitions/Entry' 276 | parameters: 277 | - $ref: '#/components/parameters/projectUUID' 278 | - name: entryUUID 279 | required: true 280 | in: path 281 | type: string 282 | 283 | # MediaAsset 284 | 285 | /project/{projectUUID}/media/: 286 | get: 287 | tags: 288 | - MediaAsset 289 | description: '' 290 | responses: 291 | '200': 292 | description: Returns a list of MediaAssets. 293 | schema: 294 | type: array 295 | items: 296 | $ref: '#/components/definitions/MediaAsset' 297 | parameters: 298 | - $ref: '#/components/parameters/projectUUID' 299 | 300 | /project/{projectUUID}/media/{mediaUUID}/: 301 | get: 302 | tags: 303 | - MediaAsset 304 | description: '' 305 | responses: 306 | '200': 307 | description: Returns a single MediaAsset. 308 | schema: 309 | type: array 310 | items: 311 | $ref: '#/components/definitions/MediaAsset' 312 | parameters: 313 | - $ref: '#/components/parameters/projectUUID' 314 | - name: mediaUUID 315 | required: true 316 | in: path 317 | type: string 318 | 319 | # MediaTag 320 | 321 | /project/{projectUUID}/media-tag/: 322 | get: 323 | tags: 324 | - MediaTag 325 | description: '' 326 | responses: 327 | '200': 328 | description: Returns a list of MediaTags. 329 | schema: 330 | type: array 331 | items: 332 | $ref: '#/components/definitions/MediaTag' 333 | parameters: 334 | - $ref: '#/components/parameters/projectUUID' 335 | 336 | /project/{projectUUID}/media-tag/{mediaTagUUID}/: 337 | get: 338 | tags: 339 | - MediaTag 340 | description: '' 341 | responses: 342 | '200': 343 | description: Returns a single MediaAsset. 344 | schema: 345 | type: array 346 | items: 347 | $ref: '#/components/definitions/MediaTag' 348 | parameters: 349 | - $ref: '#/components/parameters/projectUUID' 350 | - name: mediaTagUUID 351 | required: true 352 | in: path 353 | type: string 354 | 355 | components: 356 | definitions: 357 | 358 | ProjectInvite: 359 | type: object 360 | properties: 361 | uuid: 362 | type: string 363 | name: 364 | type: string 365 | email: 366 | type: string 367 | 368 | Project: 369 | type: object 370 | properties: 371 | uuid: 372 | type: string 373 | name: 374 | type: string 375 | 376 | EntryType: 377 | type: object 378 | properties: 379 | uuid: 380 | type: string 381 | name: 382 | type: string 383 | 384 | EntryTag: 385 | type: object 386 | properties: 387 | name: 388 | type: string 389 | 390 | Entry: 391 | type: object 392 | properties: 393 | uuid: 394 | type: string 395 | name: 396 | type: string 397 | 398 | MediaAsset: 399 | type: object 400 | properties: 401 | uuid: 402 | type: string 403 | name: 404 | type: string 405 | 406 | MediaTag: 407 | type: object 408 | properties: 409 | name: 410 | type: string 411 | 412 | User: 413 | type: object 414 | properties: 415 | uuid: 416 | type: string 417 | name: 418 | type: string 419 | email: 420 | type: string 421 | 422 | parameters: 423 | 424 | projectUUID: 425 | name: projectUUID 426 | required: true 427 | in: path 428 | type: string 429 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/contentjet/openapi-ui/1fb05e62de8ea4087626539ee9780524205969a8/src/favicon.ico -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/components/common/CommonMark/CommonMark.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import commonmark from 'commonmark'; 3 | 4 | 5 | const reader = new commonmark.Parser(); 6 | const writer = new commonmark.HtmlRenderer(); 7 | 8 | 9 | class Markdown extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.getHTML = this.getHTML.bind(this); 13 | } 14 | 15 | shouldComponentUpdate(nextProps) { 16 | return ( 17 | nextProps.children !== this.props.children || 18 | nextProps.className !== this.props.className 19 | ); 20 | } 21 | 22 | getHTML() { 23 | let parsed = reader.parse(this.props.children); 24 | let result = writer.render(parsed); 25 | return { __html: result }; 26 | } 27 | 28 | render() { 29 | if (!this.props.children) return null; 30 | return ( 31 |
35 | ); 36 | } 37 | } 38 | Markdown.propTypes = { 39 | children: PropTypes.string 40 | }; 41 | 42 | 43 | export default Markdown; 44 | -------------------------------------------------------------------------------- /src/lib/components/common/FontAwesome/FontAwesome.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import 'font-awesome/css/font-awesome.css'; 4 | 5 | 6 | function FontAwesome(props) { 7 | const className = classnames('fa', 'fa-' + props.name, props.className); 8 | return ( 9 | 10 | ); 11 | } 12 | FontAwesome.propTypes = { 13 | name: PropTypes.string 14 | }; 15 | 16 | 17 | export default FontAwesome; 18 | -------------------------------------------------------------------------------- /src/lib/components/common/Heading/Heading.css: -------------------------------------------------------------------------------- 1 | .heading { 2 | margin: .9em 0; 3 | 4 | &.h1 { 5 | font-size: 2em; 6 | } 7 | 8 | &.h2 { 9 | font-size: 1.4em; 10 | } 11 | 12 | &.h3 { 13 | font-size: 1.2em; 14 | } 15 | 16 | &.h4 { 17 | font-size: 1em; 18 | } 19 | 20 | &.h5 { 21 | font-size: 1em; 22 | } 23 | 24 | &.h6 { 25 | font-size: 1em; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/components/common/Heading/Heading.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import _ from 'lodash'; 3 | import classnames from 'classnames'; 4 | import s from './Heading.css'; 5 | 6 | 7 | function Heading(props) { 8 | const { level, className } = props; 9 | let finalProps = _.omit(props, 'level'); 10 | finalProps.className = classnames( 11 | s.heading, { 12 | [s.h1]: level === 'h1', 13 | [s.h2]: level === 'h2', 14 | [s.h3]: level === 'h3', 15 | [s.h4]: level === 'h4', 16 | [s.h5]: level === 'h5', 17 | [s.h6]: level === 'h6' 18 | }, 19 | className 20 | ); 21 | if (level === 'h1') return

; 22 | if (level === 'h2') return

; 23 | if (level === 'h3') return

; 24 | if (level === 'h4') return

; 25 | if (level === 'h5') return

; 26 | if (level === 'h6') return
; 27 | } 28 | Heading.propTypes = { 29 | level: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) 30 | }; 31 | Heading.defaultProps = { 32 | level: 'h1' 33 | }; 34 | 35 | 36 | export default Heading; 37 | -------------------------------------------------------------------------------- /src/lib/components/common/LabelValueListItem/LabelValueListItem.css: -------------------------------------------------------------------------------- 1 | .labelValueListItem { 2 | lost-utility: clearfix; 3 | border-bottom: 1px #eee solid; 4 | background: #f7f7f7; 5 | padding: 3px 7px; 6 | font-size: 11px; 7 | 8 | &:last-child { 9 | border-bottom: none; 10 | } 11 | 12 | .label { 13 | display: inline-block; 14 | width: 200px; 15 | } 16 | 17 | .value { 18 | display: inline-block; 19 | width: calc(100% - 200px); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/lib/components/common/LabelValueListItem/LabelValueListItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './LabelValueListItem.css'; 4 | 5 | 6 | function LabelValueListItem(props) { 7 | const className = classnames(s.labelValueListItem, props.className); 8 | return ( 9 |
  • 10 | 11 | { props.label } 12 | 13 | 14 | { props.children } 15 | 16 |
  • 17 | ); 18 | } 19 | LabelValueListItem.propTypes = { 20 | label: PropTypes.string.isRequired, 21 | labelClassName: PropTypes.string, 22 | valueClassName: PropTypes.string 23 | }; 24 | 25 | 26 | export default LabelValueListItem; 27 | -------------------------------------------------------------------------------- /src/lib/components/common/List/List.css: -------------------------------------------------------------------------------- 1 | .list { 2 | list-style: none; 3 | padding: 0; 4 | margin: 0; 5 | 6 | li { 7 | display: block; 8 | } 9 | 10 | &.inline { 11 | li { 12 | display: inline-block; 13 | } 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/components/common/List/List.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './List.css'; 4 | 5 | 6 | function List(props) { 7 | let { children, inline } = props; 8 | let className = classnames( 9 | s.list, 10 | { 11 | [s.inline]: inline, 12 | }, 13 | props.className 14 | ); 15 | return ( 16 |
      {children}
    17 | ); 18 | } 19 | List.propTypes = { 20 | inline: PropTypes.bool 21 | }; 22 | 23 | 24 | export default List; 25 | -------------------------------------------------------------------------------- /src/lib/components/common/ToolTip/ToolTip.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: inline; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/common/ToolTip/ToolTip.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import classnames from 'classnames'; 4 | import ToolTipBubble from './components/ToolTipBubble'; 5 | import s from './ToolTip.css'; 6 | 7 | 8 | class ToolTip extends Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | mouseIsOver: false, 14 | top: 0, 15 | right: 0, 16 | bottom: 0, 17 | left: 0, 18 | width: 0, 19 | height: 0 20 | }; 21 | this.onWindowScroll = this.onWindowScroll.bind(this); 22 | this.onMouseEnter = this.onMouseEnter.bind(this); 23 | this.onMouseLeave = this.onMouseLeave.bind(this); 24 | this.renderBubble = this.renderBubble.bind(this); 25 | } 26 | 27 | componentDidMount() { 28 | window.addEventListener('scroll', this.onWindowScroll, true); 29 | // We append a new empty div just before the closing tag. 30 | this._bubbleRoot = document.createElement('div'); 31 | document.body.appendChild(this._bubbleRoot); 32 | // Force an initial render of the bubble. 33 | this.onWindowScroll(); 34 | } 35 | 36 | componentWillUnmount() { 37 | window.removeEventListener('scroll', this.onWindowScroll, true); 38 | ReactDOM.unmountComponentAtNode(this._bubbleRoot); 39 | document.body.removeChild(this._bubbleRoot); 40 | } 41 | 42 | onWindowScroll() { 43 | let rect = this._wrapper.getBoundingClientRect(); 44 | this.setState( 45 | { 46 | mouseIsOver: false, 47 | top: rect.top, 48 | right: rect.right, 49 | left: rect.left, 50 | bottom: rect.bottom, 51 | width: rect.width, 52 | height: rect.height 53 | }, 54 | this.renderBubble 55 | ); 56 | } 57 | 58 | renderBubble() { 59 | if (this.state.mouseIsOver) { 60 | ReactDOM.render( 61 | 70 | {this.props.content} 71 | , 72 | this._bubbleRoot 73 | ); 74 | } else { 75 | ReactDOM.unmountComponentAtNode(this._bubbleRoot); 76 | } 77 | } 78 | 79 | onMouseEnter() { 80 | let rect = this._wrapper.getBoundingClientRect(); 81 | this.setState( 82 | { 83 | mouseIsOver: true, 84 | top: rect.top, 85 | right: rect.right, 86 | left: rect.left, 87 | bottom: rect.bottom, 88 | width: rect.width, 89 | height: rect.height 90 | }, 91 | this.renderBubble 92 | ); 93 | } 94 | 95 | onMouseLeave() { 96 | this.setState({ mouseIsOver: false }, this.renderBubble); 97 | } 98 | 99 | render() { 100 | let { children, className } = this.props; 101 | className = classnames(s.wrapper, className); 102 | return ( 103 |
    this._wrapper = c} 106 | onMouseEnter={this.onMouseEnter} 107 | onMouseLeave={this.onMouseLeave} 108 | > 109 | {children} 110 |
    111 | ); 112 | } 113 | } 114 | ToolTip.propTypes = { 115 | children: PropTypes.node.isRequired, 116 | content: PropTypes.node.isRequired, 117 | position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), 118 | xOffset: PropTypes.number, 119 | yOffset: PropTypes.number, 120 | }; 121 | ToolTip.defaultProps = { 122 | position: 'top', 123 | xOffset: 0, 124 | yOffset: 0 125 | }; 126 | 127 | 128 | 129 | export default ToolTip; 130 | -------------------------------------------------------------------------------- /src/lib/components/common/ToolTip/components/ToolTipBubble/ToolTipBubble.css: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | @define-mixin arrowBoxBottom $background, $arrowSize: 8px { 4 | &::after { 5 | /* Setting top: 100% has a gap in IE :( */ 6 | top: 99%; 7 | left: 50%; 8 | border: solid transparent; 9 | content: " "; 10 | height: 0; 11 | width: 0; 12 | position: absolute; 13 | pointer-events: none; 14 | border-top-color: $background; 15 | border-width: $arrowSize; 16 | margin-left: -$arrowSize; 17 | } 18 | } 19 | 20 | @define-mixin arrowBoxTop $background, $arrowSize: 8px { 21 | &::after { 22 | /* Setting top: 100% has a gap in IE :( */ 23 | bottom: 99%; 24 | left: 50%; 25 | border: solid transparent; 26 | content: " "; 27 | height: 0; 28 | width: 0; 29 | position: absolute; 30 | pointer-events: none; 31 | border-bottom-color: $background; 32 | border-width: $arrowSize; 33 | margin-left: -$arrowSize; 34 | } 35 | } 36 | 37 | @define-mixin arrowBoxLeft $background, $arrowSize: 8px { 38 | &::after { 39 | right: 100%; 40 | top: 50%; 41 | border: solid transparent; 42 | content: " "; 43 | height: 0; 44 | width: 0; 45 | position: absolute; 46 | pointer-events: none; 47 | border-right-color: $background; 48 | border-width: $arrowSize; 49 | margin-top: -$arrowSize; 50 | } 51 | } 52 | 53 | @define-mixin arrowBoxRight $background, $arrowSize: 8px { 54 | &::after { 55 | left: 100%; 56 | top: 50%; 57 | border: solid transparent; 58 | content: " "; 59 | height: 0; 60 | width: 0; 61 | position: absolute; 62 | pointer-events: none; 63 | border-left-color: $background; 64 | border-width: $arrowSize; 65 | margin-top: -$arrowSize; 66 | } 67 | } 68 | 69 | $bubble-background-color: #666 !default; 70 | 71 | .bubble { 72 | position: fixed; 73 | background: $bubble-background-color; 74 | color: white; 75 | pointer-events: none; 76 | font-size: 11px; 77 | line-height: 1.5em; 78 | padding: 8px; 79 | border-radius: 4px; 80 | max-width: 250px; 81 | z-index: 1001; 82 | 83 | &.top { 84 | @mixin arrowBoxBottom $bubble-background-color; 85 | } 86 | 87 | &.bottom { 88 | @mixin arrowBoxTop $bubble-background-color; 89 | } 90 | 91 | &.left { 92 | @mixin arrowBoxRight $bubble-background-color; 93 | } 94 | 95 | &.right { 96 | @mixin arrowBoxLeft $bubble-background-color; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/lib/components/common/ToolTip/components/ToolTipBubble/ToolTipBubble.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './ToolTipBubble.css'; 4 | 5 | 6 | const ARROW_SIZE = 8; 7 | 8 | 9 | class ToolTipBubble extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | style: { 15 | left: -2000, 16 | top: -2000 17 | } 18 | }; 19 | this.calculateStyle = this.calculateStyle.bind(this); 20 | } 21 | 22 | componentDidMount() { 23 | this.setState({ style: this.calculateStyle() }); 24 | } 25 | 26 | calculateStyle() { 27 | let { 28 | wrapperWidth, wrapperHeight, wrapperLeft, wrapperTop, 29 | position, xOffset, yOffset 30 | } = this.props; 31 | 32 | let rect = this._bubble.getBoundingClientRect(); 33 | let style = {}; 34 | // Calculate the left and top position. 35 | if (position === 'top' || position === 'bottom') { 36 | if (position === 'top') { 37 | style.top = wrapperTop - rect.height - ARROW_SIZE; 38 | } else { 39 | style.top = wrapperTop + wrapperHeight + ARROW_SIZE; 40 | } 41 | style.left = wrapperLeft + (wrapperWidth - rect.width) * 0.5; 42 | } else if (position === 'left' || position === 'right') { 43 | if (position === 'left') { 44 | style.left = wrapperLeft - rect.width - ARROW_SIZE; 45 | } else { 46 | style.left = wrapperLeft + wrapperWidth + ARROW_SIZE; 47 | } 48 | style.top = wrapperTop + (wrapperHeight - rect.height) * 0.5; 49 | } 50 | // Apply any additional offset 51 | style.left = style.left + xOffset; 52 | style.top = style.top + yOffset; 53 | return style; 54 | } 55 | 56 | render() { 57 | let { className, position, children } = this.props; 58 | 59 | className = classnames( 60 | s.bubble, 61 | { 62 | [s.top]: position === 'top', 63 | [s.bottom]: position === 'bottom', 64 | [s.left]: position === 'left', 65 | [s.right]: position === 'right', 66 | }, 67 | className 68 | ); 69 | 70 | return ( 71 |
    this._bubble = c} 75 | > 76 | {children} 77 |
    78 | ); 79 | } 80 | 81 | } 82 | ToolTipBubble.propTypes = { 83 | wrapperLeft: PropTypes.number.isRequired, 84 | wrapperTop: PropTypes.number.isRequired, 85 | wrapperWidth: PropTypes.number.isRequired, 86 | wrapperHeight: PropTypes.number.isRequired, 87 | position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), 88 | xOffset: PropTypes.number, 89 | yOffset: PropTypes.number, 90 | }; 91 | ToolTipBubble.defaultProps = { 92 | position: 'top', 93 | xOffset: 0, 94 | yOffset: 0 95 | }; 96 | 97 | 98 | export default ToolTipBubble; 99 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Contact/Contact.css: -------------------------------------------------------------------------------- 1 | .contact { 2 | margin: 10px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Contact/Contact.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import LabelValueListItem from 'lib/components/common/LabelValueListItem'; 6 | import s from './Contact.css'; 7 | 8 | 9 | function Contact(props) { 10 | if (!props.name && !props.url && !props.email) return null; 11 | const className = classnames(s.contact, props.className); 12 | return ( 13 |
    14 | Contact 15 | 16 | 17 | { props.name } 18 | 19 | 20 | { props.url } 21 | 22 | 23 | { props.email } 24 | 25 | 26 |
    27 | ); 28 | } 29 | Contact.propTypes = { 30 | name: PropTypes.string, 31 | url: PropTypes.string, 32 | email: PropTypes.string 33 | }; 34 | 35 | 36 | export default Contact; 37 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Content/Content.css: -------------------------------------------------------------------------------- 1 | .content { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Content/Content.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import _ from 'lodash'; 3 | import classnames from 'classnames'; 4 | import MediaType from 'lib/components/openapi/MediaType'; 5 | import s from './Content.css'; 6 | 7 | 8 | function Content(props) { 9 | if (!props.content) return null; 10 | const className = classnames(s.content, props.className); 11 | return ( 12 |
    13 | { 14 | _.map(props.content, (value, key) => { 15 | return ( 16 | 21 | ); 22 | }) 23 | } 24 |
    25 | ); 26 | } 27 | Content.propTypes = { 28 | content: PropTypes.object, 29 | }; 30 | 31 | 32 | export default Content; 33 | -------------------------------------------------------------------------------- /src/lib/components/openapi/EncodingProperty/EncodingProperty.css: -------------------------------------------------------------------------------- 1 | .encodingProperty { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/EncodingProperty/EncodingProperty.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './EncodingProperty.css'; 4 | 5 | 6 | function EncodingProperty(props) { 7 | const className = classnames(s.encodingProperty, props.className); 8 | return ( 9 |
    10 | 11 |
    12 | ); 13 | } 14 | EncodingProperty.propTypes = { 15 | contentType: PropTypes.string, 16 | headers: PropTypes.object, 17 | style: PropTypes.string, 18 | explode: PropTypes.bool 19 | }; 20 | 21 | 22 | export default EncodingProperty; 23 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ExternalDocumentation/ExternalDocumentation.css: -------------------------------------------------------------------------------- 1 | .externalDocumentation { 2 | 3 | .url { 4 | display: block; 5 | margin: .6em 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ExternalDocumentation/ExternalDocumentation.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import Heading from 'lib/components/common/Heading'; 4 | import CommonMark from 'lib/components/common/CommonMark'; 5 | import s from './ExternalDocumentation.css'; 6 | 7 | 8 | function ExternalDocumentation(props) { 9 | const className = classnames(s.externalDocumentation, props.className); 10 | return ( 11 |
    12 | 13 | External documentation 14 | 15 | { props.description } 16 | 20 | { props.url } 21 | 22 |
    23 | ); 24 | } 25 | ExternalDocumentation.propTypes = { 26 | url: PropTypes.string.isRequired, 27 | description: PropTypes.string, 28 | headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) 29 | }; 30 | ExternalDocumentation.defaultProps = { 31 | headingLevel: 'h2' 32 | }; 33 | 34 | 35 | export default ExternalDocumentation; 36 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Info/Info.css: -------------------------------------------------------------------------------- 1 | .info { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Info/Info.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import CommonMark from 'lib/components/common/CommonMark'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import Contact from 'lib/components/openapi/Contact'; 6 | import License from 'lib/components/openapi/License'; 7 | import s from './Info.css'; 8 | 9 | 10 | function Info(props) { 11 | const className = classnames(s.info, props.className); 12 | return ( 13 |
    14 | 18 | { props.title } 19 | 20 | { props.description } 21 | { props.termsOfService ? Terms of service : null } 22 | 23 | { props.license ? : null } 24 |
    25 | ); 26 | } 27 | Info.propTypes = { 28 | title: PropTypes.string.isRequired, 29 | description: PropTypes.string, 30 | termsOfService: PropTypes.string, 31 | contact: PropTypes.object, 32 | license: PropTypes.object, 33 | version: PropTypes.string.isRequired 34 | }; 35 | 36 | 37 | export default Info; 38 | -------------------------------------------------------------------------------- /src/lib/components/openapi/License/License.css: -------------------------------------------------------------------------------- 1 | .license { 2 | margin: 10px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/License/License.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import LabelValueListItem from 'lib/components/common/LabelValueListItem'; 6 | import s from './License.css'; 7 | 8 | 9 | function License(props) { 10 | const className = classnames(s.license, props.className); 11 | return ( 12 |
    13 | License 14 | 15 | 16 | { props.name } 17 | 18 | 19 | { props.url } 20 | 21 | 22 |
    23 | ); 24 | } 25 | License.propTypes = { 26 | name: PropTypes.string.isRequired, 27 | url: PropTypes.string 28 | }; 29 | 30 | 31 | export default License; 32 | -------------------------------------------------------------------------------- /src/lib/components/openapi/MediaType/MediaType.css: -------------------------------------------------------------------------------- 1 | .mediaType { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/MediaType/MediaType.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import Heading from 'lib/components/common/Heading'; 4 | import s from './MediaType.css'; 5 | 6 | 7 | function MediaType(props) { 8 | const className = classnames(s.mediaType, props.className); 9 | return ( 10 |
    11 | { props.mediaType } 12 |
    13 | ); 14 | } 15 | MediaType.propTypes = { 16 | mediaType: PropTypes.string.isRequired, 17 | schema: PropTypes.object, 18 | examples: PropTypes.array, 19 | example: PropTypes.object, 20 | encoding: PropTypes.object 21 | }; 22 | 23 | 24 | export default MediaType; 25 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OAuthFlow/OAuthFlow.css: -------------------------------------------------------------------------------- 1 | .oAuthFlow { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OAuthFlow/OAuthFlow.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './OAuthFlow.css'; 4 | 5 | 6 | function OAuthFlow(props) { 7 | const className = classnames(s.oAuthFlow, props.className); 8 | return ( 9 |
    10 | 11 |
    12 | ); 13 | } 14 | OAuthFlow.propTypes = { 15 | authorizationUrl: PropTypes.string, 16 | tokenUrl: PropTypes.string, 17 | refreshUrl: PropTypes.string, 18 | scopes: PropTypes.object 19 | }; 20 | 21 | 22 | export default OAuthFlow; 23 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OAuthFlows/OAuthFlows.css: -------------------------------------------------------------------------------- 1 | .oAuthFlows { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OAuthFlows/OAuthFlows.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './OAuthFlows.css'; 4 | 5 | 6 | function OAuthFlows(props) { 7 | const className = classnames(s.oAuthFlows, props.className); 8 | return ( 9 |
    10 | ); 11 | } 12 | OAuthFlows.propTypes = { 13 | implicit: PropTypes.object, 14 | password: PropTypes.object, 15 | clientCredentials: PropTypes.object, 16 | authorizationCod: PropTypes.object 17 | }; 18 | 19 | 20 | export default OAuthFlows; 21 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OpenAPI/OpenAPI.css: -------------------------------------------------------------------------------- 1 | .openAPI { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/OpenAPI/OpenAPI.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import _ from 'lodash'; 3 | import classnames from 'classnames'; 4 | import Info from 'lib/components/openapi/Info'; 5 | import Paths from 'lib/components/openapi/Paths'; 6 | import Security from 'lib/components/openapi/Security'; 7 | import ExternalDocumentation from 'lib/components/openapi/ExternalDocumentation'; 8 | import Servers from 'lib/components/openapi/Servers'; 9 | import s from './OpenAPI.css'; 10 | 11 | 12 | function OpenAPI(props) { 13 | const className = classnames(s.openAPI, props.className); 14 | return ( 15 |
    16 | 17 | { props.externalDocs && !_.isEmpty(props.externalDocs) ? : null } 18 | 19 | 20 | 21 |
    22 | ); 23 | } 24 | OpenAPI.propTypes = { 25 | info: PropTypes.object.isRequired, 26 | servers: PropTypes.array, 27 | paths: PropTypes.object.isRequired, 28 | security: PropTypes.array, 29 | externalDocs: PropTypes.object 30 | }; 31 | 32 | 33 | export default OpenAPI; 34 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Operation/Operation.css: -------------------------------------------------------------------------------- 1 | $operation-color-get: #0dd0ff !default; 2 | $operation-color-post: #0ce894 !default; 3 | $operation-color-put: #ffc010 !default; 4 | $operation-color-delete: #e83f0c !default; 5 | $operation-color-options: #ce0be8 !default; 6 | $operation-color-head: #0b16e8 !default; 7 | $operation-color-patch: #e80ba9 !default; 8 | $operation-color-trace: #5f0be8 !default; 9 | 10 | $operation-header-background-get: color($operation-color-get lightness(+35%)) !default; 11 | $operation-header-background-post: color($operation-color-post lightness(+35%)) !default; 12 | $operation-header-background-put: color($operation-color-put lightness(+35%)) !default; 13 | $operation-header-background-delete: color($operation-color-delete lightness(+35%)) !default; 14 | $operation-header-background-options: color($operation-color-options lightness(+35%)) !default; 15 | $operation-header-background-head: color($operation-color-head lightness(+35%)) !default; 16 | $operation-header-background-patch: color($operation-color-patch lightness(+35%)) !default; 17 | $operation-header-background-trace: color($operation-color-trace lightness(+35%)) !default; 18 | 19 | .operation { 20 | border: 1px solid #eee; 21 | border-radius: 4px; 22 | padding: 5px; 23 | margin: 10px 0; 24 | 25 | .header { 26 | border-radius: 4px 4px 0 0; 27 | overflow: hidden; 28 | lost-utility: clearfix; 29 | line-height: 2.2em; 30 | margin: 0 0 1em 0; 31 | 32 | &.methodGet { 33 | background: $operation-header-background-get; 34 | 35 | .heading > .inner { 36 | background: $operation-color-get; 37 | } 38 | } 39 | 40 | &.methodPut { 41 | background: $operation-header-background-put; 42 | 43 | .heading > .inner { 44 | background: $operation-color-put; 45 | } 46 | } 47 | 48 | &.methodPost { 49 | background: $operation-header-background-post; 50 | 51 | .heading > .inner { 52 | background: $operation-color-post; 53 | } 54 | } 55 | 56 | &.methodDelete { 57 | background: $operation-header-background-delete; 58 | 59 | .heading > .inner { 60 | background: $operation-color-delete; 61 | } 62 | } 63 | 64 | &.methodOptions { 65 | background: $operation-header-background-options; 66 | 67 | .heading > .inner { 68 | background: $operation-color-options; 69 | } 70 | } 71 | 72 | &.methodHead { 73 | background: $operation-header-background-head; 74 | 75 | .heading > .inner { 76 | background: $operation-color-head; 77 | } 78 | } 79 | 80 | &.methodPatch { 81 | background: $operation-header-background-patch; 82 | 83 | .heading > .inner { 84 | background: $operation-color-patch; 85 | } 86 | } 87 | 88 | &.methodTrace { 89 | background: $operation-header-background-trace; 90 | 91 | .heading > .inner { 92 | background: $operation-color-trace; 93 | } 94 | } 95 | } 96 | 97 | .summary { 98 | lost-column: 8/12; 99 | text-align: right; 100 | padding-right: 10px; 101 | box-sizing: border-box; 102 | } 103 | 104 | .heading { 105 | lost-column: 4/12; 106 | margin: 0; 107 | text-transform: uppercase; 108 | color: #fff; 109 | 110 | .inner { 111 | display: inline-block; 112 | padding: 0 5px; 113 | } 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Operation/Operation.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import CommonMark from 'lib/components/common/CommonMark'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import RequestBodies from 'lib/components/openapi/RequestBodies'; 6 | import Parameters from 'lib/components/openapi/Parameters'; 7 | import Security from 'lib/components/openapi/Security'; 8 | import ExternalDocumentation from 'lib/components/openapi/ExternalDocumentation'; 9 | import s from './Operation.css'; 10 | 11 | 12 | function Operation(props) { 13 | const className = classnames(s.operation, props.className); 14 | 15 | const headerClassName = classnames(s.header, { 16 | [s.methodGet]: props.method === 'get', 17 | [s.methodPut]: props.method === 'put', 18 | [s.methodPost]: props.method === 'post', 19 | [s.methodDelete]: props.method === 'delete', 20 | [s.methodOptions]: props.method === 'options', 21 | [s.methodHead]: props.method === 'head', 22 | [s.methodPatch]: props.method === 'patch', 23 | [s.methodTrace]: props.method === 'trace' 24 | }); 25 | 26 | return ( 27 |
    31 |
    32 | 36 | { props.method } 37 | 38 | {props.summary} 39 |
    40 | {props.description} 41 | { 42 | props.externalDocs && 43 | 47 | } 48 | 49 | 53 | 54 |
    55 | ); 56 | } 57 | Operation.propTypes = { 58 | method: PropTypes.string, 59 | tags: PropTypes.array, 60 | summary: PropTypes.string, 61 | description: PropTypes.string, 62 | externalDocs: PropTypes.object, 63 | operationId: PropTypes.string, 64 | parameters: PropTypes.array, 65 | requestBody: PropTypes.array, 66 | responses: PropTypes.object.isRequired, 67 | callbacks: PropTypes.object, 68 | deprecated: PropTypes.bool, 69 | security: PropTypes.object, 70 | servers: PropTypes.array 71 | }; 72 | 73 | 74 | export default Operation; 75 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Parameter/Parameter.css: -------------------------------------------------------------------------------- 1 | .parameter { 2 | margin: 5px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Parameter/Parameter.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import CommonMark from 'lib/components/common/CommonMark'; 4 | import List from 'lib/components/common/List'; 5 | import LabelValueListItem from 'lib/components/common/LabelValueListItem'; 6 | import s from './Parameter.css'; 7 | 8 | 9 | const defaultStyles = { 10 | query: 'form', 11 | header: 'simple', 12 | path: 'simple', 13 | cookie: 'form' 14 | }; 15 | 16 | 17 | function Parameter(props) { 18 | const className = classnames(s.parameter, props.className); 19 | 20 | let style = props.style || defaultStyles[props.in]; 21 | 22 | if (props.description) { 23 | var description = ( 24 | 25 | { props.description } 26 | 27 | ); 28 | } 29 | 30 | if (props.explode) { 31 | var explode = ( 32 | 33 | Yes 34 | 35 | ); 36 | } 37 | 38 | if (props.allowReserved) { 39 | var allowReserved = ( 40 | 41 | Yes 42 | 43 | ); 44 | } 45 | 46 | return ( 47 |
    48 | 49 | 50 | { props.name } 51 | 52 | 53 | { description } 54 | 55 | 56 | { style } 57 | 58 | 59 | { explode } 60 | 61 | { allowReserved } 62 | 63 |
    64 | ); 65 | } 66 | Parameter.propTypes = { 67 | name: PropTypes.string.isRequired, 68 | in: PropTypes.oneOf(['query', 'header', 'path', 'cookie']).isRequired, 69 | description: PropTypes.string, 70 | required: PropTypes.bool, 71 | deprecated: PropTypes.bool, 72 | allowEmptyValue: PropTypes.bool, 73 | 74 | style: PropTypes.oneOf(['matrix', 'label', 'form', 'simple', 'spaceDelimited', 'pipeDelimited', 'deepObject']), 75 | explode: PropTypes.bool, 76 | allowReserved: PropTypes.bool, 77 | schema: PropTypes.object, 78 | examples: PropTypes.array, 79 | example: PropTypes.object, 80 | 81 | content: PropTypes.object 82 | }; 83 | 84 | 85 | export default Parameter; 86 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Parameters/Parameters.css: -------------------------------------------------------------------------------- 1 | .parameters { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Parameters/Parameters.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import Heading from 'lib/components/common/Heading'; 4 | import Parameter from 'lib/components/openapi/Parameter'; 5 | import s from './Parameters.css'; 6 | 7 | 8 | function Parameters(props) { 9 | const className = classnames(s.parameters, props.className); 10 | 11 | const pathParameters = props.parameters.filter(parameter => parameter.in === 'path'); 12 | const queryParameters = props.parameters.filter(parameter => parameter.in === 'query'); 13 | const headerParameters = props.parameters.filter(parameter => parameter.in === 'header'); 14 | const cookieParameters = props.parameters.filter(parameter => parameter.in === 'cookie'); 15 | 16 | const create = (parameter) => ; 17 | 18 | return ( 19 |
    20 | { pathParameters.length > 0 && Path parameters } 21 | { pathParameters.map(create) } 22 | 23 | { queryParameters.length > 0 && Query parameters } 24 | { queryParameters.map(create) } 25 | 26 | { headerParameters.length > 0 && Header parameters } 27 | { headerParameters.map(create) } 28 | 29 | { cookieParameters.length > 0 && Cookie parameters } 30 | { cookieParameters.map(create) } 31 |
    32 | ); 33 | } 34 | Parameters.propTypes = { 35 | parameters: PropTypes.array 36 | }; 37 | 38 | 39 | export default Parameters; 40 | -------------------------------------------------------------------------------- /src/lib/components/openapi/PathItem/PathItem.css: -------------------------------------------------------------------------------- 1 | .pathItem { 2 | border: 1px solid #eee; 3 | border-radius: 4px; 4 | padding: 5px; 5 | margin: 10px 0; 6 | 7 | .header { 8 | lost-utility: clearfix; 9 | background: #eee; 10 | padding: 0 10px; 11 | border-radius: 4px 4px 0 0; 12 | overflow: hidden; 13 | line-height: 2.1em; 14 | } 15 | 16 | .heading { 17 | font-size: 14px; 18 | lost-column: 4/12; 19 | margin: 0; 20 | letter-spacing: 1px; 21 | font-weight: normal; 22 | } 23 | 24 | .summary { 25 | lost-column: 8/12; 26 | text-align: right; 27 | padding-right: 10px; 28 | box-sizing: border-box; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/components/openapi/PathItem/PathItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import _ from 'lodash'; 3 | import classnames from 'classnames'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import Operation from 'lib/components/openapi/Operation'; 6 | import CommonMark from 'lib/components/common/CommonMark'; 7 | import { operationMethods } from 'constants'; 8 | import s from './PathItem.css'; 9 | 10 | 11 | const mergeParameters = (pathItemParameters = [], operationParameters = []) => { 12 | let parameters = pathItemParameters.concat(operationParameters).reverse(); 13 | parameters = _.uniqBy(parameters, parameter => `${parameter.name}${parameter.in}`); 14 | parameters = parameters.reverse(); 15 | return parameters; 16 | }; 17 | 18 | 19 | function PathItem(props) { 20 | const className = classnames(s.pathItem, props.className); 21 | return ( 22 |
    23 |
    24 | 28 | { props.path } 29 | 30 | {props.summary} 31 |
    32 | { props.description } 33 | { 34 | operationMethods 35 | .filter(method => props[method]) 36 | .map(method => { 37 | let operation = props[method]; 38 | let parameters = mergeParameters(props.parameters, operation.parameters); 39 | return ( 40 | 46 | ); 47 | }) 48 | } 49 |
    50 | ); 51 | } 52 | PathItem.propTypes = { 53 | path: PropTypes.string.isRequired, 54 | summary: PropTypes.string, 55 | description: PropTypes.string, 56 | get: PropTypes.object, 57 | put: PropTypes.object, 58 | post: PropTypes.object, 59 | delete: PropTypes.object, 60 | options: PropTypes.object, 61 | head: PropTypes.object, 62 | patch: PropTypes.object, 63 | trace: PropTypes.object, 64 | servers: PropTypes.array, 65 | parameters: PropTypes.array 66 | }; 67 | 68 | 69 | export default PathItem; 70 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Paths/Paths.css: -------------------------------------------------------------------------------- 1 | .paths { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Paths/Paths.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import Heading from 'lib/components/common/Heading'; 4 | import PathItem from 'lib/components/openapi/PathItem'; 5 | import s from './Paths.css'; 6 | 7 | 8 | function Paths(props) { 9 | const className = classnames(s.paths, props.className); 10 | return ( 11 |
    12 | 16 | Paths 17 | 18 | { 19 | Object.keys(props.paths).map(path => { 20 | return ( 21 | 26 | ); 27 | }) 28 | } 29 |
    30 | ); 31 | } 32 | Paths.propTypes = { 33 | paths: PropTypes.object.isRequired 34 | }; 35 | 36 | 37 | export default Paths; 38 | -------------------------------------------------------------------------------- /src/lib/components/openapi/RequestBodies/RequestBodies.css: -------------------------------------------------------------------------------- 1 | .requestBodies { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/RequestBodies/RequestBodies.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import Heading from 'lib/components/common/Heading'; 4 | import RequestBody from 'lib/components/openapi/RequestBody'; 5 | import s from './RequestBodies.css'; 6 | 7 | 8 | function RequestBodies(props) { 9 | if (!props.requestBodies || !props.requestBodies.length) return null; 10 | const className = classnames(s.requestBodies, props.className); 11 | return ( 12 |
    13 | Request body 14 | { 15 | props.requestBodies.map(requestBody => { 16 | return ( 17 | 21 | ); 22 | }) 23 | } 24 |
    25 | ); 26 | } 27 | RequestBodies.propTypes = { 28 | requestBodies: PropTypes.array 29 | }; 30 | 31 | 32 | export default RequestBodies; 33 | -------------------------------------------------------------------------------- /src/lib/components/openapi/RequestBody/RequestBody.css: -------------------------------------------------------------------------------- 1 | .requestBody { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/RequestBody/RequestBody.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import CommonMark from 'lib/components/common/CommonMark'; 4 | import Content from 'lib/components/openapi/Content'; 5 | import s from './RequestBody.css'; 6 | 7 | 8 | function RequestBody(props) { 9 | const className = classnames(s.requestBody, props.className); 10 | return ( 11 |
    12 | { props.description } 13 | 14 |
    15 | ); 16 | } 17 | RequestBody.propTypes = { 18 | description: PropTypes.string, 19 | content: PropTypes.object, 20 | required: PropTypes.bool, 21 | }; 22 | RequestBody.defaultProps = { 23 | required: true 24 | }; 25 | 26 | 27 | export default RequestBody; 28 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Security/Security.css: -------------------------------------------------------------------------------- 1 | .security { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Security/Security.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import SecurityRequirement from 'lib/components/openapi/SecurityRequirement'; 6 | import s from './Security.css'; 7 | 8 | 9 | function Security(props) { 10 | if (!props.securityRequirements || !props.securityRequirements.length) return null; 11 | const className = classnames(s.security, props.className); 12 | return ( 13 |
    14 | Security 15 | 16 | { 17 | props.securityRequirements.map((securityRequirement, i) => { 18 | return ( 19 |
  • 20 | 21 |
  • 22 | ); 23 | }) 24 | } 25 |
    26 |
    27 | ); 28 | } 29 | Security.propTypes = { 30 | securityRequirements: PropTypes.array, 31 | headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) 32 | }; 33 | 34 | 35 | export default Security; 36 | -------------------------------------------------------------------------------- /src/lib/components/openapi/SecurityRequirement/SecurityRequirement.css: -------------------------------------------------------------------------------- 1 | .securityRequirement { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/SecurityRequirement/SecurityRequirement.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import s from './SecurityRequirement.css'; 5 | 6 | 7 | function SecurityRequirement(props) { 8 | const className = classnames(s.securityRequirement, props.className); 9 | return ( 10 |
    11 | 12 | { 13 | Object.keys(props.object).map(name => { 14 | return ( 15 |
  • 16 | { name } 17 |
      18 | { 19 | props.object[name].map((value, i) => { 20 | return ( 21 |
    • { value }
    • 22 | ); 23 | }) 24 | } 25 |
    26 |
  • 27 | ); 28 | }) 29 | } 30 |
    31 |
    32 | ); 33 | } 34 | SecurityRequirement.propTypes = { 35 | object: PropTypes.object 36 | }; 37 | 38 | 39 | export default SecurityRequirement; 40 | -------------------------------------------------------------------------------- /src/lib/components/openapi/SecurityScheme/SecurityScheme.css: -------------------------------------------------------------------------------- 1 | .securityScheme { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/SecurityScheme/SecurityScheme.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import s from './SecurityScheme.css'; 4 | 5 | 6 | function SecurityScheme(props) { 7 | const className = classnames(s.securityScheme, props.className); 8 | return ( 9 |
    10 | ); 11 | } 12 | // TODO: Proptype validation is conditional dependant on type. 13 | SecurityScheme.propTypes = { 14 | type: PropTypes.oneOf(['apiKey', 'http', 'oauth2', 'openIdConnect']).isRequired, 15 | description: PropTypes.string, 16 | name: PropTypes.string.isRequired, 17 | in: PropTypes.oneOf(['query', 'header']).isRequired, 18 | scheme: PropTypes.string.isRequired, 19 | bearerFormat: PropTypes.string, 20 | flow: PropTypes.object.isRequired, 21 | openIdConnectUrl: PropTypes.string 22 | 23 | }; 24 | 25 | 26 | export default SecurityScheme; 27 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Server/Server.css: -------------------------------------------------------------------------------- 1 | .server { 2 | border: 1px solid #eee; 3 | border-radius: 4px; 4 | padding: 5px; 5 | margin: 10px 0; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Server/Server.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import CommonMark from 'lib/components/common/CommonMark'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import ServerVariables from 'lib/components/openapi/ServerVariables'; 6 | import s from './Server.css'; 7 | 8 | 9 | function Server(props) { 10 | const className = classnames(s.server, props.className); 11 | return ( 12 |
    13 | 14 | { props.url } 15 | 16 | { props.description } 17 | 18 |
    19 | ); 20 | } 21 | Server.propTypes = { 22 | url: PropTypes.string.isRequired, 23 | description: PropTypes.string, 24 | variables: PropTypes.object 25 | }; 26 | 27 | 28 | export default Server; 29 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ServerVariable/ServerVariable.css: -------------------------------------------------------------------------------- 1 | .serverVariable { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ServerVariable/ServerVariable.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import LabelValueListItem from 'lib/components/common/LabelValueListItem'; 5 | import s from './ServerVariable.css'; 6 | 7 | 8 | function ServerVariable(props) { 9 | const className = classnames(s.serverVariable, props.className); 10 | return ( 11 |
    12 | 13 | 14 | { props.name } 15 | 16 | 17 | { props.default } 18 | 19 | 20 | { props.enum } 21 | 22 | 23 | { props.description } 24 | 25 | 26 |
    27 | ); 28 | } 29 | ServerVariable.propTypes = { 30 | name: PropTypes.string.isRequired, 31 | enum: PropTypes.array, 32 | default: PropTypes.node.isRequired, 33 | description: PropTypes.string 34 | }; 35 | 36 | 37 | export default ServerVariable; 38 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ServerVariables/ServerVariables.css: -------------------------------------------------------------------------------- 1 | .serverVariables { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/ServerVariables/ServerVariables.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import _ from 'lodash'; 4 | import List from 'lib/components/common/List'; 5 | import Heading from 'lib/components/common/Heading'; 6 | import ServerVariable from 'lib/components/openapi/ServerVariable'; 7 | import s from './ServerVariables.css'; 8 | 9 | 10 | function ServerVariables(props) { 11 | if (!props.variables) return null; 12 | const className = classnames(s.serverVariables, props.className); 13 | return ( 14 |
    15 | Variables 16 | 17 | { 18 | Object.keys(props.variables).map(name => { 19 | let variable = props.variables[name]; 20 | return ( 21 |
  • 22 | 28 |
  • 29 | ); 30 | }) 31 | } 32 |
    33 |
    34 | ); 35 | } 36 | ServerVariables.propTypes = { 37 | variables: PropTypes.object 38 | }; 39 | 40 | 41 | export default ServerVariables; 42 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Servers/Servers.css: -------------------------------------------------------------------------------- 1 | .servers { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/lib/components/openapi/Servers/Servers.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import classnames from 'classnames'; 3 | import List from 'lib/components/common/List'; 4 | import Heading from 'lib/components/common/Heading'; 5 | import Server from 'lib/components/openapi/Server'; 6 | import s from './Servers.css'; 7 | 8 | 9 | function Servers(props) { 10 | if (!props.servers) return null; 11 | const className = classnames(s.servers, props.className); 12 | return ( 13 |
    14 | 15 | Servers 16 | 17 | 18 | { 19 | props.servers.map(server => { 20 | return ( 21 |
  • 22 | 23 |
  • 24 | ); 25 | }) 26 | } 27 |
    28 |
    29 | ); 30 | } 31 | Servers.propTypes = { 32 | servers: PropTypes.array, 33 | headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']) 34 | }; 35 | Servers.defaultProps = { 36 | headingLevel: 'h2' 37 | }; 38 | 39 | 40 | export default Servers; 41 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | @import 'normalize.css/normalize.css'; 2 | @import 'variables'; 3 | 4 | 5 | html, 6 | body { 7 | color: $text-color; 8 | font-family: $font-family; 9 | font-size: $font-size; 10 | line-height: $line-height; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | a { 16 | color: $anchor-color; 17 | text-decoration: $anchor-text-decoration; 18 | cursor: pointer; 19 | 20 | &:hover { 21 | color: $anchor-color-hover; 22 | text-decoration: $anchor-text-decoration-hover; 23 | } 24 | } 25 | 26 | p { 27 | margin: .6em 0; 28 | } 29 | 30 | hr { 31 | border-bottom: 0; 32 | border-color: #ccc; 33 | border-style: solid; 34 | } 35 | 36 | th { 37 | font-weight: normal; 38 | } 39 | 40 | address { 41 | font-style: normal; 42 | } 43 | 44 | h1, 45 | h2, 46 | h3, 47 | h4, 48 | h5 { 49 | font-size: 1em; 50 | margin: 0; 51 | font-weight: bold; 52 | } 53 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import './favicon.ico'; 2 | import './main.css'; 3 | import 'data/openapi.yml'; 4 | 5 | import store from 'store'; 6 | import React from 'react'; 7 | import { Provider } from 'react-redux'; 8 | import ReactDOM from 'react-dom'; 9 | import OpenAPIActions from 'actions/OpenAPIActions'; 10 | import App from 'screens/App'; 11 | 12 | const app = ( 13 | 14 | 15 | 16 | ); 17 | 18 | ReactDOM.render(app, document.getElementsByClassName('main')[0]); 19 | 20 | // Load spec 21 | store.dispatch(OpenAPIActions.load('/openapi.yml')); 22 | -------------------------------------------------------------------------------- /src/reducers/OpenAPIReducer.js: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions'; 2 | import Immutable from 'immutable'; 3 | 4 | 5 | const initialState = Immutable.fromJS({ 6 | isLoading: false, 7 | spec: { 8 | openapi: '', 9 | info: { 10 | title: '', 11 | version: '' 12 | }, 13 | servers: [], 14 | paths: {}, 15 | security: [], 16 | tags: [], 17 | externalDocs: {} 18 | }, 19 | errors: [] 20 | }); 21 | 22 | 23 | export default handleActions({ 24 | 25 | 'LOAD_SPEC': (state) => { 26 | return state.set('isLoading', true); 27 | }, 28 | 29 | 'LOAD_SPEC_COMPLETED': (state, action) => { 30 | return initialState.mergeIn(['spec'], Immutable.fromJS(action.payload)); 31 | }, 32 | 33 | 'LOAD_SPEC_FAILED': (state, action) => { 34 | return initialState.set('errors', action.payload); 35 | }, 36 | 37 | 38 | }, initialState); 39 | -------------------------------------------------------------------------------- /src/screens/App/App.css: -------------------------------------------------------------------------------- 1 | $app-sidebar-width: 220px !default; 2 | $app-sidebar-background: #333 !default; 3 | $app-body-padding: 20px 15px 100px !default; 4 | $app-body-box-sizing: border-box !default; 5 | 6 | .app { 7 | 8 | .sidebar { 9 | position: fixed; 10 | width: $app-sidebar-width; 11 | height: 100%; 12 | background: $app-sidebar-background; 13 | } 14 | 15 | .body { 16 | width: calc(100% - $app-sidebar-width); 17 | transform: translateX($app-sidebar-width); 18 | padding: $app-body-padding; 19 | box-sizing: $app-body-box-sizing; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/screens/App/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import OpenAPI from 'lib/components/openapi/OpenAPI'; 4 | import OpenAPISelectors from 'selectors/OpenAPISelectors'; 5 | import s from './App.css'; 6 | 7 | 8 | function App(props) { 9 | 10 | if (props.isLoading) return null; 11 | 12 | return ( 13 |
    14 |
    15 |
    16 | 23 |
    24 |
    25 | ); 26 | 27 | } 28 | App.propTypes = { 29 | isLoading: PropTypes.bool, 30 | info: PropTypes.object.isRequired, 31 | servers: PropTypes.array, 32 | paths: PropTypes.object.isRequired, 33 | security: PropTypes.array, 34 | externalDocs: PropTypes.object, 35 | }; 36 | 37 | 38 | const mapStateToProps = (state) => { 39 | return { 40 | isLoading: OpenAPISelectors.isLoading(state), 41 | info: OpenAPISelectors.getInfo(state).toJS(), 42 | servers: OpenAPISelectors.getServers(state).toJS(), 43 | paths: OpenAPISelectors.getPaths(state).toJS(), 44 | security: OpenAPISelectors.getSecurity(state).toJS(), 45 | externalDocs: OpenAPISelectors.getExternalDocs(state).toJS(), 46 | }; 47 | }; 48 | 49 | export default connect(mapStateToProps)(App); 50 | -------------------------------------------------------------------------------- /src/selectors/OpenAPISelectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | 4 | export const getSpec = (state) => state.getIn(['openapi', 'spec']); 5 | 6 | 7 | export const isLoading = (state) => state.getIn(['openapi', 'isLoading']); 8 | 9 | 10 | export const getInfo = createSelector(getSpec, spec => spec.get('info')); 11 | 12 | 13 | export const getServers = createSelector(getSpec, spec => spec.get('servers')); 14 | 15 | 16 | export const getPaths = createSelector(getSpec, spec => spec.get('paths')); 17 | 18 | 19 | export const getSecurity = createSelector(getSpec, spec => spec.get('security')); 20 | 21 | 22 | export const getTags = createSelector(getSpec, spec => spec.get('tags')); 23 | 24 | 25 | export const getExternalDocs = createSelector(getSpec, spec => spec.get('externalDocs')); 26 | 27 | 28 | export default { 29 | isLoading, 30 | getInfo, 31 | getServers, 32 | getPaths, 33 | getSecurity, 34 | getTags, 35 | getExternalDocs 36 | }; 37 | -------------------------------------------------------------------------------- /src/settings/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux-immutable'; 2 | import { createStore, applyMiddleware, compose } from 'redux'; 3 | import Immutable from 'immutable'; 4 | import thunk from 'redux-thunk'; 5 | import openapi from 'reducers/OpenAPIReducer'; 6 | import { promiseMiddleware } from 'store/middleware'; 7 | 8 | 9 | // Note we use Immutable.js for our state tree, not plain objects. Redux's 10 | // combineReducers function assumes plain objects which is why we're using 11 | // combineReducers from redux-immutable 12 | const initialState = Immutable.Map(); 13 | const appReducer = combineReducers({ 14 | openapi 15 | }); 16 | 17 | // So we can use redux-devtools-extension in chrome. 18 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 19 | 20 | export default createStore( 21 | appReducer, 22 | initialState, 23 | composeEnhancers( 24 | applyMiddleware(promiseMiddleware, thunk), 25 | ) 26 | ); 27 | -------------------------------------------------------------------------------- /src/store/middleware.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | 4 | export const promiseMiddleware = store => next => action => { 5 | // This Redux middleware detects if an action's payload is a Promise 6 | // (specifically if it's 'thenable') and dispatches the result of the 7 | // Promise as a new action back into the store, appending either 8 | // '_COMPLETED' or '_FAILED' to the original action's type. 9 | if (_.isFunction(_.get(action.payload, 'then'))) { 10 | action.payload.then( 11 | (result) => { 12 | store.dispatch(_.assign({}, action, { type: `${action.type}_COMPLETED`, payload: result })); 13 | return result; 14 | }, 15 | (error) => { 16 | store.dispatch(_.assign({}, action, { type: `${action.type}_FAILED`, payload: error })); 17 | return error; 18 | } 19 | ); 20 | } 21 | return next(action); 22 | }; 23 | -------------------------------------------------------------------------------- /src/styles/variables.css: -------------------------------------------------------------------------------- 1 | $font-family: Arial, serif; 2 | $font-size: 14px; 3 | $line-height: 1.4em; 4 | $text-color: #444; 5 | 6 | $anchor-color: #20cbfd; 7 | $anchor-text-decoration: none; 8 | $anchor-color-hover: color($anchor-color lightness(-7%)); 9 | $anchor-text-decoration-hover: none; 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var webpack = require('webpack'); 3 | var path = require('path'); 4 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | var CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | var DirectoryNamedWebpackPlugin = require('directory-named-webpack-plugin'); 7 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 8 | 9 | 10 | const extractText = new ExtractTextPlugin({ 11 | filename: '[name].css', 12 | disable: process.env.npm_lifecycle_event !== 'build' 13 | }); 14 | 15 | 16 | const postcssOptions = { 17 | // NOTE: The `ident` option might not be required in a future version of webpack: 18 | // https://github.com/postcss/postcss-loader/issues/92 19 | ident: 'postcss', 20 | plugins: function () { 21 | return [ 22 | require('lost'), 23 | require('autoprefixer'), 24 | require('postcss-partial-import')({ 25 | path: [ 26 | path.join(__dirname, '/src/styles'), 27 | path.join(__dirname, 'node_modules') 28 | ] 29 | }), 30 | require('postcss-mixins'), 31 | require('postcss-advanced-variables'), 32 | require('postcss-custom-selectors'), 33 | require('postcss-custom-media'), 34 | require('postcss-custom-properties'), 35 | require('postcss-media-minmax'), 36 | require('postcss-color-function'), 37 | require('postcss-nesting'), 38 | require('postcss-nested'), 39 | require('postcss-atroot'), 40 | require('postcss-property-lookup'), 41 | require('postcss-extend'), 42 | require('postcss-selector-matches'), 43 | require('postcss-selector-not') 44 | ]; 45 | } 46 | }; 47 | 48 | 49 | var config = { 50 | entry: { 51 | main: ['babel-polyfill', './src/main.jsx'] 52 | }, 53 | output: { 54 | path: path.join(__dirname, '/dist/'), 55 | filename: '[name].js', 56 | chunkFilename: '[id].js' 57 | }, 58 | devServer: { 59 | contentBase: path.join(__dirname, 'dist'), 60 | historyApiFallback: false, 61 | port: 9000 62 | }, 63 | cache: true, 64 | devtool: false, 65 | stats: { 66 | colors: true, 67 | reasons: true 68 | }, 69 | resolve: { 70 | extensions: ['.js', '.jsx'], 71 | alias: { 72 | 'actions': path.join(__dirname, '/src/actions/'), 73 | 'constants': path.join(__dirname, '/src/constants/'), 74 | 'data': path.join(__dirname, '/src/data/'), 75 | 'filters': path.join(__dirname, '/src/filters/'), 76 | 'img': path.join(__dirname, '/src/img/'), 77 | 'lib': path.join(__dirname, '/src/lib/'), 78 | 'reducers': path.join(__dirname, '/src/reducers/'), 79 | 'screens': path.join(__dirname, '/src/screens/'), 80 | 'selectors': path.join(__dirname, '/src/selectors/'), 81 | 'settings': path.join(__dirname, '/src/settings/development.js'), 82 | 'store': path.join(__dirname, '/src/store/') 83 | }, 84 | plugins: [ 85 | new DirectoryNamedWebpackPlugin({ 86 | ignoreFn: function(webpackResolveRequest) { 87 | return !webpackResolveRequest.path.includes(path.join(__dirname, '/src/')); 88 | }, 89 | transformFn: function(dirName) { 90 | return `${dirName}.jsx`; 91 | } 92 | }) 93 | ] 94 | }, 95 | node: { 96 | fs: 'empty' 97 | }, 98 | module: { 99 | rules: [ 100 | { 101 | test: /\.(js|jsx)$/, 102 | exclude: /node_modules/, 103 | use: [ 104 | 'babel-loader' 105 | ] 106 | }, 107 | { 108 | test: /\.css$/, 109 | include: [/node_modules/], 110 | use: extractText.extract({ 111 | fallback: 'style-loader', 112 | use: [ 113 | 'css-loader' 114 | ] 115 | }) 116 | }, 117 | { 118 | test: /\.css$/, 119 | exclude: [/node_modules/], 120 | use: extractText.extract({ 121 | fallback: 'style-loader', 122 | use: [ 123 | { 124 | loader: 'css-loader', 125 | options: { 126 | modules: true, 127 | sourceMap: true, 128 | importLoaders: 1, 129 | localIdentName: '[name]_[local]' 130 | } 131 | }, 132 | { 133 | loader: 'postcss-loader', 134 | options: postcssOptions 135 | } 136 | ] 137 | }) 138 | }, 139 | { 140 | test: /\.(png|jpg|gif)$/, 141 | loader: 'url-loader', 142 | options: { 143 | limit: 8192 144 | } 145 | }, 146 | { 147 | test: /\.woff(2)?(\?.*)?$/, 148 | loader: 'file-loader' 149 | }, 150 | { 151 | test: /\.(ttf|eot|svg|otf)(\?.*)?$/, 152 | loader: 'file-loader', 153 | options: { 154 | name: '[name].[ext]' 155 | } 156 | }, 157 | { 158 | test: /\.(html|txt|ico|yml)$/, 159 | loader: 'file-loader', 160 | options: { 161 | name: '[name].[ext]' 162 | } 163 | } 164 | ] 165 | }, 166 | plugins: [ 167 | extractText, 168 | new HtmlWebpackPlugin({ 169 | template: 'src/index.ejs' 170 | }) 171 | ] 172 | }; 173 | 174 | if (process.env.npm_lifecycle_event === 'build') { 175 | 176 | config.resolve.alias['settings'] = path.join(__dirname, '/src/settings/', process.env.SETTINGS_FILE || 'development.js'); 177 | 178 | config.plugins = config.plugins.concat([ 179 | new ExtractTextPlugin('[name].css'), 180 | new CleanWebpackPlugin(['dist']), 181 | new webpack.DefinePlugin({ 182 | 'process.env':{ 183 | 'NODE_ENV': JSON.stringify('production') 184 | } 185 | }), 186 | new webpack.optimize.UglifyJsPlugin({ 187 | sourceMap: true 188 | }), 189 | new webpack.optimize.AggressiveMergingPlugin(), 190 | new webpack.NoEmitOnErrorsPlugin() 191 | ]); 192 | 193 | } 194 | 195 | module.exports = config; 196 | --------------------------------------------------------------------------------