├── .gitignore ├── .jshintrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── actions │ ├── api.js │ ├── routing.js │ └── search.js ├── api │ ├── components-api.js │ ├── github-api.js │ └── npm-api.js ├── components │ ├── component-item.jsx │ ├── component-link.jsx │ ├── component-list.jsx │ ├── container.jsx │ ├── footer.jsx │ ├── home-link.jsx │ ├── layout.jsx │ ├── loader.jsx │ ├── markdown-readme.jsx │ ├── no-result.jsx │ ├── react-logo.jsx │ ├── results-item.jsx │ ├── results-table.jsx │ └── search-input.jsx ├── config.js ├── controllers │ └── components.js ├── database │ └── index.js ├── pages │ ├── component-info.jsx │ ├── front.jsx │ └── search.jsx ├── react │ └── renderer.js ├── root.js ├── router.js ├── routes.js ├── search │ └── filter.js ├── server │ └── routes.js ├── stores │ ├── component-store.shared.js │ ├── components-store.browser.js │ └── components-store.js └── util │ ├── deep.js │ ├── escape-regex.js │ ├── github-account.js │ └── number-formatter.js ├── cron ├── fetch-components.js └── fetch-stars.js ├── data └── purpose.md ├── gulpfile.js ├── index.js ├── node_modules └── app ├── package.json ├── public ├── css │ ├── codemirror.css │ ├── components.css │ └── pure-min.css ├── dist │ └── empty.txt ├── favicon.ico ├── favicon.svg ├── img │ └── react.svg ├── js │ ├── analytics.js │ ├── codemirror-compressed.js │ └── lodash.min.js └── less │ ├── _base.less │ ├── _codemirror.less │ ├── _component-info.less │ ├── _content.less │ ├── _footer.less │ ├── _front.less │ ├── _header.less │ ├── _loader.less │ ├── _mini-list.less │ ├── _readme.less │ ├── _search-input.less │ ├── _search.less │ ├── _variables.less │ └── components.less ├── templates └── default.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Compiled binary addons (http://nodejs.org/api/addons.html) 17 | build/Release 18 | 19 | # Dependency directory 20 | # Deployed apps should consider commenting this line out: 21 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 22 | node_modules 23 | 24 | # Data/cache dir 25 | data 26 | 27 | # Dist build 28 | public/dist 29 | 30 | # Ignore gz-files 31 | public/css/*.gz 32 | public/js/*.gz 33 | 34 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": false, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 4, 10 | "latedef": "func", 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true 20 | } 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to react-components.com 2 | 3 | ## Pull Requests 4 | 5 | Pull requests should be a branch from `master`. 6 | 7 | ## Style Guide 8 | 9 | ### Code 10 | 11 | * Use semicolons; 12 | * Commas last, 13 | * 4 spaces for indentation (no tabs) 14 | * Prefer `'` over `"` 15 | * `'use strict';` 16 | * "Attractive" 17 | 18 | ## License 19 | 20 | By contributing to react-components.com, you agree that your contributions will be licensed under the [MIT license](LICENSE). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 VaffelNinja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-components 2 | ================ 3 | 4 | Searchable repository of React-components 5 | 6 | 7 | ## Installation 8 | 9 | In order to build this project you need to have [node](https://nodejs.org/) and [redis](http://redis.io/) installed in your machine. Once you do, clone the repository and install it, using the following commands: 10 | 11 | ``` 12 | git clone git@github.com:vaffel/react-components.git 13 | cd react-components 14 | npm install 15 | npm run fetch:database 16 | ``` 17 | 18 | To run the project in development mode, use: 19 | 20 | ``` 21 | gulp watch 22 | ``` 23 | 24 | ### Importing modules 25 | 26 | This project relies on a database of npm components. Before being able to run the project for the first time, you need to fetch the components. The command to do so can also be used any time you wish to update your database. 27 | 28 | ``` 29 | npm run fetch:database 30 | ``` 31 | 32 | ### Possible sources of Installation problems For mac 33 | 34 | #### Problems during npm install 35 | 36 | The current version of the project is not yet fully compatible with node v4.0.0. One possible work around is to use Node Version Manager ([NVM](https://github.com/creationix/nvm)), and install node v0.12.7. This can be accomplished as follows: 37 | 38 | ``` 39 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.28.0/install.sh | bash 40 | nvm install v0 41 | nvm use v0 42 | ``` 43 | 44 | #### [Problems during hiredis Installation](https://github.com/redis/hiredis-node/issues/102) 45 | 46 | If while doing the npm install you run into the following error: 47 | ``` 48 | ld: library not found for -lgcc_s.10.5 49 | make: *** [Release/hiredis.node] Error 1 50 | ``` 51 | You probably have a problem with your version of Xcode. The easiest way to solve it, is to update your Xcode and open it. 52 | 53 | #### Using Macports 54 | 55 | If you use macports to install your dependencies, then the issue might be that you are not using the [default gcc compiler](http://apple.stackexchange.com/questions/129608/switch-back-to-clang-after-installing-gcc-through-macports-on-mavericks). To verify that, do: 56 | ``` 57 | gcc --version 58 | ``` 59 | If what you get is not the default clang compiler from Xcode, edit your ~/.bash_profile as follows: 60 | ``` 61 | vim ~/.bash_profile 62 | ``` 63 | And comment out the line: 64 | ``` 65 | export PATH=/opt/local/bin:/opt/local/sbin:$PATH 66 | ``` 67 | -------------------------------------------------------------------------------- /app/actions/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Reflux = require('reflux'); 4 | 5 | module.exports = Reflux.createActions([ 6 | 'fetchComponents', 7 | 'fetchFailed', 8 | 'componentsFetched', 9 | 10 | 'fetchComponentInfo', 11 | 'fetchComponentFailed', 12 | 'componentFetched' 13 | ]); -------------------------------------------------------------------------------- /app/actions/routing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Reflux = require('reflux'); 4 | 5 | module.exports = Reflux.createActions([ 6 | 'locationChange' 7 | ]); -------------------------------------------------------------------------------- /app/actions/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Reflux = require('reflux'); 4 | 5 | module.exports = Reflux.createActions([ 6 | 'search' 7 | ]); -------------------------------------------------------------------------------- /app/api/components-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var ApiActions = require('app/actions/api'); 5 | var request = require('xhr'); 6 | var isFetchingList = false; 7 | 8 | var ComponentsApi = { 9 | fetchComponents: function() { 10 | isFetchingList = true; 11 | 12 | request({ url: '/api/components', json: true }, function(err, xhr, body) { 13 | if (err) { 14 | return ApiActions.fetchFailed(err); 15 | } 16 | 17 | var assignKey = _.partial(_.zipObject, body.keys), 18 | components = _.map(body.items, assignKey); 19 | 20 | isFetchingList = false; 21 | ApiActions.componentsFetched(components); 22 | }); 23 | }, 24 | 25 | fetchComponentInfo: function(name) { 26 | request({ url: '/api/components/' + encodeURIComponent(name), json: true }, function(err, xhr, body) { 27 | if (err) { 28 | return ApiActions.fetchComponentFailed(err); 29 | } 30 | 31 | ApiActions.componentFetched(body); 32 | }); 33 | }, 34 | 35 | listen: function() { 36 | ApiActions.fetchComponents.shouldEmit = function() { 37 | return !isFetchingList; 38 | }; 39 | 40 | ApiActions.fetchComponents.listen(ComponentsApi.fetchComponents); 41 | ApiActions.fetchComponentInfo.listen(ComponentsApi.fetchComponentInfo); 42 | } 43 | }; 44 | 45 | module.exports = ComponentsApi; -------------------------------------------------------------------------------- /app/api/github-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('app/config'); 4 | var Github = require('github'); 5 | var client = new Github({ 6 | version: '3.0.0', 7 | protocol: 'https' 8 | }); 9 | 10 | var getGithubAccount = require('app/util/github-account'); 11 | 12 | client.authenticate(config.github); 13 | 14 | var GithubApi = { 15 | 16 | getStarCountForModule: function(module, callback) { 17 | var repo = getGithubAccount(module); 18 | 19 | if (!repo) { 20 | return callback('Repo not resolvable for module ' + module.name); 21 | } 22 | 23 | client.repos.get({ 24 | user: repo.split('/', 1).join(''), 25 | repo: repo.replace(/.*?\//, '') 26 | }, function(err, data) { 27 | if (err) { 28 | return callback(err); 29 | } 30 | 31 | callback(err, (data || {}).stargazers_count || 0); 32 | }); 33 | } 34 | 35 | }; 36 | 37 | module.exports = GithubApi; 38 | -------------------------------------------------------------------------------- /app/api/npm-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var config = require('app/config'), 4 | request = require('request'), 5 | JSONStream = require('JSONStream'), 6 | eventStream = require('event-stream'), 7 | qs = require('querystring'); 8 | 9 | function getError(err, res) { 10 | return err || (res.statusCode !== 200 ? 'HTTP ' + res.statusCode : null); 11 | } 12 | 13 | var registryUrl = 'https://registry.npmjs.org', 14 | viewsPath = '-/_view', 15 | keywordView = 'byKeyword', 16 | keyword = config['npm-keyword'], 17 | dlCountUrl = 'https://api.npmjs.org/downloads/point/last-week', 18 | isFetching = false; 19 | 20 | function parseModule(mod) { 21 | return { 22 | name: mod[1], 23 | description: mod[2] 24 | }; 25 | } 26 | 27 | var NpmApi = { 28 | 29 | getModules: function(callback) { 30 | var url = [registryUrl, viewsPath, keywordView].join('/') + '?' + qs.stringify({ 31 | startkey: '["' + keyword + '"]', 32 | endkey: '["' + keyword + '",{}]', 33 | group_level: 3 34 | }); 35 | 36 | var onComplete = function(err, modules) { 37 | isFetching = false; 38 | callback(err, modules); 39 | }; 40 | 41 | isFetching = true; 42 | request({ url: url }) 43 | .on('error', function(err) { console.error('Error fetching modules: ', err); }) 44 | .pipe(JSONStream.parse('rows.*.key')) 45 | .pipe(eventStream.mapSync(parseModule)) 46 | .pipe(eventStream.map(NpmApi.getModuleInfo)) 47 | .on('error', function(err) { console.error('Error fetching module info: ', err); }) 48 | .pipe(eventStream.map(NpmApi.getModuleDownloadCount)) 49 | .on('error', function(err) { console.error('Error fetching module download count: ', err); }) 50 | .pipe(eventStream.writeArray(onComplete)); 51 | }, 52 | 53 | getModuleDownloadCount: function(module, callback) { 54 | var moduleName = module.name ? module.name : module, 55 | url = [dlCountUrl, encodeURIComponent(moduleName)].join('/'); 56 | 57 | request({ url: url, json: true }, function(err, res, response) { 58 | module.downloads = (response || {}).downloads; 59 | callback(getError(err, res), module); 60 | }); 61 | }, 62 | 63 | getModuleInfo: function(module, callback) { 64 | var moduleName = module.name ? module.name : module, 65 | url = [registryUrl, moduleName].join('/'); 66 | 67 | request({ url: url, json: true }, function(err, res, body) { 68 | err = getError(err, res); 69 | if (typeof body !== 'object') { 70 | err = 'Body was not an object'; 71 | } 72 | 73 | callback(err, body); 74 | }); 75 | } 76 | }; 77 | 78 | module.exports = NpmApi; 79 | -------------------------------------------------------------------------------- /app/components/component-item.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var ComponentLink = require('app/components/component-link.jsx'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'ComponentItem', 9 | 10 | propTypes: { 11 | component: React.PropTypes.object.isRequired 12 | }, 13 | 14 | /* jshint quotmark:false, newcap:false */ 15 | render: function() { 16 | return ( 17 |
  • 18 | 19 |
  • 20 | ); 21 | } 22 | }); -------------------------------------------------------------------------------- /app/components/component-link.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var config = require('app/config'); 6 | var router = require('app/router'); 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'ComponentLink', 10 | 11 | getUrl: function() { 12 | return '/component/' + encodeURIComponent(this.props.component.name); 13 | }, 14 | 15 | onClick: function(e) { 16 | // If trying to open a new window, fall back 17 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.button !== 0) { 18 | return; 19 | } 20 | 21 | e.preventDefault(); 22 | 23 | var pageTitle = this.props.component.name + ' - ' + config['page-title']; 24 | history.pushState({}, pageTitle, e.target.href); 25 | router.locationChanged(); 26 | 27 | if (window.URL && window.ga) { 28 | var url = new URL(e.target.href); 29 | window.ga('send', { 30 | hitType: 'pageview', 31 | page: url.pathname, 32 | title: pageTitle 33 | }); 34 | } 35 | }, 36 | 37 | /* jshint quotmark:false, newcap:false */ 38 | render: function() { 39 | return ( 40 | 41 | {this.props.children || this.props.component.name} 42 | 43 | ); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /app/components/component-list.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var ComponentItem = require('app/components/component-item.jsx'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'LatestComponents', 9 | 10 | propTypes: { 11 | components: React.PropTypes.array.isRequired, 12 | listName: React.PropTypes.string.isRequired, 13 | className: React.PropTypes.string 14 | }, 15 | 16 | getDefaultProps: function() { 17 | return { 18 | className: 'component-list' 19 | }; 20 | }, 21 | 22 | getComponentItem: function(c) { 23 | return new ComponentItem({ 24 | key: c.name, 25 | component: c 26 | }); 27 | }, 28 | 29 | /* jshint quotmark:false, newcap:false */ 30 | render: function() { 31 | return ( 32 |
    33 |

    {this.props.listName}

    34 | 35 | 38 |
    39 | ); 40 | } 41 | }); -------------------------------------------------------------------------------- /app/components/container.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | 6 | module.exports = React.createClass({ 7 | displayName: 'Container', 8 | 9 | /* jshint trailing:false, quotmark:false, newcap:false */ 10 | render: function() { 11 | return this.transferPropsTo( 12 |
    {this.props.children}
    13 | ); 14 | } 15 | }); -------------------------------------------------------------------------------- /app/components/footer.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | 6 | module.exports = React.createClass({ 7 | displayName: 'Footer', 8 | 9 | /* jshint quotmark:false, newcap:false */ 10 | render: function() { 11 | return ( 12 | 15 | ); 16 | } 17 | }); -------------------------------------------------------------------------------- /app/components/home-link.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var config = require('app/config'); 6 | var router = require('app/router'); 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'HomeLink', 10 | 11 | goHome: function(e) { 12 | // If trying to open a new window, fall back 13 | if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.button === 2) { 14 | return; 15 | } 16 | 17 | e.preventDefault(); 18 | 19 | var pageTitle = config['page-title']; 20 | history.pushState({}, pageTitle, e.currentTarget.href); 21 | router.locationChanged(); 22 | 23 | if (window.URL && window.ga) { 24 | var url = new URL(e.currentTarget.href); 25 | window.ga('send', { 26 | hitType: 'pageview', 27 | page: url.pathname, 28 | title: pageTitle 29 | }); 30 | } 31 | }, 32 | 33 | /* jshint trailing:false, quotmark:false, newcap:false */ 34 | render: function() { 35 | return ( 36 | {this.props.children} 37 | ); 38 | } 39 | }); -------------------------------------------------------------------------------- /app/components/layout.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var config = require('app/config'); 6 | var Footer = require('app/components/footer.jsx'); 7 | var Container = require('app/components/container.jsx'); 8 | var ReactLogo = require('app/components/react-logo.jsx'); 9 | var HomeLink = require('app/components/home-link.jsx'); 10 | var SearchInput = require('app/components/search-input.jsx'); 11 | 12 | module.exports = React.createClass({ 13 | displayName: 'Layout', 14 | 15 | /* jshint quotmark:false, newcap:false */ 16 | render: function() { 17 | return ( 18 |
    19 |
    20 | 21 | 22 |

    {config['page-title']}

    23 | 24 | 25 |
    26 |
    27 | 28 |
    29 | 30 | {this.props.children} 31 | 32 |
    33 | 34 |
    36 | ); 37 | } 38 | }); -------------------------------------------------------------------------------- /app/components/loader.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | 6 | module.exports = React.createClass({ 7 | displayName: 'Loader', 8 | 9 | /* jshint quotmark:false, newcap:false */ 10 | render: function() { 11 | return ( 12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 | ); 20 | } 21 | }); -------------------------------------------------------------------------------- /app/components/markdown-readme.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | var _ = require('lodash'); 4 | var React = require('react'); 5 | var marked = require('marked'); 6 | var config = require('app/config'); 7 | var escapeRegex = require('app/util/escape-regex'); 8 | var getGithubAccount = require('app/util/github-account'); 9 | var codeMirror = typeof window === 'undefined' ? function() {} : window.CodeMirror; 10 | 11 | var mirrorOptions = { 12 | lineNumbers: false, 13 | lineWrapping: true, 14 | smartIndent: false, 15 | matchBrackets: true, 16 | theme: 'solarized-light', 17 | readOnly: true 18 | }; 19 | 20 | module.exports = React.createClass({ 21 | displayName: 'MarkdownReadme', 22 | 23 | componentDidMount: function() { 24 | // Apply CodeMirror to multi-line code elements 25 | var codeEls = this.getDOMNode().querySelectorAll('pre > code'), preEl, lang; 26 | for (var i = 0; i < codeEls.length; i++) { 27 | preEl = codeEls[i].parentNode; 28 | lang = (codeEls[i].getAttribute('class') || ''); 29 | lang = lang.replace(/.*?lang\-(.*)/, '$1').split(/\s+/)[0]; 30 | 31 | codeMirror(function(elt) { 32 | preEl.parentNode.replaceChild(elt, preEl); 33 | }, _.merge({ 34 | value: codeEls[i].innerText.trim(), 35 | mode: config['codemirror-modes'][lang] || 'javascript' 36 | }, mirrorOptions)); 37 | } 38 | }, 39 | 40 | fixRelativeUrls: function(html) { 41 | var branch = '/blob/' + this.props.component.branch; 42 | var githubUrl = this.getGithubUrl() + branch, matches; 43 | var matcher = /', 'g'), 54 | '' 55 | ); 56 | } 57 | 58 | return html; 59 | }, 60 | 61 | getGithubUrl: function() { 62 | var account = getGithubAccount(this.props.component); 63 | if (!account) { 64 | return false; 65 | } 66 | 67 | return 'https://github.com/' + account; 68 | }, 69 | 70 | /* jshint quotmark:false, newcap:false */ 71 | render: function() { 72 | var html = this.fixRelativeUrls(marked(this.props.component.readme)); 73 | return ( 74 |
    78 | ); 79 | } 80 | }); -------------------------------------------------------------------------------- /app/components/no-result.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | 6 | module.exports = React.createClass({ 7 | displayName: 'NoResult', 8 | 9 | /* jshint quotmark:false, newcap:false */ 10 | render: function() { 11 | return ( 12 | 13 | 14 | Your search did not return any results, unfortunately. 15 | 16 | 17 | ); 18 | } 19 | }); -------------------------------------------------------------------------------- /app/components/react-logo.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var React = require('react'); 5 | 6 | module.exports = React.createClass({ 7 | displayName: 'ReactLogo', 8 | 9 | /* jshint trailing:false, quotmark:false, newcap:false */ 10 | render: function() { 11 | var classNames = ['react-logo'].concat(this.props.className); 12 | 13 | return React.DOM.img(_.merge({}, this.props, { 14 | src: '/img/react.svg', 15 | alt: '', 16 | className: classNames.join(' ') 17 | })); 18 | } 19 | }); -------------------------------------------------------------------------------- /app/components/results-item.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var ComponentLink = require('app/components/component-link.jsx'); 6 | 7 | module.exports = React.createClass({ 8 | displayName: 'SearchResultItem', 9 | 10 | /* jshint quotmark:false, newcap:false */ 11 | render: function() { 12 | return ( 13 | 14 | 15 | 16 |

    {this.props.component.description}

    17 | 18 |
    19 |
    20 |
    {this.props.component.stars || 0}
    21 | 22 |
    23 |
    {this.props.component.modified.fromNow()}
    24 | 25 |
    26 |
    {this.props.component.author}
    27 |
    28 | 29 | {this.props.component.author} 30 | {this.props.component.stars || 0} 31 | {this.props.component.modified.fromNow()} 32 | 33 | ); 34 | } 35 | }); -------------------------------------------------------------------------------- /app/components/results-table.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var SearchResult = require('app/components/results-item.jsx'); 6 | var NoResult = require('app/components/no-result.jsx'); 7 | 8 | module.exports = React.createClass({ 9 | displayName: 'SearchResultsTable', 10 | 11 | getComponentItem: function(item) { 12 | return new SearchResult({ 13 | key: item.name, 14 | component: item 15 | }); 16 | }, 17 | 18 | getSearchResults: function() { 19 | return this.props.results.map(this.getComponentItem); 20 | }, 21 | 22 | sortByName: function(e) { 23 | this.sortBy(e, 'name'); 24 | }, 25 | 26 | sortByAuthor: function(e) { 27 | this.sortBy(e, 'author'); 28 | }, 29 | 30 | sortByStars: function(e) { 31 | this.sortBy(e, 'stars'); 32 | }, 33 | 34 | sortByUpdated: function(e) { 35 | this.sortBy(e, 'modified'); 36 | }, 37 | 38 | sortBy: function(e, by) { 39 | e.preventDefault(); 40 | this.props.onSortClicked(by); 41 | }, 42 | 43 | /* jshint quotmark:false, newcap:false */ 44 | render: function() { 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | { this.props.results.length ? 57 | this.getSearchResults() : 58 | 59 | } 60 | 61 |
    NameAuthorStarsUpdated
    62 | ); 63 | } 64 | }); -------------------------------------------------------------------------------- /app/components/search-input.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var React = require('react'); 6 | var config = require('app/config'); 7 | var router = require('app/router'); 8 | 9 | module.exports = React.createClass({ 10 | displayName: 'SearchInput', 11 | 12 | propTypes: { 13 | autoFocus: React.PropTypes.bool, 14 | placeholder: React.PropTypes.string, 15 | query: React.PropTypes.string 16 | }, 17 | 18 | getDefaultProps: function() { 19 | return { 20 | autoFocus: true, 21 | placeholder: 'Component name, keyword or similar', 22 | query: '' 23 | }; 24 | }, 25 | 26 | getInitialState: function() { 27 | return { 28 | query: this.props.query 29 | }; 30 | }, 31 | 32 | componentDidMount: function() { 33 | // Use to bring up the "looking glass"-icon 34 | this.getDOMNode().setAttribute('results', 5); 35 | 36 | this.changeQueryDebounced = _.debounce(this.changeQuery, 250); 37 | 38 | // Focus the END of the input (if it has a value and autofocus is set to true) 39 | if (this.props.query && this.props.autoFocus) { 40 | this.moveCaretToEnd(); 41 | } 42 | }, 43 | 44 | getPageTitle: function(query) { 45 | return (query ? (query + ' - ') : '') + config['page-title']; 46 | }, 47 | 48 | moveCaretToEnd: function() { 49 | var el = this.getDOMNode(); 50 | if (typeof el.selectionStart === 'number') { 51 | el.selectionStart = el.selectionEnd = el.value.length; 52 | } else if (typeof el.createTextRange !== 'undefined') { 53 | el.focus(); 54 | var range = el.createTextRange(); 55 | range.collapse(false); 56 | range.select(); 57 | } 58 | }, 59 | 60 | onQueryChange: function(e) { 61 | this.changeQueryDebounced(e.target.value.replace(/^\s+$/g, '')); 62 | }, 63 | 64 | changeQuery: function(query) { 65 | var state = { query: query }, 66 | url = state.query ? '/search/' + encodeURIComponent(state.query) : '/', 67 | title = this.getPageTitle(state.query); 68 | 69 | if (this.state.query) { 70 | history.replaceState(state, title, url); 71 | } else { 72 | history.pushState(state, title, url); 73 | } 74 | 75 | router.locationChanged(); 76 | 77 | window.document.title = title; 78 | this.setState(state); 79 | }, 80 | 81 | /* jshint trailing:false, quotmark:false, newcap:false */ 82 | render: function() { 83 | return ( 84 | 92 | ); 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /app/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'page-title': 'React Components', 3 | 'npm-keyword': 'react-component', 4 | 'exclude-keywords': ['react', 'react-component'], 5 | 'codemirror-modes': { 6 | 'cs': 'coffeescript', 7 | 'coffeescript': 'coffeescript', 8 | 'coffee': 'coffeescript', 9 | 'css': 'css', 10 | 'html': 'htmlmixed', 11 | 'javascript': 'javscript', 12 | 'js': 'javascript', 13 | 'perl': 'perl', 14 | 'pl': 'perl', 15 | 'php': 'php', 16 | 'ruby': 'ruby', 17 | 'rb': 'rb', 18 | 'shell': 'shell', 19 | 'sh': 'shell', 20 | 'bash': 'shell', 21 | 'batch': 'shell', 22 | 'xml': 'xml', 23 | 'yaml': 'yaml' 24 | }, 25 | 'github': { 26 | 'type': 'oauth', 27 | 'key': process.env.GITHUB_KEY, 28 | 'secret': process.env.GITHUB_SECRET 29 | }, 30 | 'redis': { 31 | 'databaseNumber': 1 32 | } 33 | }; -------------------------------------------------------------------------------- /app/controllers/components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var moment = require('moment'); 5 | var ComponentsStore = require('app/stores/components-store'); 6 | var dateFormat = 'ddd, DD MMM YYYY HH:mm:ss [GMT]'; // "Sun, 31 Aug 2014 12:32:57 GMT" 7 | 8 | module.exports = { 9 | 10 | componentsList: function(request, reply) { 11 | var summaries = ComponentsStore.getSummaries(); 12 | var response = { 13 | keys: _.keys(summaries[0] || {}), 14 | items: _.map(summaries, _.values) 15 | }; 16 | 17 | reply(response) 18 | .header('Last-Modified', new Date(ComponentsStore.getLastUpdated()).toGMTString()); 19 | }, 20 | 21 | componentInfo: function(request, reply) { 22 | var component = ComponentsStore.get(request.params.component); 23 | if (!component) { 24 | return reply('Component not found').code(404); 25 | } 26 | 27 | reply(_.omit(component, 'versions')) 28 | .header('Last-Modified', moment.utc(component.time.modified).format(dateFormat)) 29 | .header('Cache-Control', 's-max-age=600, must-revalidate, public'); 30 | } 31 | 32 | }; -------------------------------------------------------------------------------- /app/database/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var config = require('app/config'); 5 | var redis = require('redis'); 6 | var async = require('async'); 7 | var client; 8 | 9 | function getDb() { 10 | if (client) { 11 | return client; 12 | } 13 | 14 | client = redis.createClient(); 15 | client.select(config.redis.databaseNumber); 16 | 17 | client.on('error', function(err) { 18 | console.error('Redis error: ', err); 19 | }); 20 | 21 | return client; 22 | } 23 | 24 | function getModuleNames(callback) { 25 | getDb().smembers('module:list', callback); 26 | } 27 | 28 | function getModule(name, callback) { 29 | getDb().get('module:info:' + name, function(err, data) { 30 | callback(err, err ? null : jsonParse(data)); 31 | }); 32 | } 33 | 34 | function getModuleWithStars(name, callback) { 35 | var stars = 0, module, error; 36 | var done = _.after(2, function() { 37 | module.starCount = stars; 38 | callback(error, module); 39 | }); 40 | 41 | getModule(name, function(err, data) { 42 | module = data; 43 | error = err; 44 | done(); 45 | }); 46 | 47 | getModuleStars(name, function(err, starCount) { 48 | stars = starCount | 0; 49 | done(); 50 | }); 51 | } 52 | 53 | function getModules(callback) { 54 | getModuleNames(function(err, names) { 55 | if (err) { 56 | return callback(err); 57 | } 58 | 59 | async.map(names, getModuleWithStars, callback); 60 | }); 61 | } 62 | 63 | function setModule(module, callback) { 64 | var client = getDb(); 65 | 66 | var error, done = _.after(2, callback); 67 | var setComplete = function(err) { 68 | error = error || err; 69 | done(error); 70 | }; 71 | 72 | client.set('module:info:' + module._id, JSON.stringify(module), setComplete); 73 | client.sadd('module:list', module._id, setComplete); 74 | } 75 | 76 | function getModuleStars(name, callback) { 77 | getDb().get('module:stars:' + name, callback); 78 | } 79 | 80 | function setModuleStars(name, stars, callback) { 81 | getDb().set('module:stars:' + name, stars, callback); 82 | } 83 | 84 | function unref() { 85 | if (client) { 86 | client.unref(); 87 | } 88 | } 89 | 90 | function quit() { 91 | if (client) { 92 | client.quit(); 93 | } 94 | } 95 | 96 | function jsonParse(json) { 97 | try { 98 | return JSON.parse(json); 99 | } catch (e) { 100 | return null; 101 | } 102 | } 103 | 104 | module.exports = { 105 | getModuleNames: getModuleNames, 106 | getModules: getModules, 107 | getModule: getModule, 108 | setModule: setModule, 109 | 110 | getModuleStars: getModuleStars, 111 | setModuleStars: setModuleStars, 112 | 113 | getModuleWithStars: getModuleWithStars, 114 | 115 | unref: unref, 116 | quit: quit 117 | }; -------------------------------------------------------------------------------- /app/pages/component-info.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var Reflux = require('reflux'); 6 | var config = require('app/config'); 7 | var Loader = require('app/components/loader.jsx'); 8 | var Layout = require('app/components/layout.jsx'); 9 | var MarkdownReadme = require('app/components/markdown-readme.jsx'); 10 | var ComponentStore = require('app/stores/components-store'); 11 | var getGithubAccount = require('app/util/github-account'); 12 | var numFormat = require('app/util/number-formatter'); 13 | 14 | function getStateFromStores(name) { 15 | return { 16 | componentInfo: ComponentStore.get(name), 17 | componentSummary: ComponentStore.getSummary(name) 18 | }; 19 | } 20 | 21 | module.exports = React.createClass({ 22 | displayName: 'ComponentInfo', 23 | 24 | mixins: [Reflux.ListenerMixin], 25 | 26 | getInitialState: function() { 27 | return getStateFromStores(this.props.route.componentName); 28 | }, 29 | 30 | componentDidMount: function() { 31 | this.listenTo(ComponentStore, this.onComponentInfoChanged); 32 | }, 33 | 34 | onComponentInfoChanged: function() { 35 | this.setState(getStateFromStores(this.props.route.componentName)); 36 | 37 | window.document.title = this.getPageTitle(); 38 | }, 39 | 40 | getPageTitle: function() { 41 | var componentName = this.props.route.componentName; 42 | return componentName + ' - ' + config['page-title']; 43 | }, 44 | 45 | getGithubUrl: function() { 46 | var account = getGithubAccount(this.state.componentInfo); 47 | if (!account) { 48 | return false; 49 | } 50 | 51 | return 'https://github.com/' + account; 52 | }, 53 | 54 | /* jshint quotmark:false, newcap:false */ 55 | getHomepageButton: function() { 56 | var githubUrl = this.getGithubUrl(); 57 | var homePageUrl = this.state.componentInfo.homepage || ''; 58 | 59 | if (homePageUrl.match(/https?:\/\//i) && githubUrl !== homePageUrl) { 60 | return ( 61 |
    62 | Homepage 63 | 64 | ); 65 | } 66 | 67 | return null; 68 | }, 69 | 70 | /* jshint quotmark:false, newcap:false */ 71 | getGithubButton: function() { 72 | var githubUrl = this.getGithubUrl(); 73 | if (!githubUrl) { 74 | return null; 75 | } 76 | 77 | return ( 78 | 79 | GitHub 80 | 81 | ); 82 | }, 83 | 84 | /* jshint quotmark:false, newcap:false */ 85 | getGithubStarsButton: function() { 86 | var githubUrl = this.getGithubUrl(); 87 | 88 | if (!githubUrl) { 89 | return null; 90 | } 91 | 92 | return ( 93 | 94 | { numFormat(this.state.componentSummary.stars || 0) } 95 | 96 | ); 97 | }, 98 | 99 | /* jshint quotmark:false, newcap:false */ 100 | getDownloadsButton: function() { 101 | return ( 102 | 103 | { numFormat(this.state.componentSummary.downloads || 0) } 104 | 105 | ); 106 | }, 107 | 108 | /* jshint quotmark:false, newcap:false */ 109 | render: function() { 110 | return ( 111 | 112 | { 113 | this.state.componentInfo ? 114 |
    115 |
    116 | {this.state.componentInfo._id} 117 |
    by {this.state.componentInfo.author.name}
    118 |
    119 |
    120 | 128 | 129 |
    : 130 | 131 | } 132 |
    133 | ); 134 | } 135 | }); 136 | -------------------------------------------------------------------------------- /app/pages/front.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var React = require('react'); 5 | var Reflux = require('reflux'); 6 | var Layout = require('app/components/layout.jsx'); 7 | var ComponentList = require('app/components/component-list.jsx'); 8 | var ComponentStore = require('app/stores/components-store'); 9 | 10 | function getStateFromStores() { 11 | return { 12 | recentlyCreated: ComponentStore.getMostRecentlyCreated(), 13 | recentlyUpdated: ComponentStore.getMostRecentlyUpdated(), 14 | mostStarred: ComponentStore.getMostStarred() 15 | }; 16 | } 17 | 18 | var FrontPage = React.createClass({ 19 | displayName: 'FrontPage', 20 | 21 | mixins: [Reflux.ListenerMixin], 22 | 23 | getInitialState: function() { 24 | return getStateFromStores(); 25 | }, 26 | 27 | componentDidMount: function() { 28 | this.listenTo(ComponentStore, this.onComponentsChanged); 29 | }, 30 | 31 | onComponentsChanged: function() { 32 | this.setState(getStateFromStores); 33 | }, 34 | 35 | /* jshint trailing:false, quotmark:false, newcap:false */ 36 | render: function() { 37 | return ( 38 | 39 |

    Searchable database of React components

    40 |
    41 | 42 |
    43 | 48 | 49 | 54 | 55 | 60 |
    61 | 62 |
    63 |

    How it works

    64 |

    65 | Every module registered on NPM using the keyword react-component will show up in the list. 66 | It really is that simple. 67 |

    68 | 69 |

    How do I add my component to the list?

    70 |
      71 |
    1. Ensure your package.json file contains an array of keywords which includes react-component.
    2. 72 |
    3. Publish your component to NPM (learn how at npmjs.org).
    4. 73 |
    5. Wait for it to show up! Shouldn't take longer than 10-15 minutes.
    6. 74 |
    75 | 76 |

    Missing any features?

    77 |

    Let us know! We're always looking for ways to improve.

    78 | 79 |

    Who made this? Can I contribute?

    80 |

    81 | Developed and currently hosted by VaffelNinja, but it's an open-source, MIT-licensed solution. 82 |

    83 |

    84 | Contributions are very welcome! 85 | Please make sure you read the contribution guidelines. 86 |

    87 |
    88 | 89 |
    90 | ); 91 | } 92 | }); 93 | 94 | module.exports = FrontPage; -------------------------------------------------------------------------------- /app/pages/search.jsx: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var React = require('react'); 6 | var Layout = require('app/components/layout.jsx'); 7 | var ResultsTable = require('app/components/results-table.jsx'); 8 | var SearchFilter = require('app/search/filter'); 9 | 10 | module.exports = React.createClass({ 11 | displayName: 'SearchPage', 12 | 13 | getInitialState: function() { 14 | return { 15 | sortBy: 'score', 16 | sortOrder: 'desc' 17 | }; 18 | }, 19 | 20 | getSearchResults: function() { 21 | var results = SearchFilter.filter(this.props.route.query), 22 | sorted = _.sortBy(results, this.state.sortBy); 23 | 24 | return this.state.sortOrder === 'desc' ? sorted.reverse() : sorted; 25 | }, 26 | 27 | shouldComponentUpdate: function(nextProps, nextState) { 28 | return ( 29 | this.props.route.query !== nextProps.route.query || 30 | this.state.sortBy !== nextState.sortBy || 31 | this.state.sortOrder !== nextState.sortOrder 32 | ); 33 | }, 34 | 35 | onSortClicked: function(sortBy) { 36 | var state = { sortBy: sortBy }; 37 | 38 | if (sortBy === this.state.sortBy) { 39 | // Sort by the same prop? Reverse order 40 | state.sortOrder = this.state.sortOrder === 'asc' ? 'desc' : 'asc'; 41 | } else if (sortBy === 'stars' || sortBy === 'modified') { 42 | // We usually want to see stars and modified dates in descending order 43 | state.sortOrder = 'desc'; 44 | } 45 | 46 | this.setState(state); 47 | }, 48 | 49 | /* jshint quotmark:false, newcap:false */ 50 | render: function() { 51 | return ( 52 | 53 | 54 | 55 | ); 56 | } 57 | }); -------------------------------------------------------------------------------- /app/react/renderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var fs = require('fs'); 5 | var App = require('app/root'); 6 | var React = require('react/addons'); 7 | var isDev = process.env.NODE_ENV === 'development'; 8 | var crawlerMatcher = /yahoo|msnbot|yandex|googlebot|bing|ask/i; 9 | var templates = {}; 10 | 11 | module.exports = function(request, params, template) { 12 | var isCrawler = (request.headers['user-agent'] || '').match(crawlerMatcher); 13 | var app = new App({ path: request.url.path }); 14 | var body = ''; 15 | 16 | if (isCrawler) { 17 | body = React.renderComponentToStaticMarkup(app); 18 | } else { 19 | body = React.renderComponentToString(app); 20 | } 21 | 22 | // Set content of the page 23 | params.page.body = body; 24 | 25 | // In dev-mode, re-read the template each time (to allow changes) 26 | if (isDev || !templates[template]) { 27 | templates[template] = _.template( 28 | fs.readFileSync(template, { encoding: 'utf8' }) 29 | ); 30 | } 31 | 32 | // Render template 33 | var rendered = templates[template](params); 34 | 35 | // Inject livereload? 36 | return isDev ? liveReloadify(request, rendered) : rendered; 37 | }; 38 | 39 | function liveReloadify(request, rendered) { 40 | var port = 35729, 41 | host = request.headers.host.replace(/:\d+/, ''), 42 | url = '//' + host + ':' + port + '/livereload.js?snipver=1', 43 | snippet = ''; 44 | 45 | return rendered.replace(/<\/body>/, function(w) { 46 | return snippet + w; 47 | }); 48 | } -------------------------------------------------------------------------------- /app/root.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | 'use strict'; 3 | 4 | var _ = require('lodash'); 5 | var React = require('react/addons'); 6 | var Reflux = require('reflux'); 7 | var router = require('app/router'); 8 | var isBrowser = typeof window !== 'undefined'; 9 | 10 | var RoutingActions = require('app/actions/routing'); 11 | var ComponentStore = require('app/stores/components-store'); 12 | var ComponentApi = require('app/api/components-api'); 13 | var SearchIndex = require('app/search/filter'); 14 | 15 | // We'll want to use react-router when server-side rendering is ready 16 | router.setRoutes(require('app/routes')); 17 | 18 | var App = React.createClass({ 19 | displayName: 'App', 20 | 21 | mixins: [Reflux.ListenerMixin], 22 | 23 | propTypes: { 24 | path: React.PropTypes.string.isRequired 25 | }, 26 | 27 | getInitialState: function() { 28 | return { 29 | path: false 30 | }; 31 | }, 32 | 33 | componentDidMount: function() { 34 | window.addEventListener('popstate', this.onLocationChanged, false); 35 | 36 | this.listenTo(RoutingActions.locationChange, this.onLocationChanged); 37 | }, 38 | 39 | componentWillUnmount: function() { 40 | window.removeEventListener('popstate', this.onLocationChanged, false); 41 | }, 42 | 43 | onLocationChanged: function() { 44 | this.setState({ path: window.location.pathname + window.location.search }); 45 | }, 46 | 47 | render: function() { 48 | var match = router.match(this.state.path || this.props.path); 49 | 50 | if (!match) { 51 | console.error('Could not match URL (' + this.props.path + ')'); 52 | return null; // @todo render error-view? 53 | } 54 | 55 | return new match.page(_.merge({}, this.state, { 56 | query: match.query || {}, 57 | route: match.route || {} 58 | })); 59 | } 60 | }); 61 | 62 | // Have the API and search index listen for dispatcher events 63 | ComponentApi.listen(); 64 | SearchIndex.listen(); 65 | 66 | if (isBrowser) { 67 | // Allow React to leak into global namespace - enables devtools etc 68 | window.React = React; 69 | 70 | // Wait for components list to be ready 71 | ComponentStore.listen(function() { 72 | // Render the app once the components list is ready 73 | // (Normally, we'd just show a "loading"-state, but since 74 | // we're rendering on server side...) 75 | React.renderComponent(new App({ 76 | path: window.location.pathname + window.location.search 77 | }), document.getElementById('root')); 78 | }); 79 | } 80 | 81 | // Prime component store 82 | ComponentStore.populateFromDatabase(); 83 | 84 | // Have component store fetch new components every once in a while 85 | setInterval( 86 | ComponentStore.populateFromDatabase.bind(ComponentStore), 87 | isBrowser ? 1000 * 60 * 15 : 1000 * 60 * 5 // 15 minutes in browser, 5 on server 88 | ); 89 | 90 | module.exports = App; -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var queryParser = /(?:^|&)([^&=]*)=?([^&]*)/g; 4 | var RoutingActions = require('app/actions/routing'); 5 | var map = {}; 6 | 7 | var Router = { 8 | setRoutes: function(routeMap) { 9 | map = routeMap; 10 | }, 11 | 12 | match: function(url) { 13 | var parts = url.split('?'), 14 | qs = parts[1] || '', 15 | path = parts[0], 16 | query = {}, 17 | pattern, params; 18 | 19 | qs.replace(queryParser, function ($0, $1, $2) { 20 | if ($1) { 21 | query[$1] = $2; 22 | } 23 | }); 24 | 25 | for (pattern in map) { 26 | params = matchPattern(pattern, path); 27 | 28 | if (params) { 29 | return { 30 | route: params, 31 | query: query, 32 | page : map[pattern] 33 | }; 34 | } 35 | } 36 | 37 | return null; 38 | }, 39 | 40 | locationChanged: function() { 41 | RoutingActions.locationChange(); 42 | } 43 | }; 44 | 45 | module.exports = Router; 46 | 47 | function matchPattern(pattern, url) { 48 | var vars = pattern.match(/(:[a-zA-Z0-9]+)/g), 49 | re = new RegExp('^' + pattern.replace(/(:[a-zA-Z0-9]+)/g, '(.*?)') + '$'), 50 | matches = url.match(re), 51 | params = {}, 52 | varname; 53 | 54 | if (!matches) { 55 | return null; 56 | } 57 | 58 | for (var i = 1; i < matches.length; i++) { 59 | varname = vars[i - 1].substring(1); 60 | params[varname] = decodeURIComponent(matches[i]); 61 | } 62 | 63 | return params; 64 | } -------------------------------------------------------------------------------- /app/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Front = require('app/pages/front.jsx'); 4 | var Search = require('app/pages/search.jsx'); 5 | var ComponentInfo = require('app/pages/component-info.jsx'); 6 | 7 | module.exports = { 8 | '/': Front, 9 | '/search/:query': Search, 10 | '/component/:componentName': ComponentInfo 11 | }; -------------------------------------------------------------------------------- /app/search/filter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require('lodash'); 3 | var lunr = require('lunr'); 4 | var ComponentStore = require('app/stores/components-store'); 5 | var index = getNewIndex(); 6 | 7 | function getNewIndex() { 8 | return lunr(function() { 9 | this.field('name', { boost: 10 }); 10 | this.field('keywords', { boost: 5 }); 11 | this.field('description'); 12 | }); 13 | } 14 | 15 | var SearchFilter = { 16 | listen: function() { 17 | ComponentStore.listen(this.onComponentsChanged); 18 | }, 19 | 20 | onComponentsChanged: function() { 21 | var components = ComponentStore.getSummaries(); 22 | 23 | // Reset search index 24 | index = getNewIndex(); 25 | 26 | // Add components 27 | components.map(this.indexComponent); 28 | }, 29 | 30 | indexComponent: function(mod, id) { 31 | index.add({ 32 | id: id, 33 | name: mod.name, 34 | keywords: mod.keywords.join(' '), 35 | description: mod.description 36 | }); 37 | }, 38 | 39 | filter: function(query) { 40 | var summaries = ComponentStore.getSummaries(); 41 | return index.search(query).map(function(match) { 42 | return _.merge(summaries[match.ref], match); 43 | }); 44 | } 45 | }; 46 | 47 | module.exports = _.bindAll(SearchFilter); -------------------------------------------------------------------------------- /app/server/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var basePath = require('path').resolve(__dirname + '/../../'); 4 | var _ = require('lodash'); 5 | var config = require('app/config'); 6 | var isDev = process.env.NODE_ENV === 'development'; 7 | var render = require('app/react/renderer'); 8 | var template = basePath + '/templates/default.html'; 9 | var pkgInfo = require(basePath + '/package.json'); 10 | var params = { 11 | 'package': pkgInfo, 12 | 'page': { 13 | description: pkgInfo.description, 14 | title: 'React Components' 15 | } 16 | }; 17 | 18 | var controllers = { 19 | components: require('app/controllers/components') 20 | }; 21 | 22 | var stores = { 23 | components: require('app/stores/components-store') 24 | }; 25 | 26 | function getPageTitle(query) { 27 | if (!query) { 28 | return params.page.title; 29 | } 30 | 31 | return query + ' - ' + params.page.title; 32 | } 33 | 34 | function handleRequest(request, reply) { 35 | var reqParams = _.merge({}, params, { 36 | page: { 37 | title: getPageTitle(request.params.query || request.params.component) 38 | } 39 | }); 40 | 41 | var liveReloadSrc = isDev ? ' localhost:35729' : ''; 42 | var analyticsSrc = ' http://www.google-analytics.com https://www.google-analytics.com'; 43 | 44 | reply(render(request, reqParams, template)) 45 | .header('X-UA-Compatible', 'IE=edge,chrome=1') 46 | .header('Cache-Control', 'public, must-revalidate, max-age=150') 47 | .header('Content-Security-Policy', [ 48 | 'script-src \'self\'' + liveReloadSrc + analyticsSrc, 49 | 'frame-src \'none\'', 50 | 'object-src \'none\'' 51 | ].join(';')); 52 | } 53 | 54 | module.exports = function(server) { 55 | server.route({ 56 | method: 'GET', 57 | path: '/', 58 | handler: handleRequest 59 | }); 60 | 61 | server.route({ 62 | method: 'GET', 63 | path: '/search/{query}', 64 | handler: handleRequest 65 | }); 66 | 67 | server.route({ 68 | method: 'GET', 69 | path: '/component/{component}', 70 | handler: handleRequest 71 | }); 72 | 73 | server.route({ 74 | method: 'GET', 75 | path: '/api/components', 76 | config: { 77 | handler: controllers.components.componentsList, 78 | cache: { expiresIn: config['poll-interval'], privacy: 'public' } 79 | } 80 | }); 81 | 82 | server.route({ 83 | method: 'GET', 84 | path: '/api/components/{component}', 85 | config: { 86 | handler: controllers.components.componentInfo 87 | } 88 | }); 89 | 90 | server.route({ 91 | method: 'GET', 92 | path: '/{param*}', 93 | config: { 94 | handler: { 95 | directory: { 96 | path: 'public', 97 | lookupCompressed: true 98 | } 99 | }, 100 | cache: { expiresIn: 3600 * 1000, privacy: 'public' } 101 | } 102 | }); 103 | }; -------------------------------------------------------------------------------- /app/stores/component-store.shared.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var config = require('app/config'); 5 | 6 | module.exports = { 7 | 8 | getSummary: function(name) { 9 | return _.find(this.componentSummaries, { name: name }); 10 | }, 11 | 12 | getAll: function() { 13 | return this.components; 14 | }, 15 | 16 | getSummaries: function() { 17 | return this.componentSummaries; 18 | }, 19 | 20 | isUncommonKeyword: function(keyword) { 21 | return config['exclude-keywords'].indexOf(keyword) === -1; 22 | } 23 | 24 | }; -------------------------------------------------------------------------------- /app/stores/components-store.browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var moment = require('moment'); 5 | var Reflux = require('reflux'); 6 | var sharedMethods = require('app/stores/component-store.shared'); 7 | var ApiActions = require('app/actions/api'); 8 | 9 | var ComponentStore = Reflux.createStore(_.merge({}, sharedMethods, { 10 | init: function() { 11 | this.components = {}; 12 | this.componentSummaries = []; 13 | 14 | this.listenTo(ApiActions.componentsFetched, this.populate); 15 | this.listenTo(ApiActions.componentFetched, this.addComponentInfo); 16 | }, 17 | 18 | get: function(name) { 19 | if (this.components[name]) { 20 | return this.components[name]; 21 | } 22 | 23 | ApiActions.fetchComponentInfo(name); 24 | }, 25 | 26 | getMostRecentlyCreated: function(limit) { 27 | return ( 28 | _.sortBy(this.componentSummaries, 'created') 29 | .reverse() 30 | .slice(0, limit || 10) 31 | ); 32 | }, 33 | 34 | getMostRecentlyUpdated: function(limit) { 35 | var mostRecent = this.getMostRecentlyCreated(); 36 | var lastUpdated = _.sortBy(this.componentSummaries, 'modified').reverse(); 37 | 38 | return _.without.apply(null, 39 | [lastUpdated].concat(mostRecent) 40 | ).slice(0, limit || 10); 41 | }, 42 | 43 | getMostStarred: function(limit) { 44 | return ( 45 | _.sortBy(this.componentSummaries, 'stars') 46 | .reverse() 47 | .slice(0, limit || 10) 48 | ); 49 | }, 50 | 51 | populateFromDatabase: function() { 52 | ApiActions.fetchComponents(); 53 | }, 54 | 55 | populate: function(components) { 56 | components.map(this.addComponent); 57 | this.trigger('change'); 58 | }, 59 | 60 | parseComponent: function(component) { 61 | component.modified = moment.utc(component.modified); 62 | component.created = moment.utc(component.created); 63 | 64 | return component; 65 | }, 66 | 67 | addComponent: function(component) { 68 | component = this.parseComponent(component); 69 | 70 | this.componentSummaries.push(component); 71 | }, 72 | 73 | addComponentInfo: function(component) { 74 | this.components[component.name] = component; 75 | this.trigger('change'); 76 | } 77 | })); 78 | 79 | module.exports = _.bindAll(ComponentStore); -------------------------------------------------------------------------------- /app/stores/components-store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var moment = require('moment'); 5 | var Reflux = require('reflux'); 6 | var db = require('app/database'); 7 | var winston = require('winston'); 8 | 9 | var sharedMethods = require('app/stores/component-store.shared'); 10 | 11 | var ComponentStore = Reflux.createStore(_.merge({}, sharedMethods, { 12 | components: {}, 13 | componentSummaries: [], 14 | lastUpdated: Date.now(), 15 | 16 | get: function(name) { 17 | return this.components[name]; 18 | }, 19 | 20 | getLastUpdated: function() { 21 | return this.lastUpdated; 22 | }, 23 | 24 | getMostRecentlyCreated: function(limit) { 25 | return ( 26 | _.sortBy(this.components, 'created') 27 | .reverse() 28 | .slice(0, limit || 10) 29 | ); 30 | }, 31 | 32 | getMostRecentlyUpdated: function(limit) { 33 | var mostRecent = this.getMostRecentlyCreated(); 34 | var lastUpdated = _.sortBy(this.components, 'modified').reverse(); 35 | 36 | return _.without.apply(null, 37 | [lastUpdated].concat(mostRecent) 38 | ).slice(0, limit || 10); 39 | }, 40 | 41 | getMostStarred: function(limit) { 42 | return ( 43 | _.sortBy(this.componentSummaries, 'stars') 44 | .reverse() 45 | .slice(0, limit || 10) 46 | ); 47 | }, 48 | 49 | populateFromDatabase: function() { 50 | var addComponent = this.addComponent.bind(this); 51 | 52 | db.getModules(function(err, modules) { 53 | if (err || !modules || !modules.length) { 54 | return winston.error( 55 | 'Failed to fetch modules from DB: ' + 56 | (err || 'No modules returned') 57 | ); 58 | } 59 | 60 | modules.forEach(addComponent); 61 | 62 | this.trigger('change'); 63 | this.lastUpdated = Date.now(); 64 | }.bind(this)); 65 | }, 66 | 67 | parseAuthor: function(component) { 68 | var distTags = component['dist-tags'] || {}, 69 | latest = component.versions[distTags.latest] || {}; 70 | 71 | return (latest._npmUser || component.author).name; 72 | }, 73 | 74 | parseComponentSummary: function(component) { 75 | return { 76 | name: component.name, 77 | description: component.description, 78 | author: this.parseAuthor(component), 79 | modified: moment.utc(component.time.modified), 80 | created: moment.utc(component.time.created), 81 | keywords: (component.keywords || []).filter(this.isUncommonKeyword), 82 | downloads: component.downloads || 0, 83 | stars: component.starCount 84 | }; 85 | }, 86 | 87 | parseComponent: function(component) { 88 | var distTags = component['dist-tags'] || {}, 89 | versions = component.versions || {}, 90 | latest = versions[distTags.latest] || {}; 91 | 92 | if (!component.time) { 93 | component.created = '1980-01-01T00:00:00.000Z'; 94 | component.modified = '1980-01-01T00:00:00.000Z'; 95 | 96 | winston.warn( 97 | 'Component with name "' + component.name + '" has no time settings' 98 | ); 99 | } else { 100 | component.created = component.time.created; 101 | component.modified = component.time.modified; 102 | } 103 | 104 | component.branch = latest.gitHead || 'master'; 105 | 106 | return component; 107 | }, 108 | 109 | addComponent: function(component) { 110 | this.components[component.name] = this.parseComponent(component); 111 | 112 | var existing = _.find(this.componentSummaries, { name: component.name }); 113 | _.pull(this.componentSummaries, existing); 114 | 115 | this.componentSummaries.push(this.parseComponentSummary(component)); 116 | } 117 | })); 118 | 119 | module.exports = ComponentStore; 120 | -------------------------------------------------------------------------------- /app/util/deep.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function deep(obj, prop) { 4 | var segs = prop.split('.'); 5 | while (segs.length) { 6 | obj = obj[segs.shift()]; 7 | } 8 | return obj; 9 | } 10 | 11 | module.exports = function(prop) { 12 | return function(item) { 13 | return deep(item, prop); 14 | }; 15 | }; -------------------------------------------------------------------------------- /app/util/escape-regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function escapeRegExp(str) { 4 | return ('' + str).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); 5 | }; -------------------------------------------------------------------------------- /app/util/github-account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var GithubRegex = /github\.com[\/:](.*?\/.*?)(\?|\/|\.git$)/i; 4 | 5 | module.exports = function(info) { 6 | var repo = (info.repository || {}).url, 7 | page = info.homepage, 8 | bugs = (info.bugs || {}).url, 9 | parts = [repo, page, bugs].filter(Boolean); 10 | 11 | var i = parts.length, matches; 12 | while (i--) { 13 | matches = parts[i].match(GithubRegex); 14 | if (matches && matches[1]) { 15 | return matches[1]; 16 | } 17 | } 18 | 19 | return false; 20 | }; -------------------------------------------------------------------------------- /app/util/number-formatter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sepThousands = /\B(?=(?:\d{3})+(?!\d))/g; 4 | 5 | module.exports = function(num) { 6 | if (num < 1000) { 7 | return num; 8 | } 9 | 10 | if (num < 10000) { 11 | return (num + '').replace(sepThousands, ' '); 12 | } 13 | 14 | if (num < 1000000) { 15 | return Math.floor(num / 1000) + 'K'; 16 | } 17 | 18 | return (num / 1000000).toFixed(1) + 'M'; 19 | }; -------------------------------------------------------------------------------- /cron/fetch-components.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var NpmApi = require('app/api/npm-api'); 5 | var db = require('app/database'); 6 | 7 | NpmApi.getModules(function(err, modules) { 8 | if (err) { 9 | console.error('Module fetching failed: ', err); 10 | return; 11 | } 12 | 13 | var done = _.after(modules.length, function() { 14 | db.quit(); 15 | }); 16 | 17 | modules.forEach(function(module) { 18 | db.setModule(module, done); 19 | }); 20 | }); -------------------------------------------------------------------------------- /cron/fetch-stars.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var debug = require('debug')('cron'); 4 | var db = require('app/database'); 5 | var async = require('async'); 6 | var GithubApi = require('app/api/github-api'); 7 | 8 | db.getModules(function(err, modules) { 9 | if (err) { 10 | console.error('Failed to fetch modules from DB: ', err); 11 | db.quit(); 12 | return; 13 | } 14 | 15 | async.map(modules, getStarCountAndUpdate, function(err) { 16 | db.quit(); 17 | 18 | if (err) { 19 | console.error('Failed to update DB: ', err); 20 | } 21 | }); 22 | }); 23 | 24 | function getStarCountAndUpdate(module, callback) { 25 | GithubApi.getStarCountForModule(module, function(err, starCount) { 26 | if (err) { 27 | debug('Failed to fetch star count for module "%s" - %s', module.name, err.message); 28 | return callback(); 29 | } 30 | 31 | debug('Star count for module "%s": %d', module.name, starCount); 32 | db.setModuleStars(module._id, starCount, callback); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /data/purpose.md: -------------------------------------------------------------------------------- 1 | # What's this folder for? 2 | 3 | Basically, it caches data. 4 | Github stars, NPM download counts, things like this. 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var production = process.env.NODE_ENV === 'production'; 4 | var _ = require('lodash'); 5 | var rimraf = require('gulp-rimraf'); 6 | var webpackConfig = require('./webpack.config'); 7 | var webpack = require('webpack'); 8 | var gzip = require('gulp-gzip'); 9 | var gutil = require('gulp-util'); 10 | var less = require('gulp-less'); 11 | var path = require('path'); 12 | var gulp = require('gulp'); 13 | 14 | if (!production) { 15 | var livereload = require('gulp-livereload'); 16 | var sourcemaps = require('gulp-sourcemaps'); 17 | } 18 | 19 | gulp.task('default', ['less', 'webpack:build', 'clean-compressed']); 20 | gulp.task('watch', ['serve', 'webpack:build-dev'], function() { 21 | livereload.listen(); 22 | 23 | gulp.watch(['app/**/*'], ['webpack:build-dev']); 24 | gulp.watch(['public/less/**/*.less'], ['less']); 25 | gulp.watch(['public/css/**/*.css']).on('change', livereload.changed); 26 | }); 27 | 28 | gulp.task('serve', function() { 29 | require('./index'); 30 | }); 31 | 32 | gulp.task('less', function() { 33 | var lessc = less({ 34 | paths: [ path.join(__dirname, 'public', 'less', 'includes') ] 35 | }); 36 | 37 | lessc.on('error',function(e){ 38 | gutil.log(e); 39 | lessc.end(); 40 | }); 41 | 42 | if (production) { 43 | return gulp.src('./public/less/components.less') 44 | .pipe(lessc) 45 | .pipe(gulp.dest('./public/css')); 46 | } else { 47 | return gulp.src('./public/less/components.less') 48 | .pipe(sourcemaps.init()) 49 | .pipe(lessc) 50 | .pipe(sourcemaps.write()) 51 | .pipe(gulp.dest('./public/css')); 52 | } 53 | }); 54 | 55 | gulp.task('clean-compressed', function() { 56 | return gulp.src('./public/**/*.gz', { read: false }).pipe(rimraf()); 57 | }); 58 | 59 | gulp.task('compress-css', function() { 60 | return gulp.src('./public/css/*.css') 61 | .pipe(gzip()) 62 | .pipe(gulp.dest('./public/css')); 63 | }); 64 | 65 | gulp.task('compress-dist', function() { 66 | return gulp.src('./public/dist/bundle.js') 67 | .pipe(gzip()) 68 | .pipe(gulp.dest('./public/dist')); 69 | }); 70 | 71 | gulp.task('compress-js', function() { 72 | return gulp.src('./public/js/*.js') 73 | .pipe(gzip()) 74 | .pipe(gulp.dest('./public/js')); 75 | }); 76 | 77 | gulp.task('compress', ['compress-css', 'compress-dist', 'compress-js']); 78 | 79 | gulp.task('webpack:build', function(callback) { 80 | var config = Object.create(webpackConfig); 81 | config.plugins = config.plugins.concat( 82 | new webpack.DefinePlugin({ 83 | 'process.env': { 84 | 'NODE_ENV': JSON.stringify('production'), 85 | } 86 | }), 87 | new webpack.optimize.DedupePlugin(), 88 | new webpack.optimize.UglifyJsPlugin() 89 | ); 90 | 91 | webpack(config, function(err, stats) { 92 | if (err) { 93 | throw new gutil.PluginError('webpack:build', err); 94 | } 95 | 96 | gutil.log('[webpack:build]', stats.toString({ 97 | colors: true 98 | })); 99 | 100 | callback(); 101 | }); 102 | }); 103 | 104 | var devConfig = _.merge({}, webpackConfig, { 105 | devtool: 'source-map', 106 | debug: true 107 | }), devCompiler = webpack(devConfig); 108 | 109 | gulp.task('webpack:build-dev', function(callback) { 110 | devCompiler.run(function(err, stats) { 111 | if (err) { 112 | throw new gutil.PluginError('webpack:build-dev', err); 113 | } 114 | 115 | gutil.log('[webpack:build-dev]', stats.toString({ 116 | colors: true 117 | })); 118 | 119 | callback(); 120 | }); 121 | }); 122 | 123 | /** Current unused - might try it again at some point 124 | 125 | gulp.task('webpack-dev-server', function() { 126 | var config = Object.create(webpackConfig); 127 | config.devtool = 'eval'; 128 | config.debug = true; 129 | 130 | // Start a webpack-dev-server 131 | new WebpackDevServer(webpack(config), { 132 | publicPath: '/' + config.output.publicPath, 133 | stats: { 134 | colors: true 135 | } 136 | }).listen(8080, 'localhost', function(err) { 137 | if (err) { 138 | throw new gutil.PluginError('webpack-dev-server', err); 139 | } 140 | 141 | gutil.log('[webpack-dev-server]', 'http://localhost:8080/webpack-dev-server/index.html'); 142 | }); 143 | }); 144 | 145 | **/ 146 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('node-jsx').install({ extension: '.jsx' }); 4 | 5 | var isDev = process.env.NODE_ENV === 'development'; 6 | var winston = require('winston'); 7 | var Pushover = require('winston-pushover').Pushover; 8 | 9 | if (!isDev) { 10 | var pushOpts = { 11 | userKey: process.env.PUSHOVER_USER_KEY, 12 | token: process.env.PUSHOVER_TOKEN, 13 | handleExceptions: true 14 | }; 15 | 16 | winston.add(Pushover, pushOpts); 17 | winston.handleExceptions(new Pushover(pushOpts)); 18 | } 19 | 20 | var _ = require('lodash'); 21 | var Hapi = require('hapi'); 22 | var server = new Hapi.Server(process.env.REACT_COMPONENTS_PORT || 3000); 23 | var render = require('app/react/renderer'); 24 | var pkgInfo = require('./package.json'); 25 | var tpl = function(file) { return __dirname + '/templates/' + file + '.html'; }; 26 | var params = { 27 | 'package': pkgInfo, 28 | 'page': { 29 | description: pkgInfo.description, 30 | title: 'React Components' 31 | }, 32 | 'resources': { 33 | css: [ 34 | '/css/pure-min.css', 35 | '/css/codemirror.css', 36 | '/css/components.css' 37 | ], 38 | js: [ 39 | '/js/codemirror-compressed.js', 40 | '/js/analytics.js', 41 | '/dist/vendor.bundle.js', 42 | '/dist/bundle.js', 43 | ] 44 | } 45 | }; 46 | 47 | var controllers = { 48 | components: require('app/controllers/components') 49 | }; 50 | 51 | function getPageTitle(query) { 52 | if (!query) { 53 | return params.page.title; 54 | } 55 | 56 | return query + ' - ' + params.page.title; 57 | } 58 | 59 | function handleRequest(request, reply) { 60 | var reqParams = _.merge({}, params, { 61 | page: { 62 | title: getPageTitle(request.params.query || request.params.component) 63 | } 64 | }); 65 | 66 | var liveReloadSrc = isDev ? ' localhost:35729' : ''; 67 | var analyticsSrc = ' http://www.google-analytics.com https://www.google-analytics.com'; 68 | 69 | reply(render(request, reqParams, tpl('default'))) 70 | .header('X-UA-Compatible', 'IE=edge,chrome=1') 71 | .header('Cache-Control', 'public, must-revalidate, max-age=150') 72 | .header('Content-Security-Policy', [ 73 | 'script-src \'self\'' + liveReloadSrc + analyticsSrc, 74 | 'frame-src \'none\'', 75 | 'object-src \'none\'' 76 | ].join(';')); 77 | } 78 | 79 | server.route({ 80 | method: 'GET', 81 | path: '/', 82 | handler: handleRequest 83 | }); 84 | 85 | server.route({ 86 | method: 'GET', 87 | path: '/search/{query}', 88 | handler: handleRequest 89 | }); 90 | 91 | server.route({ 92 | method: 'GET', 93 | path: '/component/{component}', 94 | handler: handleRequest 95 | }); 96 | 97 | server.route({ 98 | method: 'GET', 99 | path: '/api/components', 100 | config: { 101 | handler: controllers.components.componentsList, 102 | cache: { expiresIn: 1000 * 60 * 5, privacy: 'public' } 103 | } 104 | }); 105 | 106 | server.route({ 107 | method: 'GET', 108 | path: '/api/components/{component}', 109 | config: { 110 | handler: controllers.components.componentInfo 111 | } 112 | }); 113 | 114 | server.route({ 115 | method: 'GET', 116 | path: '/{param*}', 117 | config: { 118 | handler: { 119 | directory: { 120 | path: 'public', 121 | lookupCompressed: true 122 | } 123 | }, 124 | cache: { expiresIn: 3600 * 1000, privacy: 'public' } 125 | } 126 | }); 127 | 128 | server.start(function() { 129 | console.log('Server running at:', server.info.uri); 130 | }); 131 | -------------------------------------------------------------------------------- /node_modules/app: -------------------------------------------------------------------------------- 1 | ../app/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-components", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "Searchable repository of React-components", 6 | "main": "index.js", 7 | "scripts": { 8 | "fetch:database": "echo Fetching database. Please wait. && node cron/fetch-components.js && echo Database downloaded.", 9 | "test": "node test/*.test.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:vaffel/react-components.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "components", 18 | "repository", 19 | "registry" 20 | ], 21 | "browser": { 22 | "request": "xhr", 23 | "app/stores/components-store": "app/stores/components-store.browser", 24 | "lodash": "./public/js/lodash.min" 25 | }, 26 | "author": "Espen Hovlandsdal ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/vaffel/react-components/issues" 30 | }, 31 | "homepage": "https://github.com/vaffel/react-components", 32 | "dependencies": { 33 | "JSONStream": "^0.9.0", 34 | "async": "^0.9.0", 35 | "debug": "^2.2.0", 36 | "event-stream": "^3.1.7", 37 | "github": "^0.2.1", 38 | "gulp": "^3.8.7", 39 | "gulp-gzip": "^1.2.0", 40 | "gulp-less": "^1.3.5", 41 | "gulp-rimraf": "^0.1.0", 42 | "gulp-util": "^3.0.0", 43 | "gulp-webpack": "^0.3.0", 44 | "hapi": "^6.5.1", 45 | "hiredis": "^0.4.1", 46 | "jsx-loader": "^0.11.0", 47 | "lodash": "^2.4.1", 48 | "lru-cache": "^2.5.0", 49 | "lunr": "^0.5.5", 50 | "marked": "^0.3.2", 51 | "moment": "^2.8.2", 52 | "node-jsx": "^0.11.0", 53 | "react": "^0.11.1", 54 | "redis": "^0.12.1", 55 | "reflux": "^0.1.7", 56 | "request": "^2.40.0", 57 | "webpack": "^1.3.3-beta2", 58 | "winston": "^0.8.3", 59 | "winston-pushover": "^0.1.7", 60 | "xhr": "^1.15.0" 61 | }, 62 | "devDependencies": { 63 | "gulp-sourcemaps": "^1.1.1", 64 | "gulp-livereload": "^2.1.1", 65 | "webpack-dev-server": "^1.4.10" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/css/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | font-size: 1.2em; 7 | line-height: 1.4em !important; 8 | } 9 | .CodeMirror-scroll { 10 | /* Set scrolling behaviour here */ 11 | overflow: auto; 12 | } 13 | 14 | /* PADDING */ 15 | 16 | .CodeMirror-lines { 17 | padding: 14px 0; /* Vertical padding around content */ 18 | } 19 | .CodeMirror pre { 20 | padding: 0 14px; /* Horizontal padding of content */ 21 | } 22 | 23 | .CodeMirror-scrollbar-filler { 24 | background-color: white; /* The little square between H and V scrollbars */ 25 | } 26 | 27 | /* GUTTER */ 28 | 29 | .CodeMirror-gutters { 30 | border-right: 1px solid #ddd; 31 | background-color: #f7f7f7; 32 | } 33 | .CodeMirror-linenumbers {} 34 | .CodeMirror-linenumber { 35 | padding: 0 3px 0 5px; 36 | min-width: 20px; 37 | text-align: right; 38 | color: #999; 39 | } 40 | 41 | /* CURSOR */ 42 | 43 | .CodeMirror div.CodeMirror-cursor { 44 | border-left: 1px solid black; 45 | } 46 | /* Shown when moving in bi-directional text */ 47 | .CodeMirror div.CodeMirror-secondarycursor { 48 | border-left: 1px solid silver; 49 | } 50 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor { 51 | width: auto; 52 | border: 0; 53 | background: transparent; 54 | background: rgba(0, 200, 0, .4); 55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); 56 | } 57 | /* Kludge to turn off filter in ie9+, which also accepts rgba */ 58 | .CodeMirror.cm-keymap-fat-cursor div.CodeMirror-cursor:not(#nonsense_id) { 59 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 60 | } 61 | /* Can style cursor different in overwrite (non-insert) mode */ 62 | .CodeMirror div.CodeMirror-cursor.CodeMirror-overwrite {} 63 | 64 | /* DEFAULT THEME */ 65 | 66 | .cm-s-default .cm-keyword {color: #708;} 67 | .cm-s-default .cm-atom {color: #219;} 68 | .cm-s-default .cm-number {color: #164;} 69 | .cm-s-default .cm-def {color: #00f;} 70 | .cm-s-default .cm-variable {color: black;} 71 | .cm-s-default .cm-variable-2 {color: #05a;} 72 | .cm-s-default .cm-variable-3 {color: #085;} 73 | .cm-s-default .cm-property {color: black;} 74 | .cm-s-default .cm-operator {color: black;} 75 | .cm-s-default .cm-comment {color: #a50;} 76 | .cm-s-default .cm-string {color: #a11;} 77 | .cm-s-default .cm-string-2 {color: #f50;} 78 | .cm-s-default .cm-meta {color: #555;} 79 | .cm-s-default .cm-error {color: #f00;} 80 | .cm-s-default .cm-qualifier {color: #555;} 81 | .cm-s-default .cm-builtin {color: #30a;} 82 | .cm-s-default .cm-bracket {color: #997;} 83 | .cm-s-default .cm-tag {color: #170;} 84 | .cm-s-default .cm-attribute {color: #00c;} 85 | .cm-s-default .cm-header {color: blue;} 86 | .cm-s-default .cm-quote {color: #090;} 87 | .cm-s-default .cm-hr {color: #999;} 88 | .cm-s-default .cm-link {color: #00c;} 89 | 90 | .cm-negative {color: #d44;} 91 | .cm-positive {color: #292;} 92 | .cm-header, .cm-strong {font-weight: bold;} 93 | .cm-em {font-style: italic;} 94 | .cm-emstrong {font-style: italic; font-weight: bold;} 95 | .cm-link {text-decoration: underline;} 96 | 97 | .cm-invalidchar {color: #f00;} 98 | 99 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 100 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 101 | 102 | /* STOP */ 103 | 104 | /* The rest of this file contains styles related to the mechanics of 105 | the editor. You probably shouldn't touch them. */ 106 | 107 | .CodeMirror { 108 | line-height: 1; 109 | position: relative; 110 | overflow: hidden; 111 | } 112 | 113 | .CodeMirror-scroll { 114 | /* 30px is the magic margin used to hide the element's real scrollbars */ 115 | /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ 116 | margin-bottom: -30px; margin-right: -30px; 117 | padding-bottom: 30px; padding-right: 30px; 118 | height: 100%; 119 | outline: none; /* Prevent dragging from highlighting the element */ 120 | position: relative; 121 | } 122 | .CodeMirror-sizer { 123 | position: relative; 124 | } 125 | 126 | /* The fake, visible scrollbars. Used to force redraw during scrolling 127 | before actuall scrolling happens, thus preventing shaking and 128 | flickering artifacts. */ 129 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { 130 | position: absolute; 131 | z-index: 6; 132 | display: none; 133 | } 134 | .CodeMirror-vscrollbar { 135 | right: 0; top: 0; 136 | overflow-x: hidden; 137 | overflow-y: scroll; 138 | } 139 | .CodeMirror-hscrollbar { 140 | bottom: 0; left: 0; 141 | overflow-y: hidden; 142 | overflow-x: scroll; 143 | } 144 | .CodeMirror-scrollbar-filler { 145 | right: 0; bottom: 0; 146 | z-index: 6; 147 | } 148 | 149 | .CodeMirror-gutters { 150 | position: absolute; left: 0; top: 0; 151 | height: 100%; 152 | z-index: 3; 153 | } 154 | .CodeMirror-gutter { 155 | height: 100%; 156 | display: inline-block; 157 | /* Hack to make IE7 behave */ 158 | *zoom:1; 159 | *display:inline; 160 | } 161 | .CodeMirror-gutter-elt { 162 | position: absolute; 163 | cursor: default; 164 | z-index: 4; 165 | } 166 | 167 | .CodeMirror-lines { 168 | cursor: text; 169 | } 170 | .CodeMirror pre { 171 | /* Reset some styles that the rest of the page might have set */ 172 | -moz-border-radius: 0; -webkit-border-radius: 0; -o-border-radius: 0; border-radius: 0; 173 | border-width: 0; 174 | background: transparent; 175 | font-family: inherit; 176 | font-size: inherit; 177 | margin: 0; 178 | white-space: pre; 179 | word-wrap: normal; 180 | line-height: inherit; 181 | color: inherit; 182 | z-index: 2; 183 | position: relative; 184 | overflow: visible; 185 | } 186 | .CodeMirror-wrap pre { 187 | word-wrap: break-word; 188 | white-space: pre-wrap; 189 | word-break: normal; 190 | } 191 | .CodeMirror-linebackground { 192 | position: absolute; 193 | left: 0; right: 0; top: 0; bottom: 0; 194 | z-index: 0; 195 | } 196 | 197 | .CodeMirror-linewidget { 198 | position: relative; 199 | z-index: 2; 200 | overflow: auto; 201 | } 202 | 203 | .CodeMirror-wrap .CodeMirror-scroll { 204 | overflow-x: hidden; 205 | } 206 | 207 | .CodeMirror-measure { 208 | position: absolute; 209 | width: 100%; height: 0px; 210 | overflow: hidden; 211 | visibility: hidden; 212 | } 213 | .CodeMirror-measure pre { position: static; } 214 | 215 | .CodeMirror div.CodeMirror-cursor { 216 | position: absolute; 217 | visibility: hidden; 218 | border-right: none; 219 | width: 0; 220 | } 221 | .CodeMirror-focused div.CodeMirror-cursor { 222 | visibility: visible; 223 | } 224 | 225 | .CodeMirror-selected { background: #d9d9d9; } 226 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 227 | 228 | .cm-searching { 229 | background: #ffa; 230 | background: rgba(255, 255, 0, .4); 231 | } 232 | 233 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 234 | .CodeMirror span { *vertical-align: text-bottom; } 235 | 236 | @media print { 237 | /* Hide the cursor when printing */ 238 | .CodeMirror div.CodeMirror-cursor { 239 | visibility: hidden; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /public/css/components.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | } 6 | body { 7 | background: #f9f9f9; 8 | color: #484848; 9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | } 11 | a { 12 | color: #c05b4d; 13 | text-decoration: none; 14 | } 15 | a:hover { 16 | text-decoration: underline; 17 | } 18 | code { 19 | background: rgba(0, 0, 0, 0.04); 20 | color: #555555; 21 | padding: 0.25em; 22 | margin: 0 0.25em; 23 | } 24 | .container { 25 | max-width: 900px; 26 | margin: 0 auto; 27 | } 28 | header { 29 | background: #2d2d2d; 30 | color: #e9e9e9; 31 | padding: 10px 0; 32 | min-height: 180px; 33 | font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif; 34 | } 35 | @media only screen and (max-width: 400px) { 36 | header { 37 | padding-bottom: 0; 38 | margin-bottom: 0; 39 | min-height: 160px; 40 | } 41 | } 42 | header h1, 43 | header h2, 44 | header h3, 45 | header a { 46 | color: #61dafb; 47 | } 48 | header a:hover { 49 | text-decoration: none; 50 | } 51 | header h1 { 52 | font-size: 60px; 53 | font-weight: normal; 54 | margin: 0.1em 0; 55 | padding-top: 0.3em; 56 | } 57 | @media only screen and (max-width: 620px) { 58 | header h1 { 59 | font-size: 50px; 60 | } 61 | } 62 | @media only screen and (max-width: 555px) { 63 | header h1 { 64 | font-size: 33.33333333px; 65 | margin-top: -5px; 66 | } 67 | } 68 | header .react-logo { 69 | width: 180px; 70 | height: 180px; 71 | float: left; 72 | margin-right: 22.5px; 73 | } 74 | @media only screen and (max-width: 555px) { 75 | header .react-logo { 76 | width: 100px; 77 | height: 100px; 78 | margin-left: 5px; 79 | } 80 | } 81 | header .container:before, 82 | header .container:after { 83 | content: ' '; 84 | display: table; 85 | } 86 | header .container:after { 87 | clear: both; 88 | } 89 | main h1 { 90 | text-align: center; 91 | } 92 | main h2, 93 | main h3 { 94 | font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif; 95 | font-weight: normal; 96 | } 97 | main hr { 98 | height: 0; 99 | border-top: 1px solid #bbb; 100 | border-bottom: 1px solid #eee; 101 | margin: 0 auto; 102 | } 103 | main .emphasize { 104 | font-weight: bold; 105 | font-style: italic; 106 | } 107 | main .container { 108 | padding: 10px; 109 | margin-top: 20px; 110 | margin-bottom: 20px; 111 | background: #ffffff; 112 | border: 1px solid rgba(16, 16, 16, 0.1); 113 | border-radius: 3px; 114 | } 115 | input.search { 116 | font-size: 18px; 117 | padding: 12px 0; 118 | } 119 | @media only screen and (min-width: 580px) { 120 | header input.search { 121 | width: 580px; 122 | } 123 | } 124 | @media only screen and (max-width: 580px) { 125 | header input.search { 126 | width: 100%; 127 | } 128 | } 129 | @media only screen and (max-width: 812px) { 130 | header input.search { 131 | width: 85%; 132 | display: block; 133 | margin: 0 auto; 134 | } 135 | } 136 | .loader { 137 | width: 150px; 138 | height: 60px; 139 | margin: 30px auto 0 auto; 140 | position: relative; 141 | } 142 | .loader .dot { 143 | width: 30px; 144 | height: 30px; 145 | background: #61dafb; 146 | border-radius: 50%; 147 | position: absolute; 148 | -webkit-animation: loading-anim 2s linear infinite; 149 | -moz-animation: loading-anim 2s linear infinite; 150 | -ms-animation: loading-anim 2s linear infinite; 151 | -o-animation: loading-anim 2s linear infinite; 152 | animation: loading-anim 2s linear infinite; 153 | } 154 | .loader .dot:nth-child(2) { 155 | -webkit-animation: loading-anim 2s linear infinite -0.4s; 156 | -moz-animation: loading-anim 2s linear infinite -0.4s; 157 | -ms-animation: loading-anim 2s linear infinite -0.4s; 158 | -o-animation: loading-anim 2s linear infinite -0.4s; 159 | animation: loading-anim 2s linear infinite -0.4s; 160 | } 161 | .loader .dot:nth-child(3) { 162 | -webkit-animation: loading-anim 2s linear infinite -0.8s; 163 | -moz-animation: loading-anim 2s linear infinite -0.8s; 164 | -ms-animation: loading-anim 2s linear infinite -0.8s; 165 | -o-animation: loading-anim 2s linear infinite -0.8s; 166 | animation: loading-anim 2s linear infinite -0.8s; 167 | } 168 | .loader .dot:nth-child(4) { 169 | -webkit-animation: loading-anim 2s linear infinite -1.2s; 170 | -moz-animation: loading-anim 2s linear infinite -1.2s; 171 | -ms-animation: loading-anim 2s linear infinite -1.2s; 172 | -o-animation: loading-anim 2s linear infinite -1.2s; 173 | animation: loading-anim 2s linear infinite -1.2s; 174 | } 175 | .loader .dot:nth-child(5) { 176 | -webkit-animation: loading-anim 2s linear infinite -1.6s; 177 | -moz-animation: loading-anim 2s linear infinite -1.6s; 178 | -ms-animation: loading-anim 2s linear infinite -1.6s; 179 | -o-animation: loading-anim 2s linear infinite -1.6s; 180 | animation: loading-anim 2s linear infinite -1.6s; 181 | } 182 | @-webkit-keyframes loading-anim { 183 | 0% { 184 | left: 100px; 185 | top: 0; 186 | } 187 | 80% { 188 | left: 0; 189 | top: 0; 190 | } 191 | 85% { 192 | left: 0; 193 | top: -20px; 194 | width: 20px; 195 | height: 20px; 196 | } 197 | 90% { 198 | width: 40px; 199 | height: 15px; 200 | } 201 | 95% { 202 | left: 100px; 203 | top: -20px; 204 | width: 20px; 205 | height: 20px; 206 | } 207 | 100% { 208 | left: 100px; 209 | top: 0; 210 | } 211 | } 212 | @-moz-keyframes loading-anim { 213 | 0% { 214 | left: 100px; 215 | top: 0; 216 | } 217 | 80% { 218 | left: 0; 219 | top: 0; 220 | } 221 | 85% { 222 | left: 0; 223 | top: -20px; 224 | width: 20px; 225 | height: 20px; 226 | } 227 | 90% { 228 | width: 40px; 229 | height: 15px; 230 | } 231 | 95% { 232 | left: 100px; 233 | top: -20px; 234 | width: 20px; 235 | height: 20px; 236 | } 237 | 100% { 238 | left: 100px; 239 | top: 0; 240 | } 241 | } 242 | @-ms-keyframes loading-anim { 243 | 0% { 244 | left: 100px; 245 | top: 0; 246 | } 247 | 80% { 248 | left: 0; 249 | top: 0; 250 | } 251 | 85% { 252 | left: 0; 253 | top: -20px; 254 | width: 20px; 255 | height: 20px; 256 | } 257 | 90% { 258 | width: 40px; 259 | height: 15px; 260 | } 261 | 95% { 262 | left: 100px; 263 | top: -20px; 264 | width: 20px; 265 | height: 20px; 266 | } 267 | 100% { 268 | left: 100px; 269 | top: 0; 270 | } 271 | } 272 | @-oi-keyframes loading-anim { 273 | 0% { 274 | left: 100px; 275 | top: 0; 276 | } 277 | 80% { 278 | left: 0; 279 | top: 0; 280 | } 281 | 85% { 282 | left: 0; 283 | top: -20px; 284 | width: 20px; 285 | height: 20px; 286 | } 287 | 90% { 288 | width: 40px; 289 | height: 15px; 290 | } 291 | 95% { 292 | left: 100px; 293 | top: -20px; 294 | width: 20px; 295 | height: 20px; 296 | } 297 | 100% { 298 | left: 100px; 299 | top: 0; 300 | } 301 | } 302 | @keyframes loading-anim { 303 | 0% { 304 | left: 100px; 305 | top: 0; 306 | } 307 | 80% { 308 | left: 0; 309 | top: 0; 310 | } 311 | 85% { 312 | left: 0; 313 | top: -20px; 314 | width: 20px; 315 | height: 20px; 316 | } 317 | 90% { 318 | width: 40px; 319 | height: 15px; 320 | } 321 | 95% { 322 | left: 100px; 323 | top: -20px; 324 | width: 20px; 325 | height: 20px; 326 | } 327 | 100% { 328 | left: 100px; 329 | top: 0; 330 | } 331 | } 332 | .front li { 333 | margin: 15px; 334 | } 335 | .front h2 { 336 | margin: 40px 0 10px 0; 337 | } 338 | @media only screen and (min-width: 650px) { 339 | .front .faq, 340 | .front .component-lists { 341 | float: right; 342 | width: 60%; 343 | margin: 0 3%; 344 | } 345 | .front .component-lists { 346 | width: 28%; 347 | } 348 | } 349 | .front .faq { 350 | line-height: 1.45em; 351 | } 352 | .front:after, 353 | .front:before { 354 | display: table; 355 | content: ' '; 356 | } 357 | .front:after { 358 | clear: both; 359 | } 360 | .component-lists ul { 361 | padding: 0 0 0 10px; 362 | } 363 | .results-table { 364 | width: 100%; 365 | border: 0; 366 | } 367 | .results-table thead { 368 | background: transparent; 369 | font-size: 1.25em; 370 | } 371 | .results-table th { 372 | padding-left: 15px; 373 | } 374 | .results-table th a { 375 | display: block; 376 | } 377 | .results-table th.updated { 378 | width: 15%; 379 | } 380 | .results-table th.stars { 381 | width: 8%; 382 | } 383 | .results-table td { 384 | border-color: #ebebeb; 385 | } 386 | .results-table .component-name { 387 | font-size: 1.35em; 388 | display: block; 389 | margin: 13px 0; 390 | } 391 | .results-table .no-result { 392 | padding: 50px 0; 393 | text-align: center; 394 | font-size: 1.35em; 395 | } 396 | .results-table .component-meta { 397 | display: none; 398 | } 399 | @media only screen and (max-width: 500px) { 400 | .results-table th, 401 | .results-table td { 402 | display: none; 403 | } 404 | .results-table th:first-child, 405 | .results-table td:first-child { 406 | display: table-cell; 407 | padding: 0; 408 | } 409 | .results-table .component-meta { 410 | display: block; 411 | } 412 | .results-table .component-meta dt { 413 | display: inline-block; 414 | } 415 | .results-table .component-meta dd { 416 | display: inline-block; 417 | margin: 0 10px 0 5px; 418 | } 419 | } 420 | @media only screen and (max-width: 650px) { 421 | .results-table td:nth-child(2), 422 | .results-table th:nth-child(2) { 423 | display: none; 424 | } 425 | } 426 | .cm-s-solarized-light { 427 | background-color: #f8f5ec; 428 | color: #637c84; 429 | } 430 | .cm-s-solarized-light .emphasis { 431 | font-weight: bold; 432 | } 433 | .cm-s-solarized-light .dotted { 434 | border-bottom: 1px dotted #cb4b16; 435 | } 436 | .cm-s-solarized-light .CodeMirror-gutter { 437 | background-color: #eee8d5; 438 | border-right: 3px solid #eee8d5; 439 | } 440 | .cm-s-solarized-light .CodeMirror-gutter .CodeMirror-gutter-text { 441 | color: #93a1a1; 442 | } 443 | .cm-s-solarized-light .CodeMirror-cursor { 444 | border-left-color: #002b36 !important; 445 | } 446 | .cm-s-solarized-light .CodeMirror-matchingbracket { 447 | color: #002b36; 448 | background-color: #eee8d5; 449 | box-shadow: 0 0 10px #eee8d5; 450 | font-weight: bold; 451 | } 452 | .cm-s-solarized-light .CodeMirror-nonmatchingbracket { 453 | color: #002b36; 454 | background-color: #eee8d5; 455 | box-shadow: 0 0 10px #eee8d5; 456 | font-weight: bold; 457 | color: #dc322f; 458 | border-bottom: 1px dotted #cb4b16; 459 | } 460 | .cm-s-solarized-light span.cm-keyword { 461 | color: #268bd2; 462 | } 463 | .cm-s-solarized-light span.cm-atom { 464 | color: #2aa198; 465 | } 466 | .cm-s-solarized-light span.cm-number { 467 | color: #586e75; 468 | } 469 | .cm-s-solarized-light span.cm-def { 470 | color: #637c84; 471 | } 472 | .cm-s-solarized-light span.cm-variable { 473 | color: #637c84; 474 | } 475 | .cm-s-solarized-light span.cm-variable-2 { 476 | color: #b58900; 477 | } 478 | .cm-s-solarized-light span.cm-variable-3 { 479 | color: #cb4b16; 480 | } 481 | .cm-s-solarized-light span.cm-comment { 482 | color: #93a1a1; 483 | } 484 | .cm-s-solarized-light span.cm-property { 485 | color: #637c84; 486 | } 487 | .cm-s-solarized-light span.cm-operator { 488 | color: #657b83; 489 | } 490 | .cm-s-solarized-light span.cm-string { 491 | color: #36958e; 492 | } 493 | .cm-s-solarized-light span.cm-bracket { 494 | color: #cb4b16; 495 | } 496 | .cm-s-solarized-light span.cm-tag { 497 | color: #657b83; 498 | } 499 | .cm-s-solarized-light span.cm-attribute { 500 | color: #586e75; 501 | font-weight: bold; 502 | } 503 | .cm-s-solarized-light span.cm-meta { 504 | color: #268bd2; 505 | } 506 | .cm-s-solarized-dark { 507 | background-color: #002b36; 508 | color: #839496; 509 | } 510 | .cm-s-solarized-dark .emphasis { 511 | font-weight: bold; 512 | } 513 | .cm-s-solarized-dark .dotted { 514 | border-bottom: 1px dotted #cb4b16; 515 | } 516 | .cm-s-solarized-dark .CodeMirror-gutter { 517 | background-color: #073642; 518 | border-right: 3px solid #073642; 519 | } 520 | .cm-s-solarized-dark .CodeMirror-gutter .CodeMirror-gutter-text { 521 | color: #586e75; 522 | } 523 | .cm-s-solarized-dark .CodeMirror-cursor { 524 | border-left-color: #fdf6e3 !important; 525 | } 526 | .cm-s-solarized-dark .CodeMirror-matchingbracket { 527 | color: #fdf6e3; 528 | background-color: #073642; 529 | box-shadow: 0 0 10px #073642; 530 | font-weight: bold; 531 | } 532 | .cm-s-solarized-dark .CodeMirror-nonmatchingbracket { 533 | color: #fdf6e3; 534 | background-color: #073642; 535 | box-shadow: 0 0 10px #073642; 536 | font-weight: bold; 537 | color: #dc322f; 538 | border-bottom: 1px dotted #cb4b16; 539 | } 540 | .cm-s-solarized-dark span.cm-keyword { 541 | color: #839496; 542 | font-weight: bold; 543 | } 544 | .cm-s-solarized-dark span.cm-atom { 545 | color: #2aa198; 546 | } 547 | .cm-s-solarized-dark span.cm-number { 548 | color: #93a1a1; 549 | } 550 | .cm-s-solarized-dark span.cm-def { 551 | color: #268bd2; 552 | } 553 | .cm-s-solarized-dark span.cm-variable { 554 | color: #cb4b16; 555 | } 556 | .cm-s-solarized-dark span.cm-variable-2 { 557 | color: #cb4b16; 558 | } 559 | .cm-s-solarized-dark span.cm-variable-3 { 560 | color: #cb4b16; 561 | } 562 | .cm-s-solarized-dark span.cm-comment { 563 | color: #586e75; 564 | } 565 | .cm-s-solarized-dark span.cm-property { 566 | color: #b58900; 567 | } 568 | .cm-s-solarized-dark span.cm-operator { 569 | color: #839496; 570 | } 571 | .cm-s-solarized-dark span.cm-string { 572 | color: #6c71c4; 573 | } 574 | .cm-s-solarized-dark span.cm-bracket { 575 | color: #cb4b16; 576 | } 577 | .cm-s-solarized-dark span.cm-tag { 578 | color: #839496; 579 | } 580 | .cm-s-solarized-dark span.cm-attribute { 581 | color: #93a1a1; 582 | font-weight: bold; 583 | } 584 | .cm-s-solarized-dark span.cm-meta { 585 | color: #268bd2; 586 | } 587 | .component-info aside { 588 | float: right; 589 | margin-bottom: 15px; 590 | } 591 | .component-info aside .pure-button { 592 | font-size: 1.5em; 593 | text-decoration: none; 594 | margin-left: 0.5em; 595 | } 596 | .component-info aside .pure-button:first-child { 597 | margin-left: 0; 598 | } 599 | @media only screen and (max-width: 400px) { 600 | .component-info aside .pure-button { 601 | font-size: 1em; 602 | } 603 | } 604 | .component-header { 605 | text-align: center; 606 | padding: 20px; 607 | margin-bottom: 10px; 608 | } 609 | a.component-title { 610 | text-transform: capitalize; 611 | font-size: 2em; 612 | font-weight: bold; 613 | } 614 | .component-subtitle { 615 | font-size: 1.5em; 616 | } 617 | .toolbar, 618 | .readme { 619 | margin-top: 40px; 620 | } 621 | .readme { 622 | line-height: 1.4em; 623 | } 624 | .readme h1:first-child { 625 | margin-top: 5px; 626 | font-size: 2.5em; 627 | } 628 | .readme h1, 629 | .readme h2, 630 | .readme h3, 631 | .readme h4, 632 | .readme h5 { 633 | text-align: left; 634 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 635 | line-height: 1em; 636 | } 637 | .readme img { 638 | max-width: 100%; 639 | } 640 | footer { 641 | max-width: 900px; 642 | margin: 0 auto; 643 | padding: 10px; 644 | border-top: 1px solid #e9e9e9; 645 | opacity: 0.5; 646 | text-align: center; 647 | } 648 | footer:hover { 649 | opacity: 1; 650 | } 651 | 652 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIl9iYXNlLmxlc3MiLCJfdmFyaWFibGVzLmxlc3MiLCJfaGVhZGVyLmxlc3MiLCJfY29udGVudC5sZXNzIiwiX3NlYXJjaC1pbnB1dC5sZXNzIiwiX2xvYWRlci5sZXNzIiwiX2Zyb250Lmxlc3MiLCJfbWluaS1saXN0Lmxlc3MiLCJfc2VhcmNoLmxlc3MiLCJfY29kZW1pcnJvci5sZXNzIiwiX2NvbXBvbmVudC1pbmZvLmxlc3MiLCJfcmVhZG1lLmxlc3MiLCJfZm9vdGVyLmxlc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7QUFBTTtFQUNGLFVBQUE7RUFDQSxTQUFBOztBQUdKO0VBQ0ksbUJBQUE7RUFDQSxjQUFBO0VBQ0EsYUNFb0IsOENERnBCOztBQUdKO0VBQ0ksY0FBQTtFQUNBLHFCQUFBOztBQUVBLENBQUM7RUFDRywwQkFBQTs7QUFJUjtFQUNJLCtCQUFBO0VBQ0EsY0FBQTtFQUNBLGVBQUE7RUFDQSxnQkFBQTs7QUFHSjtFQUNJLGdCQUFBO0VBQ0EsY0FBQTs7QUU3Qko7RUFDSSxtQkFBQTtFQUNBLGNBQUE7RUFDQSxlQUFBO0VBQ0EsaUJBQUE7RUFDQSxhQUFhLGNBQWMsOENBQTNCOztBQVFBLHdCQU4wQztFQU0xQztJQUxJLGlCQUFBO0lBQ0EsZ0JBQUE7SUFDQSxpQkFBQTs7O0FBVlIsTUFhSTtBQWJKLE1BYVE7QUFiUixNQWFZO0FBYlosTUFhZ0I7RUFDUixjQUFBOztBQWRSLE1BaUJJLEVBQUM7RUFDRyxxQkFBQTs7QUFsQlIsTUFxQkk7RUFDSSxlQUFBO0VBQ0EsbUJBQUE7RUFDQSxlQUFBO0VBQ0Esa0JBQUE7O0FBTUEsd0JBSjBDO0VBSTFDLE1BVko7SUFPUSxlQUFBOzs7QUFPUix3QkFKOEM7RUFJOUMsTUFkQTtJQVdRLHdCQUFBO0lBQ0EsZ0JBQUE7OztBQWpDWixNQXFDSTtFQUNJLFlBQUE7RUFDQSxhQUFBO0VBQ0EsV0FBQTtFQUNBLG9CQUFBOztBQU9KLHdCQUw4QztFQUs5QyxNQVhBO0lBT1EsWUFBQTtJQUNBLGFBQUE7SUFDQSxnQkFBQTs7O0FBOUNaLE1Ba0RJLFdBQVU7QUFsRGQsTUFrRHVCLFdBQVU7RUFDekIsU0FBUyxHQUFUO0VBQ0EsY0FBQTs7QUFwRFIsTUF1REksV0FBVTtFQUNOLFdBQUE7O0FDeERSLElBQ0k7RUFDSSxrQkFBQTs7QUFGUixJQUtJO0FBTEosSUFLUTtFQUNBLGFBQWEsY0FBYyw4Q0FBM0I7RUFDQSxtQkFBQTs7QUFQUixJQVVJO0VBQ0ksU0FBQTtFQUNBLDBCQUFBO0VBQ0EsNkJBQUE7RUFDQSxjQUFBOztBQWRSLElBaUJJO0VBQ0ksaUJBQUE7RUFDQSxrQkFBQTs7QUFuQlIsSUFzQkk7RUFDSSxhQUFBO0VBQ0EsZ0JBQUE7RUFDQSxtQkFBQTtFQUVBLG1CQUFBO0VBQ0EsdUNBQUE7RUFDQSxrQkFBQTs7QUM3QlIsS0FBSztFQUNELGVBQUE7RUFDQSxlQUFBOztBQVVJLHdCQUorRDtFQUkvRCxNQU5KLE1BQUs7SUFHRyxZQUFBOzs7QUFPSix3QkFKK0Q7RUFJL0QsTUFWSixNQUFLO0lBT0csV0FBQTs7O0FBUVIsd0JBTDhDO0VBSzlDLE1BZkEsTUFBSztJQVdHLFVBQUE7SUFDQSxjQUFBO0lBQ0EsY0FBQTs7O0FDbEJaO0VBQ0ksWUFBQTtFQUNBLFlBQUE7RUFDQSx3QkFBQTtFQUNBLGtCQUFBOztBQUpKLE9BTUk7RUFDSSxXQUFBO0VBQ0EsWUFBQTtFQUNBLG1CQUFBO0VBQ0Esa0JBQUE7RUFDQSxrQkFBQTtFQWdDSixrREFBQTtFQUNBLCtDQUFBO0VBQ0EsOENBQUE7RUFDQSw2Q0FBQTtFQUNBLDBDQUFBOztBQWhDSSxPQVRKLEtBU0ssVUFBVTtFQTRCZix3REFBQTtFQUNBLHFEQUFBO0VBQ0Esb0RBQUE7RUFDQSxtREFBQTtFQUNBLGdEQUFBOztBQTVCSSxPQWJKLEtBYUssVUFBVTtFQXdCZix3REFBQTtFQUNBLHFEQUFBO0VBQ0Esb0RBQUE7RUFDQSxtREFBQTtFQUNBLGdEQUFBOztBQXhCSSxPQWpCSixLQWlCSyxVQUFVO0VBb0JmLHdEQUFBO0VBQ0EscURBQUE7RUFDQSxvREFBQTtFQUNBLG1EQUFBO0VBQ0EsZ0RBQUE7O0FBcEJJLE9BckJKLEtBcUJLLFVBQVU7RUFnQmYsd0RBQUE7RUFDQSxxREFBQTtFQUNBLG9EQUFBO0VBQ0EsbURBQUE7RUFDQSxnREFBQTs7QUFHSjtFQWhCSTtJQUFLLFdBQUE7SUFBYSxNQUFBOztFQUNsQjtJQUFNLE9BQUE7SUFBUyxNQUFBOztFQUNmO0lBQU0sT0FBQTtJQUFTLFVBQUE7SUFBWSxXQUFBO0lBQWEsWUFBQTs7RUFDeEM7SUFBTSxXQUFBO0lBQWEsWUFBQTs7RUFDbkI7SUFBTSxXQUFBO0lBQWEsVUFBQTtJQUFZLFdBQUE7SUFBYSxZQUFBOztFQUM1QztJQUFPLFdBQUE7SUFBYSxNQUFBOzs7QUFZeEI7RUFqQkk7SUFBSyxXQUFBO0lBQWEsTUFBQTs7RUFDbEI7SUFBTSxPQUFBO0lBQVMsTUFBQTs7RUFDZjtJQUFNLE9BQUE7SUFBUyxVQUFBO0lBQVksV0FBQTtJQUFhLFlBQUE7O0VBQ3hDO0lBQU0sV0FBQTtJQUFhLFlBQUE7O0VBQ25CO0lBQU0sV0FBQTtJQUFhLFVBQUE7SUFBWSxXQUFBO0lBQWEsWUFBQTs7RUFDNUM7SUFBTyxXQUFBO0lBQWEsTUFBQTs7O0FBYXhCO0VBbEJJO0lBQUssV0FBQTtJQUFhLE1BQUE7O0VBQ2xCO0lBQU0sT0FBQTtJQUFTLE1BQUE7O0VBQ2Y7SUFBTSxPQUFBO0lBQVMsVUFBQTtJQUFZLFdBQUE7SUFBYSxZQUFBOztFQUN4QztJQUFNLFdBQUE7SUFBYSxZQUFBOztFQUNuQjtJQUFNLFdBQUE7SUFBYSxVQUFBO0lBQVksV0FBQTtJQUFhLFlBQUE7O0VBQzVDO0lBQU8sV0FBQTtJQUFhLE1BQUE7OztBQWN4QjtFQW5CSTtJQUFLLFdBQUE7SUFBYSxNQUFBOztFQUNsQjtJQUFNLE9BQUE7SUFBUyxNQUFBOztFQUNmO0lBQU0sT0FBQTtJQUFTLFVBQUE7SUFBWSxXQUFBO0lBQWEsWUFBQTs7RUFDeEM7SUFBTSxXQUFBO0lBQWEsWUFBQTs7RUFDbkI7SUFBTSxXQUFBO0lBQWEsVUFBQTtJQUFZLFdBQUE7SUFBYSxZQUFBOztFQUM1QztJQUFPLFdBQUE7SUFBYSxNQUFBOzs7QUFleEI7RUFwQkk7SUFBSyxXQUFBO0lBQWEsTUFBQTs7RUFDbEI7SUFBTSxPQUFBO0lBQVMsTUFBQTs7RUFDZjtJQUFNLE9BQUE7SUFBUyxVQUFBO0lBQVksV0FBQTtJQUFhLFlBQUE7O0VBQ3hDO0lBQU0sV0FBQTtJQUFhLFlBQUE7O0VBQ25CO0lBQU0sV0FBQTtJQUFhLFVBQUE7SUFBWSxXQUFBO0lBQWEsWUFBQTs7RUFDNUM7SUFBTyxXQUFBO0lBQWEsTUFBQTs7O0FDeEN4QixNQUNJO0VBQ0ksWUFBQTs7QUFGUixNQUtJO0VBQ0kscUJBQUE7O0FBaUJKLHdCQWQwQztFQWMxQyxNQVpJO0VBWUosTUFaVTtJQUNGLFlBQUE7SUFDQSxVQUFBO0lBQ0EsWUFBQTs7RUFTUixNQU5JO0lBQ0ksVUFBQTs7O0FBbEJaLE1BdUJJO0VBQ0ksbUJBQUE7O0FBR0osTUFBQztBQUFRLE1BQUM7RUFDTixjQUFBO0VBQ0EsU0FBUyxHQUFUOztBQUdKLE1BQUM7RUFDRyxXQUFBOztBQ25DUixnQkFDSTtFQUNJLG1CQUFBOztBQ0ZSO0VBQ0ksV0FBQTtFQUNBLFNBQUE7O0FBRkosY0FJSTtFQUNJLHVCQUFBO0VBQ0EsaUJBQUE7O0FBTlIsY0FTSTtFQUNJLGtCQUFBOztBQVZSLGNBYUksR0FBRztFQUNDLGNBQUE7O0FBZFIsY0FpQkksR0FBRTtFQUNFLFVBQUE7O0FBbEJSLGNBcUJJLEdBQUU7RUFDRSxTQUFBOztBQXRCUixjQXlCSTtFQUNJLHFCQUFBOztBQTFCUixjQTZCSTtFQUNJLGlCQUFBO0VBQ0EsY0FBQTtFQUNBLGNBQUE7O0FBaENSLGNBbUNJO0VBQ0ksZUFBQTtFQUNBLGtCQUFBO0VBQ0EsaUJBQUE7O0FBdENSLGNBeUNJO0VBQ0ksYUFBQTs7QUEyQkosd0JBeEIwQztFQXdCMUMsY0F2Qkk7RUF1QkosY0F2QlE7SUFDQSxhQUFBOztFQXNCUixjQW5CSSxHQUFFO0VBbUJOLGNBbkJvQixHQUFFO0lBQ2QsbUJBQUE7SUFDQSxVQUFBOztFQWlCUixjQWRJO0lBQ0ksY0FBQTs7RUFhUixjQWRJLGdCQUdJO0lBQ0kscUJBQUE7O0VBVVosY0FkSSxnQkFPSTtJQUNJLHFCQUFBO0lBQ0Esb0JBQUE7OztBQVVoQix3QkFMOEM7RUFLOUMsY0FKUSxHQUFFLFVBQVU7RUFJcEIsY0FKeUIsR0FBRSxVQUFVO0lBQ3pCLGFBQUE7OztBQ3ZFWjtFQUNJLHlCQUFBO0VBQ0EsY0FBQTs7QUFFSixxQkFBc0I7RUFDbEIsaUJBQUE7O0FBRUoscUJBQXNCO0VBQ2xCLGlDQUFBOztBQUVKLHFCQUFzQjtFQUNsQix5QkFBQTtFQUNBLCtCQUFBOztBQUVKLHFCQUFzQixtQkFBbUI7RUFDckMsY0FBQTs7QUFFSixxQkFBc0I7RUFDbEIscUNBQUE7O0FBRUoscUJBQXNCO0VBQ2xCLGNBQUE7RUFDQSx5QkFBQTtFQUNBLDRCQUFBO0VBQ0EsaUJBQUE7O0FBRUoscUJBQXNCO0VBQ2xCLGNBQUE7RUFDQSx5QkFBQTtFQUNBLDRCQUFBO0VBQ0EsaUJBQUE7RUFDQSxjQUFBO0VBQ0EsaUNBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSixxQkFBc0IsS0FBSTtFQUN0QixjQUFBOztBQUVKLHFCQUFzQixLQUFJO0VBQ3RCLGNBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSixxQkFBc0IsS0FBSTtFQUN0QixjQUFBOztBQUVKLHFCQUFzQixLQUFJO0VBQ3RCLGNBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSixxQkFBc0IsS0FBSTtFQUN0QixjQUFBOztBQUVKLHFCQUFzQixLQUFJO0VBQ3RCLGNBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSixxQkFBc0IsS0FBSTtFQUN0QixjQUFBOztBQUVKLHFCQUFzQixLQUFJO0VBQ3RCLGNBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSixxQkFBc0IsS0FBSTtFQUN0QixjQUFBO0VBQ0EsaUJBQUE7O0FBRUoscUJBQXNCLEtBQUk7RUFDdEIsY0FBQTs7QUFFSjtFQUNJLHlCQUFBO0VBQ0EsY0FBQTs7QUFFSixvQkFBcUI7RUFDakIsaUJBQUE7O0FBRUosb0JBQXFCO0VBQ2pCLGlDQUFBOztBQUVKLG9CQUFxQjtFQUNqQix5QkFBQTtFQUNBLCtCQUFBOztBQUVKLG9CQUFxQixtQkFBbUI7RUFDcEMsY0FBQTs7QUFFSixvQkFBcUI7RUFDakIscUNBQUE7O0FBRUosb0JBQXFCO0VBQ2pCLGNBQUE7RUFDQSx5QkFBQTtFQUNBLDRCQUFBO0VBQ0EsaUJBQUE7O0FBRUosb0JBQXFCO0VBQ2pCLGNBQUE7RUFDQSx5QkFBQTtFQUNBLDRCQUFBO0VBQ0EsaUJBQUE7RUFDQSxjQUFBO0VBQ0EsaUNBQUE7O0FBRUosb0JBQXFCLEtBQUk7RUFDckIsY0FBQTtFQUNBLGlCQUFBOztBQUVKLG9CQUFxQixLQUFJO0VBQ3JCLGNBQUE7O0FBRUosb0JBQXFCLEtBQUk7RUFDckIsY0FBQTs7QUFFSixvQkFBcUIsS0FBSTtFQUNyQixjQUFBOztBQUVKLG9CQUFxQixLQUFJO0VBQ3JCLGNBQUE7O0FBRUosb0JBQXFCLEtBQUk7RUFDckIsY0FBQTs7QUFFSixvQkFBcUIsS0FBSTtFQUNyQixjQUFBOztBQUVKLG9CQUFxQixLQUFJO0VBQ3JCLGNBQUE7O0FBRUosb0JBQXFCLEtBQUk7RUFDckIsY0FBQTs7QUFFSixvQkFBcUIsS0FBSTtFQUNyQixjQUFBOztBQUVKLG9CQUFxQixLQUFJO0VBQ3JCLGNBQUE7O0FBRUosb0JBQXFCLEtBQUk7RUFDckIsY0FBQTs7QUFFSixvQkFBcUIsS0FBSTtFQUNyQixjQUFBOztBQUVKLG9CQUFxQixLQUFJO0VBQ3JCLGNBQUE7RUFDQSxpQkFBQTs7QUFFSixvQkFBcUIsS0FBSTtFQUNyQixjQUFBOztBQy9KSixlQUNJO0VBQ0ksWUFBQTtFQUNBLG1CQUFBOztBQUhSLGVBQ0ksTUFJSTtFQUNJLGdCQUFBO0VBQ0EscUJBQUE7RUFDQSxrQkFBQTs7QUFFQSxlQVRSLE1BSUksYUFLSztFQUNHLGNBQUE7O0FBTVIsd0JBSDhDO0VBRzlDLGVBaEJKLE1BSUk7SUFVUSxjQUFBOzs7QUFNaEI7RUFDSSxrQkFBQTtFQUNBLGFBQUE7RUFDQSxtQkFBQTs7QUFHSixDQUFDO0VBQ0csMEJBQUE7RUFDQSxjQUFBO0VBQ0EsaUJBQUE7O0FBR0o7RUFDSSxnQkFBQTs7QUFHSjtBQUFVO0VBQ04sZ0JBQUE7O0FDdENKO0VBRUksa0JBQUE7O0FBRkosT0FJSSxHQUFFO0VBQ0UsZUFBQTtFQUNBLGdCQUFBOztBQU5SLE9BU0k7QUFUSixPQVNRO0FBVFIsT0FTWTtBQVRaLE9BU2dCO0FBVGhCLE9BU29CO0VBQ1osZ0JBQUE7RUFDQSxhVkNnQiw4Q1VEaEI7RUFDQSxnQkFBQTs7QUFaUixPQWVJO0VBQ0ksZUFBQTs7QUNkUjtFQUNJLGdCQUFBO0VBQ0EsY0FBQTtFQUNBLGFBQUE7RUFDQSw2QkFBQTtFQUVBLFlBQUE7RUFDQSxrQkFBQTs7QUFFQSxNQUFDO0VBQ0csVUFBQSIsImZpbGUiOiJjb21wb25lbnRzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIkBpbXBvcnQgJ192YXJpYWJsZXMnO1xuXG5odG1sLCBib2R5IHtcbiAgICBwYWRkaW5nOiBAcGFnZS1wYWRkaW5nO1xuICAgIG1hcmdpbjogQHBhZ2UtbWFyZ2luO1xufVxuXG5ib2R5IHtcbiAgICBiYWNrZ3JvdW5kOiBAcmVhY3QtbGlnaHQ7XG4gICAgY29sb3I6IEByZWFjdC10ZXh0O1xuICAgIGZvbnQtZmFtaWx5OiBAcGFnZS1zYW5zLXNlcmlmLWZvbnRzO1xufVxuXG5hIHtcbiAgICBjb2xvcjogQHJlYWN0LW1hcm9vbjtcbiAgICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG5cbiAgICAmOmhvdmVyIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmU7XG4gICAgfVxufVxuXG5jb2RlIHtcbiAgICBiYWNrZ3JvdW5kOiBAcmVhY3QtY29kZS1iZztcbiAgICBjb2xvcjogQHJlYWN0LWNvZGUtdGV4dDtcbiAgICBwYWRkaW5nOiAwLjI1ZW07XG4gICAgbWFyZ2luOiAwIDAuMjVlbTtcbn1cblxuLmNvbnRhaW5lciB7XG4gICAgbWF4LXdpZHRoOiBAcGFnZS1tYXgtd2lkdGg7XG4gICAgbWFyZ2luOiAwIGF1dG87XG59IiwiQHJlYWN0LWRhcms6ICAgICAgICMyZDJkMmQ7XG5AcmVhY3QtbGlnaHQ6ICAgICAgI2Y5ZjlmOTtcbkByZWFjdC1ibHVlOiAgICAgICAjNjFkYWZiO1xuQHJlYWN0LW1hcm9vbjogICAgICNjMDViNGQ7XG5AcmVhY3QtdGV4dDogICAgICAgIzQ4NDg0ODtcbkByZWFjdC1saWdodC10ZXh0OiAjZTllOWU5O1xuQHJlYWN0LWNvZGUtdGV4dDogICM1NTU7XG5AcmVhY3QtY29kZS1iZzogICAgZmFkZSgjMDAwLCA0JSk7XG5cbkBwYWdlLW1hcmdpbjogMDtcbkBwYWdlLXBhZGRpbmc6IDA7XG5AcGFnZS1tYXgtd2lkdGg6IDkwMHB4O1xuQHBhZ2Utc2Fucy1zZXJpZi1mb250czogXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO1xuXG5AaGVhZGVyLXBhZGRpbmc6IDEwcHggMDtcbkBoZWFkZXItbG9nby1zaXplOiAxODBweDtcblxuQHNlYXJjaC1pbnB1dC1mb250LXNpemU6IDE4cHg7XG5Ac2VhcmNoLWlucHV0LXBhZGRpbmc6IDEycHggMDtcbkBzZWFyY2gtaW5wdXQtaGVhZGVyLXdpZHRoOiA1ODBweDtcblxuQGNvbnRlbnQtcGFkZGluZzogMTBweDtcbkBjb250ZW50LW1hcmdpbjogMjBweDtcbkBjb250ZW50LWJvcmRlcjogMXB4IHNvbGlkIHJnYmEoMTYsIDE2LCAxNiwgMC4xKTtcbkBjb250ZW50LWJvcmRlci1yYWRpdXM6IDNweDtcbkBjb250ZW50LWJhY2tncm91bmQ6ICNmZmY7XG4iLCJAaW1wb3J0ICdfdmFyaWFibGVzJztcblxuaGVhZGVyIHtcbiAgICBiYWNrZ3JvdW5kOiBAcmVhY3QtZGFyaztcbiAgICBjb2xvcjogQHJlYWN0LWxpZ2h0LXRleHQ7XG4gICAgcGFkZGluZzogQGhlYWRlci1wYWRkaW5nO1xuICAgIG1pbi1oZWlnaHQ6IEBoZWFkZXItbG9nby1zaXplO1xuICAgIGZvbnQtZmFtaWx5OiBcIk1vbnRzZXJyYXRcIiwgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmO1xuXG4gICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA0MDBweCkge1xuICAgICAgICBwYWRkaW5nLWJvdHRvbTogMDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICAgICAgbWluLWhlaWdodDogMTYwcHg7XG4gICAgfVxuXG4gICAgaDEsIGgyLCBoMywgYSB7XG4gICAgICAgIGNvbG9yOiBAcmVhY3QtYmx1ZTtcbiAgICB9XG5cbiAgICBhOmhvdmVyIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lO1xuICAgIH1cblxuICAgIGgxIHtcbiAgICAgICAgZm9udC1zaXplOiBAaGVhZGVyLWxvZ28tc2l6ZSAvIDM7XG4gICAgICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7XG4gICAgICAgIG1hcmdpbjogMC4xZW0gMDtcbiAgICAgICAgcGFkZGluZy10b3A6IDAuM2VtO1xuXG4gICAgICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNjIwcHgpIHtcbiAgICAgICAgICAgIGZvbnQtc2l6ZTogMTAwcHggLyAyO1xuICAgICAgICB9XG5cbiAgICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA1NTVweCkge1xuICAgICAgICAgICAgZm9udC1zaXplOiAxMDBweCAvIDM7XG4gICAgICAgICAgICBtYXJnaW4tdG9wOiAtNXB4O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLnJlYWN0LWxvZ28ge1xuICAgICAgICB3aWR0aDogQGhlYWRlci1sb2dvLXNpemU7XG4gICAgICAgIGhlaWdodDogQGhlYWRlci1sb2dvLXNpemU7XG4gICAgICAgIGZsb2F0OiBsZWZ0O1xuICAgICAgICBtYXJnaW4tcmlnaHQ6IEBoZWFkZXItbG9nby1zaXplIC8gODtcblxuICAgICAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDU1NXB4KSB7XG4gICAgICAgICAgICB3aWR0aDogMTAwcHg7XG4gICAgICAgICAgICBoZWlnaHQ6IDEwMHB4O1xuICAgICAgICAgICAgbWFyZ2luLWxlZnQ6IDVweDtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC5jb250YWluZXI6YmVmb3JlLCAuY29udGFpbmVyOmFmdGVyIHtcbiAgICAgICAgY29udGVudDogJyAnO1xuICAgICAgICBkaXNwbGF5OiB0YWJsZTtcbiAgICB9XG5cbiAgICAuY29udGFpbmVyOmFmdGVyIHtcbiAgICAgICAgY2xlYXI6IGJvdGg7XG4gICAgfVxufSIsIkBpbXBvcnQgJ192YXJpYWJsZXMnO1xuXG5tYWluIHtcbiAgICBoMSB7XG4gICAgICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICB9XG5cbiAgICBoMiwgaDMge1xuICAgICAgICBmb250LWZhbWlseTogXCJNb250c2VycmF0XCIsIFwiSGVsdmV0aWNhIE5ldWVcIiwgSGVsdmV0aWNhLCBBcmlhbCwgc2Fucy1zZXJpZjtcbiAgICAgICAgZm9udC13ZWlnaHQ6IG5vcm1hbDtcbiAgICB9XG5cbiAgICBociB7XG4gICAgICAgIGhlaWdodDogMDtcbiAgICAgICAgYm9yZGVyLXRvcDogMXB4IHNvbGlkICNiYmI7XG4gICAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZWVlO1xuICAgICAgICBtYXJnaW46IDAgYXV0bztcbiAgICB9XG5cbiAgICAuZW1waGFzaXplIHtcbiAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG4gICAgICAgIGZvbnQtc3R5bGU6IGl0YWxpYztcbiAgICB9XG5cbiAgICAuY29udGFpbmVyIHtcbiAgICAgICAgcGFkZGluZzogQGNvbnRlbnQtcGFkZGluZztcbiAgICAgICAgbWFyZ2luLXRvcDogQGNvbnRlbnQtbWFyZ2luO1xuICAgICAgICBtYXJnaW4tYm90dG9tOiBAY29udGVudC1tYXJnaW47XG5cbiAgICAgICAgYmFja2dyb3VuZDogQGNvbnRlbnQtYmFja2dyb3VuZDtcbiAgICAgICAgYm9yZGVyOiBAY29udGVudC1ib3JkZXI7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IEBjb250ZW50LWJvcmRlci1yYWRpdXM7XG4gICAgfVxufSIsIkBpbXBvcnQgJ192YXJpYWJsZXMnO1xuXG5pbnB1dC5zZWFyY2gge1xuICAgIGZvbnQtc2l6ZTogQHNlYXJjaC1pbnB1dC1mb250LXNpemU7XG4gICAgcGFkZGluZzogQHNlYXJjaC1pbnB1dC1wYWRkaW5nO1xufVxuXG5oZWFkZXIge1xuICAgIGlucHV0LnNlYXJjaCB7XG5cbiAgICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXdpZHRoOiBAc2VhcmNoLWlucHV0LWhlYWRlci13aWR0aCkge1xuICAgICAgICAgICAgd2lkdGg6IEBzZWFyY2gtaW5wdXQtaGVhZGVyLXdpZHRoO1xuICAgICAgICB9XG5cbiAgICAgICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWF4LXdpZHRoOiBAc2VhcmNoLWlucHV0LWhlYWRlci13aWR0aCkge1xuICAgICAgICAgICAgd2lkdGg6IDEwMCU7XG4gICAgICAgIH1cblxuICAgICAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDgxMnB4KSB7XG4gICAgICAgICAgICB3aWR0aDogODUlO1xuICAgICAgICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICAgICAgICBtYXJnaW46IDAgYXV0bztcbiAgICAgICAgfVxuICAgIH1cbn0iLCJAaW1wb3J0ICdfdmFyaWFibGVzJztcbkBsb2FkZXItZG90LXNpemU6IDMwcHg7XG5cbi5sb2FkZXIge1xuICAgIHdpZHRoOiBAbG9hZGVyLWRvdC1zaXplICogNTtcbiAgICBoZWlnaHQ6IEBsb2FkZXItZG90LXNpemUgKiAyO1xuICAgIG1hcmdpbjogQGxvYWRlci1kb3Qtc2l6ZSBhdXRvIDAgYXV0bztcbiAgICBwb3NpdGlvbjogcmVsYXRpdmU7XG5cbiAgICAuZG90IHtcbiAgICAgICAgd2lkdGg6IEBsb2FkZXItZG90LXNpemU7XG4gICAgICAgIGhlaWdodDogQGxvYWRlci1kb3Qtc2l6ZTtcbiAgICAgICAgYmFja2dyb3VuZDogQHJlYWN0LWJsdWU7XG4gICAgICAgIGJvcmRlci1yYWRpdXM6IDUwJTtcbiAgICAgICAgcG9zaXRpb246IGFic29sdXRlO1xuXG4gICAgICAgIC5hbmltYXRpb24obG9hZGluZy1hbmltIDJzIGxpbmVhciBpbmZpbml0ZSk7XG5cbiAgICAgICAgJjpudGgtY2hpbGQoMikge1xuICAgICAgICAgICAgLmFuaW1hdGlvbihsb2FkaW5nLWFuaW0gMnMgbGluZWFyIGluZmluaXRlIC0uNHMpO1xuICAgICAgICB9XG5cbiAgICAgICAgJjpudGgtY2hpbGQoMykge1xuICAgICAgICAgICAgLmFuaW1hdGlvbihsb2FkaW5nLWFuaW0gMnMgbGluZWFyIGluZmluaXRlIC0uOHMpO1xuICAgICAgICB9XG5cbiAgICAgICAgJjpudGgtY2hpbGQoNCkge1xuICAgICAgICAgICAgLmFuaW1hdGlvbihsb2FkaW5nLWFuaW0gMnMgbGluZWFyIGluZmluaXRlIC0xLjJzKTtcbiAgICAgICAgfVxuXG4gICAgICAgICY6bnRoLWNoaWxkKDUpIHtcbiAgICAgICAgICAgIC5hbmltYXRpb24obG9hZGluZy1hbmltIDJzIGxpbmVhciBpbmZpbml0ZSAtMS42cyk7XG4gICAgICAgIH1cbiAgICB9XG59XG5cbi5sb2FkaW5nLWFuaW0oKSB7XG4gICAgMCUgeyBsZWZ0OiAxMDBweDsgdG9wOiAwOyB9XG4gICAgODAlIHsgbGVmdDogMDsgdG9wOiAwOyB9XG4gICAgODUlIHsgbGVmdDogMDsgdG9wOiAtMjBweDsgd2lkdGg6IDIwcHg7IGhlaWdodDogMjBweDsgfVxuICAgIDkwJSB7IHdpZHRoOiA0MHB4OyBoZWlnaHQ6IDE1cHg7IH1cbiAgICA5NSUgeyBsZWZ0OiAxMDBweDsgdG9wOiAtMjBweDsgd2lkdGg6IDIwcHg7IGhlaWdodDogMjBweDsgfVxuICAgIDEwMCUgeyBsZWZ0OiAxMDBweDsgdG9wOiAwOyB9XG59XG5cbi5hbmltYXRpb24oQHZhbHVlKSB7XG4gICAgLXdlYmtpdC1hbmltYXRpb246IEB2YWx1ZTtcbiAgICAtbW96LWFuaW1hdGlvbjogQHZhbHVlO1xuICAgIC1tcy1hbmltYXRpb246IEB2YWx1ZTtcbiAgICAtby1hbmltYXRpb246IEB2YWx1ZTtcbiAgICBhbmltYXRpb246IEB2YWx1ZTtcbn1cblxuQC13ZWJraXQta2V5ZnJhbWVzIGxvYWRpbmctYW5pbSB7IC5sb2FkaW5nLWFuaW07IH1cbkAtbW96LWtleWZyYW1lcyBsb2FkaW5nLWFuaW0geyAubG9hZGluZy1hbmltIH1cbkAtbXMta2V5ZnJhbWVzIGxvYWRpbmctYW5pbSB7IC5sb2FkaW5nLWFuaW0gfVxuQC1vaS1rZXlmcmFtZXMgbG9hZGluZy1hbmltIHsgLmxvYWRpbmctYW5pbSB9XG5Aa2V5ZnJhbWVzIGxvYWRpbmctYW5pbSB7IC5sb2FkaW5nLWFuaW0gfVxuIiwiQGltcG9ydCAnX3ZhcmlhYmxlcyc7XG5cbi5mcm9udCB7XG4gICAgbGkge1xuICAgICAgICBtYXJnaW46IDE1cHg7XG4gICAgfVxuXG4gICAgaDIge1xuICAgICAgICBtYXJnaW46IDQwcHggMCAxMHB4IDA7XG4gICAgfVxuXG4gICAgQG1lZGlhIG9ubHkgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA2NTBweCkge1xuICAgICAgICBcbiAgICAgICAgLmZhcSwgLmNvbXBvbmVudC1saXN0cyB7XG4gICAgICAgICAgICBmbG9hdDogcmlnaHQ7XG4gICAgICAgICAgICB3aWR0aDogNjAlO1xuICAgICAgICAgICAgbWFyZ2luOiAwIDMlO1xuICAgICAgICB9XG5cbiAgICAgICAgLmNvbXBvbmVudC1saXN0cyB7XG4gICAgICAgICAgICB3aWR0aDogMjglO1xuICAgICAgICB9XG5cbiAgICB9XG5cbiAgICAuZmFxIHtcbiAgICAgICAgbGluZS1oZWlnaHQ6IDEuNDVlbTtcbiAgICB9XG5cbiAgICAmOmFmdGVyLCAmOmJlZm9yZSB7XG4gICAgICAgIGRpc3BsYXk6IHRhYmxlO1xuICAgICAgICBjb250ZW50OiAnICc7XG4gICAgfVxuXG4gICAgJjphZnRlciB7XG4gICAgICAgIGNsZWFyOiBib3RoO1xuICAgIH1cbn0iLCIuY29tcG9uZW50LWxpc3RzIHtcbiAgICB1bCB7XG4gICAgICAgIHBhZGRpbmc6IDAgMCAwIDEwcHg7XG4gICAgfVxufSIsIi5yZXN1bHRzLXRhYmxlIHtcbiAgICB3aWR0aDogMTAwJTtcbiAgICBib3JkZXI6IDA7XG5cbiAgICB0aGVhZCB7XG4gICAgICAgIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50O1xuICAgICAgICBmb250LXNpemU6IDEuMjVlbTtcbiAgICB9XG5cbiAgICB0aCB7XG4gICAgICAgIHBhZGRpbmctbGVmdDogMTVweDtcbiAgICB9XG5cbiAgICB0aCBhIHtcbiAgICAgICAgZGlzcGxheTogYmxvY2ssXG4gICAgfVxuXG4gICAgdGgudXBkYXRlZCB7XG4gICAgICAgIHdpZHRoOiAxNSU7XG4gICAgfVxuXG4gICAgdGguc3RhcnMge1xuICAgICAgICB3aWR0aDogOCU7XG4gICAgfVxuXG4gICAgdGQge1xuICAgICAgICBib3JkZXItY29sb3I6IGxpZ2h0ZW4oI0NCQ0JDQiwgMTIuNSUpO1xuICAgIH1cblxuICAgIC5jb21wb25lbnQtbmFtZSB7XG4gICAgICAgIGZvbnQtc2l6ZTogMS4zNWVtO1xuICAgICAgICBkaXNwbGF5OiBibG9jaztcbiAgICAgICAgbWFyZ2luOiAxM3B4IDA7XG4gICAgfVxuXG4gICAgLm5vLXJlc3VsdCB7XG4gICAgICAgIHBhZGRpbmc6IDUwcHggMDtcbiAgICAgICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgICAgICBmb250LXNpemU6IDEuMzVlbTtcbiAgICB9XG5cbiAgICAuY29tcG9uZW50LW1ldGEge1xuICAgICAgICBkaXNwbGF5OiBub25lO1xuICAgIH1cblxuICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNTAwcHgpIHtcbiAgICAgICAgdGgsIHRkIHtcbiAgICAgICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICAgIH1cblxuICAgICAgICB0aDpmaXJzdC1jaGlsZCwgdGQ6Zmlyc3QtY2hpbGQge1xuICAgICAgICAgICAgZGlzcGxheTogdGFibGUtY2VsbDtcbiAgICAgICAgICAgIHBhZGRpbmc6IDA7XG4gICAgICAgIH1cblxuICAgICAgICAuY29tcG9uZW50LW1ldGEge1xuICAgICAgICAgICAgZGlzcGxheTogYmxvY2s7XG5cbiAgICAgICAgICAgIGR0IHtcbiAgICAgICAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGRkIHtcbiAgICAgICAgICAgICAgICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gICAgICAgICAgICAgICAgbWFyZ2luOiAwIDEwcHggMCA1cHg7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtYXgtd2lkdGg6IDY1MHB4KSB7XG4gICAgICAgIHRkOm50aC1jaGlsZCgyKSwgdGg6bnRoLWNoaWxkKDIpIHtcbiAgICAgICAgICAgIGRpc3BsYXk6IG5vbmU7XG4gICAgICAgIH1cbiAgICB9XG59IiwiLmNtLXMtc29sYXJpemVkLWxpZ2h0IHtcbiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjhmNWVjO1xuICAgIGNvbG9yOiAjNjM3Yzg0O1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IC5lbXBoYXNpcyB7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgLmRvdHRlZCB7XG4gICAgYm9yZGVyLWJvdHRvbTogMXB4IGRvdHRlZCAjY2I0YjE2O1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IC5Db2RlTWlycm9yLWd1dHRlciB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2VlZThkNTtcbiAgICBib3JkZXItcmlnaHQ6IDNweCBzb2xpZCAjZWVlOGQ1O1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IC5Db2RlTWlycm9yLWd1dHRlciAuQ29kZU1pcnJvci1ndXR0ZXItdGV4dCB7XG4gICAgY29sb3I6ICM5M2ExYTE7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgLkNvZGVNaXJyb3ItY3Vyc29yIHtcbiAgICBib3JkZXItbGVmdC1jb2xvcjogIzAwMmIzNiAhaW1wb3J0YW50O1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IC5Db2RlTWlycm9yLW1hdGNoaW5nYnJhY2tldCB7XG4gICAgY29sb3I6ICMwMDJiMzY7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2VlZThkNTtcbiAgICBib3gtc2hhZG93OiAwIDAgMTBweCAjZWVlOGQ1O1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkO1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IC5Db2RlTWlycm9yLW5vbm1hdGNoaW5nYnJhY2tldCB7XG4gICAgY29sb3I6ICMwMDJiMzY7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogI2VlZThkNTtcbiAgICBib3gtc2hhZG93OiAwIDAgMTBweCAjZWVlOGQ1O1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkO1xuICAgIGNvbG9yOiAjZGMzMjJmO1xuICAgIGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQgI2NiNGIxNjtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLWtleXdvcmQge1xuICAgIGNvbG9yOiAjMjY4YmQyO1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IHNwYW4uY20tYXRvbSB7XG4gICAgY29sb3I6ICMyYWExOTg7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgc3Bhbi5jbS1udW1iZXIge1xuICAgIGNvbG9yOiAjNTg2ZTc1O1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IHNwYW4uY20tZGVmIHtcbiAgICBjb2xvcjogIzYzN2M4NDtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLXZhcmlhYmxlIHtcbiAgICBjb2xvcjogIzYzN2M4NDtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLXZhcmlhYmxlLTIge1xuICAgIGNvbG9yOiAjYjU4OTAwO1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IHNwYW4uY20tdmFyaWFibGUtMyB7XG4gICAgY29sb3I6ICNjYjRiMTY7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgc3Bhbi5jbS1jb21tZW50IHtcbiAgICBjb2xvcjogIzkzYTFhMTtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLXByb3BlcnR5IHtcbiAgICBjb2xvcjogIzYzN2M4NDtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLW9wZXJhdG9yIHtcbiAgICBjb2xvcjogIzY1N2I4Mztcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLXN0cmluZyB7XG4gICAgY29sb3I6ICMzNjk1OGU7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgc3Bhbi5jbS1icmFja2V0IHtcbiAgICBjb2xvcjogI2NiNGIxNjtcbn1cbi5jbS1zLXNvbGFyaXplZC1saWdodCBzcGFuLmNtLXRhZyB7XG4gICAgY29sb3I6ICM2NTdiODM7XG59XG4uY20tcy1zb2xhcml6ZWQtbGlnaHQgc3Bhbi5jbS1hdHRyaWJ1dGUge1xuICAgIGNvbG9yOiAjNTg2ZTc1O1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkO1xufVxuLmNtLXMtc29sYXJpemVkLWxpZ2h0IHNwYW4uY20tbWV0YSB7XG4gICAgY29sb3I6ICMyNjhiZDI7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayB7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogIzAwMmIzNjtcbiAgICBjb2xvcjogIzgzOTQ5Njtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIC5lbXBoYXNpcyB7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayAuZG90dGVkIHtcbiAgICBib3JkZXItYm90dG9tOiAxcHggZG90dGVkICNjYjRiMTY7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayAuQ29kZU1pcnJvci1ndXR0ZXIge1xuICAgIGJhY2tncm91bmQtY29sb3I6ICMwNzM2NDI7XG4gICAgYm9yZGVyLXJpZ2h0OiAzcHggc29saWQgIzA3MzY0Mjtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIC5Db2RlTWlycm9yLWd1dHRlciAuQ29kZU1pcnJvci1ndXR0ZXItdGV4dCB7XG4gICAgY29sb3I6ICM1ODZlNzU7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayAuQ29kZU1pcnJvci1jdXJzb3Ige1xuICAgIGJvcmRlci1sZWZ0LWNvbG9yOiAjZmRmNmUzICFpbXBvcnRhbnQ7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayAuQ29kZU1pcnJvci1tYXRjaGluZ2JyYWNrZXQge1xuICAgIGNvbG9yOiAjZmRmNmUzO1xuICAgIGJhY2tncm91bmQtY29sb3I6ICMwNzM2NDI7XG4gICAgYm94LXNoYWRvdzogMCAwIDEwcHggIzA3MzY0MjtcbiAgICBmb250LXdlaWdodDogYm9sZDtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIC5Db2RlTWlycm9yLW5vbm1hdGNoaW5nYnJhY2tldCB7XG4gICAgY29sb3I6ICNmZGY2ZTM7XG4gICAgYmFja2dyb3VuZC1jb2xvcjogIzA3MzY0MjtcbiAgICBib3gtc2hhZG93OiAwIDAgMTBweCAjMDczNjQyO1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkO1xuICAgIGNvbG9yOiAjZGMzMjJmO1xuICAgIGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQgI2NiNGIxNjtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20ta2V5d29yZCB7XG4gICAgY29sb3I6ICM4Mzk0OTY7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayBzcGFuLmNtLWF0b20ge1xuICAgIGNvbG9yOiAjMmFhMTk4O1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS1udW1iZXIge1xuICAgIGNvbG9yOiAjOTNhMWExO1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS1kZWYge1xuICAgIGNvbG9yOiAjMjY4YmQyO1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS12YXJpYWJsZSB7XG4gICAgY29sb3I6ICNjYjRiMTY7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayBzcGFuLmNtLXZhcmlhYmxlLTIge1xuICAgIGNvbG9yOiAjY2I0YjE2O1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS12YXJpYWJsZS0zIHtcbiAgICBjb2xvcjogI2NiNGIxNjtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20tY29tbWVudCB7XG4gICAgY29sb3I6ICM1ODZlNzU7XG59XG4uY20tcy1zb2xhcml6ZWQtZGFyayBzcGFuLmNtLXByb3BlcnR5IHtcbiAgICBjb2xvcjogI2I1ODkwMDtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20tb3BlcmF0b3Ige1xuICAgIGNvbG9yOiAjODM5NDk2O1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS1zdHJpbmcge1xuICAgIGNvbG9yOiAjNmM3MWM0O1xufVxuLmNtLXMtc29sYXJpemVkLWRhcmsgc3Bhbi5jbS1icmFja2V0IHtcbiAgICBjb2xvcjogI2NiNGIxNjtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20tdGFnIHtcbiAgICBjb2xvcjogIzgzOTQ5Njtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20tYXR0cmlidXRlIHtcbiAgICBjb2xvcjogIzkzYTFhMTtcbiAgICBmb250LXdlaWdodDogYm9sZDtcbn1cbi5jbS1zLXNvbGFyaXplZC1kYXJrIHNwYW4uY20tbWV0YSB7XG4gICAgY29sb3I6ICMyNjhiZDI7XG59IiwiLmNvbXBvbmVudC1pbmZvIHtcbiAgICBhc2lkZSB7XG4gICAgICAgIGZsb2F0OiByaWdodDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMTVweDtcblxuICAgICAgICAucHVyZS1idXR0b24ge1xuICAgICAgICAgICAgZm9udC1zaXplOiAxLjVlbTtcbiAgICAgICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTtcbiAgICAgICAgICAgIG1hcmdpbi1sZWZ0OiAwLjVlbTtcblxuICAgICAgICAgICAgJjpmaXJzdC1jaGlsZCB7XG4gICAgICAgICAgICAgICAgbWFyZ2luLWxlZnQ6IDA7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNDAwcHgpIHtcbiAgICAgICAgICAgICAgICBmb250LXNpemU6IDFlbTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cbn1cblxuLmNvbXBvbmVudC1oZWFkZXIge1xuICAgIHRleHQtYWxpZ246IGNlbnRlcjtcbiAgICBwYWRkaW5nOiAyMHB4O1xuICAgIG1hcmdpbi1ib3R0b206IDEwcHg7XG59XG5cbmEuY29tcG9uZW50LXRpdGxlIHtcbiAgICB0ZXh0LXRyYW5zZm9ybTogY2FwaXRhbGl6ZTtcbiAgICBmb250LXNpemU6IDJlbTtcbiAgICBmb250LXdlaWdodDogYm9sZDtcbn1cblxuLmNvbXBvbmVudC1zdWJ0aXRsZSB7XG4gICAgZm9udC1zaXplOiAxLjVlbTtcbn1cblxuLnRvb2xiYXIsIC5yZWFkbWUge1xuICAgIG1hcmdpbi10b3A6IDQwcHg7XG59XG4iLCIucmVhZG1lIHtcblxuICAgIGxpbmUtaGVpZ2h0OiAxLjRlbTtcblxuICAgIGgxOmZpcnN0LWNoaWxkIHtcbiAgICAgICAgbWFyZ2luLXRvcDogNXB4O1xuICAgICAgICBmb250LXNpemU6IDIuNWVtO1xuICAgIH1cblxuICAgIGgxLCBoMiwgaDMsIGg0LCBoNSB7XG4gICAgICAgIHRleHQtYWxpZ246IGxlZnQ7XG4gICAgICAgIGZvbnQtZmFtaWx5OiBAcGFnZS1zYW5zLXNlcmlmLWZvbnRzO1xuICAgICAgICBsaW5lLWhlaWdodDogMWVtO1xuICAgIH1cblxuICAgIGltZyB7XG4gICAgICAgIG1heC13aWR0aDogMTAwJTtcbiAgICB9XG5cbn0iLCJAaW1wb3J0ICdfdmFyaWFibGVzJztcblxuZm9vdGVyIHtcbiAgICBtYXgtd2lkdGg6IEBwYWdlLW1heC13aWR0aDtcbiAgICBtYXJnaW46IDAgYXV0bztcbiAgICBwYWRkaW5nOiAxMHB4O1xuICAgIGJvcmRlci10b3A6IDFweCBzb2xpZCBAcmVhY3QtbGlnaHQtdGV4dDtcblxuICAgIG9wYWNpdHk6IDAuNTtcbiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG5cbiAgICAmOmhvdmVyIHtcbiAgICAgICAgb3BhY2l0eTogMTtcbiAgICB9XG59Il0sInNvdXJjZVJvb3QiOiIvc291cmNlLyJ9 */ 653 | -------------------------------------------------------------------------------- /public/css/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.5.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yui/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v1.1.3 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} -------------------------------------------------------------------------------- /public/dist/empty.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaffel/react-components/97b88acb4af4360bcecea520b7120502e0e1e7d4/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/img/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 46 | 47 | -------------------------------------------------------------------------------- /public/js/analytics.js: -------------------------------------------------------------------------------- 1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 5 | ga('create', 'UA-54404455-1', 'auto'); 6 | ga('send', 'pageview'); -------------------------------------------------------------------------------- /public/js/lodash.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lo-Dash 2.4.1 (Custom Build) lodash.com/license | Underscore.js 1.5.2 underscorejs.org/LICENSE 4 | * Build: `lodash modern -o ./dist/lodash.js` 5 | */ 6 | ;(function(){function n(n,t,e){e=(e||0)-1;for(var r=n?n.length:0;++ea||typeof i=="undefined")return 1;if(ie?0:e);++r=b&&i===n,l=[];if(f){var p=o(r);p?(i=t,r=p):f=false}for(;++ui(r,p)&&l.push(p);return f&&c(r),l}function ut(n,t,e,r){r=(r||0)-1;for(var u=n?n.length:0,o=[];++r=b&&f===n,h=u||v?a():s; 18 | for(v&&(h=o(h),f=t);++if(h,y))&&((u||v)&&h.push(y),s.push(g))}return v?(l(h.k),c(h)):u&&l(h),s}function lt(n){return function(t,e,r){var u={};e=J.createCallback(e,r,3),r=-1;var o=t?t.length:0;if(typeof o=="number")for(;++re?Ie(0,o+e):e)||0,Te(n)?i=-1o&&(o=a)}}else t=null==t&&kt(n)?r:J.createCallback(t,e,3),St(n,function(n,e,r){e=t(n,e,r),e>u&&(u=e,o=n)});return o}function Dt(n,t,e,r){if(!n)return e;var u=3>arguments.length;t=J.createCallback(t,r,4);var o=-1,i=n.length;if(typeof i=="number")for(u&&(e=n[++o]);++oarguments.length;return t=J.createCallback(t,r,4),Et(n,function(n,r,o){e=u?(u=false,n):t(e,n,r,o)}),e}function Tt(n){var t=-1,e=n?n.length:0,r=Xt(typeof e=="number"?e:0);return St(n,function(n){var e=at(0,++t);r[t]=r[e],r[e]=n}),r}function Ft(n,t,e){var r;t=J.createCallback(t,e,3),e=-1;var u=n?n.length:0;if(typeof u=="number")for(;++er?Ie(0,u+r):r||0}else if(r)return r=zt(t,e),t[r]===e?r:-1;return n(t,e,r)}function qt(n,t,e){if(typeof t!="number"&&null!=t){var r=0,u=-1,o=n?n.length:0;for(t=J.createCallback(t,e,3);++u>>1,e(n[r])e?0:e);++t=v; 29 | m?(i&&(i=ve(i)),s=f,a=n.apply(l,o)):i||(i=_e(r,v))}return m&&c?c=ve(c):c||t===h||(c=_e(u,t)),e&&(m=true,a=n.apply(l,o)),!m||c||i||(o=l=null),a}}function Ut(n){return n}function Gt(n,t,e){var r=true,u=t&&bt(t);t&&(e||u.length)||(null==e&&(e=t),o=Q,t=n,n=J,u=bt(t)),false===e?r=false:wt(e)&&"chain"in e&&(r=e.chain);var o=n,i=dt(o);St(u,function(e){var u=n[e]=t[e];i&&(o.prototype[e]=function(){var t=this.__chain__,e=this.__wrapped__,i=[e];if(be.apply(i,arguments),i=u.apply(n,i),r||t){if(e===i&&wt(i))return this; 30 | i=new o(i),i.__chain__=t}return i})})}function Ht(){}function Jt(n){return function(t){return t[n]}}function Qt(){return this.__wrapped__}e=e?Y.defaults(G.Object(),e,Y.pick(G,A)):G;var Xt=e.Array,Yt=e.Boolean,Zt=e.Date,ne=e.Function,te=e.Math,ee=e.Number,re=e.Object,ue=e.RegExp,oe=e.String,ie=e.TypeError,ae=[],fe=re.prototype,le=e._,ce=fe.toString,pe=ue("^"+oe(ce).replace(/[.*+?^${}()|[\]\\]/g,"\\$&").replace(/toString| for [^\]]+/g,".*?")+"$"),se=te.ceil,ve=e.clearTimeout,he=te.floor,ge=ne.prototype.toString,ye=vt(ye=re.getPrototypeOf)&&ye,me=fe.hasOwnProperty,be=ae.push,_e=e.setTimeout,de=ae.splice,we=ae.unshift,je=function(){try{var n={},t=vt(t=re.defineProperty)&&t,e=t(n,n,n)&&t 31 | }catch(r){}return e}(),ke=vt(ke=re.create)&&ke,xe=vt(xe=Xt.isArray)&&xe,Ce=e.isFinite,Oe=e.isNaN,Ne=vt(Ne=re.keys)&&Ne,Ie=te.max,Se=te.min,Ee=e.parseInt,Re=te.random,Ae={};Ae[$]=Xt,Ae[T]=Yt,Ae[F]=Zt,Ae[B]=ne,Ae[q]=re,Ae[W]=ee,Ae[z]=ue,Ae[P]=oe,Q.prototype=J.prototype;var De=J.support={};De.funcDecomp=!vt(e.a)&&E.test(s),De.funcNames=typeof ne.name=="string",J.templateSettings={escape:/<%-([\s\S]+?)%>/g,evaluate:/<%([\s\S]+?)%>/g,interpolate:N,variable:"",imports:{_:J}},ke||(nt=function(){function n(){}return function(t){if(wt(t)){n.prototype=t; 32 | var r=new n;n.prototype=null}return r||e.Object()}}());var $e=je?function(n,t){M.value=t,je(n,"__bindData__",M)}:Ht,Te=xe||function(n){return n&&typeof n=="object"&&typeof n.length=="number"&&ce.call(n)==$||false},Fe=Ne?function(n){return wt(n)?Ne(n):[]}:H,Be={"&":"&","<":"<",">":">",'"':""","'":"'"},We=_t(Be),qe=ue("("+Fe(We).join("|")+")","g"),ze=ue("["+Fe(Be).join("")+"]","g"),Pe=ye?function(n){if(!n||ce.call(n)!=q)return false;var t=n.valueOf,e=vt(t)&&(e=ye(t))&&ye(e);return e?n==e||ye(n)==e:ht(n) 33 | }:ht,Ke=lt(function(n,t,e){me.call(n,e)?n[e]++:n[e]=1}),Le=lt(function(n,t,e){(me.call(n,e)?n[e]:n[e]=[]).push(t)}),Me=lt(function(n,t,e){n[e]=t}),Ve=Rt,Ue=vt(Ue=Zt.now)&&Ue||function(){return(new Zt).getTime()},Ge=8==Ee(d+"08")?Ee:function(n,t){return Ee(kt(n)?n.replace(I,""):n,t||0)};return J.after=function(n,t){if(!dt(t))throw new ie;return function(){return 1>--n?t.apply(this,arguments):void 0}},J.assign=U,J.at=function(n){for(var t=arguments,e=-1,r=ut(t,true,false,1),t=t[2]&&t[2][t[1]]===n?1:r.length,u=Xt(t);++e=b&&o(r?e[r]:s)))}var p=e[0],h=-1,g=p?p.length:0,y=[];n:for(;++h(m?t(m,v):f(s,v))){for(r=u,(m||s).push(v);--r;)if(m=i[r],0>(m?t(m,v):f(e[r],v)))continue n;y.push(v)}}for(;u--;)(m=i[u])&&c(m);return l(i),l(s),y},J.invert=_t,J.invoke=function(n,t){var e=p(arguments,2),r=-1,u=typeof t=="function",o=n?n.length:0,i=Xt(typeof o=="number"?o:0);return St(n,function(n){i[++r]=(u?t:n[t]).apply(n,e)}),i},J.keys=Fe,J.map=Rt,J.mapValues=function(n,t,e){var r={}; 39 | return t=J.createCallback(t,e,3),h(n,function(n,e,u){r[e]=t(n,e,u)}),r},J.max=At,J.memoize=function(n,t){function e(){var r=e.cache,u=t?t.apply(this,arguments):m+arguments[0];return me.call(r,u)?r[u]:r[u]=n.apply(this,arguments)}if(!dt(n))throw new ie;return e.cache={},e},J.merge=function(n){var t=arguments,e=2;if(!wt(n))return n;if("number"!=typeof t[2]&&(e=t.length),3e?Ie(0,r+e):Se(e,r-1))+1);r--;)if(n[r]===t)return r;return-1},J.mixin=Gt,J.noConflict=function(){return e._=le,this},J.noop=Ht,J.now=Ue,J.parseInt=Ge,J.random=function(n,t,e){var r=null==n,u=null==t;return null==e&&(typeof n=="boolean"&&u?(e=n,n=1):u||typeof t!="boolean"||(e=t,u=true)),r&&u&&(t=1),n=+n||0,u?(t=n,n=0):t=+t||0,e||n%1||t%1?(e=Re(),Se(n+e*(t-n+parseFloat("1e-"+((e+"").length-1))),t)):at(n,t) 50 | },J.reduce=Dt,J.reduceRight=$t,J.result=function(n,t){if(n){var e=n[t];return dt(e)?n[t]():e}},J.runInContext=s,J.size=function(n){var t=n?n.length:0;return typeof t=="number"?t:Fe(n).length},J.some=Ft,J.sortedIndex=zt,J.template=function(n,t,e){var r=J.templateSettings;n=oe(n||""),e=_({},e,r);var u,o=_({},e.imports,r.imports),r=Fe(o),o=xt(o),a=0,f=e.interpolate||S,l="__p+='",f=ue((e.escape||S).source+"|"+f.source+"|"+(f===N?x:S).source+"|"+(e.evaluate||S).source+"|$","g");n.replace(f,function(t,e,r,o,f,c){return r||(r=o),l+=n.slice(a,c).replace(R,i),e&&(l+="'+__e("+e+")+'"),f&&(u=true,l+="';"+f+";\n__p+='"),r&&(l+="'+((__t=("+r+"))==null?'':__t)+'"),a=c+t.length,t 51 | }),l+="';",f=e=e.variable,f||(e="obj",l="with("+e+"){"+l+"}"),l=(u?l.replace(w,""):l).replace(j,"$1").replace(k,"$1;"),l="function("+e+"){"+(f?"":e+"||("+e+"={});")+"var __t,__p='',__e=_.escape"+(u?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}";try{var c=ne(r,"return "+l).apply(v,o)}catch(p){throw p.source=l,p}return t?c(t):(c.source=l,c)},J.unescape=function(n){return null==n?"":oe(n).replace(qe,gt)},J.uniqueId=function(n){var t=++y;return oe(null==n?"":n)+t 52 | },J.all=Ot,J.any=Ft,J.detect=It,J.findWhere=It,J.foldl=Dt,J.foldr=$t,J.include=Ct,J.inject=Dt,Gt(function(){var n={};return h(J,function(t,e){J.prototype[e]||(n[e]=t)}),n}(),false),J.first=Bt,J.last=function(n,t,e){var r=0,u=n?n.length:0;if(typeof t!="number"&&null!=t){var o=u;for(t=J.createCallback(t,e,3);o--&&t(n[o],o,n);)r++}else if(r=t,null==r||e)return n?n[u-1]:v;return p(n,Ie(0,u-r))},J.sample=function(n,t,e){return n&&typeof n.length!="number"&&(n=xt(n)),null==t||e?n?n[at(0,n.length-1)]:v:(n=Tt(n),n.length=Se(Ie(0,t),n.length),n) 53 | },J.take=Bt,J.head=Bt,h(J,function(n,t){var e="sample"!==t;J.prototype[t]||(J.prototype[t]=function(t,r){var u=this.__chain__,o=n(this.__wrapped__,t,r);return u||null!=t&&(!r||e&&typeof t=="function")?new Q(o,u):o})}),J.VERSION="2.4.1",J.prototype.chain=function(){return this.__chain__=true,this},J.prototype.toString=function(){return oe(this.__wrapped__)},J.prototype.value=Qt,J.prototype.valueOf=Qt,St(["join","pop","shift"],function(n){var t=ae[n];J.prototype[n]=function(){var n=this.__chain__,e=t.apply(this.__wrapped__,arguments); 54 | return n?new Q(e,n):e}}),St(["push","reverse","sort","unshift"],function(n){var t=ae[n];J.prototype[n]=function(){return t.apply(this.__wrapped__,arguments),this}}),St(["concat","slice","splice"],function(n){var t=ae[n];J.prototype[n]=function(){return new Q(t.apply(this.__wrapped__,arguments),this.__chain__)}}),J}var v,h=[],g=[],y=0,m=+new Date+"",b=75,_=40,d=" \t\x0B\f\xa0\ufeff\n\r\u2028\u2029\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000",w=/\b__p\+='';/g,j=/\b(__p\+=)''\+/g,k=/(__e\(.*?\)|\b__t\))\+'';/g,x=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,C=/\w*$/,O=/^\s*function[ \n\r\t]+\w/,N=/<%=([\s\S]+?)%>/g,I=RegExp("^["+d+"]*0+(?=.$)"),S=/($^)/,E=/\bthis\b/,R=/['\n\r\t\u2028\u2029\\]/g,A="Array Boolean Date Function Math Number Object RegExp String _ attachEvent clearTimeout isFinite isNaN parseInt setTimeout".split(" "),D="[object Arguments]",$="[object Array]",T="[object Boolean]",F="[object Date]",B="[object Function]",W="[object Number]",q="[object Object]",z="[object RegExp]",P="[object String]",K={}; 55 | K[B]=false,K[D]=K[$]=K[T]=K[F]=K[W]=K[q]=K[z]=K[P]=true;var L={leading:false,maxWait:0,trailing:false},M={configurable:false,enumerable:false,value:null,writable:false},V={"boolean":false,"function":true,object:true,number:false,string:false,undefined:false},U={"\\":"\\","'":"'","\n":"n","\r":"r","\t":"t","\u2028":"u2028","\u2029":"u2029"},G=V[typeof window]&&window||this,H=V[typeof exports]&&exports&&!exports.nodeType&&exports,J=V[typeof module]&&module&&!module.nodeType&&module,Q=J&&J.exports===H&&H,X=V[typeof global]&&global;!X||X.global!==X&&X.window!==X||(G=X); 56 | var Y=s();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(G._=Y, define(function(){return Y})):H&&J?Q?(J.exports=Y)._=Y:H._=Y:G._=Y}).call(this); -------------------------------------------------------------------------------- /public/less/_base.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | html, body { 4 | padding: @page-padding; 5 | margin: @page-margin; 6 | } 7 | 8 | body { 9 | background: @react-light; 10 | color: @react-text; 11 | font-family: @page-sans-serif-fonts; 12 | } 13 | 14 | a { 15 | color: @react-maroon; 16 | text-decoration: none; 17 | 18 | &:hover { 19 | text-decoration: underline; 20 | } 21 | } 22 | 23 | code { 24 | background: @react-code-bg; 25 | color: @react-code-text; 26 | padding: 0.25em; 27 | margin: 0 0.25em; 28 | } 29 | 30 | .container { 31 | max-width: @page-max-width; 32 | margin: 0 auto; 33 | } -------------------------------------------------------------------------------- /public/less/_codemirror.less: -------------------------------------------------------------------------------- 1 | .cm-s-solarized-light { 2 | background-color: #f8f5ec; 3 | color: #637c84; 4 | } 5 | .cm-s-solarized-light .emphasis { 6 | font-weight: bold; 7 | } 8 | .cm-s-solarized-light .dotted { 9 | border-bottom: 1px dotted #cb4b16; 10 | } 11 | .cm-s-solarized-light .CodeMirror-gutter { 12 | background-color: #eee8d5; 13 | border-right: 3px solid #eee8d5; 14 | } 15 | .cm-s-solarized-light .CodeMirror-gutter .CodeMirror-gutter-text { 16 | color: #93a1a1; 17 | } 18 | .cm-s-solarized-light .CodeMirror-cursor { 19 | border-left-color: #002b36 !important; 20 | } 21 | .cm-s-solarized-light .CodeMirror-matchingbracket { 22 | color: #002b36; 23 | background-color: #eee8d5; 24 | box-shadow: 0 0 10px #eee8d5; 25 | font-weight: bold; 26 | } 27 | .cm-s-solarized-light .CodeMirror-nonmatchingbracket { 28 | color: #002b36; 29 | background-color: #eee8d5; 30 | box-shadow: 0 0 10px #eee8d5; 31 | font-weight: bold; 32 | color: #dc322f; 33 | border-bottom: 1px dotted #cb4b16; 34 | } 35 | .cm-s-solarized-light span.cm-keyword { 36 | color: #268bd2; 37 | } 38 | .cm-s-solarized-light span.cm-atom { 39 | color: #2aa198; 40 | } 41 | .cm-s-solarized-light span.cm-number { 42 | color: #586e75; 43 | } 44 | .cm-s-solarized-light span.cm-def { 45 | color: #637c84; 46 | } 47 | .cm-s-solarized-light span.cm-variable { 48 | color: #637c84; 49 | } 50 | .cm-s-solarized-light span.cm-variable-2 { 51 | color: #b58900; 52 | } 53 | .cm-s-solarized-light span.cm-variable-3 { 54 | color: #cb4b16; 55 | } 56 | .cm-s-solarized-light span.cm-comment { 57 | color: #93a1a1; 58 | } 59 | .cm-s-solarized-light span.cm-property { 60 | color: #637c84; 61 | } 62 | .cm-s-solarized-light span.cm-operator { 63 | color: #657b83; 64 | } 65 | .cm-s-solarized-light span.cm-string { 66 | color: #36958e; 67 | } 68 | .cm-s-solarized-light span.cm-bracket { 69 | color: #cb4b16; 70 | } 71 | .cm-s-solarized-light span.cm-tag { 72 | color: #657b83; 73 | } 74 | .cm-s-solarized-light span.cm-attribute { 75 | color: #586e75; 76 | font-weight: bold; 77 | } 78 | .cm-s-solarized-light span.cm-meta { 79 | color: #268bd2; 80 | } 81 | .cm-s-solarized-dark { 82 | background-color: #002b36; 83 | color: #839496; 84 | } 85 | .cm-s-solarized-dark .emphasis { 86 | font-weight: bold; 87 | } 88 | .cm-s-solarized-dark .dotted { 89 | border-bottom: 1px dotted #cb4b16; 90 | } 91 | .cm-s-solarized-dark .CodeMirror-gutter { 92 | background-color: #073642; 93 | border-right: 3px solid #073642; 94 | } 95 | .cm-s-solarized-dark .CodeMirror-gutter .CodeMirror-gutter-text { 96 | color: #586e75; 97 | } 98 | .cm-s-solarized-dark .CodeMirror-cursor { 99 | border-left-color: #fdf6e3 !important; 100 | } 101 | .cm-s-solarized-dark .CodeMirror-matchingbracket { 102 | color: #fdf6e3; 103 | background-color: #073642; 104 | box-shadow: 0 0 10px #073642; 105 | font-weight: bold; 106 | } 107 | .cm-s-solarized-dark .CodeMirror-nonmatchingbracket { 108 | color: #fdf6e3; 109 | background-color: #073642; 110 | box-shadow: 0 0 10px #073642; 111 | font-weight: bold; 112 | color: #dc322f; 113 | border-bottom: 1px dotted #cb4b16; 114 | } 115 | .cm-s-solarized-dark span.cm-keyword { 116 | color: #839496; 117 | font-weight: bold; 118 | } 119 | .cm-s-solarized-dark span.cm-atom { 120 | color: #2aa198; 121 | } 122 | .cm-s-solarized-dark span.cm-number { 123 | color: #93a1a1; 124 | } 125 | .cm-s-solarized-dark span.cm-def { 126 | color: #268bd2; 127 | } 128 | .cm-s-solarized-dark span.cm-variable { 129 | color: #cb4b16; 130 | } 131 | .cm-s-solarized-dark span.cm-variable-2 { 132 | color: #cb4b16; 133 | } 134 | .cm-s-solarized-dark span.cm-variable-3 { 135 | color: #cb4b16; 136 | } 137 | .cm-s-solarized-dark span.cm-comment { 138 | color: #586e75; 139 | } 140 | .cm-s-solarized-dark span.cm-property { 141 | color: #b58900; 142 | } 143 | .cm-s-solarized-dark span.cm-operator { 144 | color: #839496; 145 | } 146 | .cm-s-solarized-dark span.cm-string { 147 | color: #6c71c4; 148 | } 149 | .cm-s-solarized-dark span.cm-bracket { 150 | color: #cb4b16; 151 | } 152 | .cm-s-solarized-dark span.cm-tag { 153 | color: #839496; 154 | } 155 | .cm-s-solarized-dark span.cm-attribute { 156 | color: #93a1a1; 157 | font-weight: bold; 158 | } 159 | .cm-s-solarized-dark span.cm-meta { 160 | color: #268bd2; 161 | } -------------------------------------------------------------------------------- /public/less/_component-info.less: -------------------------------------------------------------------------------- 1 | .component-info { 2 | aside { 3 | float: right; 4 | margin-bottom: 15px; 5 | 6 | .pure-button { 7 | font-size: 1.5em; 8 | text-decoration: none; 9 | margin-left: 0.5em; 10 | 11 | &:first-child { 12 | margin-left: 0; 13 | } 14 | 15 | @media only screen and (max-width: 400px) { 16 | font-size: 1em; 17 | } 18 | } 19 | } 20 | } 21 | 22 | .component-header { 23 | text-align: center; 24 | padding: 20px; 25 | margin-bottom: 10px; 26 | } 27 | 28 | a.component-title { 29 | text-transform: capitalize; 30 | font-size: 2em; 31 | font-weight: bold; 32 | } 33 | 34 | .component-subtitle { 35 | font-size: 1.5em; 36 | } 37 | 38 | .toolbar, .readme { 39 | margin-top: 40px; 40 | } 41 | -------------------------------------------------------------------------------- /public/less/_content.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | main { 4 | h1 { 5 | text-align: center; 6 | } 7 | 8 | h2, h3 { 9 | font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif; 10 | font-weight: normal; 11 | } 12 | 13 | hr { 14 | height: 0; 15 | border-top: 1px solid #bbb; 16 | border-bottom: 1px solid #eee; 17 | margin: 0 auto; 18 | } 19 | 20 | .emphasize { 21 | font-weight: bold; 22 | font-style: italic; 23 | } 24 | 25 | .container { 26 | padding: @content-padding; 27 | margin-top: @content-margin; 28 | margin-bottom: @content-margin; 29 | 30 | background: @content-background; 31 | border: @content-border; 32 | border-radius: @content-border-radius; 33 | } 34 | } -------------------------------------------------------------------------------- /public/less/_footer.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | footer { 4 | max-width: @page-max-width; 5 | margin: 0 auto; 6 | padding: 10px; 7 | border-top: 1px solid @react-light-text; 8 | 9 | opacity: 0.5; 10 | text-align: center; 11 | 12 | &:hover { 13 | opacity: 1; 14 | } 15 | } -------------------------------------------------------------------------------- /public/less/_front.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | .front { 4 | li { 5 | margin: 15px; 6 | } 7 | 8 | h2 { 9 | margin: 40px 0 10px 0; 10 | } 11 | 12 | @media only screen and (min-width: 650px) { 13 | 14 | .faq, .component-lists { 15 | float: right; 16 | width: 60%; 17 | margin: 0 3%; 18 | } 19 | 20 | .component-lists { 21 | width: 28%; 22 | } 23 | 24 | } 25 | 26 | .faq { 27 | line-height: 1.45em; 28 | } 29 | 30 | &:after, &:before { 31 | display: table; 32 | content: ' '; 33 | } 34 | 35 | &:after { 36 | clear: both; 37 | } 38 | } -------------------------------------------------------------------------------- /public/less/_header.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | header { 4 | background: @react-dark; 5 | color: @react-light-text; 6 | padding: @header-padding; 7 | min-height: @header-logo-size; 8 | font-family: "Montserrat", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | 10 | @media only screen and (max-width: 400px) { 11 | padding-bottom: 0; 12 | margin-bottom: 0; 13 | min-height: 160px; 14 | } 15 | 16 | h1, h2, h3, a { 17 | color: @react-blue; 18 | } 19 | 20 | a:hover { 21 | text-decoration: none; 22 | } 23 | 24 | h1 { 25 | font-size: @header-logo-size / 3; 26 | font-weight: normal; 27 | margin: 0.1em 0; 28 | padding-top: 0.3em; 29 | 30 | @media only screen and (max-width: 620px) { 31 | font-size: 100px / 2; 32 | } 33 | 34 | @media only screen and (max-width: 555px) { 35 | font-size: 100px / 3; 36 | margin-top: -5px; 37 | } 38 | } 39 | 40 | .react-logo { 41 | width: @header-logo-size; 42 | height: @header-logo-size; 43 | float: left; 44 | margin-right: @header-logo-size / 8; 45 | 46 | @media only screen and (max-width: 555px) { 47 | width: 100px; 48 | height: 100px; 49 | margin-left: 5px; 50 | } 51 | } 52 | 53 | .container:before, .container:after { 54 | content: ' '; 55 | display: table; 56 | } 57 | 58 | .container:after { 59 | clear: both; 60 | } 61 | } -------------------------------------------------------------------------------- /public/less/_loader.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | @loader-dot-size: 30px; 3 | 4 | .loader { 5 | width: @loader-dot-size * 5; 6 | height: @loader-dot-size * 2; 7 | margin: @loader-dot-size auto 0 auto; 8 | position: relative; 9 | 10 | .dot { 11 | width: @loader-dot-size; 12 | height: @loader-dot-size; 13 | background: @react-blue; 14 | border-radius: 50%; 15 | position: absolute; 16 | 17 | .animation(loading-anim 2s linear infinite); 18 | 19 | &:nth-child(2) { 20 | .animation(loading-anim 2s linear infinite -.4s); 21 | } 22 | 23 | &:nth-child(3) { 24 | .animation(loading-anim 2s linear infinite -.8s); 25 | } 26 | 27 | &:nth-child(4) { 28 | .animation(loading-anim 2s linear infinite -1.2s); 29 | } 30 | 31 | &:nth-child(5) { 32 | .animation(loading-anim 2s linear infinite -1.6s); 33 | } 34 | } 35 | } 36 | 37 | .loading-anim() { 38 | 0% { left: 100px; top: 0; } 39 | 80% { left: 0; top: 0; } 40 | 85% { left: 0; top: -20px; width: 20px; height: 20px; } 41 | 90% { width: 40px; height: 15px; } 42 | 95% { left: 100px; top: -20px; width: 20px; height: 20px; } 43 | 100% { left: 100px; top: 0; } 44 | } 45 | 46 | .animation(@value) { 47 | -webkit-animation: @value; 48 | -moz-animation: @value; 49 | -ms-animation: @value; 50 | -o-animation: @value; 51 | animation: @value; 52 | } 53 | 54 | @-webkit-keyframes loading-anim { .loading-anim; } 55 | @-moz-keyframes loading-anim { .loading-anim } 56 | @-ms-keyframes loading-anim { .loading-anim } 57 | @-oi-keyframes loading-anim { .loading-anim } 58 | @keyframes loading-anim { .loading-anim } 59 | -------------------------------------------------------------------------------- /public/less/_mini-list.less: -------------------------------------------------------------------------------- 1 | .component-lists { 2 | ul { 3 | padding: 0 0 0 10px; 4 | } 5 | } -------------------------------------------------------------------------------- /public/less/_readme.less: -------------------------------------------------------------------------------- 1 | .readme { 2 | 3 | line-height: 1.4em; 4 | 5 | h1:first-child { 6 | margin-top: 5px; 7 | font-size: 2.5em; 8 | } 9 | 10 | h1, h2, h3, h4, h5 { 11 | text-align: left; 12 | font-family: @page-sans-serif-fonts; 13 | line-height: 1em; 14 | } 15 | 16 | img { 17 | max-width: 100%; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /public/less/_search-input.less: -------------------------------------------------------------------------------- 1 | @import '_variables'; 2 | 3 | input.search { 4 | font-size: @search-input-font-size; 5 | padding: @search-input-padding; 6 | } 7 | 8 | header { 9 | input.search { 10 | 11 | @media only screen and (min-width: @search-input-header-width) { 12 | width: @search-input-header-width; 13 | } 14 | 15 | @media only screen and (max-width: @search-input-header-width) { 16 | width: 100%; 17 | } 18 | 19 | @media only screen and (max-width: 812px) { 20 | width: 85%; 21 | display: block; 22 | margin: 0 auto; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /public/less/_search.less: -------------------------------------------------------------------------------- 1 | .results-table { 2 | width: 100%; 3 | border: 0; 4 | 5 | thead { 6 | background: transparent; 7 | font-size: 1.25em; 8 | } 9 | 10 | th { 11 | padding-left: 15px; 12 | } 13 | 14 | th a { 15 | display: block, 16 | } 17 | 18 | th.updated { 19 | width: 15%; 20 | } 21 | 22 | th.stars { 23 | width: 8%; 24 | } 25 | 26 | td { 27 | border-color: lighten(#CBCBCB, 12.5%); 28 | } 29 | 30 | .component-name { 31 | font-size: 1.35em; 32 | display: block; 33 | margin: 13px 0; 34 | } 35 | 36 | .no-result { 37 | padding: 50px 0; 38 | text-align: center; 39 | font-size: 1.35em; 40 | } 41 | 42 | .component-meta { 43 | display: none; 44 | } 45 | 46 | @media only screen and (max-width: 500px) { 47 | th, td { 48 | display: none; 49 | } 50 | 51 | th:first-child, td:first-child { 52 | display: table-cell; 53 | padding: 0; 54 | } 55 | 56 | .component-meta { 57 | display: block; 58 | 59 | dt { 60 | display: inline-block; 61 | } 62 | 63 | dd { 64 | display: inline-block; 65 | margin: 0 10px 0 5px; 66 | } 67 | } 68 | } 69 | 70 | @media only screen and (max-width: 650px) { 71 | td:nth-child(2), th:nth-child(2) { 72 | display: none; 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /public/less/_variables.less: -------------------------------------------------------------------------------- 1 | @react-dark: #2d2d2d; 2 | @react-light: #f9f9f9; 3 | @react-blue: #61dafb; 4 | @react-maroon: #c05b4d; 5 | @react-text: #484848; 6 | @react-light-text: #e9e9e9; 7 | @react-code-text: #555; 8 | @react-code-bg: fade(#000, 4%); 9 | 10 | @page-margin: 0; 11 | @page-padding: 0; 12 | @page-max-width: 900px; 13 | @page-sans-serif-fonts: "Helvetica Neue", Helvetica, Arial, sans-serif; 14 | 15 | @header-padding: 10px 0; 16 | @header-logo-size: 180px; 17 | 18 | @search-input-font-size: 18px; 19 | @search-input-padding: 12px 0; 20 | @search-input-header-width: 580px; 21 | 22 | @content-padding: 10px; 23 | @content-margin: 20px; 24 | @content-border: 1px solid rgba(16, 16, 16, 0.1); 25 | @content-border-radius: 3px; 26 | @content-background: #fff; 27 | -------------------------------------------------------------------------------- /public/less/components.less: -------------------------------------------------------------------------------- 1 | @import '_base'; 2 | @import '_header'; 3 | @import '_content'; 4 | @import '_search-input'; 5 | @import '_loader'; 6 | @import '_front'; 7 | @import '_mini-list'; 8 | @import '_search'; 9 | @import '_codemirror'; 10 | @import '_component-info'; 11 | @import '_readme'; 12 | @import '_footer'; 13 | -------------------------------------------------------------------------------- /templates/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <%- page.title %> 12 | 13 | 14 | 15 | 16 | 17 | <% _.forEach(resources.css, function(resource) { %> 18 | 19 | <% }); %> 20 | 21 | 22 | 23 |
    <%= page.body %>
    24 | 25 | <% _.forEach(resources.js, function(resource) { %> 26 | 27 | <% }); %> 28 | 29 | 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var config = { 4 | cache: true, 5 | entry: { 6 | bundle: './app/root', 7 | vendor: ['react/addons', 'lodash', 'lunr', 'moment', 'reflux', 'xhr', 'marked'], 8 | }, 9 | output: { 10 | path: path.join(__dirname, 'public', 'dist'), 11 | publicPath: 'public/dist/', 12 | filename: '[name].js', 13 | chunkFilename: '[chunkhash].js' 14 | }, 15 | module: { 16 | loaders: [ 17 | // required to write 'require('./style.css')' 18 | { test: /\.css$/, loader: 'style-loader!css-loader' }, 19 | 20 | // required for react jsx 21 | { test: /\.jsx?$/, loader: 'jsx-loader' } 22 | ] 23 | }, 24 | plugins: [ 25 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'), 26 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 27 | new webpack.ContextReplacementPlugin(/moment\.js[\/\\]lang$/, /^\.\/(de|pl)$/) 28 | ] 29 | }; 30 | 31 | if (process.env.NODE_ENV !== 'development') { 32 | config.plugins.push(new webpack.optimize.DedupePlugin()); 33 | config.plugins.push(new webpack.optimize.OccurenceOrderPlugin(true)); 34 | config.plugins.push(new webpack.optimize.UglifyJsPlugin()); 35 | } 36 | 37 | module.exports = config; --------------------------------------------------------------------------------