├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------