├── .babelrc ├── .eslintrc ├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── README.md ├── components ├── app.js ├── constraints.js ├── definition.js ├── endpoint.js ├── exampleObject.js ├── head.js ├── helpers.js ├── objectDefinitionTable.js ├── schema.js └── sidebar.js ├── index.js ├── package.json └── styles ├── .gitkeep └── styles.css /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins": ["transform-runtime", "transform-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser" : "babel-eslint", 3 | "extends" : ["airbnb"], 4 | "rules": { 5 | "react/prefer-stateless-function": 0 6 | }, 7 | "globals": { 8 | "IS_JAVASCRIPT": false, 9 | "LAST_MODIFIED": false, 10 | "process.env": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | build 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, CloudFlare. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doca-bootstrap-theme (DEPRECATED) 2 | 3 | The `doca` package for which this was written has been depreacted in favor of `@cloudflare/doca`, 4 | in the [json-schema-tools](https://github.com/cloudflare/json-schema-tools) repository. 5 | While `@cloudflare/doca` is conceptually the same, the back-end tools use a different 6 | format that retains compatibility with JSON Schema, so the theme requirements are different 7 | 8 | Also in that repository you will find a new `@cloudflare/doca-default-theme`, which is 9 | currently only a bare-bones debugging display for the back end tools. We will be expanding 10 | it into a fully functional new theme, and feature requests will be handled there from now on. 11 | 12 | ---- 13 | 14 | The new `@cloudflare/doca` does not yet have a fully functioning theme, so this package 15 | is still the produciton-ready one. But we will be implementing feature requests on the new 16 | code, and moving most open issues to the new repo whenever it makes sense. 17 | 18 | Simple [Twitter Boostrap 3](http://getbootstrap.com/) based theme for [doca](https://github.com/cloudflare/doca). 19 | 20 | It's supposed to be used in combination with [doca](https://github.com/cloudflare/doca) - a tool that scaffolds API documentation based on JSON HyperSchemas. 21 | 22 | Please file any issues at the [doca](https://github.com/cloudflare/doca/issues) repository. 23 | 24 | ## Usage 25 | 26 | ``` 27 | npm install -g doca 28 | doca init -t bootstrap 29 | ``` 30 | 31 | This creates a new API documentation with `doca-bootstrap-theme` as a dependency. 32 | 33 | # How to create your own theme 34 | 35 | **The best way is to fork and modify this repository.** The integration with doca is pretty loose and it makes just a few assumptions about your theme. 36 | 37 | 38 | ## React Components 39 | 40 | Doca expects to import **two React components** from your theme (otherwise it fails): 41 | 42 | - **`App`** - main root component that gets all schemas through the props 43 | - **`Head`** - `` part of the final documentation 44 | 45 | ### App component 46 | 47 | [App component](https://github.com/cloudflare/doca-bootstrap-theme/blob/master/components/app.js) can expect to receive two props: 48 | 49 | - **`this.props.schemas`** : *Immutable.List* - an array of all imported schemas, the schema format can be found [here](https://github.com/cloudflare/json-schema-example-loader) - it's the ouput of [json-schema-example-loader](https://github.com/cloudflare/json-schema-example-loader). However, the whole data structure is additionally turned into immutable one by [Immutable.js](https://facebook.github.io/immutable-js) library and `Immutable.fromJS()` function. It deeply converts all arrays into [Immutable.List](https://facebook.github.io/immutable-js/docs/#/List) and all objects into [Immutable.Map](https://facebook.github.io/immutable-js/docs/#/Map). Thus, you have to use slightly different methods for iteration or prop access. 50 | - **`this.props.config`** : *object* - a plain object exported by users from `config.js`, it should always have the key `title`. 51 | 52 | ### Head component 53 | 54 | [Head component](https://github.com/cloudflare/doca-bootstrap-theme/blob/master/components/head.js) can expect to receive two props: 55 | 56 | - **`this.props.title`** : *string* - the title specified in `config.js` 57 | - **`this.props.cssBundle`** : *string* - you should put this code into your Head component: 58 | 59 | ```jsx 60 | {this.props.cssBundle && } 61 | ``` 62 | 63 | ### State 64 | 65 | - You should just use native `this.state` and keep the state in your components. It's perfect for things like toggling. 66 | - **Advanced:** If your theme is getting bigger (a lot of state everywhere), you can consider using [Redux](http://redux.js.org/). Doca is already using Redux for handling schemas. Your theme can export a reducer ([check this pattern](https://github.com/cloudflare/cf-ui/tree/master/packages/cf-builder-card/src) in our cf-ui components). However, it's up to you to modify the scaffolded application and import your reducer in `/src/client/reducers/index.js`. Doca app currently doesn't do any assumptions about this (it could auto-import the theme reducer in future though). So if you want to make your theme useful out-of-the-box, try to use native `this.state` instead. 67 | 68 | ## Global variables 69 | 70 | You can use these three global variables (provided by webpack): 71 | 72 | - **`IS_JAVASCRIPT`** : *bool* - doca provides `npm run build:nojs` script. You can ship your API docs without JS bundle. This variable is `true` when `nojs` flag is used. It gives you opportunity to do some changes in your components (show/hide sections should be visible by default etc.) 73 | - **`LAST_MODIFIED`** : *number* - `Date.now()` value, in case you want to display "last modified" information somewhere 74 | - **`process.env.NODE_ENV`** : *enum* - can be `development` or `production` 75 | 76 | ## Styles 77 | 78 | You have three options how to style your React components: 79 | 80 | - [React inline styles](https://facebook.github.io/react/tips/inline-styles.html). 81 | - Link directly your external stylesheet in the Head component. 82 | - Doca's webpack **expects to find folder `/styles` in your theme**. It [dynamically imports](https://webpack.github.io/docs/context.html) and processes all css, less and scss files from this folder. **Magic!** Then, it bundles them into a single css file and passes `this.props.cssBundle` to your Head component. You can leave this folder empty, then `this.props.cssBundle` is going to be empty. 83 | 84 | Unfortunately, you can't directly import styles from your React components since webpack can't resolve such requires in `node_modules`. 85 | 86 | ## Publishing 87 | 88 | We would be happy to see more open source doca themes! Let us know if you publish some. It should follow this name convention: 89 | 90 | ``` 91 | doca-XXXXXXXX-theme 92 | ``` 93 | 94 | ## Tips 95 | 96 | - **Immutable.js** is used because resulting app (page) can be pretty big and we want to keep re-rendering fast. All your components should implement `shouldComponentUpdate()` method, so it prevents unnecessary re-renders. Or you can simply extend your components from [react-pure-render](https://github.com/gaearon/react-pure-render). 97 | 98 | - When you're developing a new theme, you can streamline the process by copy&pasting your components into Doca app. That will give you hot-reloading! Otherwise, you can use [npm link](https://docs.npmjs.com/cli/link). With every change, you have to call `npm link` again, so it triggers `npm prepublish` and rebuilds your components (babel/JSX -> ES5). 99 | 100 | - `npm run lint` (use node v4+) 101 | 102 | -------------------------------------------------------------------------------- /components/app.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ImmutablePropTypes = require('react-immutable-proptypes'); 3 | const Component = require('react-pure-render/component'); 4 | const Sidebar = require('./sidebar'); 5 | const Schema = require('./schema'); 6 | 7 | class App extends Component { 8 | 9 | static propTypes = { 10 | schemas: ImmutablePropTypes.list.isRequired, 11 | config: React.PropTypes.object, 12 | }; 13 | 14 | render() { 15 | const { schemas, config } = this.props; 16 | 17 | return ( 18 |
19 | 20 |
21 |
22 |
23 |
24 |

{config.title}

25 | {schemas 26 | .filter(schema => !schema.get('cfHidden')) 27 | .valueSeq() 28 | .map(schema => ) 29 | } 30 |
31 |
32 |
33 |
34 |
35 | ); 36 | } 37 | 38 | } 39 | 40 | module.exports = App; 41 | -------------------------------------------------------------------------------- /components/constraints.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const Component = require('react-pure-render/component'); 3 | const _ = require('lodash/core'); 4 | 5 | class Constraints extends Component { 6 | 7 | static propTypes = { 8 | constraints: React.PropTypes.object, 9 | }; 10 | 11 | considerType(value) { 12 | if (_.isString(value)) { 13 | return `"${value}"`; 14 | } 15 | if (_.isNull(value)) { 16 | return 'null'; 17 | } 18 | return value; 19 | } 20 | 21 | render() { 22 | const { constraints } = this.props; 23 | if (!constraints) return
; 24 | return ( 25 | 83 | ); 84 | } 85 | 86 | } 87 | 88 | module.exports = Constraints; 89 | -------------------------------------------------------------------------------- /components/definition.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | 3 | const React = require('react'); 4 | const ImmutablePropTypes = require('react-immutable-proptypes'); 5 | const Component = require('react-pure-render/component'); 6 | 7 | class Definition extends Component { 8 | 9 | static propTypes = { 10 | definitions: ImmutablePropTypes.map, 11 | contextId: React.PropTypes.string, 12 | fieldPointer: React.PropTypes.string, 13 | }; 14 | 15 | state = { 16 | showDefinition: false, 17 | }; 18 | 19 | handleToggle = () => { 20 | this.setState(prevState => ({ 21 | showDefinition: !prevState.showDefinition, 22 | })); 23 | }; 24 | 25 | renderDefTable(definitions) { 26 | const ObjectDefinitionTable = require('./objectDefinitionTable'); 27 | return ; 28 | } 29 | 30 | render() { 31 | const { definitions } = this.props; 32 | const { showDefinition } = this.state; 33 | 34 | return ( 35 |
36 | {IS_JAVASCRIPT && 37 | 41 | {showDefinition ? 'Hide' : 'Show'} definition » 42 | 43 | } 44 | {(showDefinition || !IS_JAVASCRIPT) && this.renderDefTable(definitions)} 45 |
46 | ); 47 | } 48 | 49 | } 50 | 51 | module.exports = Definition; 52 | -------------------------------------------------------------------------------- /components/endpoint.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const ObjectDefinitionTable = require('./objectDefinitionTable'); 3 | const MarkdownPreview = require('react-marked-markdown').MarkdownPreview; 4 | const ImmutablePropTypes = require('react-immutable-proptypes'); 5 | const Component = require('react-pure-render/component'); 6 | 7 | class Endpoint extends Component { 8 | 9 | static propTypes = { 10 | link: ImmutablePropTypes.map.isRequired, 11 | }; 12 | 13 | render() { 14 | const { link } = this.props; 15 | 16 | return ( 17 |
18 |

19 |
{link.get('method')}
{' '} 20 |
{link.get('title')}
21 |

22 | {link.get('description') && } 23 |
24 |           {link.get('method')} {link.get('uri')}
25 |         
26 | 27 | {link.getIn(['parameters', 'required_props', 0]) && 28 |
29 |

Required parameters

30 | 33 | link.getIn(['parameters', 'required_props']).indexOf(key) > -1 34 | ) 35 | } 36 | contextId={link.get('title')} 37 | fieldPointer="/properties" 38 | /> 39 |
40 | } 41 | 42 | {link.getIn(['parameters', 'optional_props', 0]) && 43 |
44 |

Optional parameters

45 | 48 | link.getIn(['parameters', 'optional_props']).indexOf(key) > -1 49 | ) 50 | } 51 | contextId={link.get('title')} 52 | fieldPointer="/properties" 53 | /> 54 |
55 | } 56 | 57 |

cURL

58 |
59 |
{link.get('curl')}
60 |
61 | 62 |

Response

63 |
64 |
{link.get('response')}
65 |
66 |
67 | ); 68 | } 69 | 70 | } 71 | 72 | module.exports = Endpoint; 73 | -------------------------------------------------------------------------------- /components/exampleObject.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const Component = require('react-pure-render/component'); 3 | 4 | class ExampleObject extends Component { 5 | 6 | static propTypes = { 7 | example: React.PropTypes.string.isRequired, 8 | }; 9 | 10 | render() { 11 | const { example } = this.props; 12 | return ( 13 |
14 |
15 |
Example object
16 |
17 |
18 |
{example}
19 |
20 |
21 | ); 22 | } 23 | 24 | } 25 | 26 | module.exports = ExampleObject; 27 | -------------------------------------------------------------------------------- /components/head.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | 3 | const Head = ({ cssBundle, title }) => 4 | 5 | 6 | {title} 7 | 11 | 12 | {cssBundle && } 13 | ; 14 | 15 | 16 | Head.propTypes = { 17 | cssBundle: React.PropTypes.string, 18 | title: React.PropTypes.string, 19 | }; 20 | 21 | Head.displayName = 'Head'; 22 | module.exports = Head; 23 | -------------------------------------------------------------------------------- /components/helpers.js: -------------------------------------------------------------------------------- 1 | // return offsetTop relative to the whole page 2 | export function offsetTop(elem) { 3 | const body = document.body; 4 | const docEl = document.documentElement; 5 | const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; 6 | const clientTop = docEl.clientTop || body.clientTop || 0; 7 | if (!elem) return scrollTop - clientTop; 8 | const box = elem.getBoundingClientRect(); 9 | const top = box.top + scrollTop - clientTop; 10 | return Math.round(top); 11 | } 12 | -------------------------------------------------------------------------------- /components/objectDefinitionTable.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const Constraints = require('./constraints'); 3 | const MarkdownPreview = require('react-marked-markdown').MarkdownPreview; 4 | const List = require('immutable').List; 5 | const ImmutableMap = require('immutable').Map; 6 | const ImmutablePropTypes = require('react-immutable-proptypes'); 7 | const Component = require('react-pure-render/component'); 8 | const Definition = require('./definition'); 9 | 10 | class ObjectDefinitionTable extends Component { 11 | 12 | static propTypes = { 13 | definitions: ImmutablePropTypes.map, 14 | contextId: React.PropTypes.string, 15 | fieldPointer: React.PropTypes.string 16 | }; 17 | 18 | render() { 19 | const { definitions } = this.props; 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {definitions && definitions.entrySeq().map(([key, definition]) => 31 | 32 | 44 | 102 | 105 | 106 | )} 107 | 108 |

Name /type

Description /example

Constraints

33 |

34 | {key}
35 | 36 | {List.isList(definition.get('type')) ? 37 | definition.get('type').valueSeq().join(', ') : 38 | definition.get('type')} 39 | {definition.get('format') && 40 | ' (' + definition.get('format') + ')'} 41 | 42 |

43 |
45 | {definition.get('description') && 46 | } 47 |
48 | {definition.get('example') && 49 | 50 | {definition.get('example')} 51 | 52 | } 53 | {definition.get('oneOf') &&
One of the following:
} 54 | {definition.get('anyOf') &&
Any of the following:
} 55 |
56 | 57 | {definition.get('properties') && 58 | 63 | } 64 | 65 | {definition.get('items') && 66 | 71 | } 72 | 73 | {definition.get('oneOf') && 74 | definition.get('oneOf').entrySeq().map(([subkey, subdefinition]) => 75 |
76 |
{subdefinition.get('description')}
77 | 84 |
85 | )} 86 | 87 | {definition.get('anyOf') && 88 | definition.get('anyOf').entrySeq().map(([subkey, subdefinition]) => 89 |
90 |
{subdefinition.get('description')}
91 | 99 |
100 | )} 101 |
103 | 104 |
109 | ); 110 | } 111 | 112 | } 113 | 114 | module.exports = ObjectDefinitionTable; 115 | -------------------------------------------------------------------------------- /components/schema.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const Endpoint = require('./endpoint'); 3 | const ObjectDefinitionTable = require('./objectDefinitionTable'); 4 | const MarkdownPreview = require('react-marked-markdown').MarkdownPreview; 5 | const ImmutablePropTypes = require('react-immutable-proptypes'); 6 | const Component = require('react-pure-render/component'); 7 | const ExampleObject = require('./exampleObject'); 8 | 9 | class Schema extends Component { 10 | 11 | static propTypes = { 12 | schema: ImmutablePropTypes.map.isRequired, 13 | }; 14 | 15 | state = { 16 | showDefinition: false, 17 | }; 18 | 19 | handleToggle = () => { 20 | this.setState(prevState => ({ 21 | showDefinition: !prevState.showDefinition, 22 | })); 23 | }; 24 | 25 | render() { 26 | const { schema } = this.props; 27 | const { showDefinition } = this.state; 28 | return ( 29 |
30 |
31 |
32 |

{schema.get('title')}

33 |
34 |
35 |

{schema.get('description')}

36 | {schema.get('cfExtendedDescription') && 37 | } 38 | 39 |
40 | {IS_JAVASCRIPT && 41 |

42 | 43 | {showDefinition ? 'Hide' : 'Show'}{' '} 44 | properties and constraints defined on the object 45 | 46 |

47 | } 48 |
49 | 50 | {(showDefinition || !IS_JAVASCRIPT) && 51 |
52 | {schema.getIn(['object_definition', 'objects']).count() ? 53 |
54 | {schema.getIn(['object_definition', 'objects']).valueSeq().map((obj, index) => 55 |
56 | {obj.get('title') && 57 |
58 |

{obj.get('title')}

59 |
60 | } 61 | {obj.get('example') && } 62 | 70 |
71 | )} 72 |
73 | : 74 |
75 | {schema.getIn(['object_definition', 'example']) && 76 | 77 | } 78 | 79 | 84 |
85 | } 86 |
87 | } 88 |
89 |
90 | {schema 91 | .get('links') 92 | .filter(link => !link.get('cfPrivate')) 93 | .valueSeq() 94 | .map(link => ) 95 | } 96 |
97 |
98 | ); 99 | } 100 | 101 | } 102 | 103 | module.exports = Schema; 104 | -------------------------------------------------------------------------------- /components/sidebar.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const Component = require('react-pure-render/component'); 3 | const ImmutablePropTypes = require('react-immutable-proptypes'); 4 | const _ = require('lodash'); 5 | const offsetTop = require('./helpers').offsetTop; 6 | 7 | 8 | const getLinks = (links, search) => 9 | links 10 | .filter(link => { 11 | if (link.get('cfPrivate')) return false; 12 | if (search && 13 | link.get('title').toLowerCase().indexOf(search.toLowerCase()) === -1) { 14 | return false; 15 | } 16 | return true; 17 | }); 18 | 19 | class Sidebar extends Component { 20 | 21 | static propTypes = { 22 | schemas: ImmutablePropTypes.list.isRequired, 23 | }; 24 | 25 | constructor() { 26 | super(); 27 | this.handleScroll = _.throttle(this.handleScroll, 150); 28 | } 29 | 30 | state = { 31 | activeId: null, 32 | search: '', 33 | }; 34 | 35 | componentDidMount() { 36 | window.addEventListener('scroll', this.handleScroll); 37 | window.addEventListener('keydown', this.handleKeydown); 38 | } 39 | 40 | componentWillUnmount() { 41 | window.removeEventListener('scroll', this.handleScroll); 42 | window.removeEventListener('keydown', this.handleKeydown); 43 | } 44 | 45 | handleSearchChange = (e) => { 46 | this.setState({ search: e.target.value }); 47 | } 48 | 49 | cancelSearch = () => { 50 | this.setState({ search: '' }); 51 | } 52 | 53 | handleKeydown = (e) => { 54 | // ESC 55 | if (e.keyCode === 27) { 56 | this.cancelSearch(); 57 | } 58 | } 59 | 60 | // highlighting of sidebar links and section toggling 61 | handleScroll = () => { 62 | // list of all link #ids 63 | const ids = this.props.schemas.reduce((result, schema) => { 64 | let res = result; 65 | if (!schema.get('cfHidden')) { 66 | res = res.concat([schema.get('html_id')]); 67 | res = res.concat([`${schema.get('html_id')}-properties`]); 68 | } 69 | return res.concat( 70 | schema 71 | .get('links') 72 | .filter(link => !link.get('cfPrivate')) 73 | .map(link => link.get('html_id')) 74 | .toJS() 75 | ); 76 | } 77 | , []); 78 | 79 | const scrollTop = window.pageYOffset || document.documentElement.scrollTop || 80 | document.body.scrollTop || 0; 81 | 82 | // finds the first link that has top offset > top scroll position and breaks 83 | let activeId = null; 84 | // a small offset so the coming section is highlighted a bit sooner 85 | // before its main title touches the top of browser and starts disappearing 86 | const VERTICAL_OFFSET = 30; 87 | for (let i = 0; i < ids.length; i++) { 88 | if (offsetTop(document.getElementById(ids[i])) - VERTICAL_OFFSET > scrollTop) { 89 | activeId = ids[i > 0 ? i - 1 : i]; 90 | break; 91 | } 92 | } 93 | 94 | // updates URL bar 95 | if (global.history) { 96 | global.history.replaceState({ id: activeId }, activeId, `#${activeId}`); 97 | } 98 | 99 | this.setState({ activeId }); 100 | } 101 | 102 | render() { 103 | const { schemas } = this.props; 104 | const { activeId, search } = this.state; 105 | 106 | return ( 107 | 134 | ); 135 | } 136 | 137 | } 138 | 139 | module.exports = Sidebar; 140 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | App: require('./build/app'), 5 | Constraints: require('./build/constraints'), 6 | Definition: require('./build/definition'), 7 | Endpoint: require('./build/endpoint'), 8 | ExampleObject: require('./build/exampleObject'), 9 | Head: require('./build/head'), 10 | ObjectDefinitionTable: require('./build/objectDefinitionTable'), 11 | Schema: require('./build/schema'), 12 | Sidebar: require('./build/sidebar') 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doca-bootstrap-theme", 3 | "version": "1.0.0", 4 | "description": "Doca theme using Twitter Bootstrap", 5 | "main": "index.js", 6 | "author": "Vojtech Miksu ", 7 | "license": "BSD-3-Clause", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/cloudflare/doca-bootstrap-theme" 11 | }, 12 | "scripts": { 13 | "build": "npm run clean && mkdir -p build && babel components --out-dir build", 14 | "lint": "eslint components || true", 15 | "prepublish": "cross-env NODE_ENV=production npm run build", 16 | "clean": "rimraf build" 17 | }, 18 | "files": [ 19 | "*.md", 20 | "build", 21 | "fonts", 22 | "styles", 23 | "assets" 24 | ], 25 | "dependencies": { 26 | "immutable": "^3.8.1", 27 | "lodash": "^4.13.1", 28 | "react": "^15.2.0", 29 | "react-dom": "^15.2.0", 30 | "react-immutable-proptypes": "^1.7.1", 31 | "react-marked-markdown": "^1.4.0", 32 | "react-pure-render": "^1.0.2" 33 | }, 34 | "peerDependencies": { 35 | "json-schema-example-loader": "^3.0.0" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.8.0", 39 | "babel-core": "^6.8.0", 40 | "babel-eslint": "^6.1.1", 41 | "babel-plugin-transform-class-properties": "^6.10.2", 42 | "babel-plugin-transform-runtime": "^6.8.0", 43 | "babel-preset-es2015": "^6.6.0", 44 | "babel-preset-react": "^6.5.0", 45 | "babel-preset-react-hmre": "^1.1.1", 46 | "cross-env": "^1.0.8", 47 | "eslint": "^3.0.1", 48 | "eslint-config-airbnb": "^9.0.1", 49 | "eslint-loader": "^1.4.1", 50 | "eslint-plugin-import": "^1.10.2", 51 | "eslint-plugin-jsx-a11y": "^1.5.5", 52 | "eslint-plugin-react": "^5.1.1", 53 | "rimraf": "^2.5.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/doca-bootstrap-theme/219d67e6aab777b3eab0d5f61f0d84cb947a37d2/styles/.gitkeep -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Simple Sidebar (http://startbootstrap.com/) 3 | * Copyright 2013-2016 Start Bootstrap 4 | * Licensed under MIT (https://github.com/BlackrockDigital/startbootstrap/blob/gh-pages/LICENSE) 5 | */ 6 | 7 | body { 8 | overflow-x: hidden; 9 | } 10 | 11 | /* Toggle Styles */ 12 | 13 | #wrapper { 14 | padding-left: 0; 15 | -webkit-transition: all 0.5s ease; 16 | -moz-transition: all 0.5s ease; 17 | -o-transition: all 0.5s ease; 18 | transition: all 0.5s ease; 19 | } 20 | 21 | #wrapper.toggled { 22 | padding-left: 250px; 23 | } 24 | 25 | #sidebar-wrapper { 26 | z-index: 1000; 27 | position: fixed; 28 | left: 250px; 29 | width: 0; 30 | height: 100%; 31 | margin-left: -250px; 32 | overflow-y: auto; 33 | background: #000; 34 | -webkit-transition: all 0.5s ease; 35 | -moz-transition: all 0.5s ease; 36 | -o-transition: all 0.5s ease; 37 | transition: all 0.5s ease; 38 | } 39 | 40 | #wrapper.toggled #sidebar-wrapper { 41 | width: 250px; 42 | } 43 | 44 | #page-content-wrapper { 45 | width: 100%; 46 | position: absolute; 47 | padding-left: 15px; 48 | padding-right: 15px; 49 | } 50 | 51 | #wrapper.toggled #page-content-wrapper { 52 | position: absolute; 53 | margin-right: -250px; 54 | } 55 | 56 | /* Sidebar Styles */ 57 | 58 | .sidebar-nav { 59 | width: 250px; 60 | margin: 0; 61 | padding: 0; 62 | list-style: none; 63 | } 64 | 65 | .sidebar-nav li { 66 | text-indent: 25px; 67 | line-height: 25px; 68 | } 69 | 70 | .sidebar-nav li a { 71 | display: block; 72 | text-decoration: none; 73 | color: #999999; 74 | } 75 | 76 | .sidebar-nav li a:hover { 77 | text-decoration: none; 78 | color: #fff; 79 | background: rgba(255,255,255,0.2); 80 | } 81 | 82 | .sidebar-nav > .active a { 83 | color: #fff; 84 | } 85 | 86 | .sidebar-nav li a:active, 87 | .sidebar-nav li a:focus { 88 | text-decoration: none; 89 | } 90 | 91 | .sidebar-nav > .sidebar-category { 92 | margin-top: 25px; 93 | font-size: 18px; 94 | line-height: 50px; 95 | color: #555; 96 | } 97 | 98 | .sidebar-nav > .sidebar-category a { 99 | color: #999999; 100 | } 101 | 102 | .sidebar-nav > .sidebar-category a:hover { 103 | color: #fff; 104 | background: none; 105 | } 106 | 107 | @media(min-width:768px) { 108 | #wrapper { 109 | padding-left: 250px; 110 | } 111 | 112 | #wrapper.toggled { 113 | padding-left: 0; 114 | } 115 | 116 | #sidebar-wrapper { 117 | width: 250px; 118 | } 119 | 120 | #wrapper.toggled #sidebar-wrapper { 121 | width: 0; 122 | } 123 | 124 | #page-content-wrapper { 125 | position: relative; 126 | padding-left: 25px; 127 | padding-right: 25px; 128 | } 129 | 130 | #wrapper.toggled #page-content-wrapper { 131 | position: relative; 132 | margin-right: 0; 133 | } 134 | } 135 | 136 | h1 { 137 | font-size: 42px; 138 | line-height: 50px; 139 | margin: 75px 0px 0px 0px; 140 | } 141 | 142 | h2 { 143 | font-size: 22px; 144 | line-height: 25px; 145 | margin: 0px; 146 | padding: 12.5px 25px 12.5px 25px; 147 | } 148 | 149 | h3 { 150 | font-size: 22px; 151 | line-height: 25px; 152 | margin: 40px 0px 10px 0px; 153 | } 154 | 155 | h4 { 156 | font-size: 18px; 157 | line-height: 25px; 158 | margin: 0px; 159 | margin: 20px 0px 5px 0px; 160 | } 161 | 162 | h5 { 163 | font-size: 16px; 164 | line-height: 25px; 165 | margin: 0px; 166 | padding: 6px 0px 3px 0px; 167 | } 168 | 169 | .panel-primary, .panel, .list-group-item { 170 | border: none; 171 | } 172 | 173 | .panel { 174 | margin-bottom: 25px; 175 | margin-top: 25px; 176 | } 177 | 178 | .panel-body, .list-group-item { 179 | padding: 0px; 180 | } 181 | .panel-primary > .panel-heading { 182 | background-color: #80ADA0; 183 | border: none; 184 | } 185 | 186 | .panel-heading { 187 | border-radius: 3.3px; 188 | padding: 0px; 189 | margin: 75px 0px 0px 0px; 190 | } 191 | 192 | .btn { 193 | line-height: 25px; 194 | padding: 0px 10px 0px 10px; 195 | } 196 | 197 | .btn-info { 198 | background-color: #9E8DAA; 199 | border: none; 200 | } 201 | 202 | .btn-info:hover { 203 | background-color: #524A59; 204 | border: none; 205 | } 206 | 207 | .label-success { 208 | float: left; 209 | margin: 2px 9px 0px 0px; 210 | font-size: 60%; 211 | padding: .3em .5em .3em; 212 | background-color: #8AEA92; 213 | border: none; 214 | } 215 | 216 | p { 217 | margin: 0px; 218 | line-height: 25px; 219 | } 220 | 221 | pre { 222 | padding: 12.5px; 223 | margin: 0px; 224 | } 225 | 226 | .table { 227 | padding: 0px; 228 | margin: 0px; 229 | } 230 | 231 | .table>tbody>tr>td, 232 | .table>tbody>tr>th, 233 | .table>tfoot>tr>td, 234 | .table>tfoot>tr>th, 235 | .table>thead>tr>td, 236 | .table>thead>tr>th { 237 | padding: 12.5px 25px 12.5px 0px; 238 | } 239 | 240 | .constraints { 241 | list-style: none; 242 | padding-left: 0px; 243 | line-height: 25px 244 | } 245 | 246 | .search { 247 | text-align: center; 248 | margin: 25px; 249 | } 250 | 251 | .search > input { 252 | background: #000; 253 | border-width: 0 0 1px 0; 254 | border-color: #666; 255 | padding: 6px 0px 6px 0px; 256 | box-sizing: border-box; 257 | width: 100%; 258 | height: 25px; 259 | outline: none; 260 | color: #fff; 261 | border-radius: 0; 262 | } 263 | 264 | code { 265 | max-height: 16.66667em; 266 | max-width: 30em; 267 | overflow: scroll; 268 | white-space: pre; 269 | } 270 | 271 | li.read-only { 272 | font-weight: bolder; 273 | font-style: italic ; 274 | } 275 | --------------------------------------------------------------------------------