From 02f70f3b473210535c2f8ce9e13b58826898cff1 Mon Sep 17 00:00:00 2001 From: Kirk-Wang Date: Thu, 19 Jul 2018 23:05:00 +0800 Subject: [PATCH] first commit --- .eslintrc.json | 3 + .gitignore | 75 + .travis.yml | 20 + .travis/deploy_key.enc | Bin 0 -> 3248 bytes LICENSE | 21 + README.md | 7 + crowdin.yaml | 49 + deploy.sh | 11 + docs/actions.md | 509 ++ docs/admin-component.md | 557 ++ docs/authentication.md | 348 ++ docs/authorization.md | 303 + docs/creat-edit-view-components.md | 699 +++ docs/custom-app.md | 110 + docs/data-providers.md | 664 +++ docs/ecosystem.md | 41 + docs/faq.md | 98 + docs/field-components.md | 1010 ++++ docs/input-components.md | 1280 +++++ docs/intro.md | 188 + docs/list-view-component.md | 796 +++ docs/reference.md | 97 + docs/resource-component.md | 115 + docs/show-view-component.md | 260 + docs/theming.md | 629 +++ docs/translation.md | 395 ++ docs/tutorial.md | 697 +++ lerna.json | 13 + package.json | 40 + renovate.json | 6 + website/.gitignore | 2 + website/core/Footer.js | 49 + website/i18n/en.json | 41 + website/languages.js | 13 + website/package.json | 19 + website/pages/en/help.js | 36 + website/pages/en/index.js | 61 + website/pages/en/users.js | 39 + website/sidebars.json | 25 + website/siteConfig.js | 37 + website/static/css/custom.css | 108 + website/static/img/bg_home.png | Bin 0 -> 11936 bytes website/static/img/favicon/favicon.ico | Bin 0 -> 4286 bytes website/static/img/icon_common.png | Bin 0 -> 1394 bytes yarn.lock | 7218 ++++++++++++++++++++++++ 45 files changed, 16689 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 .travis/deploy_key.enc create mode 100644 LICENSE create mode 100644 README.md create mode 100644 crowdin.yaml create mode 100644 deploy.sh create mode 100644 docs/actions.md create mode 100644 docs/admin-component.md create mode 100644 docs/authentication.md create mode 100644 docs/authorization.md create mode 100644 docs/creat-edit-view-components.md create mode 100644 docs/custom-app.md create mode 100644 docs/data-providers.md create mode 100644 docs/ecosystem.md create mode 100644 docs/faq.md create mode 100644 docs/field-components.md create mode 100644 docs/input-components.md create mode 100644 docs/intro.md create mode 100644 docs/list-view-component.md create mode 100644 docs/reference.md create mode 100644 docs/resource-component.md create mode 100644 docs/show-view-component.md create mode 100644 docs/theming.md create mode 100644 docs/translation.md create mode 100644 docs/tutorial.md create mode 100644 lerna.json create mode 100644 package.json create mode 100644 renovate.json create mode 100644 website/.gitignore create mode 100644 website/core/Footer.js create mode 100644 website/i18n/en.json create mode 100644 website/languages.js create mode 100644 website/package.json create mode 100755 website/pages/en/help.js create mode 100755 website/pages/en/index.js create mode 100644 website/pages/en/users.js create mode 100644 website/sidebars.json create mode 100644 website/siteConfig.js create mode 100644 website/static/css/custom.css create mode 100644 website/static/img/bg_home.png create mode 100644 website/static/img/favicon/favicon.ico create mode 100644 website/static/img/icon_common.png create mode 100644 yarn.lock diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..0935b7e --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["@9renpoto/eslint-config", "@9renpoto/eslint-config-react"] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e66e563 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +package-lock.json + +lib/core/metadata.js +lib/core/MetadataBlog.js +website/translated_docs +website/build/ +website/yarn.lock +website/node_modules +website/.yarn-cache + +website/i18n/* +!website/i18n/en.json + +### https://raw.github.com/github/gitignore/5f15ed83c7a3d5db29845edae5205ca2d795c3d8/Node.gitignore + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +.yarn-cache diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..de823a5 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: node_js +sudo: true +node_js: stable +before_install: +- openssl aes-256-cbc -K $encrypted_9419dece98d7_key -iv $encrypted_9419dece98d7_iv -in .travis/deploy_key.enc -out deploy_key -d +- chmod 600 deploy_key +- eval `ssh-agent -s` +- ssh-add deploy_key +- git config --global user.name "9renpoto" +- git config --global user.email "9renpoto+kanto-riders@gmail.com" +env: + matrix: + - CMD=test + - CMD=build +script: +- yarn $CMD +after_success: +- '[ "$TRAVIS_BRANCH" == master ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$CMD" == "build" ] && bash ./deploy.sh' +notifications: + email: false diff --git a/.travis/deploy_key.enc b/.travis/deploy_key.enc new file mode 100644 index 0000000000000000000000000000000000000000..7a971181dc29f5137fc5b60cd1b3bcf4a83cd7f0 GIT binary patch literal 3248 zcmV;h3{UfB{D(Us1CJ0j2+cVH^U5t^OLawSo`Pp5@x^r9tM5{&{zAuX{O zs(MON$82umw5L0iR3IU_9vZjng;(Igqr0Ko!84o-eQpNV%BBV^y5PxSZiv7OezVW! zf>{l`cn?~Z3>_mJ+OFS!O`0j_giN}h&&=W!Sm-@A{t~NF=PG|f}#vBkw#w8i`P4COUhOPC#JNlmZ6nr~7I>Gngb zfI40S5SaL)*JFFWd2e9&4;x)v|0iQ}jTp+@#~*F?ao+S=cgrZA#9|_-l_w1M z!T0bR58!IOJq>$g*cchHKo6&wRbtYjww()5df**AlO#t%h~{;-09>>-Xa|=6Xz(PC z5}w)tkTWO}y#u&gaKVM#dtB?aM=Ecb2ggbrqkUK-@C!AnIzl`vNXhV5;e zH&I99#2DumXl8(V2v1<+Q4kV=9ZEXR#Bh54DAB4JvQr)=pS-HUv^0FRj$;DJ zeVU&651MAW%23wYHaMFL*vdZL3bZ<9s3>$de`*n_q|$%8o%CJ7&vlL#kMm;~X6^wm z;V2~j_2I17RoD6ao_&ye{s$uWs)r9jAm?eRS2@d(%ogw$FK>xc$cGuG&Oogt32vd| z$3LfL3%IO!5l^*5|6znk1EM(q0|-arR2QbDkev$yHVBWQIOBZGM$mRrEtGP zB;1tHVq_f6VH(ikj`#h&W{tILIoY%CG!Al$S}%`0@JA}@O`op^{NmUe2sr^y!`I7f z?0kNb^7%(8Rh+5;pvDmdntp?azCLpT|0gq7F@n1h%0Wk&AMqq5K`bXewg9Tc z@DXKfK3JJ<0%b=WE7>ZEUb-_Tk>oHPH0i&|Y3<75snp4U_cr#AK@ zc|pbSH94jd>LUq7LhIFNI^1$?E}Y?=OO_q~z2y65o6I#j5GjT|^;>5|KoKyrdCWfh ze(HQ)q79yAU5=aKTU>tcnh%F?On9(U z20PBdMEc#KybLl@%#Qi8A|o;K*h7gT$xB5w{V1%n=bCW{YUyR2Sqwm75%!pt&gnL< zr9-bYkv<3-{t3^2VGQb(9#6{#Jf ze-V!1+0e@27-Nd=Oe{z`$YFXIBb2?dH6JDNkb}$?0aj*Re2I3UF~AJ6(`x&2FZ=ZW zAv`XAPNWryzATQ5-K{oCfJIG9XZGGKl9Y~=dd(~NX=uhCY;rqa4Uf!Tg!jovCts{< z+F)vDl@)kBu8ul87DuN%XKV6|db{7M;BY0;P$DZ8VyoIaJ^jhp(a zjD`!)h#LCl73bOUuz5B}>*1s*-GI(~~9DI?SrmCguJR_gaSXpaaiYkDv|7#wiK$}MDBYuU`%y^dK8 z*tJUZTx0C?2#NbL-jtTWPg!U6ZlveG4&a;*T_Ih6V&g|lb5kSuk3lC@;w(O_Y+cf8 zl_y?AcUPBMfLB)85JD#CU|1d`axS~A>i$?kIt-0IJV6aT^^T~Dkz<@Kn;MShEdC3? z=EaR4^9m$+t)=4hYXAId*%&VxdR9^?W$DB4BtIx9Di81Yhabldv+-^UMqKf3;X&vK zSp7?o`cC;^OATsxj}l*TrgFuku;`1ME1($Rh(Gs)C1Utou5nL}I{Xfr5Vt9jCguG8 zocRm35mn-SZN4d0wdO3RhfUgbBr^xr0n1k)Q2jszV~mqrj#)*k*}W__U62wt+KldB z2=S7h*EsggCEyQt_Fr8-tu3~PDrFtu&fWi;p>s7L#6rKc?XeYH^&Hr<_Vf_P5KvU| z2%Z=}<4HTR)^8RA?Pu;ESdya4lS#vElJRU>14jF6>;iEe7|}HSMzfMR?vWBT0>a^Q zL~R!WK{#Xq(5#{nwE#k|2BBzH^4%g95D)SvO0zws3{8FDoC_)26bE;twU5V#{sAm~ zR1*8%Y+ka9LC)H+P5GCL&k6=M(@9iGu{R&d276Oq4z-drqO10^PT%P?BU+3fBB^k; zBsX8mSFG%lSKmihr`ijXAnI3;M6L(hy#Oi3I7>}Y#g_N+fH^E9P+EC`%z6Ie0?oY1 z{MSV^^F(FLkV4>Q|{jKu=8hbqaD%rFn0`(+qkuK0)8ozn=tv)g!KRcU_W&}Z$Jcm`~=_e zmp=xdUYtFohjFk?ZE^|skMsC?oy59IrbvoJH>~nq8vICSJo2{;!0@xlvY{+SKyX&# za9-Vr&S9R)nCn&6pq89dFHkeIjPh{%Z`1AdhU+FJvg2$~B^Spk7yuZ=6rJqaN4+Ug z32oW6JTR4I0y3W7Zi%;yen6o3;oc5(6*R6#iDCZc_k;f7i9Dig5fMe{5RKxc*u^4Q{StX4gY)SCX#~EWKj?ku-H~uoSNCM^#j*O#;i9Ki434xBBSD}(^ zMUQ9&NM6qb%a9kcz$+5<>k;G=18UJ)`my=;3OIYw-a1vt?aQbtc0@fB9aa3(^k9p{ zK^%iJrr-xapXF0A~bXD3TP=90ESzk8%u)3=nZqif)H{ zC%OY7Ls>O=+13lipwh)JtMg%D#%552<18?8S3=X*Yw-`vZbQ*>XPB^S(F*Nw#RL1V z71^-f6H-ePlYCK0d;)GLpOAA;jb(=6TllxxyHa>>_PDOB>AHq^8du26m|6f8w}C7G zFI+X?TN{vt9y$4e;Fzc1EU`0{Ut7@%?R86neY$3wT(45fcjSa^zd8}LY-K=2d8bDA z#7S*#yeR*zbM!{vHUmC77xL30k+SP#BF!At_`*Jz=|fzRfkw6>|LRq{&pG4pqmq{w z(1+ptjsI~Ds5<0eUj0N?zu8&crw5qB;hh%0a0K^?mg?MVTBR|MsE{Op(7X^YX*$Ix z7n|u8(%biOMk#hgS5KpnWCA@c(ffw1VWC;gKUka^Kmmm!UesW0qmrYur8}4jjrk3- i2{k| Hello Guys~ + +## License + +MIT © Kirk-Wang diff --git a/crowdin.yaml b/crowdin.yaml new file mode 100644 index 0000000..7dd56a1 --- /dev/null +++ b/crowdin.yaml @@ -0,0 +1,49 @@ +project_identifier_env: CROWDIN_DOCUSAURUS_PROJECT_ID +api_key_env: CROWDIN_DOCUSAURUS_API_KEY +base_path: "./" +preserve_hierarchy: true + +files: + - + source: '/docs/**/*.md' + translation: '/website/translated_docs/%locale%/**/%original_file_name%' + languages_mapping: &anchor + locale: + 'af': 'af' + 'ar': 'ar' + 'bs-BA': 'bs-BA' + 'ca': 'ca' + 'cs': 'cs' + 'da': 'da' + 'de': 'de' + 'el': 'el' + 'es-ES': 'es-ES' + 'fa': 'fa-IR' + 'fi': 'fi' + 'fr': 'fr' + 'he': 'he' + 'hu': 'hu' + 'id': 'id-ID' + 'it': 'it' + 'ja': 'ja' + 'ko': 'ko' + 'mr': 'mr-IN' + 'nl': 'nl' + 'no': 'no-NO' + 'pl': 'pl' + 'pt-BR': 'pt-BR' + 'pt-PT': 'pt-PT' + 'ro': 'ro' + 'ru': 'ru' + 'sk': 'sk-SK' + 'sr': 'sr' + 'sv-SE': 'sv-SE' + 'tr': 'tr' + 'uk': 'uk' + 'vi': 'vi' + 'zh-CN': 'zh-CN' + 'zh-TW': 'zh-TW' + - + source: '/website/i18n/en.json' + translation: '/website/i18n/%locale%.json' + languages_mapping: *anchor diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..9c862a4 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +yarn && cd website && yarn run write-translations +sudo apt-get update +sudo apt-get install default-jre rsync +wget https://artifacts.crowdin.com/repo/deb/crowdin.deb -O crowdin.deb +sudo dpkg -i crowdin.deb +sleep 5 +yarn run crowdin-upload +yarn run crowdin-download +GIT_USER=9renpoto USE_SSH=true yarn run publish-gh-pages diff --git a/docs/actions.md b/docs/actions.md new file mode 100644 index 0000000..1bbc9fb --- /dev/null +++ b/docs/actions.md @@ -0,0 +1,509 @@ +--- +id: actions +title: Writing Actions +--- + +Admin interfaces often have to offer custom actions, beyond the simple CRUD. For instance, in an administration for comments, an "Approve" button (allowing to update the `is_approved` property and to save the updated record in one click) - is a must have. + +How can you add such custom actions with react-admin? The answer is twofold, and learning to do it properly will give you a better understanding of how react-admin uses Redux and redux-saga. + +## The Simple Way + +Here is an implementation of the "Approve" button that works perfectly: + +```jsx +// in src/comments/ApproveButton.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import FlatButton from '@material-ui/core/FlatButton'; +import { showNotification as showNotificationAction } from 'react-admin'; +import { push as pushAction } from 'react-router-redux'; + +class ApproveButton extends Component { + handleClick = () => { + const { push, record, showNotification } = this.props; + const updatedRecord = { ...record, is_approved: true }; + fetch(`/comments/${record.id}`, { method: 'PUT', body: updatedRecord }) + .then(() => { + showNotification('Comment approved'); + push('/comments'); + }) + .catch((e) => { + console.error(e); + showNotification('Error: comment not approved', 'warning') + }); + } + + render() { + return ; + } +} + +ApproveButton.propTypes = { + push: PropTypes.func, + record: PropTypes.object, + showNotification: PropTypes.func, +}; + +export default connect(null, { + showNotification: showNotificationAction, + push: pushAction, +})(ApproveButton); +``` + +The `handleClick` function makes a `PUT` request the REST API with `fetch`, then displays a notification (with `showNotification`) and redirects to the comments list page (with `push`); + +`showNotification` and `push` are *action creators*. This is a Redux term for functions that return a simple action object. When given an object of action creators in the second argument, `connect()` will [decorate each action creator](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) with Redux' `dispatch` method, so in the `handleClick` function, a call to `showNotification()` is actually a call to `dispatch(showNotification())`. + +This `ApproveButton` can be used right away, for instance in the list of comments, where `` automatically injects the `record` to its children: + +```jsx +// in src/comments/index.js +import ApproveButton from './ApproveButton'; + +export const CommentList = (props) => + + + + + + + + + ; +``` + +Or, in the `` page, as a [custom action](./CreateEdit.md#actions): + +```jsx +// in src/comments/CommentEditActions.js +import React from 'react'; +import CardActions from '@material-ui/core/CardActions'; +import { ListButton, DeleteButton } from 'react-admin'; +import ApproveButton from './ApproveButton'; + +const cardActionStyle = { + zIndex: 2, + display: 'inline-block', + float: 'right', +}; + +const CommentEditActions = ({ basePath, data, resource }) => ( + + + + + +); + +export default CommentEditActions; + +// in src/comments/index.js +import CommentEditActions from './CommentEditActions'; + +export const CommentEdit = (props) => + }> + ... + ; +``` + +## Using a Data Provider Instead of Fetch + +The previous code uses `fetch()`, which means it has to make raw HTTP requests. The REST logic often requires a bit of HTTP plumbing to deal with query parameters, encoding, headers, body formatting, etc. It turns out you probably already have a function that maps from a REST request to an HTTP request: the [Data Provider](./DataProviders.md). So it's a good idea to use this function instead of `fetch` - provided you have exported it: + +```jsx +// in src/dataProvider.js +import jsonServerProvider from 'ra-data-json-server'; +export default jsonServerProvider('http://Mydomain.com/api/'); + +// in src/comments/ApproveButton.js +import { UPDATE } from 'react-admin'; +import dataProvider from '../dataProvider'; + +class ApproveButton extends Component { + handleClick = () => { + const { push, record, showNotification } = this.props; + const updatedRecord = { ...record, is_approved: true }; + dataProvider(UPDATE, 'comments', { id: record.id, data: updatedRecord }) + .then(() => { + showNotification('Comment approved'); + push('/comments'); + }) + .catch((e) => { + console.error(e); + showNotification('Error: comment not approved', 'warning') + }); + } + + render() { + return ; + } +} +``` + +There you go: no more `fetch`. Just like `fetch`, the `dataProvider` returns a `Promise`. It's signature is: + +```jsx +/** + * Query a data provider and return a promise for a response + * + * @example + * dataProvider(GET_ONE, 'posts', { id: 123 }) + * => new Promise(resolve => resolve({ id: 123, title: "hello, world" })) + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the action type + * @returns {Promise} the Promise for a response + */ +const dataProvider = (type, resource, params) => new Promise(); +``` + +As for the syntax of the various request types (`GET_LIST`, `GET_ONE`, `UPDATE`, etc.), head to the [Data Provider documentation](./DataProviders.md#request-format) for more details. + +## Using a Custom Action Creator + +Fetching data right inside the component is easy. But if you're a Redux user, you might want to do it in a more idiomatic way - by dispatching actions. First, create your own action creator to replace the call to `dataProvider`: + +```jsx +// in src/comment/commentActions.js +import { UPDATE } from 'react-admin'; +export const COMMENT_APPROVE = 'COMMENT_APPROVE'; +export const commentApprove = (id, data, basePath) => ({ + type: COMMENT_APPROVE, + payload: { id, data: { ...data, is_approved: true } }, + meta: { resource: 'comments', fetch: UPDATE }, +}); +``` + +This action creator takes advantage of react-admin's built in fetcher, which listens to actions with the `fetch` meta. Upon dispatch, this action will trigger the call to `dataProvider(UPDATE, 'comments')`, dispatch a `COMMENT_APPROVE_LOADING` action, then after receiving the response, dispatch either a `COMMENT_APPROVE_SUCCESS`, or a `COMMENT_APPROVE_FAILURE`. + +To use the new action creator in the component, `connect` it: + +```jsx +// in src/comments/ApproveButton.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import Button from '@material-ui/core/Button'; +import { commentApprove as commentApproveAction } from './commentActions'; + +class ApproveButton extends Component { + handleClick = () => { + const { commentApprove, record } = this.props; + commentApprove(record.id, record); + // how about push and showNotification? + } + + render() { + return ; + } +} + +ApproveButton.propTypes = { + commentApprove: PropTypes.func, + record: PropTypes.object, +}; + +export default connect(null, { + commentApprove: commentApproveAction, +})(ApproveButton); +``` + +This works fine: when a user presses the "Approve" button, the API receives the `UPDATE` call, and that approves the comment. But it's not possible to call `push` or `showNotification` in `handleClick` anymore. This is because `commentApprove()` returns immediately, whether the API call succeeds or not. How can you run a function only when the action succeeds? + +## Handling Side Effects + +Fetching data is called a *side effect*, since it calls the outside world, and is asynchronous. Usual actions may have other side effects, like showing a notification, or redirecting the user to another page. Just like for the `fetch` side effect, you can associate side effects to an action declaratively by setting the appropriate keys in the action `meta`. + +For instance, to display a notification when the `COMMENT_APPROVE` action is dispatched, add the `notification` meta: + +```diff +// in src/comment/commentActions.js +import { UPDATE } from 'react-admin'; +export const COMMENT_APPROVE = 'COMMENT_APPROVE'; +export const commentApprove = (id, data, basePath) => ({ + type: COMMENT_APPROVE, + payload: { id, data: { ...data, is_approved: true } }, + meta: { + resource: 'comments', + fetch: UPDATE, ++ notification: { ++ body: 'resources.comments.notification.approved_success', ++ level: 'info', ++ }, ++ redirectTo: '/comments', ++ basePath, + }, +}); +``` + +React-admin can handle the following side effects metas: + +- `notification`: Display a notification. The property value should be an object describing the notification to display. The `body` can be a translation key. `level` can be either `info` or `warning`. +- `redirectTo`: Redirect the user to another page. The property value should be the path to redirect the user to. +- `refresh`: Force a rerender of the current view (equivalent to pressing the Refresh button). Set to true to enable. +- `unselectAll`: Unselect all lines in the current datagrid. Set to true to enable. +- `basePath`: This is not a side effect, but it's used internaly to compute redirection paths. Set it when you have a redirection side effect. + +## Success and Failure Side Effects + +In the previous example, the "notification approved" notification appears when the `COMMENT_APPROVE` action is dispatched, i.e. *before* the server is even called. That's a bit too early: what if the server returns an error? + +In practice, most side effects must be triggered after the `fetch` side effect succeeds or fails. To support that, you can enclose side effects under the `onSuccess` and `onFailure` keys in the `meta` property of an action: + +```diff +// in src/comment/commentActions.js +import { UPDATE } from 'react-admin'; +export const COMMENT_APPROVE = 'COMMENT_APPROVE'; +export const commentApprove = (id, data, basePath) => ({ + type: COMMENT_APPROVE, + payload: { id, data: { ...data, is_approved: true } }, + meta: { + resource: 'comments', + fetch: UPDATE, +- notification: { +- body: 'resources.comments.notification.approved_success', +- level: 'info', +- }, +- redirectTo: '/comments', +- basePath, ++ onSuccess: { ++ notification: { ++ body: 'resources.comments.notification.approved_success', ++ level: 'info', ++ }, ++ redirectTo: '/comments', ++ basePath, ++ }, ++ onFailure: { ++ notification: { ++ body: 'resources.comments.notification.approved_failure', ++ level: 'warning', ++ }, ++ }, + }, +}); +``` + +In this case, no side effect is triggered when the `COMMENT_APPROVE` action is dispatched. However, when the `fetch` side effects returns successfully, react-admin dispatches a `COMMENT_APPROVE_SUCCESS` action, and copies the `onSuccess` side effects into the `meta` property. So it will dispatch an action looking like: + +```js +{ + type: COMMENT_APPROVE_SUCCESS, + payload: { data: { /* data returned by the server */ } }, + meta: { + resource: 'comments', + notification: { + body: 'resources.comments.notification.approved_success', + level: 'info', + }, + redirectTo: '/comments', + basePath, + }, +} +``` + +And then, the side effects will trigger. With this code, approving a review now displays the correct notification, and redirects to the comment list. + +You can use `onSuccess` and `onFailure` metas in your own actions to handle side effects. + +## Custom sagas + +Sometimes, you may want to trigger other *side effects*. React-admin promotes a programming style where side effects are decoupled from the rest of the code, which has the benefit of making them testable. + +In react-admin, side effects are handled by Sagas. [Redux-saga](https://redux-saga.github.io/redux-saga/) is a side effect library built for Redux, where side effects are defined by generator functions. If this is new to you, take a few minutes to go through the Saga documentation. + +Here is the generator function necessary to handle the side effects for a failed `COMMENT_APPROVE` action which would log the error with an external service such as [Sentry](https://sentry.io): + +```jsx +// in src/comments/commentSaga.js +import { call, takeEvery } from 'redux-saga/effects'; + +function* commentApproveFailure({ error }) { + yield call(Raven.captureException, error); +} + +export default function* commentSaga() { + yield takeEvery('COMMENT_APPROVE_FAILURE', commentApproveFailure); +} +``` + +Let's explain all of that, starting with the final `commentSaga` generator function. A [generator function](http://exploringjs.com/es6/ch_generators.html) (denoted by the `*` in the function name) gets paused on statements called by `yield` - until the yielded statement returns. `yield takeEvery([ACTION_NAME], callback)` executes the provided callback [every time the related action is called](https://redux-saga.github.io/redux-saga/docs/basics/UsingSagaHelpers.html). To summarize, this will execute `commentApproveFailure` when the fetch initiated by `commentApprove()` fails. + +As for `commentApproveFailure`, it just dispatch a [`call`](https://redux-saga.js.org/docs/api/#callfn-args) side effect to the `captureException` function from the global `Raven` object. + +To use this saga, pass it in the `customSagas` props of the `` component: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; + +import { CommentList } from './comments'; +import commentSaga from './comments/commentSaga'; + +const App = () => ( + + + +); + +export default App; +``` + +With this code, a failed review approval now sends the the correct signal to Sentry. + +**Tip**: The side effects are [testable](https://redux-saga.github.io/redux-saga/docs/introduction/BeginnerTutorial.html#making-our-code-testable), too. + +## Optimistic Rendering and Undo + +In the previous example, after clicking on the "Approve" button, a spinner displays while the data provider is fetched. Then, users are redirected to the comments list. But in most cases, the server returns a success response, so the user waits for this response for nothing. + +For its own fetch actions, react-admin uses an approach called *optimistic rendering*. The idea is to handle the `fetch` actions on the client side first (i.e. updating entities in the Redux store), and re-render the screen immediately. The user sees the effect of their action with no delay. Then, react-admin applies the success side effects, and only after that it triggers the fetch to the data provider. If the fetch ends with a success, react-admin does nothing more than a refresh to grab the latest data from the server, but in most cases, the user sees no difference (the data in the Redux store and the data from the data provider are the same). If the fetch fails, react-admin shows an error notification, and forces a refresh, too. + +As a bonus, while the success notification is displayed, users have the ability to cancel the action *before* the data provider is even called. + +To make an action with a `fetch` meta optimistic, decorate it with the `startUndoable` action creator: + +```diff +// in src/comments/ApproveButton.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import Button from '@material-ui/core/Button'; ++ import { startUndoable as startUndoableAction } from 'ra-core'; +- import { commentApprove as commentApproveAction } from './commentActions'; ++ import { commentApprove } from './commentActions'; + +class ApproveButton extends Component { + handleClick = () => { +- const { commentApprove, record } = this.props; +- commentApprove(record.id, record); ++ const { startUndoable, record } = this.props; ++ startUndoable(commentApprove(record.id, record)); + } + + render() { + return ; + } +} + +ApproveButton.propTypes = { +- commentApprove: PropTypes.func, ++ startUndoable: PropTypes.func, + record: PropTypes.object, +}; + +export default connect(null, { +- commentApprove: commentApproveAction, ++ startUndoable: startUndoableAction, +})(ApproveButton); +``` + +And that's all it takes to make a fetch action optimistic. Note that the `startUndoable` action creator is passed to Redux `connect` as `mapDispatchToProp`, to be decorated with `dispatch` - but `commentApprove` is not. Only the first action must be decorated with dispatch. + +The fact that react-admin updates the internal store if you use custom actions with the `fetch` meta should be another motivation to avoid using raw `fetch`. + +## Using a Custom Reducer + +In addition to triggering REST calls, you may want to store the effect of your own actions in the application state. For instance, if you want to display a widget showing the current exchange rate for the bitcoin, you might need the following action: + +```jsx +// in src/bitcoinRateReceived.js +export const BITCOIN_RATE_RECEIVED = 'BITCOIN_RATE_RECEIVED'; +export const bitcoinRateReceived = (rate) => ({ + type: BITCOIN_RATE_RECEIVED, + payload: { rate }, +}); +``` + +This action can be triggered on mount by the following component: + +```jsx +// in src/BitCoinRate.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bitcoinRateReceived as bitcoinRateReceivedAction } from './bitcoinRateReceived'; + +class BitCoinRate extends Component { + componentWillMount() { + fetch('https://blockchain.info/fr/ticker') + .then(response => response.json()) + .then(rates => rates.USD['15m']) + .then(bitcoinRateReceived) // dispatch action when the response is received + } + + render() { + const { rate } = this.props; + return
Current bitcoin value: {rate}$
+ } +} + +BitCoinRate.propTypes = { + bitcoinRateReceived: PropTypes.func, + rate: PropTypes.number, +}; + +const mapStateToProps = state => ({ rate: state.bitcoinRate }); + +export default connect(mapStateToProps, { + bitcoinRateReceived: bitcoinRateReceivedAction, +})(BitCoinRate); +``` + +In order to put the rate passed to `bitcoinRateReceived()` into the Redux store, you'll need a reducer: + +```jsx +// in src/rateReducer.js +import { BITCOIN_RATE_RECEIVED } from './bitcoinRateReceived'; + +export default (previousState = 0, { type, payload }) => { + if (type === BITCOIN_RATE_RECEIVED) { + return payload.rate; + } + return previousState; +} +``` + +Now the question is: How can you put this reducer in the `` app? Simple: use the `customReducers` props: + + +```jsx +// in src/App.js +import React from 'react'; +import { Admin } from 'react-admin'; + +import rate from './rateReducer'; + +const App = () => ( + + ... + +); + +export default App; +``` + + +**Tip**: You can avoid storing data in the Redux state by storing data in a component state instead. It's much less complicated to deal with, and more performant, too. Use the global state only when you really need to. + +## List Bulk Actions + +Almost everything we saw before is true for custom `List` bulk actions too, with the following few differences: + +* They receive the following props: `resource`, `selectedIds` and `filterValues` +* They do not receive the current record in the `record` prop as there are many of them. +* They must render as a material-ui [`MenuItem`](http://www.material-ui.com/#/components/menu). + +You can find a complete example in the `List` documentation, in the [`bulk-actions`](/List.html#bulk-actions) section. + +## Conclusion + +Which style should you choose for your own action buttons? + +The first version (with `fetch`) is perfectly fine, and if you're not into unit testing your components, or decoupling side effects from pure functions, then you can stick with it without problem. + +On the other hand, if you want to promote reusability, separation of concerns, adhere to react-admin's coding standards, and if you know enough Redux and Saga, use the final version. diff --git a/docs/admin-component.md b/docs/admin-component.md new file mode 100644 index 0000000..2d9e9c3 --- /dev/null +++ b/docs/admin-component.md @@ -0,0 +1,557 @@ +--- +id: admin-component +title: +--- + +The `` component creates an application with its own state, routing, and controller logic. `` requires only a `dataProvider` prop, and at least one child `` to work: + +```jsx +// in src/App.js +import React from 'react'; + +import { Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +import { PostList } from './posts'; + +const App = () => ( + + + +); + +export default App; +``` + +Here are all the props accepted by the component: + +* [`dataProvider`](#dataprovider) +* [`title`](#title) +* [`dashboard`](#dashboard) +* [`catchAll`](#catchall) +* [`menu`](#menu) (deprecated) +* [`theme`](#theme) +* [`appLayout`](#applayout) +* [`customReducers`](#customreducers) +* [`customSagas`](#customsagas) +* [`customRoutes`](#customroutes) +* [`authProvider`](#authprovider) +* [`loginPage`](#loginpage) +* [`logoutButton`](#logoutbutton) +* [`locale`](#internationalization) +* [`messages`](#internationalization) +* [`initialState`](#initialstate) +* [`history`](#history) + +## `dataProvider` + +The only required prop, it must be a function returning a promise, with the following signature: + +```jsx +/** + * Query a data provider and return a promise for a response + * + * @example + * dataProvider(GET_ONE, 'posts', { id: 123 }) + * => new Promise(resolve => resolve({ id: 123, title: "hello, world" })) + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the action type + * @returns {Promise} the Promise for a response + */ +const dataProvider = (type, resource, params) => new Promise(); +``` + +The `dataProvider` is also the ideal place to add custom HTTP headers, authentication, etc. The [Data Providers Chapter](./DataProviders.html) of the documentation lists available data providers, and explains how to build your own. + +## `title` + +By default, the header of an admin app uses 'React Admin' as the main app title. It's probably the first thing you'll want to customize. The `title` prop serves exactly that purpose. + +```jsx +const App = () => ( + + // ... + +); +``` + +## `dashboard` + +By default, the homepage of an an admin app is the `list` of the first child ``. But you can also specify a custom component instead. To fit in the general design, use Material UI's `` component, and react-admin's `` component: + +```jsx +// in src/Dashboard.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import { ViewTitle } from 'react-admin'; +export default () => ( + + + Lorem ipsum sic dolor amet... + +); +``` + +```jsx +// in src/App.js +import Dashboard from './Dashboard'; + +const App = () => ( + + // ... + +); +``` + +![Custom home page](https://marmelab.com/react-admin/img/dashboard.png) + +**Tip**: Adding the `` component will also allow the header to be displayed in mobile resolutions. + +## `catchAll` + +When users type URLs that don't match any of the children `` components, they see a default "Not Found" page. + +![Not Found](https://marmelab.com/react-admin/img/not-found.png) + +You can customize this page to use the component of your choice by passing it as the `catchAll` prop. To fit in the general design, use Material UI's `` component, and react-admin's `` component: + +```jsx +// in src/NotFound.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import { ViewTitle } from 'react-admin'; + +export default () => ( + + + +

404: Page not found

+
+
+); +``` + +```jsx +// in src/App.js +import NotFound from './NotFound'; + +const App = () => ( + + // ... + +); +``` + +**Tip**: If your custom `catchAll` component contains react-router `` components, this allows you to register new routes displayed within the react-admin layout easily. Note that these routes will match *after* all the react-admin resource routes have been tested. To add custom routes *before* the react-admin ones, and therefore override the default resource routes, use the [`customRoutes` prop](#customroutes) instead. + +## `menu` + +**Tip**: This prop is deprecated. To override the menu component, use a [custom layout](#appLayout) instead. + +React-admin uses the list of `` components passed as children of `` to build a menu to each resource with a `list` component. + +If you want to add or remove menu items, for instance to link to non-resources pages, you can create your own menu component: + +```jsx +// in src/Menu.js +import React, { createElement } from 'react'; +import { connect } from 'react-redux'; +import { MenuItemLink, getResources } from 'react-admin'; +import { withRouter } from 'react-router-dom'; +import LabelIcon from '@material-ui/icons/Label'; + +import Responsive from '../layout/Responsive'; + +const Menu = ({ resources, onMenuClick, logout }) => ( +
+ {resources.map(resource => ( + + ))} + } + onClick={onMenuClick} /> + +
+); + +const mapStateToProps = state => ({ + resources: getResources(state), +}); + +export default withRouter(connect(mapStateToProps)(Menu)); +``` + +**Tip**: Note the `MenuItemLink` component. It must be used to avoid unwanted side effects in mobile views. It supports a custom text and icon (which must be a material-ui ``). + +**Tip**: Note that we include the `logout` item only on small devices. Indeed, the `logout` button is already displayed in the AppBar on larger devices. + +**Tip**: Note that we use React Router [`withRouter`](https://reacttraining.com/react-router/web/api/withRouter) Higher Order Component and that it is used **before** Redux [`connect](https://github.com/reactjs/react-redux/blob/master/docs/api.html#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options). This is required if you want the active menu item to be highlighted. + +Then, pass it to the `` component as the `menu` prop: + +```jsx +// in src/App.js +import Menu from './Menu'; + +const App = () => ( + + // ... + +); +``` + +See the [Theming documentation](./Theming.html#using-a-custom-menu) for more details. + +## `theme` + +Material UI supports [theming](http://www.material-ui.com/#/customization/themes). This lets you customize the look and feel of an admin by overriding fonts, colors, and spacing. You can provide a custom material ui theme by using the `theme` prop: + +```jsx +import { createMuiTheme } from '@material-ui/core/styles'; + +const theme = createMuiTheme({ + palette: { + type: 'dark', // Switching the dark mode on is a single property value change. + }, +}); + +const App = () => ( + + // ... + +); +``` + +![Dark theme](https://marmelab.com/react-admin/img/dark-theme.png) + +For more details on predefined themes and custom themes, refer to the [Material UI Customization documentation](https://material-ui.com/customization/themes/). + +## `appLayout` + +If you want to deeply customize the app header, the menu, or the notifications, the best way is to provide a custom layout component. It must contain a `{children}` placeholder, where react-admin will render the resources. If you use material UI fields and inputs, it should contain a `` element. And finally, if you want to show the spinner in the app header when the app fetches data in the background, the Layout should connect to the redux store. + +Use the [default layout](https://github.com/marmelab/react-admin/blob/master/packages/ra-ui-materialui/src/layout/Layout.js) as a starting point, and check [the Theming documentation](./Theming.html#using-a-custom-layout) for examples. + +```jsx +// in src/App.js +import MyLayout from './MyLayout'; + +const App = () => ( + + // ... + +); +``` + +Your custom layout can simply extend the default `` component if you only want to override the appBar, the menu, or the notification component. For instance: + +```jsx +// in src/MyLayout.js +import { Layout } from 'react-admin'; +import MyAppBar from './MyAppBar'; +import MyMenu from './MyMenu'; +import MyNotification from './MyNotification'; + +const MyLayout = (props) => ; + +export default MyLayout; +``` + +For more details on custom layouts, check [the Theming documentation](./Theming.html#using-a-custom-layout). + +## `customReducers` + +The `` app uses [Redux](http://redux.js.org/) to manage state. The state has the following keys: + +```jsx +{ + admin: { /*...*/ }, // used by react-admin + form: { /*...*/ }, // used by redux-form + routing: { /*...*/ }, // used by react-router-redux +} +``` + +If your components dispatch custom actions, you probably need to register your own reducers to update the state with these actions. Let's imagine that you want to keep the bitcoin exchange rate inside the `bitcoinRate` key in the state. You probably have a reducer looking like the following: + +```jsx +// in src/bitcoinRateReducer.js +export default (previousState = 0, { type, payload }) => { + if (type === 'BITCOIN_RATE_RECEIVED') { + return payload.rate; + } + return previousState; +} +``` + +To register this reducer in the `` app, simply pass it in the `customReducers` prop: + +{% raw %} +```jsx +// in src/App.js +import React from 'react'; +import { Admin } from 'react-admin'; + +import bitcoinRateReducer from './bitcoinRateReducer'; + +const App = () => ( + + ... + +); + +export default App; +``` +{% endraw %} + +Now the state will look like: + +```jsx +{ + admin: { /*...*/ }, // used by react-admin + form: { /*...*/ }, // used by redux-form + routing: { /*...*/ }, // used by react-router-redux + bitcoinRate: 123, // managed by rateReducer +} +``` + +## `customSagas` + +The `` app uses [redux-saga](https://github.com/redux-saga/redux-saga) to handle side effects (AJAX calls, notifications, redirections, etc). + +If your components dispatch custom actions, you probably need to register your own side effects as sagas. Let's imagine that you want to show a notification whenever the `BITCOIN_RATE_RECEIVED` action is dispatched. You probably have a saga looking like the following: + +```jsx +// in src/bitcoinSaga.js +import { put, takeEvery } from 'redux-saga/effects'; +import { showNotification } from 'react-admin'; + +export default function* bitcoinSaga() { + yield takeEvery('BITCOIN_RATE_RECEIVED', function* () { + yield put(showNotification('Bitcoin rate updated')); + }) +} +``` + +To register this saga in the `` app, simply pass it in the `customSagas` prop: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin } from 'react-admin'; + +import bitcoinSaga from './bitcoinSaga'; + +const App = () => ( + + ... + +); + +export default App; +``` + +## `customRoutes` + +To register your own routes, create a module returning a list of [react-router](https://github.com/ReactTraining/react-router) `` component: + +```jsx +// in src/customRoutes.js +import React from 'react'; +import { Route } from 'react-router-dom'; +import Foo from './Foo'; +import Bar from './Bar'; +import Baz from './Baz'; + +export default [ + , + , + , +]; +``` + +Then, pass this array as `customRoutes` prop in the `` component: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin } from 'react-admin'; + +import customRoutes from './customRoutes'; + +const App = () => ( + + ... + +); + +export default App; +``` + +Now, when a user browses to `/foo` or `/bar`, the components you defined will appear in the main part of the screen. +When a user browses to `/baz`, the component will appear outside of the defined Layout, leaving you the freedom +to design the screen the way you want. + +**Tip**: It's up to you to create a [custom menu](#menu) entry, or custom buttons, to lead to your custom pages. + +**Tip**: Your custom pages take precedence over react-admin's own routes. That means that `customRoutes` lets you override any route you want! If you want to add routes *after* all the react-admin routes, use the [`catchAll` prop](#catchall) instead. + +**Tip**: To look like other react-admin pages, your custom pages should have the following structure: + +```jsx +// in src/Foo.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import { ViewTitle } from 'react-admin'; + +const Foo = () => ( + + + + ... + + +)); + +export default Foo; +``` + +## `authProvider` + +The `authProvider` prop expect a function returning a Promise, to control the application authentication strategy: + +```jsx +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin'; + +const authProvider(type, params) { + // type can be any of AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, and AUTH_CHECK + // ... + return Promise.resolve(); +}; + +const App = () => ( + + ... + +); +``` + +The [Authentication documentation](./Authentication.html) explains how to implement these functions in detail. + +## `loginPage` + +If you want to customize the Login page, or switch to another authentication strategy than a username/password form, pass a component of your own as the `loginPage` prop. React-admin will display this component whenever the `/login` route is called. + +```jsx +import MyLoginPage from './MyLoginPage'; + +const App = () => ( + + ... + +); +``` + +See The [Authentication documentation](./Authentication.html#customizing-the-login-and-logout-components) for more details. + +## `logoutButton` + +If you customize the `loginPage`, you probably need to override the `logoutButton`, too - because they share the authentication strategy. + +```jsx +import MyLoginPage from './MyLoginPage'; +import MyLogoutButton from './MyLogoutButton'; + +const App = () => ( + + ... + +); +``` + +## `initialState` + +The `initialState` prop lets you pass preloaded state to Redux. See the [Redux Documentation](http://redux.js.org/docs/api/createStore.html#createstorereducer-preloadedstate-enhancer) for more details. + +## `history` + +By default, react-admin creates URLs using a hash sign (e.g. "myadmin.acme.com/#/posts/123"). The hash portion of the URL (i.e. `#/posts/123` in the example) contains the main application route. This strategy has the benefit of working without a server, and with legacy web browsers. But you may want to use another routing strategy, e.g. to allow server-side rendering. + +You can create your own `history` function (compatible with [the `history` npm package](https://github.com/reacttraining/history)), and pass it to the `` component to override the default history strategy. For instance, to use `browserHistory`: + +```js +import createHistory from 'history/createBrowserHistory'; + +const history = createHistory(); + +const App = () => ( + + ... + +); +``` + +## Internationalization + +The `locale` and `messages` props let you translate the GUI. The [Translation Documentation](./Translation.html) details this process. + +## Declaring resources at runtime + +You might want to dynamically define the resources when the app starts. The `` component accepts a function as its child and this function can return a Promise. If you also defined an `authProvider`, the function will receive the result of a call to `authProvider` with the `AUTH_GET_PERMISSIONS` type (you can read more about this in the [Authorization](./Authorization.html) chapter). + +For instance, getting the resource from an API might look like: + +```js +import React from 'react'; + +import { Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +import { PostList } from './posts'; +import { CommentList } from './comments'; + +const knownResources = [ + , + , +]; + +const fetchResources = permissions => + fetch('https://myapi/resources', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(permissions), + }) + .then(response => response.json()) + .then(json => knownResources.filter(resource => json.resources.includes(resource.props.name))); + +const App = () => ( + + {fetchResources} + +); +``` + +## Using react-admin without `` and `` + +Using `` and `` is completely optional. If you feel like bootstrapping a redux app yourself, it's totally possible. Head to [Including in another app](./CustomApp.html) for a detailed how-to. diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 0000000..acad617 --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,348 @@ +--- +id: authentication +title: Authentication +--- + +![Logout button](https://marmelab.com/react-admin/img/login.gif) + +React-admin lets you secure your admin app with the authentication strategy of your choice. Since there are many different possible strategies (Basic Auth, JWT, OAuth, etc.), react-admin simply provides hooks to execute your own authentication code. + +By default, an react-admin app doesn't require authentication. But if the REST API ever returns a 401 (Unauthorized) or a 403 (Forbidden) response, then the user is redirected to the `/login` route. You have nothing to do - it's already built in. + +## Configuring the Auth Provider + +By default, the `/login` route renders a special component called `Login`, which displays a login form asking for username and password. + +![Default Login Form](https://marmelab.com/react-admin/img/login-form.png) + +What this form does upon submission depends on the `authProvider` prop of the `` component. This function receives authentication requests `(type, params)`, and should return a Promise. `Login` calls `authProvider` with the `AUTH_LOGIN` type, and `{ login, password }` as parameters. It's the ideal place to authenticate the user, and store their credentials. + +For instance, to query an authentication route via HTTPS and store the credentials (a token) in local storage, configure `authProvider` as follows: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + const { username, password } = params; + const request = new Request('https://mydomain.com/authenticate', { + method: 'POST', + body: JSON.stringify({ username, password }), + headers: new Headers({ 'Content-Type': 'application/json' }), + }) + return fetch(request) + .then(response => { + if (response.status < 200 || response.status >= 300) { + throw new Error(response.statusText); + } + return response.json(); + }) + .then(({ token }) => { + localStorage.setItem('token', token); + }); + } + return Promise.resolve(); +} +``` + +**Tip**: It's a good idea to store credentials in `localStorage`, to avoid reconnection when opening a new browser tab. But this makes your application [open to XSS attacks](http://www.redotheweb.com/2015/11/09/api-security.html), so you'd better double down on security, and add an `httpOnly` cookie on the server side, too. + +Then, pass this client to the `` component: + +```jsx +// in src/App.js +import authProvider from './authProvider'; + +const App = () => ( + + ... + +); +``` + +Upon receiving a 403 response, the admin app shows the Login page. `authProvider` is now called when the user submits the login form. Once the promise resolves, the login form redirects to the previous page, or to the admin index if the user just arrived. + +## Sending Credentials to the API + +To use the credentials when calling a data provider, you have to tweak, this time, the `dataProvider` function. As explained in the [Data providers documentation](DataProviders.md#adding-custom-headers), `simpleRestProvider` and `jsonServerProvider` take an `httpClient` as second parameter. That's the place where you can change request headers, cookies, etc. + +For instance, to pass the token obtained during login as an `Authorization` header, configure the Data Provider as follows: + +```jsx +import { fetchUtils, Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +const httpClient = (url, options = {}) => { + if (!options.headers) { + options.headers = new Headers({ Accept: 'application/json' }); + } + const token = localStorage.getItem('token'); + options.headers.set('Authorization', `Bearer ${token}`); + return fetchUtils.fetchJson(url, options); +} +const dataProvider = simpleRestProvider('http://localhost:3000', httpClient); + +const App = () => ( + + ... + +); +``` + +If you have a custom REST client, don't forget to add credentials yourself. + +## Adding a Logout Button + +If you provide an `authProvider` prop to ``, react-admin displays a logout button in the top bar (or in the menu on mobile). When the user clicks on the logout button, this calls the `authProvider` with the `AUTH_LOGOUT` type and removes potentially sensitive data from the redux store. When resolved, the user gets redirected to the login page. + +For instance, to remove the token from local storage upon logout: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + // ... + } + if (type === AUTH_LOGOUT) { + localStorage.removeItem('token'); + return Promise.resolve(); + } + return Promise.resolve(); +}; +``` + +![Logout button](https://marmelab.com/react-admin/img/logout.gif) + +The `authProvider` is also a good place to notify the authentication API that the user credentials are no longer valid after logout. + +## Catching Authentication Errors On The API + +Even though a user may be authenticated on the client-side, their credentials may no longer be valid server-side (e.g. if the token is only valid for a couple weeks). In that case, the API usually answers to all REST requests with an error code 401 or 403 - but what about *your* API? + +Fortunately, each time the API returns an error, the `authProvider` is called with the `AUTH_ERROR` type. Once again, it's up to you to decide which HTTP status codes should let the user continue (by returning a resolved promise) or log them out (by returning a rejected promise). + +For instance, to redirect the user to the login page for both 401 and 403 codes: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + // ... + } + if (type === AUTH_LOGOUT) { + // ... + } + if (type === AUTH_ERROR) { + const status = params.status; + if (status === 401 || status === 403) { + localStorage.removeItem('token'); + return Promise.reject(); + } + return Promise.resolve(); + } + return Promise.resolve(); +}; +``` + +## Checking Credentials During Navigation + +Redirecting to the login page whenever a REST response uses a 401 status code is usually not enough, because react-admin keeps data on the client side, and could display stale data while contacting the server - even after the credentials are no longer valid. + +Fortunately, each time the user navigates, react-admin calls the `authProvider` with the `AUTH_CHECK` type, so it's the ideal place to check for credentials. + +For instance, to check for the existence of the token in local storage: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + // ... + } + if (type === AUTH_LOGOUT) { + // ... + } + if (type === AUTH_ERROR) { + // ... + } + if (type === AUTH_CHECK) { + return localStorage.getItem('token') ? Promise.resolve() : Promise.reject(); + } + return Promise.reject('Unkown method'); +}; +``` + +If the promise is rejected, react-admin redirects by default to the `/login` page. You can override where to redirect the user by passing an argument with a `redirectTo` property to the rejected promise: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + // ... + } + if (type === AUTH_LOGOUT) { + // ... + } + if (type === AUTH_ERROR) { + // ... + } + if (type === AUTH_CHECK) { + return localStorage.getItem('token') ? Promise.resolve() : Promise.reject({ redirectTo: '/no-access' }); + } + return Promise.reject('Unkown method'); +}; +``` + +**Tip**: For the `AUTH_CHECK` call, the `params` argument contains the `resource` name, so you can implement different checks for different resources: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + // ... + } + if (type === AUTH_LOGOUT) { + // ... + } + if (type === AUTH_ERROR) { + // ... + } + if (type === AUTH_CHECK) { + const { resource } = params; + if (resource === 'posts') { + // check credentials for the posts resource + } + if (resource === 'comments') { + // check credentials for the comments resource + } + } + return Promise.reject('Unkown method'); +}; +``` + +**Tip**: The `authProvider` can only be called with `AUTH_LOGIN`, `AUTH_LOGOUT`, `AUTH_ERROR`, or `AUTH_CHECK`; that's why the final return is a rejected promise. + +## Customizing The Login and Logout Components + +Using `authProvider` and `checkCredentials` is enough to implement a full-featured authorization system if the authentication relies on a username and password. + +But what if you want to use an email instead of a username? What if you want to use a Single-Sign-On (SSO) with a third-party authentication service? What if you want to use two-factor authentication? + +For all these cases, it's up to you to implement your own `LoginPage` component, which will be displayed under the `/login` route instead of the default username/password form, and your own `LogoutButton` component, which will be displayed in the sidebar. Pass both these components to the `` component: + +**Tip**: Use the `userLogin` and `userLogout` actions in your custom `Login` and `Logout` components. + +```jsx +// in src/MyLoginPage.js +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { userLogin } from 'react-admin'; + +class MyLoginPage extends Component { + submit = (e) => { + e.preventDefault(); + // gather your data/credentials here + const credentials = { }; + + // Dispatch the userLogin action (injected by connect) + this.props.userLogin(credentials); + } + + render() { + return ( +
+ ... +
+ ); + } +}; + +export default connect(undefined, { userLogin })(MyLoginPage); + +// in src/MyLogoutButton.js +import React from 'react'; +import { connect } from 'react-redux'; +import { Responsive, userLogout } from 'react-admin'; +import MenuItem from '@material-ui/core/MenuItem'; +import Button from '@material-ui/core/Button'; +import ExitIcon from '@material-ui/icons/PowerSettingsNew'; + +const MyLogoutButton = ({ userLogout, ...rest }) => ( + + Logout + + } + medium={ + + } + /> +); +export default connect(undefined, { userLogout: userLogout() })(MyLogoutButton); + +// in src/App.js +import MyLoginPage from './MyLoginPage'; +import MyLogoutButton from './MyLogoutButton'; + +const App = () => ( + + ... + +); +``` + +## Restricting Access To A Custom Page + +If you add [custom pages](./Actions.md), of if you [create an admin app from scratch](./CustomApp.md), you may need to secure access to pages manually. That's the purpose of the `` component, that you can use as a decorator for your own components. + + +```jsx +// in src/MyPage.js +import { withRouter } from 'react-router-dom'; +import { Authenticated } from 'react-admin'; + +const MyPage = ({ location }) => ( + +
+ ... +
+
+); + +export default withRouter(MyPage); +``` + + +The `` component calls the `authProvider` function with `AUTH_CHECK` and `authParams`. If the response is a fulfilled promise, the child component is rendered. If the response is a rejected promise, `` redirects to the login form. Upon successful login, the user is redirected to the initial location (that's why it's necessary to get the location from the router). + + +## Redirect After Logout + +By default, react-admin redirects the user to '/login' after they log out. This can be changed by passing the url to redirect to as parameter to the `userLogout()` action creator when you `connect` the `MyLogoutButton` component: + +```diff +// in src/MyLogoutButton.js +// ... +- export default connect(undefined, { userLogout: userLogout() })(MyLogoutButton); ++ export default connect(undefined, { userLogout: userLogout('/') })(MyLogoutButton); +``` diff --git a/docs/authorization.md b/docs/authorization.md new file mode 100644 index 0000000..a6a275d --- /dev/null +++ b/docs/authorization.md @@ -0,0 +1,303 @@ +--- +id: authorization +title: Authorization +--- + +Some applications may require to determine what level of access a particular authenticated user should have to secured resources. Since there are many different possible strategies (single role, multiple roles or rights, etc.), react-admin simply provides hooks to execute your own authorization code. + +By default, a react-admin app doesn't require authorization. However, if needed, it will rely on the `authProvider` introduced in the [Authentication](./Authentication.html) section. + +## Configuring the Auth Provider + +A call to the `authProvider` with the `AUTH_GET_PERMISSIONS` type will be made each time a component requires to check the user's permissions. + +Following is an example where the `authProvider` stores the user's role upon authentication, and returns it when called for a permissions check: + + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_GET_PERMISSIONS } from 'react-admin'; +import decodeJwt from 'jwt-decode'; + +export default (type, params) => { + if (type === AUTH_LOGIN) { + const { username, password } = params; + const request = new Request('https://mydomain.com/authenticate', { + method: 'POST', + body: JSON.stringify({ username, password }), + headers: new Headers({ 'Content-Type': 'application/json' }), + }) + return fetch(request) + .then(response => { + if (response.status < 200 || response.status >= 300) { + throw new Error(response.statusText); + } + return response.json(); + }) + .then(({ token }) => { + const decodedToken = decodeJwt(token); + localStorage.setItem('token', token); + localStorage.setItem('role', decodedToken.role); + }); + } + if (type === AUTH_LOGOUT) { + localStorage.removeItem('token'); + localStorage.removeItem('role'); + return Promise.resolve(); + } + if (type === AUTH_ERROR) { + // ... + } + if (type === AUTH_CHECK) { + return localStorage.getItem('token') ? Promise.resolve() : Promise.reject(); + } + if (type === AUTH_GET_PERMISSIONS) { + const role = localStorage.getItem('role'); + return role ? Promise.resolve(role) : Promise.reject(); + } + return Promise.reject('Unkown method'); +}; +``` + + +## Restricting Access to Resources or Views + +It's possible to restrict access to resources or their views inside the `Admin` component. To do so, you must specify a function as the `Admin` only child. This function will be called with the permissions returned by the `authProvider`. + + +```jsx + + {permissions => [ + // Restrict access to the edit and remove views to admin only + , + // Only include the categories resource for admin users + permissions === 'admin' + ? + : null, + ]} + +``` + + +Note that the function returns an array of React elements. This is required to avoid having to wrap them in a container element which would prevent the `Admin` from working. + +**Tip** Even if that's possible, be careful when completely excluding a resource (like with the `categories` resource in this example) as it will prevent you to reference them in the other resource views, too. + +## Restricting Access to Fields and Inputs + +You might want to display some fields or inputs only to users with specific permissions. Those permissions are retrieved for each route and will provided to your component as a `permissions` prop. + +Each route will call the `authProvider` with the `AUTH_GET_PERMISSIONS` type and some parameters including the current location and route parameters. It's up to you to return whatever you need to check inside your component such as the user's role, etc. + +Here's an example inside a `Create` view with a `SimpleForm` and a custom `Toolbar`: + + +```jsx +const UserCreateToolbar = ({ permissions, ...props }) => + + + {permissions === 'admin' && + } + ; + +export const UserCreate = ({ permissions, ...props }) => + + } + defaultValue={{ role: 'user' }} + > + + {permissions === 'admin' && + } + + ; +``` + + +**Tip** Note how the `permissions` prop is passed down to the custom `toolbar` component. + +This also works inside an `Edition` view with a `TabbedForm`, and you can hide a `FormTab` completely: + + +```jsx +export const UserEdit = ({ permissions, ...props }) => + } {...props}> + + + {permissions === 'admin' && } + + + {permissions === 'admin' && + + + } + + ; +``` + + +What about the `List` view, the `DataGrid`, `SimpleList` and `Filter` components? It works there, too. + + +```jsx +const UserFilter = ({ permissions, ...props }) => + + + + {permissions === 'admin' ? : null} + ; + +export const UserList = ({ permissions, ...props }) => + } + sort={{ field: 'name', order: 'ASC' }} + > + record.name} + secondaryText={record => + permissions === 'admin' ? record.role : null} + /> + } + medium={ + + + + {permissions === 'admin' && } + {permissions === 'admin' && } + + + } + /> + ; +``` + + +**Tip** Note how the `permissions` prop is passed down to the custom `filters` component. + +## Restricting Access to Content Inside a Dashboard + +The component provided as a [`dashboard`]('./Admin.md#dashboard) will receive the permissions in its props too: + + +```jsx +// in src/Dashboard.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import { ViewTitle } from 'react-admin'; + +export default ({ permissions }) => ( + + + Lorem ipsum sic dolor amet... + {permissions === 'admin' + ? Sensitive data + : null + } + +); +``` + + +## Restricting Access to Content Inside Custom Pages + +You might want to check user permissions inside a [custom pages](./Admin.md#customroutes). You'll have to use the `WithPermissions` component for that. It will ensure the user is authenticated then call the `authProvider` with the `AUTH_GET_PERMISSIONS` type and the `authParams` you specify: + + +```jsx +// in src/MyPage.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import { ViewTitle, WithPermissions } from 'react-admin'; +import { withRouter } from 'react-router-dom'; + +const MyPage = ({ permissions }) => ( + + + Lorem ipsum sic dolor amet... + {permissions === 'admin' + ? Sensitive data + : null + } + +) +const MyPageWithPermissions = ({ location, match }) => ( + } + /> +); + +export default MyPageWithPermissions; + +// in src/customRoutes.js +import React from 'react'; +import { Route } from 'react-router-dom'; +import Foo from './Foo'; +import Bar from './Bar'; +import Baz from './Baz'; +import MyPageWithPermissions from './MyPage'; + +export default [ + , + , + , + , +]; +``` + + +## Restricting Access to Content in Custom Menu + +What if you want to check the permissions inside a [custom menu](./Admin.html#menu) ? Much like getting permissions inside a custom page, you'll have to use the `WithPermissions` component: + + +```jsx +// in src/myMenu.js +import React from 'react'; +import { connect } from 'react-redux'; +import { MenuItemLink, WithPermissions } from 'react-admin'; + +const Menu = ({ onMenuClick, logout, permissions }) => ( +
+ + + ( + permissions === 'admin' + ? + : null + )} + /> + {logout} +
+); +``` + diff --git a/docs/creat-edit-view-components.md b/docs/creat-edit-view-components.md new file mode 100644 index 0000000..25ef643 --- /dev/null +++ b/docs/creat-edit-view-components.md @@ -0,0 +1,699 @@ +--- +layout: creat-edit-view-components +title: and Views +--- + +The Create and Edit views both display a form, initialized with an empty record (for the Create view) or with a record fetched from the API (for the Edit view). The `` and `` components then delegate the actual rendering of the form to a form component - usually ``. This form component uses its children ([``](./Inputs.md) components) to render each form input. + +![post creation form](https://marmelab.com/react-admin/img/create-view.png) + +![post edition form](https://marmelab.com/react-admin/img/edit-view.png) + +## The `Create` and `Edit` components + +The `` and `` components render the page title and actions, and fetch the record from the data provider. They are not responsible for rendering the actual form - that's the job of their child component (usually ``), to which they pass the `record` as prop. + +Here are all the props accepted by the `` and `` components: + +* [`title`](#page-title) +* [`actions`](#actions) + +Here is the minimal code necessary to display a form to create and edit comments: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostCreate, PostEdit } from './posts'; + +const App = () => ( + + + +); + +export default App; + +// in src/posts.js +import React from 'react'; +import { Create, Edit, SimpleForm, DisabledInput, TextInput, DateInput, LongTextInput, ReferenceManyField, Datagrid, TextField, DateField, EditButton } from 'react-admin'; +import RichTextInput from 'ra-input-rich-text'; + +export const PostCreate = (props) => ( + + + + + + + + +); + +export const PostEdit = (props) => ( + } {...props}> + + + + + + + + + + + + + + + +); +``` + +That's enough to display the post edit form: + +![post edition form](https://marmelab.com/react-admin/img/post-edition.png) + +**Tip**: You might find it cumbersome to repeat the same input components for both the `` and the `` view. In practice, these two views almost never have exactly the same form inputs. For instance, in the previous snippet, the `` views shows related comments to the current post, which makes no sense for a new post. Having two separate sets of input components for the two views is therefore a deliberate choice. However, if you have the same set of input components, export them as a custom Form component to avoid repetition. + + `` accepts a `record` prop, to initialize the form based on an value object. + +### Page Title + +By default, the title for the Create view is "Create [resource_name]", and the title for the Edit view is "Edit [resource_name] #[record_id]". + +You can customize this title by specifying a custom `title` prop: + +```jsx +export const PostEdit = (props) => ( + + ... + +); +``` + +More interestingly, you can pass a component as `title`. React-admin clones this component and, in the ``, injects the current `record`. This allows to customize the title according to the current record: + +```jsx +const PostTitle = ({ record }) => { + return Post {record ? `"${record.title}"` : ''}; +}; +export const PostEdit = (props) => ( + } {...props}> + ... + +); +``` + +### Actions + +You can replace the list of default actions by your own element using the `actions` prop: + +```jsx +import Button from '@material-ui/core/Button'; +import { + CardActions, + ListButton, + ShowButton, + DeleteButton, + RefreshButton, +} from 'react-admin'; + +const PostEditActions = ({ basePath, data, resource }) => ( + + + + + + {/* Add your custom actions */} + + +); + +export const PostEdit = (props) => ( + } {...props}> + ... + +); +``` + +Using a custom `EditActions` component also allow to remove the `` if you want to prevent deletions from the admin. + +## Prefilling a `Create` Record + +By default, the `` view starts with an empty `record`. You can pass a custom `record` object to start with preset values: + +```jsx +const commentDefaultValue = { nb_views: 0 }; +export const CommentCreate = (props) => ( + + + + + + + +); +``` + +While using the `record` to set default values works here, it doesn't work with ``. So it's recommended to use [the `defaultValue` prop in the Form component](#default-values) instead. + +However, there is a valid use case for presetting the `record` prop: to prepopulate a record based on a related record. For instance, in a `PostShow` component, you may want to display a button to create a comment related to the current post, that would lead to a `CommentCreate` page where the `post_id` is preset. + +To enable this, you must first update the `CommentCreate` component to read the record from the `location` object (which is injected by react-router): + +```diff +const commentDefaultValue = { nb_views: 0 }; +-export const CommentCreate = (props) => ( ++export const CommentCreate = ({ location, ...props}) => ( +- ++ + + + + + + +); +``` + +To set this `location.state`, you have to create a link or a button using react-router's `` component: + +```jsx +// in PostShow.js +import Button from '@material-ui/core/Button'; +import { Link } from 'react-router-dom'; + +const CreateRelatedCommentButton = ({ record }) => ( + +); + +export default PostShow = props => ( + + + ... + + + +) +``` + +**Tip**: To style the button with the main color from the material-ui theme, use the `Link` component from the `react-admin` package rather than the one from `react-router`. + +## The `SimpleForm` component + +The `` component receives the `record` as prop from its parent component. It is responsible for rendering the actual form. It is also responsible for validating the form data. Finally, it receives a `handleSubmit` function as prop, to be called with the updated record as argument when the user submits the form. + +The `` renders its child components line by line (within `
` components). It uses `redux-form`. + +![post edition form](https://marmelab.com/react-admin/img/post-edition.png) + +By default the `` submits the form when the user presses `ENTER`. If you want +to change this behaviour you can pass `false` for the `submitOnEnter` property, and the user will only be able to submit by pressing the save button. This can be useful e.g. if you have an input widget using `ENTER` for a special function. + +Here are all the props accepted by the `` component: + +* [`defautValue`](#default-values) +* [`validate`](#validation) +* [`submitOnEnter`](#submit-on-enter) +* [`redirect`](#redirection-after-submission) +* [`toolbar`](#toolbar) +* `save`: The function invoked when the form is submitted. This is passed automatically by `react-admin` when the form component is used inside `Create` and `Edit` components. +* `saving`: A boolean indicating whether a save operation is ongoing. This is passed automatically by `react-admin` when the form component is used inside `Create` and `Edit` components. +* `form`: The name of the [`redux-form`](https://redux-form.com/7.4.2/docs/api/reduxform.md/#-code-form-string-code-required-). It defaults to `record-form` and should only be modified when using the `SimpleForm` outside of a `Create` or `Edit` component. + +```jsx +export const PostCreate = (props) => ( + + + + + + + +); +``` + +## The `TabbedForm` component + +Just like ``, `` receives the `record` prop, renders the actual form, and handles form validation on submit. However, the `` component renders inputs grouped by tab. The tabs are set by using `` components, which expect a `label` and an `icon` prop. + +![tabbed form](https://marmelab.com/react-admin/img/tabbed-form.gif) + +By default the `` submits the form when the user presses `ENTER`, if you want +to change this behaviour you can pass `false` for the `submitOnEnter` property. + +Here are all the props accepted by the `` component: + +* [`defautValue`](#default-values) +* [`validate`](#validation) +* [`submitOnEnter`](#submit-on-enter) +* [`redirect`](#redirection-after-submission) +* [`toolbar`](#toolbar) +* `save`: The function invoked when the form is submitted. This is passed automatically by `react-admin` when the form component is used inside `Create` and `Edit` components. +* `saving`: A boolean indicating whether a save operation is ongoing. This is passed automatically by `react-admin` when the form component is used inside `Create` and `Edit` components. +* `form`: The name of the [`redux-form`](https://redux-form.com/7.4.2/docs/api/reduxform.md/#-code-form-string-code-required-). It defaults to `record-form` and should only be modified when using the `TabbedForm` outside of a `Create` or `Edit` component. + + +```jsx +import { TabbedForm, FormTab } from 'react-admin' + +export const PostEdit = (props) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); +``` + +## Default Values + +To define default values, you can add a `defaultValue` prop to form components (``, ``, etc.), or add a `defaultValue` to individual input components. Let's see each of these options. + +### Global Default Value + +The value of the form `defaultValue` prop can be an object, or a function returning an object, specifying default values for the created record. For instance: + +```jsx +const postDefaultValue = { created_at: new Date(), nb_views: 0 }; +export const PostCreate = (props) => ( + + + + + + + +); +``` + +**Tip**: You can include properties in the form `defaultValue` that are not listed as input components, like the `created_at` property in the previous example. + +### Per Input Default Value + +Alternatively, you can specify a `defaultValue` prop directly in `` components. Just like for form-level default values, an input-level default value can be a scalar, or a function returning a scalar. React-admin will merge the input default values with the form default value (input > form): + +```jsx +export const PostCreate = (props) => ( + + + uuid()}/> + + + + + +); +``` + +## Validation + +React-admin relies on [redux-form](http://redux-form.com/) for the validation. + +To validate values submitted by a form, you can add a `validate` prop to the form component, to individual inputs, or even mix both approaches. + +### Global Validation + +The value of the form `validate` prop must be a function taking the record as input, and returning an object with error messages indexed by field. For instance: + +```jsx +const validateUserCreation = (values) => { + const errors = {}; + if (!values.firstName) { + errors.firstName = ['The firstName is required']; + } + if (!values.age) { + errors.age = ['The age is required']; + } else if (values.age < 18) { + errors.age = ['Must be over 18']; + } + return errors +}; + +export const UserCreate = (props) => ( + + + + + + +); +``` + +**Tip**: The props you pass to `` and `` end up as `reduxForm()` parameters. This means that, in addition to `validate`, you can also pass `warn` or `asyncValidate` functions. Read the [`reduxForm()` documentation](http://redux-form.com/6.5.0/docs/api/ReduxForm.md/) for details. + +### Per Input Validation: Function Validator + +Alternatively, you can specify a `validate` prop directly in `` components, taking either a function, or an array of functions. These functions should return `undefined` when there is no error, or an error string. + +```jsx +const required = (message = 'Required') => + value => value ? undefined : message; +const maxLength = (max, message = 'Too short') => + value => value && value.length > max ? message : undefined; +const number = (message = 'Must be a number') => + value => value && isNaN(Number(value)) ? message : undefined; +const minValue = (min, message = 'Too small') => + value => value && value < min ? message : undefined; + +const ageValidation = (value, allValues) => { + if (!value) { + return 'The age is required'; + } + if (value < 18) { + return 'Must be over 18'; + } + return []; +} + +const validateFirstName = [required(), maxLength(15)]; +const validateAge = [required(), number(), ageValidation]; + +export const UserCreate = (props) => ( + + + + + + +); +``` + +React-admin will combine all the input-level functions into a single function looking just like the previous one. + +Input validation functions receive the current field value, and the values of all fields of the current record. This allows for complex validation scenarios (e.g. validate that two passwords are the same). + +**Tip**: Validator functions receive the form `props` as third parameter, including the `translate` function. This lets you build internationalized validators: + +```jsx +const required = (message = 'myroot.validation.required') => + (value, allValues, props) => value ? undefined : props.translate(message); +``` + +**Tip**: Make sure to define validation functions or array of functions in a variable, instead of defining them directly in JSX. This can result in a new function or array at every render, and trigger infinite rerender. + + +```jsx +const validateStock = [required(), number(), minValue(0)]; + +export const ProductEdit = ({ ...props }) => ( + + + ... + {/* do this */} + + {/* don't do that */} + + ... + + +); +``` + + +**Tip**: The props of your Input components are passed to a redux-form `` component. So in addition to `validate`, you can also use `warn`. + +**Tip**: You can use *both* Form validation and input validation. + +### Built-in Field Validators + +React-admin already bundles a few validator functions, that you can just require, and use as input-level validators: + +* `required(message)` if the field is mandatory, +* `minValue(min, message)` to specify a minimum value for integers, +* `maxValue(max, message)` to specify a maximum value for integers, +* `minLength(min, message)` to specify a minimum length for strings, +* `maxLength(max, message)` to specify a maximum length for strings, +* `number(message)` to check that the input is a valid number, +* `email(message)` to check that the input is a valid email address, +* `regex(pattern, message)` to validate that the input matches a regex, +* `choices(list, message)` to validate that the input is within a given list, + +Example usage: + +```jsx +import { + required, + minLength, + maxLength, + minValue, + maxValue, + number, + regex, + email, + choices +} from 'react-admin'; + +const validateFirstName = [required(), minLength(2), maxLength(15)]; +const validateEmail = email(); +const validateAge = [number(), minValue(18)]; +const validateZipCode = regex(/^\d{5}$/, 'Must be a valid Zip Code'); +const validateSex = choices(['m', 'f'], 'Must be Male or Female'); + +export const UserCreate = (props) => ( + + + + + + + + + +); +``` + +**Tip**: If you pass a function as a message, react-admin calls this function with `{ args, value, values,translate, ...props }` as argument. For instance: + +```jsx +const message = ({ translate }) => translate('myroot.validation.email_invalid'); +const validateEmail = email(message); +``` + +## Submit On Enter + +By default, pressing `ENTER` in any of the form fields submits the form - this is the expected behavior in most cases. However, some of your custom input components (e.g. Google Maps widget) may have special handlers for the `ENTER` key. In that case, to disable the automated form submission on enter, set the `submitOnEnter` prop of the form component to `false`: + +```jsx +export const PostEdit = (props) => ( + + + ... + + +); +``` + +## Redirection After Submission + +By default: + +- Submitting the form in the `` view redirects to the `` view +- Submitting the form in the `` view redirects to the `` view + +You can customize the redirection by setting the `redirect` prop of the form component. Possible values are "edit", "show", "list", and `false` to disable redirection. You may also specify a custom path such as `/my-custom-route`. For instance, to redirect to the `` view after edition: + +```jsx +export const PostEdit = (props) => ( + + + ... + + +); +``` + +You can also pass a custom route (e.g. "/home") or a function as `redirect` prop value. For example, if you want to redirect to a page related to the current object: + +```jsx +// redirect to the related Author show page +const redirect = (basePath, id, data) => `/author/${data.author_id}/show`; + +export const PostEdit = (props) => { + + + ... + + +); +``` + +This affects both the submit button, and the form submission when the user presses `ENTER` in one of the form fields. + +## Toolbar + +At the bottom of the form, the toolbar displays the submit button. You can override this component by setting the `toolbar` prop, to display the buttons of your choice. + +The most common use case is to display two submit buttons in the `` view: + +- one that creates and redirects to the `` view of the new resource, and +- one that redirects to a blank `` view after creation (allowing bulk creation) + +![Form toolbar](https://marmelab.com/react-admin/img/form-toolbar.png) + +For that use case, use the `` component with a custom `redirect` prop: + +```jsx +import { Edit, SimpleForm, SaveButton, Toolbar } from 'react-admin'; + +const PostCreateToolbar = props => ( + + + + +); + +export const PostEdit = (props) => ( + + } redirect="show"> + ... + + +); +``` + +Here are the props received by the `Toolbar` component when passed as the `toolbar` prop of the `SimpleForm` or `TabbedForm` components: + +* `handleSubmitWithRedirect`: The function to call in order to submit the form. It accepts a single parameter overriding the form's default redirect. +* `invalid`: A boolean indicating whether the form is invalid +* `pristine`: A boolean indicating whether the form is pristine (eg: no inputs have been changed yet) +* `redirect`: The default form's redirect +* `saving`: A boolean indicating whether a save operation is ongoing. +* `submitOnEnter`: A boolean indicating whether the form should be submitted when pressing `enter` + +**Tip**: Use react-admin's `` component instead of material-ui's `` component. The former builds up on the latter, and adds support for an alternative mobile layout (and is therefore responsive). + +**Tip**: Don't forget to also set the `redirect` prop of the Form component to handle submission by the `ENTER` key. + +## Customizing Input Container Styles + +The input components are wrapped inside a `div` to ensure a good looking form by default. You can pass a `formClassName` prop to the input components to customize the style of this `div`. For example, here is how to display two inputs on the same line: + + +```jsx +const styles = { + inlineBlock: { display: 'inline-flex', marginRight: '1rem' }, +}; +export const UserEdit = withStyles(styles)(({ classes, ...props }) => ( + + + + + {/* This input will be display below the two first ones */} + + + +``` + + +## Displaying Fields or Inputs depending on the user permissions + +You might want to display some fields, inputs or filters only to users with specific permissions. Those permissions are retrieved for each route and will provided to your component as a `permissions` prop. + +Each route will call the `authProvider` with the `AUTH_GET_PERMISSIONS` type and some parameters including the current location and route parameters. It's up to you to return whatever you need to check inside your component such as the user's role, etc. + +Here's an example inside a `Create` view with a `SimpleForm` and a custom `Toolbar`: + + +```jsx +const UserCreateToolbar = ({ permissions, ...props }) => + + + {permissions === 'admin' && + } + ; + +export const UserCreate = ({ permissions, ...props }) => + + } + defaultValue={{ role: 'user' }} + > + + {permissions === 'admin' && + } + + ; +``` + + +**Tip**: Note how the `permissions` prop is passed down to the custom `toolbar` component. + +This also works inside an `Edition` view with a `TabbedForm`, and you can hide a `FormTab` completely: + + +```jsx +export const UserEdit = ({ permissions, ...props }) => + } {...props}> + + + {permissions === 'admin' && } + + + {permissions === 'admin' && + + + } + + ; +``` + diff --git a/docs/custom-app.md b/docs/custom-app.md new file mode 100644 index 0000000..f83b58b --- /dev/null +++ b/docs/custom-app.md @@ -0,0 +1,110 @@ +--- +id: custom-app +title: Including the Admin in Another App +--- + +The `` tag is a great shortcut got be up and running with react-admin in minutes. However, in many cases, you will want to embed the admin in another application, or customize the admin deeply. Fortunately, you can do all the work that `` does on any React application. + +Beware that you need to know about [redux](http://redux.js.org/), [react-router](https://github.com/reactjs/react-router), and [redux-saga](https://github.com/yelouafi/redux-saga) to go further. + +**Tip**: Before going for the Custom App route, explore all the options of [the `` component](./Admin.md). They allow you to add custom routes, custom reducers, custom sagas, and customize the layout. + +Here is the main code for bootstrapping a barebones react-admin application with 3 resources: `posts`, `comments`, and `users`: + +```jsx +// in src/App.js +import React from 'react'; +import PropTypes from 'prop-types'; +import { render } from 'react-dom'; + +// redux, react-router, redux-form, saga, and material-ui +// form the 'kernel' on which react-admin runs +import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; +import { Provider } from 'react-redux'; +import createHistory from 'history/createHashHistory'; +import { Switch, Route } from 'react-router-dom'; +import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux'; +import { reducer as formReducer } from 'redux-form'; +import createSagaMiddleware from 'redux-saga'; +import { MuiThemeProvider } from '@material-ui/core/styles'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import Typography from '@material-ui/core/Typography'; + +// prebuilt react-admin features +import { + adminReducer, + i18nReducer, + adminSaga, + TranslationProvider, +} from 'react-admin'; +import restProvider from 'ra-data-simple-rest'; +import defaultMessages from 'ra-language-english'; + +// your app components +import Dashboard from './Dashboard'; +import { PostList, PostCreate, PostEdit, PostShow } from './Post'; +import { CommentList, CommentEdit, CommentCreate } from './Comment'; +import { UserList, UserEdit, UserCreate } from './User'; +// your app labels +import messages from './i18n'; + +// create a Redux app +const reducer = combineReducers({ + admin: adminReducer, + i18n: i18nReducer('en', messages['en']), + form: formReducer, + routing: routerReducer, +}); +const sagaMiddleware = createSagaMiddleware(); +const history = createHistory(); +const store = createStore(reducer, undefined, compose( + applyMiddleware(sagaMiddleware, routerMiddleware(history)), + window.devToolsExtension ? window.devToolsExtension() : f => f, +)); + +// side effects +const dataProvider = restProvider('http://path.to.my.api/'); +const authProvider = () => Promise.resolve(); +const i18nProvider = locale => { + if (locale !== 'en') { + return messages[locale]; + } + return defaultMessages; +}; +sagaMiddleware.run(adminSaga(dataProvider, authProvider, i18nProvider)); + +// bootstrap redux and the routes +const App = () => ( + + + + + + + + My admin + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + +); +``` + +This application has no sidebar, no theming, no [auth control](./Authentication.md#restricting-access-to-a-custom-page) - it's up to you to add these. From there on, you can customize pretty much anything you want. diff --git a/docs/data-providers.md b/docs/data-providers.md new file mode 100644 index 0000000..4ec355d --- /dev/null +++ b/docs/data-providers.md @@ -0,0 +1,664 @@ +--- +id: data-providers +title: Data Providers +--- + +React-admin can communicate with any API, whether it uses REST, GraphQL, or even SOAP, regardless of the dialect it uses. For REST servers, it can be [JSON API](http://jsonapi.org/), [HAL](http://stateless.co/hal_specification.html), [OData](http://www.odata.org/) or a custom dialect. The only thing react-admin needs is a Data Provider function. This is the place to translate data queries to HTTP requests, and HTTP responses to data responses. + +![Data Provider architecture](https://marmelab.com/react-admin/img/data-provider.png) + +The `dataProvider` parameter of the `` component must be a function with the following signature: + +```jsx +/** + * Query a data provider and return a promise for a response + * + * @example + * dataProvider(GET_ONE, 'posts', { id: 123 }) + * => Promise.resolve({ data: { id: 123, title: "hello, world" } }) + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the action type + * @returns {Promise} the Promise for a response + */ +const dataProvider = (type, resource, params) => new Promise(); +``` + +You can find a Data Provider example implementation in [`packages/ra-data-simple-rest/src/index.js`](https://github.com/marmelab/react-admin/blob/master/packages/ra-data-simple-rest/src/index.js); + +The `dataProvider` is also the ideal place to add custom HTTP headers, authentication, etc. + +## Available Providers + +The react-admin project includes 4 Data Providers: + +* Simple REST: [marmelab/ra-data-simple-rest](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-simple-rest) ([read more below](#simple-rest)). It serves mostly as an example. Incidentally, it is compatible with the [FakeRest](https://github.com/marmelab/FakeRest) API. +* **[JSON server](https://github.com/typicode/json-server)**: [marmelab/ra-data-json-server](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-json-server). Great for prototyping an admin over a yet-to-be-developed REST API. +* [Graphcool](https://www.graph.cool/): [marmelab/ra-data-graphcool](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphcool). A provider for GraphQL servers following the Graphcool convention. Incidentally, this package builds up on [marmelab/ra-data-graphql](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql), which lets you develop providers for other GraphQL conventions. +* Local JSON: [marmelab/ra-data-fakerest](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-fakerest). Based on a local object, it doesn't even use HTTP. Use it for testing purposes. + +You can find Data Providers for various backends in third-party repositories: + +* **[DynamoDb](https://github.com/abiglobalhealth/aor-dynamodb-client)**: [abiglobalhealth/aor-dynamodb-client](https://github.com/abiglobalhealth/aor-dynamodb-client) +* **[Epilogue](https://github.com/dchester/epilogue)**: [dunghuynh/aor-epilogue-client](https://github.com/dunghuynh/aor-epilogue-client) +* **[Feathersjs](http://www.feathersjs.com/)**: [josx/aor-feathers-client](https://github.com/josx/aor-feathers-client) +* **[Firebase](https://firebase.google.com/)**: [sidferreira/aor-firebase-client](https://github.com/sidferreira/aor-firebase-client) +* **[JSON API](http://jsonapi.org/)**: [moonlight-labs/aor-jsonapi-client](https://github.com/moonlight-labs/aor-jsonapi-client) +* **[Loopback](http://loopback.io/)**: [kimkha/aor-loopback](https://github.com/kimkha/aor-loopback) +* **[Parse Server](https://github.com/ParsePlatform/parse-server)**: [leperone/aor-parseserver-client](https://github.com/leperone/aor-parseserver-client) +* **[PostgREST](http://postgrest.com/en/v0.4/)**: [tomberek/aor-postgrest-client](https://github.com/tomberek/aor-postgrest-client) +* **[Xmysql](https://github.com/o1lab/xmysql)**: [soaserele/aor-xmysql](https://github.com/soaserele/aor-xmysql) + +If you've written a Data Provider for another backend, and open-sourced it, please help complete this list with your package. + +## Usage + +As an example, let's focus on the Simple REST data provider. It fits REST APIs using simple GET parameters for filters and sorting. + +Install the `ra-data-simple-rest` package to use this provider. + +```sh +npm install ra-data-simple-rest +``` + +Then, initialize the provider with the RESt backend URL, and pass the result to the `dataProvider` prop of the `` component: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +import { PostList } from './posts'; + +const App = () => ( + + + +); + +export default App; +``` + +Here is how this provider maps request types to API calls: + +| Request type | API calls +|----------------------|---------------------------------------------------------------- +| `GET_LIST` | `GET http://my.api.url/posts?sort=['title','ASC']&range=[0, 24]&filter={title:'bar'}` +| `GET_ONE` | `GET http://my.api.url/posts/123` +| `CREATE` | `POST http://my.api.url/posts/123` +| `UPDATE` | `PUT http://my.api.url/posts/123` +| `UPDATE_MANY` | Multiple calls to `PUT http://my.api.url/posts/123` +| `DELETE` | `DELETE http://my.api.url/posts/123` +| `DELETE_MANY` | Multiple calls to `DELETE http://my.api.url/posts/123` +| `GET_MANY` | `GET http://my.api.url/posts?filter={ids:[123,456,789]}` +| `GET_MANY_REFERENCE` | `GET http://my.api.url/posts?filter={author_id:345}` + +**Note**: The simple REST client expects the API to include a `Content-Range` header in the response to `GET_LIST` calls. The value must be the total number of resources in the collection. This allows react-admin to know how many pages of resources there are in total, and build the pagination controls. + +``` +Content-Range: posts 0-24/319 +``` + +If your API is on another domain as the JS code, you'll need to whitelist this header with an `Access-Control-Expose-Headers` [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) header. + +``` +Access-Control-Expose-Headers: Content-Range +``` + +## Adding Custom Headers + +The `simpleRestProvider` function accepts an HTTP client function as second argument. By default, it uses react-admin's `fetchUtils.fetchJson()` as HTTP client. It's similar to HTML5 `fetch()`, except it handles JSON decoding and HTTP error codes automatically. + +That means that if you need to add custom headers to your requests, you can just *wrap* the `fetchJson()` call inside your own function: + +```jsx +import { fetchUtils, Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +const httpClient = (url, options = {}) => { + if (!options.headers) { + options.headers = new Headers({ Accept: 'application/json' }); + } + // add your own headers here + options.headers.set('X-Custom-Header', 'foobar'); + return fetchUtils.fetchJson(url, options); +} +const dataProvider = simpleRestProvider('http://path.to.my.api/', httpClient); + +const App = () => ( + + + +); +``` + +Now all the requests to the REST API will contain the `X-Custom-Header: foobar` header. + +**Tip**: The most common usage of custom headers is for authentication. `fetchJson` has built-on support for the `Authorization` token header: + +```jsx +const httpClient = (url, options = {}) => { + options.user = { + authenticated: true, + token: 'SRTRDFVESGNJYTUKTYTHRG' + } + return fetchUtils.fetchJson(url, options); +} +const dataProvider = simpleRestProvider('http://path.to.my.api/', httpClient); +``` + +Now all the requests to the REST API will contain the `Authorization: SRTRDFVESGNJYTUKTYTHRG` header. + +## Decorating your Data Provider (Example of File Upload) + +Instead of writing your own Data Provider, you can enhance the capabilities of an existing data provider. You can even restrict the customization on a given resource. + +For instance, if you want to use upload components (such as `` one), you can decorate the provider the following way: + +```jsx +// in addUploadFeature.js +/** + * Convert a `File` object returned by the upload input into a base 64 string. + * That's not the most optimized way to store images in production, but it's + * enough to illustrate the idea of data provider decoration. + */ +const convertFileToBase64 = file => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file.rawFile); + + reader.onload = () => resolve(reader.result); + reader.onerror = reject; +}); + +/** + * For posts update only, convert uploaded image in base 64 and attach it to + * the `picture` sent property, with `src` and `title` attributes. + */ +const addUploadFeature = requestHandler => (type, resource, params) => { + if (type === 'UPDATE' && resource === 'posts') { + if (params.data.pictures && params.data.pictures.length) { + // only freshly dropped pictures are instance of File + const formerPictures = params.data.pictures.filter(p.rawFile => !(p instanceof File)); + const newPictures = params.data.pictures.filter(p.rawFile => p instanceof File); + + return Promise.all(newPictures.map(convertFileToBase64)) + .then(base64Pictures => base64Pictures.map(picture64 => ({ + src: picture64, + title: `${params.data.title}`, + }))) + .then(transformedNewPictures => requestHandler(type, resource, { + ...params, + data: { + ...params.data, + pictures: [...transformedNewPictures, ...formerPictures], + }, + })); + } + } + // for other request types and reources, fall back to the defautl request handler + return requestHandler(type, resource, params); +}; + +export default addUploadFeature; +``` + +To enhance a provider with the upload feature, compose `addUploadFeature` function with the data provider function: + +```jsx +import simpleRestProvider from 'ra-data-simple-rest'; +import addUploadFeature from './addUploadFeature'; + +const dataProvider = simpleRestProvider('http://path.to.my.api/'); +const uploadCapableDataProvider = addUploadFeature(dataProvider); + +const App = () => ( + + + +); +``` + +## Writing Your Own Data Provider + +Quite often, there is no Data Provider that suits you API - either in the core providers, or in the third-party providers. In such cases, you'll have to write your own Data Provider. + +A Data Provider is a function that receives a request, and returns a promise for a response. Both the request and the response format are standardized. + +```jsx +/** + * Query a data provider and return a promise for a response + * + * @example + * dataProvider(GET_ONE, 'posts', { id: 123 }) + * => Promise.resolve({ data: { id: 123, title: "hello, world" } }) + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the action type + * @returns {Promise} the Promise for a response + */ +const dataProvider = (type, resource, params) => new Promise(); +``` + +When you write a Data provider, your job is to route requests to your API backend(s), then transform their response to match the format returned by the Data Provider. + +### Request Format + +Data queries require a *type* (e.g. `GET_ONE`), a *resource* (e.g. 'posts') and a set of *parameters*. + +*Tip*: In comparison, HTTP requests require a *verb* (e.g. 'GET'), an *url* (e.g. 'http://myapi.com/posts'), a list of *headers* (like `Content-Type`) and a *body*. + +Possible types are: + +Type | Usage | Params format +-------------------- | ------------------------------------------------|------------------------------- +`GET_LIST` | Search for resources | `{ pagination: { page: {int} , perPage: {int} }, sort: { field: {string}, order: {string} }, filter: {Object} }` +`GET_ONE` | Read a single resource, by id | `{ id: {mixed} }` +`CREATE` | Create a single resource | `{ data: {Object} }` +`UPDATE` | Update a single resource | `{ id: {mixed}, data: {Object}, previousData: {Object} }` +`UPDATE_MANY` | Update multiple resources | `{ ids: {mixed[]}, data: {Object} }` +`DELETE` | Delete a single resource | `{ id: {mixed}, previousData: {Object} }` +`DELETE_MANY` | Delete multiple resources | `{ ids: {mixed[]} }` +`GET_MANY` | Read a list of resource, by ids | `{ ids: {mixed[]} }` +`GET_MANY_REFERENCE` | Read a list of resources related to another one | `{ target: {string}, id: {mixed}, pagination: { page: {int} , perPage: {int} }, sort: { field: {string}, order: {string} }, filter: {Object} }` + +Here are several examples of how react-admin can call the Data Provider with these types: + +```jsx +dataProvider(GET_LIST, 'posts', { + pagination: { page: 1, perPage: 5 }, + sort: { field: 'title', order: 'ASC' }, + filter: { author_id: 12 }, +}); +dataProvider(GET_ONE, 'posts', { id: 123 }); +dataProvider(CREATE, 'posts', { data: { title: "hello, world" } }); +dataProvider(UPDATE, 'posts', { + id: 123, + data: { title: "hello, world!" }, + previousData: { title: "previous title" } +}); +dataProvider(UPDATE_MANY, 'posts', { + ids: [123, 234], + data: { views: 0 }, +}); +dataProvider(DELETE, 'posts', { + id: 123, + previousData: { title: "hello, world" } +}); +dataProvider(DELETE_MANY, 'posts', { ids: [123, 234] }); +dataProvider(GET_MANY, 'posts', { ids: [123, 124, 125] }); +dataProvider(GET_MANY_REFERENCE, 'comments', { + target: 'post_id', + id: 123, + sort: { field: 'created_at', order: 'DESC' } +}); +``` + +### Example Request Processing + +Let's say that you want to map the Data Provider requests to a REST backend, like so: + + * `GET_LIST => GET http://path.to.my.api/posts?sort=["title","ASC"]&range=[0, 24]&filter={"author_id":12}` + * `GET_ONE => GET http://path.to.my.api/posts/123` + * `CREATE => POST http://path.to.my.api/posts` + * `UPDATE => PUT http://path.to.my.api/posts/123` + * `UPDATE_MANY => PUT http://path.to.my.api/posts?filter={"ids":[123,124,125]}` + * `DELETE => DELETE http://path.to.my.api/posts/123` + * `DELETE_MANY => DELETE http://path.to.my.api/posts?filter={"ids":[123,124,125]}` + * `GET_MANY => GET http://path.to.my.api/posts?filter={"ids":[123,124,125]}` + * `GET_MANY_REFERENCE => GET http://path.to.my.api/comments?sort=["created_at","DESC"]&range=[0, 24]&filter={"post_id":123}` + +Data Providers often use a `switch` statement, and finish by a call to `fetch()`. Here is an example implementation: + +```js +// in myRestProvider.js +import { stringify } from 'query-string'; +import { + GET_LIST, + GET_ONE, + CREATE, + UPDATE, + DELETE, + GET_MANY, + GET_MANY_REFERENCE, +} from 'react-admin'; + +const apiUrl = 'http://path.to.my.api/'; + +/** + * Maps react-admin queries to my REST API + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the request type + * @returns {Promise} the Promise for a data response + */ +export default (type, resource, params) => { + let url = ''; + const options = { + headers : new Headers({ + Accept: 'application/json', + }), + }; + switch (type) { + case GET_LIST: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([ + (page - 1) * perPage, + page * perPage - 1, + ]), + filter: JSON.stringify(params.filter), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case GET_ONE: + url = `${apiUrl}/${resource}/${params.id}`; + break; + case CREATE: + url = `${apiUrl}/${resource}`; + options.method = 'POST'; + options.body = JSON.stringify(params.data); + break; + case UPDATE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'PUT'; + options.body = JSON.stringify(params.data); + break; + case UPDATE_MANY: + const query = { + filter: JSON.stringify({ id: params.ids }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + options.method = 'PATCH'; + options.body = JSON.stringify(params.data); + break; + case DELETE: + url = `${apiUrl}/${resource}/${params.id}`; + options.method = 'DELETE'; + break; + case DELETE_MANY: + const query = { + filter: JSON.stringify({ id: params.ids }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + options.method = 'DELETE'; + break; + case GET_MANY: { + const query = { + filter: JSON.stringify({ id: params.ids }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + case GET_MANY_REFERENCE: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([ + (page - 1) * perPage, + page * perPage - 1, + ]), + filter: JSON.stringify({ + ...params.filter, + [params.target]: params.id, + }), + }; + url = `${apiUrl}/${resource}?${stringify(query)}`; + break; + } + default: + throw new Error(`Unsupported Data Provider request type ${type}`); + } + + return fetch(url, options) + .then(res => res.json()) + .then(response => + /* Convert HTTP Response to Data Provider Response */ + /* Covered in the next section */ + ); +}; +``` + +### Response Format + +React-admin expects responses from Data Providers to be objects with a `data` property. The data format depends on the request type. + +Request Type | Response format +-------------------- | ---------------- +`GET_LIST` | `{ data: {Record[]}, total: {int} }` +`GET_ONE` | `{ data: {Record} }` +`CREATE` | `{ data: {Record} }` +`UPDATE` | `{ data: {Record} }` +`UPDATE_MANY` | `{ data: {mixed[]} }` The ids which have been updated +`DELETE` | `{ data: {Record} }` +`DELETE_MANY` | `{ data: {mixed[]} }` The ids which have been deleted +`GET_MANY` | `{ data: {Record[]} }` +`GET_MANY_REFERENCE` | `{ data: {Record[]}, total: {int} }` + +A `{Record}` is an object literal with at least an `id` property, e.g. `{ id: 123, title: "hello, world" }`. + +Building up on the previous example, here are example responses matching the format expected by react-admin: + +```jsx +dataProvider(GET_LIST, 'posts', { + pagination: { page: 1, perPage: 5 }, + sort: { field: 'title', order: 'ASC' }, + filter: { author_id: 12 }, +}) +.then(response => console.log(response)); +// { +// data: [ +// { id: 126, title: "allo?", author_id: 12 }, +// { id: 127, title: "bien le bonjour", author_id: 12 }, +// { id: 124, title: "good day sunshine", author_id: 12 }, +// { id: 123, title: "hello, world", author_id: 12 }, +// { id: 125, title: "howdy partner", author_id: 12 }, +// ], +// total: 27 +// } + +dataProvider(GET_ONE, 'posts', { id: 123 }) +.then(response => console.log(response)); +// { +// data: { id: 123, title: "hello, world" } +// } + +dataProvider(CREATE, 'posts', { data: { title: "hello, world" } }) +.then(response => console.log(response)); +// { +// data: { id: 450, title: "hello, world" } +// } + +dataProvider(UPDATE, 'posts', { + id: 123, + data: { title: "hello, world!" }, + previousData: { title: "previous title" } +}) +.then(response => console.log(response)); +// { +// data: { id: 123, title: "hello, world!" } +// } + +dataProvider(UPDATE_MANY, 'posts', { + ids: [123, 234], + data: { views: 0 }, +}) +.then(response => console.log(response)); +// { +// data: [123, 234] +// } + +dataProvider(DELETE, 'posts', { + id: 123, + previousData: { title: "hello, world!" } +}) +.then(response => console.log(response)); +// { +// data: { id: 123, title: "hello, world" } +// } + +dataProvider(DELETE_MANY, 'posts', { ids: [123, 234] }) +.then(response => console.log(response)); +// { +// data: [123, 234] +// } + +dataProvider(GET_MANY, 'posts', { ids: [123, 124, 125] }) +.then(response => console.log(response)); +// { +// data: [ +// { id: 123, title: "hello, world" }, +// { id: 124, title: "good day sunshise" }, +// { id: 125, title: "howdy partner" }, +// ] +// } + +dataProvider(GET_MANY_REFERENCE, 'comments', { + target: 'post_id', + id: 123, + sort: { field: 'created_at', order: 'DESC' } +}); +.then(response => console.log(response)); +// { +// data: [ +// { id: 667, title: "I agree", post_id: 123 }, +// { id: 895, title: "I don't agree", post_id: 123 }, +// ], +// total: 2, +// } +``` + +### Example Response Processing + +Let's continue with the REST backend example. This backend returns responses as follows: + +``` +GET http://path.to.my.api/posts?sort=['title','ASC']&range=[0, 4]&filter={author_id:12} +Content-Range: posts 0-4/27 +[ + { "id": 126, "title": "allo?", "author_id": 12 }, + { "id": 127, "title": "bien le bonjour", "author_id": 12 }, + { "id": 124, "title": "good day sunshine", "author_id": 12 }, + { "id": 123, "title": "hello, world", "author_id": 12 }, + { "id": 125, "title": "howdy partner", "author_id": 12 } +] + +GET http://path.to.my.api/posts/123 +{ "id": 123, "title": "hello, world", "author_id": 12 } + +POST http://path.to.my.api/posts +{ "id": 123, "title": "hello, world", "author_id": 12 } + +PUT http://path.to.my.api/posts/123 +{ "id": 123, "title": "hello, world", "author_id": 12 } + +PATCH http://path.to.my.api/posts?filter={ids:[123,124,125]} +[123, 124, 125] + +DELETE http://path.to.my.api/posts/123 +{ "id": 123, "title": "hello, world", "author_id": 12 } + +DELETE http://path.to.my.api/posts?filter={ids:[123,124,125]} +[123, 124, 125] + +GET http://path.to.my.api/posts?filter={ids:[123,124,125]} +[ + { "id": 123, "title": "hello, world", "author_id": 12 }, + { "id": 124, "title": "good day sunshine", "author_id": 12 }, + { "id": 125, "title": "howdy partner", "author_id": 12 } +] + +GET http://path.to.my.api/comments?sort=['created_at','DESC']&range=[0, 24]&filter={post_id:123} +Content-Range: comments 0-1/2 +[ + { "id": 667, "title": "I agree", "post_id": 123 }, + { "id": 895, "title": "I don't agree", "post_id": 123 } +] +``` + +The Data Provider must therefore transform the response from the API backend to the expected response format. + +```js +// in myRestProvider.js +import { stringify } from 'query-string'; +import { + GET_LIST, + GET_ONE, + CREATE, + UPDATE, + UPDATE_MANY, + DELETE, + DELETE_MANY, + GET_MANY, + GET_MANY_REFERENCE, +} from 'react-admin'; + +const apiUrl = 'http://path.to.my.api/'; + +/** + * Maps react-admin queries to my REST API + * + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the request type + * @returns {Promise} the Promise for a data response + */ +export default (type, resource, params) => { + let url = ''; + const options = { + headers : new Headers({ + Accept: 'application/json', + }), + }; + switch (type) { + /* Prepare url and options as above */ + } + + let headers; + return fetch(url, options) + .then(res => { + headers = res.headers; + return res.json(); + }) + .then(json => { + switch (type) { + case GET_LIST: + case GET_MANY_REFERENCE: + if (!headers.has('content-range')) { + throw new Error( + 'The Content-Range header is missing in the HTTP Response. The simple REST data provider expects responses for lists of resources to contain this header with the total number of results to build the pagination. If you are using CORS, did you declare Content-Range in the Access-Control-Expose-Headers header?' + ); + } + return { + data: json, + total: parseInt( + headers + .get('content-range') + .split('/') + .pop(), + 10 + ), + }; + case CREATE: + return { data: { ...params.data, id: json.id } }; + default: + return { data: json }; + } + }); +}; +``` + +### Error Format + +When the API backend returns an error, the Data Provider should `throw` an `Error` object. This object should contain a `status` property with the HTTP response code (404, 500, etc.). React-admin inspects this error code, and uses it for [authentication](./Authentication.md) (in case of 401 or 403 errors). Besides, react-admin displays the error `message` on screen in a temporary notification. + +### Example implementation + +Check the code from the [simple REST client](https://github.com/marmelab/react-admin/blob/master/packages/ra-data-simple-rest/src/index.js): it's a good starting point for a custom Data Provider implementation. diff --git a/docs/ecosystem.md b/docs/ecosystem.md new file mode 100644 index 0000000..eb2a839 --- /dev/null +++ b/docs/ecosystem.md @@ -0,0 +1,41 @@ +--- +id: ecosystem +title: Ecosystem +--- + +- [Inputs and Fields](#inputs-and-fields) +- [Translations](#translations) +- [Data Providers](#data-providers) +- [Miscellaneous](#miscellaneous) + +## Inputs and Fields + +- [vascofg/react-admin-color-input](https://github.com/vascofg/react-admin-color-input): a color input using [React Color](http://casesandberg.github.io/react-color/), a collection of color pickers. +- [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML +- [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs): a collection of Date Inputs, based on [material-ui-pickers](https://material-ui-pickers.firebaseapp.com/) + +## Translations + +See the [translation](./Translation.md#available-locales) page. + +## Data Providers + +* **[Epilogue](https://github.com/dchester/epilogue)**: [dunghuynh/aor-epilogue-client](https://github.com/dunghuynh/aor-epilogue-client) +* **[Feathersjs](http://www.feathersjs.com/)**: [josx/aor-feathers-client](https://github.com/josx/aor-feathers-client) +* **[Firebase](https://firebase.google.com/)**: [sidferreira/aor-firebase-client](https://github.com/sidferreira/aor-firebase-client) +* **[GraphQL](http://graphql.org/)**: [marmelab/ra-data-graphql](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql) (uses [Apollo](http://www.apollodata.com/)) +* **[GraphCool](http://www.graph.cool/)**: [marmelab/ra-data-graphcool](https://github.com/marmelab/react-admin/tree/master/packages/ra-data-graphql) (uses [Apollo](http://www.apollodata.com/)) +* **[Hydra](http://www.hydra-cg.com/) / [JSON-LD](https://json-ld.org/)**: [api-platform/admin/hydra](https://github.com/api-platform/admin/blob/master/src/hydra/hydraClient.js) +* **[JSON API](http://jsonapi.org/)**: [moonlight-labs/aor-jsonapi-client](https://github.com/moonlight-labs/aor-jsonapi-client) +* **[JSON server](https://github.com/typicode/json-server)**: [marmelab/ra-data-json-server](https://github.com/marmelab/ra-data-json-server). +* Local JSON: [marmelab/ra-data-fakerest](https://github.com/marmelab/ra-data-fakerest) +* **[Loopback](http://loopback.io/)**: [kimkha/aor-loopback](https://github.com/kimkha/aor-loopback) +* **[Parse Server](https://github.com/ParsePlatform/parse-server)**: [leperone/aor-parseserver-client](https://github.com/leperone/aor-parseserver-client) +* **[PostgREST](http://postgrest.com/en/v0.4/)**: [tomberek/aor-postgrest-client](https://github.com/tomberek/aor-postgrest-client) +* Simple REST: [marmelab/ra-data-simple-rest](https://github.com/marmelab/ra-data-simple-rest). + +## Miscellaneous + +- [api-platform/admin](https://api-platform.com/docs/admin): create a fully featured admin using React Admin for API supporting the [Hydra Core Vocabulary](http://www.hydra-cg.com/), including but not limited to APIs created using the [API Platform framework](https://api-platform.com) +- [marmelab/ra-realtime](https://github.com/marmelab/react-admin/tree/master/packages/ra-realtime): enable realtime updates +- [zifnab87/ra-component-factory](https://github.com/zifnab87/ra-component-factory): centralized configuration of immutability/visibility of fields/menu-links/action buttons, easy re-ordering of fields/properties and tab reorganization based on permission roles diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..cffbb3a --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,98 @@ +--- +id: faq +title: FAQ +--- + +- [Can I have custom identifiers/primary keys for my resources?](#can-i-have-custom-identifiersprimary-keys-for-my-resources) +- [I get warning about unique key for child in array](#i-get-warning-about-unique-key-for-child-in-array) +- [A form with validation freezes when rendering](#a-form-with-validation-freezes-when-rendering) +- [How can I customize the UI depending on the user permissions?](#how-can-i-customize-the-ui-depending-on-the-user-permissions) +- [How can I customize forms depending on its inputs values?](#how-can-i-customize-forms-depending-on-its-inputs-values) + +## Can I have custom identifiers/primary keys for my resources? + +React-admin requires that each resource has an `id` field to identify it. If your API uses a different name for the primary key, you have to map that name to `id` in a custom [dataProvider](./DataProviders.md). For instance, to use a field named `_id` as identifier: + +```js +const convertHTTPResponse = (response, type, resource, params) => { + const { headers, json } = response; + switch (type) { + case GET_LIST: + return { + data: json.map(resource => ({ ...resource, id: resource._id }) ), + total: parseInt(headers.get('content-range').split('/').pop(), 10), + }; + case UPDATE: + case DELETE: + case GET_ONE: + return { ...json, id: json._id }; + case CREATE: + return { ...params.data, id: json._id }; + default: + return json; + } +}; +``` + +## I get warning about unique key for child in array + +When displaying a `Datagrid` component, you get the following warning: + +> Warning: Each child in an array or iterator should have a unique "key" prop. +> Check the render method of `DatagridBody`. + +This is most probably because the resource does not have an `id` property as expected by react-admin. See the previous FAQ to see how to resolve this: [Can I have custom identifiers/primary keys for my resources?](#can-i-have-custom-identifiersprimary-keys-for-my-resources) + +## A form with validation freezes when rendering + +You're probably using validator factories directly in the render method of your component: + +```jsx +export const CommentEdit = ({ ...props }) => ( + + + + + + + +); +``` + +Avoid calling functions directly inside the render method: + +```jsx +const validateMinLength = minLength(10); + +export const CommentEdit = ({ ...props }) => ( + + + + + + + +); +``` + +This is related to [redux-form](https://github.com/erikras/redux-form/issues/3288). + +## How can I customize the UI depending on the user permissions? + +Some fairly common use cases which may be dependent on the user permissions: + +- Specific views +- Having parts of a view (fields, inputs) differents for specific users +- Hiding or displaying menu items + +For all those cases, you can use the [aor-permissions](https://github.com/marmelab/aor-permissions) addon. + +## How can I customize forms depending on its inputs values? + +Some use cases: + +- Show/hide some inputs if another input has a value +- Show/hide some inputs if another input has a specific value +- Show/hide some inputs if the current form values matches specific constraints + +For all those cases, you can use the [aor-dependent-input](https://github.com/marmelab/aor-dependent-input) addon. diff --git a/docs/field-components.md b/docs/field-components.md new file mode 100644 index 0000000..f15a439 --- /dev/null +++ b/docs/field-components.md @@ -0,0 +1,1010 @@ +--- +layout: field-components +title: Components +--- + +A `Field` component displays a given property of a REST resource. Such components are used in the `List` view, but you can also use them in the `Edit` and `Create` views for read-only fields. The most usual of all field components is ``: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +export const PostList = (props) => ( + + + + + + + +); +``` + +All field components accept the following attributes: + +* `source`: Property name of your entity to view/edit. This attribute is required. +* `label`: Used as a table header of an input label. Defaults to the `source` when omitted. +* `sortable`: Should the list be sortable using `source` attribute? Defaults to `true`. +* `className`: A class name (usually generated by JSS) to customize the look and feel of the field element itself +* `cellClassName`: A class name (usually generated by JSS) to customize the look and feel of the field container (e.g. the `` in a datagrid). +* `headerClassName`: A class name (usually generated by JSS) to customize the look and feel of the field header (e.g. the `` in a datagrid). +* `addLabel`: Defines the visibility of the label when the field is not in a datagrid. Default value is ```true```. +* `textAlign`: Defines the text alignment inside a cell. Supports `left` (the default) and `right`. + +```jsx + +``` + +**Tip**: You can use field components inside the `Edit` or `Show` views, too: + +```jsx +export const PostShow = ({ ...props }) => ( + + + + + +); +``` + +**Tip**: If you display a record with a complex structure, you can use a path with dot separators as the `source` attribute. For instance, if the API returns the following 'book' record: + +```jsx +{ + id: 1234, + title: 'War and Peace', + author: { + firstName: 'Leo', + lastName: 'Tolstoi' + } +} +``` + +Then you can display the author first name as follows: + +```jsx + +``` + +**Tip**: If you want to format a field according to the value, use a higher-order component to do conditional formatting, as described in the [Theming documentation](./Theming.md#conditional-formatting). + +**Tip**: If your interface has to support multiple languages, don't use the `label` prop, and put the localized labels in a dictionary instead. See the [Translation documentation](./Translation.md#translating-resource-and-field-names) for details. + +## `ArrayField` Component + +Display a collection using `` child components. + +Ideal for embedded arrays of objects, e.g. `tags` and `backlinks` in the following `post` object: + +```js +{ + id: 123 + tags: [ + { name: 'foo' }, + { name: 'bar' } + ], + backlinks: [ + { + date: '2012-08-10T00:00:00.000Z', + url: 'http://example.com/foo/bar.html', + }, + { + date: '2012-08-14T00:00:00.000Z', + url: 'https://blog.johndoe.com/2012/08/12/foobar.html', + } + ] +} +``` + +The child must be an iterator component (like `` or ``). + +Here is how to display all the backlinks of the current post as a `` + +```jsx + + + + + + +``` + +And here is how to display all the tags of the current post as `` components: + +```jsx + + + + + +``` + +**Tip**: If you need to render a collection in a custom way, it's often simpler to write your own component: + +```jsx +const TagsField = ({ record }) => ( +
    + {record.tags.map(item => ( +
  • {item.name}
  • + ))} +
+) +TagsField.defaultProps = { addLabel: true }; +``` + +## `BooleanField` Component + +Displays a boolean value as a check. + +```jsx +import { BooleanField } from 'react-admin'; + + +``` + +![BooleanField](https://marmelab.com/react-admin/img/boolean-field.png) + +## `ChipField` Component + +Displays a value inside a ["Chip"](http://www.material-ui.com/#/components/chip), which is Material UI's term for a label. + +```jsx +import { ChipField } from 'react-admin'; + + +``` + +![ChipField](https://marmelab.com/react-admin/img/chip-field.png) + +This field type is especially useful for one to many relationships, e.g. to display a list of books for a given author: + +```jsx +import { ChipField, SingleFieldList, ReferenceManyField } from 'react-admin'; + + + + + + +``` + +## `DateField` Component + +Displays a date or datetime using the browser locale (thanks to `Date.toLocaleDateString()` and `Date.toLocaleString()`). + +```jsx +import { DateField } from 'react-admin'; + + +``` + +This component accepts a `showTime` attribute (false by default) to force the display of time in addition to date. It uses `Intl.DateTimeFormat()` if available, passing the `locales` and `options` props as arguments. If Intl is not available, it ignores the `locales` and `options` props. + +```jsx + +// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as +4/23/2017 + + +// renders the record { id: 1234, publication_date: new Date('2017-04-23 23:05') } as +4/23/2017, 11:05:00 PM + + +// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as +Sunday, April 23, 2017 + + +// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as +23/04/2017 + + +// renders the record { id: 1234, publication_date: new Date('2017-04-23') } as +4/23/2017 +``` + +See [Intl.DateTimeformat documentation](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Date/toLocaleDateString) for the `options` prop syntax. + +**Tip**: If you need more formatting options than what `Intl.DateTimeformat` can provide, build your own field component leveraging a third-party library like [moment.js](http://momentjs.com/). + +## `EmailField` Component + +`` displays an email as a `` link. + +```jsx +import { EmailField } from 'react-admin'; + + +``` + +## `FunctionField` Component + +If you need a special function to render a field, `` is the perfect match. It passes the `record` to a `render` function supplied by the developer. For instance, to display the full name of a `user` record based on `first_name` and `last_name` properties: + +```jsx +import { FunctionField } from 'react-admin' + + `${record.first_name} ${record.last_name}`} /> +``` + +**Tip**: Technically, you can omit the `source` and `sortBy` properties for the `` since you provide the render function. However, providing a `source` or a `sortBy` will allow the datagrid to make the column sortable, since when a user clicks on a column, the datagrid uses these properties to sort. Should you provide both, `sortBy` will override `source` for sorting the column. + +## `ImageField` Component + +If you need to display an image provided by your API, you can use the `` component: + +```jsx +import { ImageField } from 'react-admin'; + + +``` + +This field is also generally used within an [](http://marmelab.com/react-admin/Inputs.md#imageinput) component to display preview. + +The optional `title` prop points to the picture title property, used for both `alt` and `title` attributes. It can either be an hard-written string, or a path within your JSON object: + +```jsx +// { picture: { url: 'cover.jpg', title: 'Larry Cover (French pun intended)' } } + +// Title would be "picture.title", hence "Larry Cover (French pun intended)" + + +// Title would be "Picture", as "Picture" is not a path in previous given object + +``` + +If passed value is an existing path within your JSON object, then it uses the object attribute. Otherwise, it considers its value as an hard-written title. + + +If the record actually contains an array of images in its property defined by the `source` prop, the `src` prop will be needed to determine the `src` value of the images, for example: + +```js +// This is the record +{ + pictures: [ + { url: 'image1.jpg', desc: 'First image' }, + { url: 'image2.jpg', desc: 'Second image' }, + ], +} + + +``` + +## `FileField` Component + +If you need to display a file provided by your API, you can use the `` component: + +```jsx +import { FileField } from 'react-admin'; + + +``` + +This field is also generally used within an [](http://marmelab.com/react-admin/Inputs.md#fileinput) component to display preview. + +The optional `title` prop points to the file title property, used for `title` attributes. It can either be an hard-written string, or a path within your JSON object: + +```jsx +// { file: { url: 'doc.pdf', title: 'Presentation' } } + +// Title would be "file.title", hence "Presentation" + + +// Title would be "File", as "File" is not a path in previous given object + +``` + +If passed value is an existing path within your JSON object, then it uses the object attribute. Otherwise, it considers its value as an hard-written title. + +If the record actually contains an array of files in its property defined by the `source` prop, the `src` prop will be needed to determine the `href` value of the links, for example: + +```js +// This is the record +{ + files: [ + { url: 'image1.jpg', desc: 'First image' }, + { url: 'image2.jpg', desc: 'Second image' }, + ], +} + + +``` + +You can optionally set the `target` prop to choose which window will the link try to open in. + +```jsx +// Will make the file open in new window + +``` + +## `NumberField` Component + +Displays a number formatted according to the browser locale, right aligned. + +Uses `Intl.NumberFormat()` if available, passing the `locales` and `options` props as arguments. This allows perfect display of decimals, currencies, percentage, etc. + +If Intl is not available, it outputs number as is (and ignores the `locales` and `options` props). + + +```jsx +import { NumberField } from 'react-admin'; + + +// renders the record { id: 1234, score: 567 } as +567 + + +// renders the record { id: 1234, score: 567.3567458569 } as +567.35 + + +// renders the record { id: 1234, share: 0.2545 } as +25% + + +// renders the record { id: 1234, price: 25.99 } as +$25.99 + + +// renders the record { id: 1234, price: 25.99 } as +25,99 $US + + +// renders the record { id: 1234, score: 567 } as +567 +``` + +See [Intl.Numberformat documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString) for the `options` prop syntax. + +**Tip**: If you need more formatting options than what `Intl.Numberformat` can provide, build your own field component leveraging a third-party library like [numeral.js](http://numeraljs.com/). + +**Tip**: When used in a `Show` view, the right alignment may look weird. Disable it by resetting the `style` attribute: + +```jsx +import { NumberField } from 'react-admin'; + + +``` + +## `SelectField` Component + +When you need to display an enumerated field, `` maps the value to a string. + +For instance, if the `gender` field can take values "M" and "F", here is how to display it as "Male" or "Female": + +```jsx +import { SelectField } from 'react-admin'; + + +``` + +By default, the text is built by + +- finding a choice where the 'id' property equals the field value +- using the 'name' property an the option text + +**Warning**: This component name may conflict with material-ui's [``](http://www.material-ui.com/#/components/select-field) if you import both. + +You can also customize the properties to use for the lookup value and text, thanks to the 'optionValue' and 'optionText' attributes. + +```jsx +const choices = [ + { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, + { _id: 456, full_name: 'Jane Austen', sex: 'F' }, +]; + +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; + +``` + +`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const FullNameField = ({ record }) => {record.first_name} {record.last_name}; +}/> +``` + +The current choice is translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'M', name: 'myroot.gender.male' }, + { id: 'F', name: 'myroot.gender.female' }, +]; +``` + +However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to false. + +```jsx + +``` + +**Tip**: sets `translateChoice` to false by default. + +## `ReferenceField` Component + +This component fetches a single referenced record (using the `GET_MANY` REST method), and displays one field of this record. That's why a `` must always have a child ``. + +For instance, here is how to fetch the `post` related to `comment` records, and display the `title` for each: + +```jsx +import React from 'react'; +import { List, Datagrid, ReferenceField, TextField } from 'react-admin'; + +export const PostList = (props) => ( + + + + + + + + +); +``` + +With this configuration, `` wraps the user's name in a link to the related user `` page. + +![ReferenceField](https://marmelab.com/react-admin/img/reference-field.png) + +`` accepts a `reference` attribute, which specifies the resource to fetch for the related record. Also, you can use any `Field` component as child. + +**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu. + +```jsx + + + + +``` + +To change the link from the `` page to the `` page, set the `linkType` prop to "show". + +```jsx + + + +``` + +By default, `` is sorted by its `source`. To specify another attribute to sort by, set the `sortBy` prop to the according attribute's name. + +```jsx + + + +``` + +You can also prevent `` from adding link to children by setting `linkType` to `false`. + +```jsx +// No link + + + +``` + +**Tip**: React-admin uses `CRUD_GET_ONE_REFERENCE` action to accumulate and deduplicate the ids of the referenced records to make *one* `GET_MANY` call for the entire list, instead of n `GET_ONE` calls. So for instance, if the API returns the following list of comments: + +```jsx +[ + { + id: 123, + body: 'Totally agree', + post_id: 789, + }, + { + id: 124, + title: 'You are right my friend', + post_id: 789 + }, + { + id: 125, + title: 'Not sure about this one', + post_id: 735 + } +] +``` + +Then react-admin renders the `` with a loader for the ``, fetches the API for the related posts in one call (`GET http://path.to.my.api/posts?ids=[789,735]`), and re-renders the list once the data arrives. This accelerates the rendering, and minimizes network load. + +## `ReferenceManyField` Component + +This component fetches a list of referenced records by reverse lookup of the current `record.id` in other resource (using the `GET_MANY_REFERENCE` REST method). The field name of the current record's id in the other resource is specified by the required `target` field. The result is then passed to an iterator component (like `` or ``). The iterator component usually has one or more child `` components. + +For instance, here is how to fetch the `comments` related to a `post` record by matching `comment.post_id` to `post.id`, and then display the `author.name` for each, in a ``: + +```jsx +import React from 'react'; +import { List, Datagrid, ChipField, ReferenceManyField, SingleFieldList, TextField } from 'react-admin'; + +export const PostList = (props) => ( + + + + + + + + + + + + +); +``` + +![ReferenceManyFieldSingleFieldList](https://marmelab.com/react-admin/img/reference-many-field-single-field-list.png) + +`` accepts a `reference` attribute, which specifies the resource to fetch for the related record. It also accepts a `source` attribute which define the field containing the value to look for in the `target` field of the referenced resource. By default this is the `id` of the resource (`post.id` in the previous example). + +**Note**: You **must** add a `` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu. + +You can use a `` instead of a `` - but not inside another ``! This is useful if you want to display a read-only list of related records. For instance, if you want to show the `comments` related to a `post` in the post's `` view: + +```jsx +import React from 'react'; +import { Edit, Datagrid, SimpleForm, DisabledInput, DateField, EditButton, ReferenceManyField, TextField, TextInput } from 'react-admin'; + +export const PostEdit = (props) => ( + + + + + + + + + + + + + + +); +``` + +![ReferenceManyFieldDatagrid](https://marmelab.com/react-admin/img/reference-many-field-datagrid.png) + +By default, react-admin restricts the possible values to 25. You can change this limit by setting the `perPage` prop. + +```jsx + + ... + +``` + +By default, it orders the possible values by id desc. You can change this order by setting the `sort` prop (an object with `field` and `order` properties). + +```jsx + + ... + +``` + +Also, you can filter the query used to populate the possible values. Use the `filter` prop for that. + +```jsx + + ... + +``` + +## `ReferenceArrayField` Component + +Use `` to display an list of reference values based on an array of foreign keys. + +For instance, if a post has many tags, a post resource may look like: + +```js +{ + id: 1234, + title: 'Lorem Ipsum', + tag_ids: [1, 23, 4] +} +``` + +Where `[1, 23, 4]` refer to ids of `tag` resources. + +`` can fetch the `tag` resources related to this `post` resource by matching `post.tag_ids` to `tag.id`. `` would issue an HTTP request looking like: + +``` +http://myapi.com/tags?id=[1,23,4] +``` + +**Tip**: `` fetches the related resources using the `GET_MANY` REST method, so the actual HTTP request depends on your REST client. + +Once it receives the related resources, `` passes them to its child component using the `ids` and `data` props, so the child must be an iterator component (like `` or ``). The iterator component usually has one or more child `` components. + +Here is how to fetch the list of tags for each post in a `PostList`, and display the `name` for each `tag` in a ``: + +```jsx +import React from 'react'; +import { List, Datagrid, ChipField, ReferenceArrayField, SingleFieldList, TextField } from 'react-admin'; + +export const PostList = (props) => ( + + + + + + + + + + + + +); +``` + +**Note**: You **must** add a `` component for the reference resource to your `` component, because react-admin needs it to fetch the reference data. You can omit the `list` prop in this Resource if you don't want to show an entry for it in the sidebar menu. + +```jsx +export const App = () => ( + + + // <= this one is compulsory + +); +``` + +In an Edit of Show view, you can combine `` with `` to display a related resources in a table. For instance, to display more details about the tags related to a post in the `PostShow` view: + +```jsx +import React from 'react'; +import { Show, SimpleShowLayout, TextField, ReferenceArrayField, Datagrid, ShowButton } from 'react-admin'; + +export const PostShow = (props) => ( + + + + + + + + + + + + + + +); +``` + +## `RichTextField` Component + +This component displays some HTML content. The content is "rich" (i.e. unescaped) by default. + +```jsx +import { RichTextField } from 'react-admin'; + + +``` + +![RichTextField](https://marmelab.com/react-admin/img/rich-text-field.png) + +The `stripTags` attribute (`false` by default) allows you to remove any HTML markup, preventing some display glitches (which is especially useful in list views). + +```jsx +import { RichTextField } from 'react-admin'; + + +``` + +## `TextField` Component + +The most simple as all fields, `` simply displays the record property as plain text. + +```jsx +import { TextField } from 'react-admin'; + + +``` + +## `UrlField` Component + +`` displays an url in an `< a href="">` tag. + +```jsx +import { UrlField } from 'react-admin'; + + +``` + +## Styling Fields + +All field components accept a `className` prop, allowing you to customize their style to your liking. We advise you to use the Material UI styling solution, JSS, to generate those classes. See their [documentation](https://material-ui.com/customization/css-in-js/#api) about that. + + +```jsx +import { withStyles } from '@material-ui/core/styles'; + +const styles = { + price: { color: 'purple' }, +}; + +const PriceField = withStyles(styles)(({ classes, ...props }) => ( + +)); + +export const ProductList = (props) => ( + + + + + +); + +// renders in the datagrid as +2 +``` + +React-admin usually delegates the rendering of fields components to material-ui components. Refer to the material-ui documentation to see the default styles for elements. + +You may want to customize the cell style inside a `DataGrid`. You can use the `cellClassName` for that: + +```jsx +import { withStyles } from '@material-ui/core/styles'; + +const styles = { + priceCell: { fontWeight: 'bold' }, +}; + +const PriceField = withStyles(styles)(({ classes, ...props }) => ( + +)); + +export const ProductList = (props) => ( + + + + + +); + +// renders in the datagrid as +2 +``` + +You may want to override the field header (the `` element in the datagrid). In that case, use the `headerClassName` prop: + + +```jsx +import { withStyles } from '@material-ui/core/styles'; + +const styles = { + priceHeader: { fontWeight: 'bold' }, +}; + +const PriceField = withStyles(styles)(({ classes, ...props }) => ( + +)); + +export const ProductList = (props) => ( + + + + + +); +// renders in the table header as + +``` + +Finally, sometimes, you just want to right align the text of a cell. Use the `textAlign` prop, which accepts either `left` or `right`: + + +```jsx +const PriceField = props => ( + +); + +PriceField.defaultProps = { + textAlign: 'right', +}; +``` + +## Writing Your Own Field Component + +If you don't find what you need in the list above, you can write your own Field component. It must be a regular React component, accepting not only a `source` attribute, but also a `record` attribute. React-admin will inject the `record` based on the API response data at render time. The field component only needs to find the `source` in the `record` and display it. + +For instance, here is an equivalent of react-admin's `` component: + +```jsx +import React from 'react'; +import PropTypes from 'prop-types'; + +const TextField = ({ source, record = {} }) => {record[source]}; + +TextField.propTypes = { + label: PropTypes.string, + record: PropTypes.object, + source: PropTypes.string.isRequired, +}; + +export default TextField; +``` + +**Tip**: The `label` attribute isn't used in the `render()` method, but react-admin uses it to display the table header. + +**Tip**: If you want to support deep field sources (e.g. source values like `author.name`), use [`lodash/get`](https://www.npmjs.com/package/lodash.get) to replace the simple object lookup: + +```jsx +import get from 'lodash/get'; +const TextField = ({ source, record = {} }) => {get(record, source)}; +``` + +If you are not looking for reusability, you can create even simpler components, with no attributes. Let's say an API returns user records with `firstName` and `lastName` properties, and that you want to display a full name in a user list. + +```jsx +{ + id: 123, + firstName: 'John', + lastName: 'Doe' +} +``` + +The component will be: + +```jsx +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +const FullNameField = ({ record = {} }) => {record.firstName} {record.lastName}; +FullNameField.defaultProps = { label: 'Name' }; + +export const UserList = (props) => ( + + + + + +); +``` + +**Tip**: In such custom fields, the `source` is optional. React-admin uses it to determine which column to use for sorting when the column header is clicked. In case you use the `source` property for additional purposes, the sorting can be overridden by the `sortBy` property on any `Field` component. + +## Adding Label To Custom Field Components In The Show View + +React-admin lets you use the same Field components in the List view and in the Show view. But if you use the `` custom field component defined earlier in a Show view, something is missing: the Field label. Why do other fields have a label and not this custom Field? And how can you create a Field component that has a label in the Show view, but not in the List view? + +React-admin uses a trick: the Show view layouts (`` and ``) inspect their Field children, and whenever one has the `addLabel` prop set to `true`, the layout adds a label. + +That means that the only thing you need to add to a custom component to make it usable in a Show view is a `addLabel: true` default prop. + +```jsx +FullNameField.defaultProps = { + addLabel: true, +}; +``` + +## Hiding A Field Based On The Value Of Another + +In a Show view, you may want to display or hide fields based on the value of another field - for instance, show an `email` field only if the `hasEmail` boolean field is `true`. + +For such cases, you can use the custom field approach: use the injected `record` prop, and render another Field based on the value. + +```jsx +import React from 'react'; +import { EmailField } from 'react-admin'; + +const ConditionalEmailField = ({ record, ...rest }) => + record && record.hasEmail + ? + : null; + +export default ConditionalEmailField; +``` + +**Tip**: Always check that the `record` is defined before inspecting its properties, as react-admin displays the Show view *before* fetching the record from the data provider. So the first time it renders the show view for a resource, the `record` is undefined. + +This `ConditionalEmailField` is properly hidden when `hasEmail` is false. But when `hasEmail` is true, the Show layout renders it... without label. And if you add a `addLabel` default prop, the Show layout will render the label regardless of the `hasEmail` value... + +One solution is to add the label manually in the custom component: + +```jsx +import React from 'react'; +import { Labeled, EmailField } from 'react-admin'; + +const ConditionalEmailField = ({ record, ...rest }) => + record && record.hasEmail + ? ( + + + + ) + : null; + +export default ConditionalEmailField; +``` + +This comes with a drawback, though: the `` cannot be used in a List view anymore, as it will always have a label. If you want to reuse the custom component in a List, this isn't the right solution. + +An alternative solution is to split the `` component. Under the hood, the `` component is composed of two sub components: the `` component, which fetches the record, and the ``, which is responsible for rendering the view title, actions, and children. `` uses the *render props* pattern: + +```jsx +// inside react-admin +const Show = props => ( + + {controllerProps => } + +); +``` + +The `` fetches the `record` from the data provider, and passes it to its child function when received (among the `controllerProps`). That means the following code: + +```jsx +import { Show, SimpleShowLayout, TextField } from 'react-admin'; + +const UserShow = props => ( + + + + + + +); +``` + +Is equivalent to: + +```jsx +import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin'; + +const UserShow = props => ( + + {controllerProps => + + + + + + + } + +); +``` + +If you want one field to be displayed based on the `record`, for instance to display the email field only if the `hasEmail` field is `true`, you just need to test the value from `controllerProps.record`, as follows: + +```jsx +import { ShowController, ShowView, SimpleShowLayout, TextField } from 'react-admin'; + +const UserShow = props => ( + + {controllerProps => + + + + {controllerProps.record && controllerProps.record.hasEmail && + + } + + + } + +); +``` + +And now you can use a regular Field component, and the label displays correctly in the Show view. diff --git a/docs/input-components.md b/docs/input-components.md new file mode 100644 index 0000000..3155968 --- /dev/null +++ b/docs/input-components.md @@ -0,0 +1,1280 @@ +--- +id: input-components +title: components +--- + +An `Input` component displays an input, or a dropdown list, a list of radio buttons, etc. Such components allow to edit a record property, and are common in the ``, ``, and `` views. + +```jsx +// in src/posts.js +import React from 'react'; +import { Edit, DisabledInput, LongTextInput, ReferenceInput, SelectInput, SimpleForm, TextInput } from 'react-admin'; + +export const PostEdit = (props) => ( + } {...props}> + + + + + + + + + +); +``` + +All input components accept the following attributes: + +* `source`: Property name of your entity to view/edit. This attribute is required. +* `defaultValue`: Value to be set when the property is `null` or `undefined`. +* `validate`: Validation rules for the current property (see the [Validation Documentation](./CreateEdit.html#validation)) +* `label`: Used as a table header of an input label. Defaults to the `source` when omitted. +* `style`: A style object to customize the look and feel of the field container (e.g. the `
` in a form). +* `elStyle`: A style object to customize the look and feel of the field element itself + +```jsx + +``` + +Additional props are passed down to the underlying component (usually a material-ui component). For instance, when setting the `fullWidth` prop on a `TextInput` component, the underlying material-ui `` receives it, and goes full width. + +**Tip**: If you edit a record with a complex structure, you can use a path as the `source` parameter. For instance, if the API returns the following 'book' record: + +```jsx +{ + id: 1234, + title: 'War and Peace', + author: { + firstName: 'Leo', + lastName: 'Tolstoi' + } +} +``` + +Then you can display a text input to edit the author first name as follows: + +```jsx + +``` + +**Tip**: If your interface has to support multiple languages, don't use the `label` prop, and put the localized labels in a dictionary instead. See the [Translation documentation](./Translation.md#translating-resource-and-field-names) for details. + +## `ArrayInput` Component + +To edit arrays of data embedded inside a record, `` creates a list of sub-forms. + +```jsx +import { ArrayInput, SimpleFormIterator, DateInput, UrlInput } from 'react-admin'; + + + + + + + +``` + +![ArrayInput](https://marmelab.com/react-admin/img/array-input.png) + + `` allows the edition of embedded arrays, like the `backlinks` field in the following `post` record: + +```js +{ + id: 123 + backlinks: [ + { + date: '2012-08-10T00:00:00.000Z', + url: 'http://example.com/foo/bar.html', + }, + { + date: '2012-08-14T00:00:00.000Z', + url: 'https://blog.johndoe.com/2012/08/12/foobar.html', + } + ] +} +``` + +`` expects a single child, which must be a *form iterator* component. A form iterator is a component accepting a `fields` object as passed by [redux-form's `` component](https://redux-form.com/7.3.0/examples/fieldarrays/), and defining a layout for an array of fields. For instance, the `` component displays an array of fields in an unordered list (`
    `), one sub-form by list item (`
  • `). It also provides controls for adding and removing a sub-record (a backlink in this example). + +You can pass `disableAdd` and `disableRemove` as props of `SimpleFormIterator`, to disable `ADD` and `REMOVE` button respectively. Default value of both is `false`. + +```jsx +import { ArrayInput, SimpleFormIterator, DateInput, UrlInput } from 'react-admin'; + + + + + + + +``` + +## `AutocompleteInput` Component + +To let users choose a value in a list using a dropdown with autocompletion, use ``. It renders using [react-autosuggest](http://react-autosuggest.js.org/) and a `fuzzySearch` filter. Set the `choices` attribute to determine the options list (with `id`, `name` tuples). + +```jsx +import { AutocompleteInput } from 'react-admin'; + + +``` + +You can also customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes: + +```jsx +const choices = [ + { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, + { _id: 456, full_name: 'Jane Austen', sex: 'F' }, +]; + +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; + +``` + +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'M', name: 'myroot.gender.male' }, + { id: 'F', name: 'myroot.gender.female' }, +]; +``` + +However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to false. + +```jsx + +``` + +By default the component matches choices with the current input searchText: if it finds a match, this choice will be selected. For example, given the choices `[{ id: 'M', name: 'Male', id: 'F', name: 'Female' }]`, when the user enters the text `male`, then the component will set the input value to `M`. If you need to change how choices are matched, pass a custom function as `inputValueMatcher` prop. For example, given the choices: `[{id:1,iso2:'NL',name:'Dutch'},{id:2,iso2:'EN',name:'English'},{id:3,iso2:'FR',name:'French'}]`, if you want to match choices on the iso2 code, you can create the following `inputValueMatcher` function: + +```javascript + + input.toUpperCase().trim() === suggestion.iso2 || + input.toLowerCase().trim() === getOptionText(suggestion).toLowerCase().trim() +}/> +``` + +If you want to limit the initial choices shown to the current value only, you can set the `limitChoicesToValue` prop. + +Lastly, `` renders a meterial-ui `` component. Use the `options` attribute to override any of the `` attributes: + +{% raw %} +```jsx + +``` +{% endraw %} + +**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `` with [``](#referenceinput), and leave the `choices` empty: + +```jsx +import { AutocompleteInput, ReferenceInput } from 'react-admin' + + + + +``` + +**Tip**: `` is a stateless component, so it only allows to *filter* the list of choices, not to *extend* it. If you need to populate the list of choices based on the result from a `fetch` call (and if [``](#referenceinput) doesn't cover your need), you'll have to [write your own Input component](#writing-your-own-input-component) based on material-ui `` component. + +**Tip**: React-admin's `` has only a capital A, while material-ui's `` has a capital A and a capital C. Don't mix up the components! + +### Properties + +| Prop | Required | Type | Default | Description | +| ---|---|---|---|--- | +| `choices` | Required | `Object[]` | - | List of items to autosuggest | +| `resource` | Required | `string` | - | The resource working on. This field is passed down by wrapped components like `Create` and `Edit`. | +| `source` | Required | `string` | - | Name of field to edit, its type should match the type retrieved from `optionValue` | +| `allowEmpty` | Optional | `boolean` | `false` | If `false` and the searchText typed did not match any suggestion, the searchText will revert to the current value when the field is blurred. If `true` and the `searchText` is set to `''` then the field will set the input value to `null`. | +| `inputValueMatcher` | Optional | `Function` | `(input, suggestion, getOptionText) => input.toLowerCase().trim() === getOptionText(suggestion).toLowerCase().trim()` | Allows to define how choices are matched with the searchText while typing. | +| `optionValue` | Optional | `string` | `id` | Fieldname of record containing the value to use as input value | +| `optionText` | Optional | string | Function | `name` | Fieldname of record to display in the suggestion item or function which accepts the currect record as argument (`(record)=> {string}`) | +| `setFilter` | Optional | `Function` | null | A callback to inform the `searchText` has changed and new `choices` can be retrieved based on this `searchText`. Signature `searchText => void`. This function is automatically setup when using `ReferenceInput`. | +| `suggestionComponent` | Optional | Function | `({ suggestion, query, isHighlighted, props }) =>
    ` | Allows to override how the item is rendered. | + +## `BooleanInput` and `NullableBooleanInput` Component + +`` is a toggle button allowing you to attribute a `true` or `false` value to a record field. + +```jsx +import { BooleanInput } from 'react-admin'; + + +``` + + +![BooleanInput](https://marmelab.com/react-admin/img/boolean-input.png) + +This input does not handle `null` values. You would need the `` component if you have to handle non-set booleans. + +You can use the `options` prop to pass any option supported by the Material UI `Switch` components. For example, here's how to set a custom checked icon: + +{% raw %} +```jsx +import { BooleanInput } from 'react-admin'; +import FavoriteIcon from '@material-ui/icons/Favorite'; + +, + }} +/> +``` +{% endraw %} + +![CustomBooleanInputCheckIcon](https://marmelab.com/react-admin/img/custom-switch-icon.png) + + +Refer to [Material UI Switch documentation](http://www.material-ui.com/#/components/switch) for more details. + +`` renders as a dropdown list, allowing to choose between true, false, and null values. + +```jsx +import { NullableBooleanInput } from 'react-admin'; + + +``` + +![NullableBooleanInput](https://marmelab.com/react-admin/img/nullable-boolean-input.png) + +## `CheckboxGroupInput` Component + +If you want to let the user choose multiple values among a list of possible values by showing them all, `` is the right component. Set the `choices` attribute to determine the options (with `id`, `name` tuples): + +```jsx +import { CheckboxGroupInput } from 'react-admin'; + + +``` + +![CheckboxGroupInput](https://marmelab.com/react-admin/img/checkbox-group-input.png) + +You can also customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes: + +```jsx +const choices = [ + { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, + { _id: 456, full_name: 'Jane Austen', sex: 'F' }, +]; + +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; + +``` + +`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const FullNameField = ({ record }) => {record.first_name} {record.last_name}; +}/> +``` + +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'programming', name: 'myroot.category.programming' }, + { id: 'lifestyle', name: 'myroot.category.lifestyle' }, + { id: 'photography', name: 'myroot.category.photography' }, +]; +``` + +However, in some cases (e.g. inside a ``), you may not want the choice to be translated. In that case, set the `translateChoice` prop to false. + +```jsx + +``` + +Lastly, use the `options` attribute if you want to override any of Material UI's `` attributes: + +{% raw %} +```jsx + +``` +{% endraw %} + +Refer to [Material UI Checkbox documentation](http://www.material-ui.com/#/components/checkbox) for more details. + +## `DateInput` Component + +Ideal for editing dates, `` renders a standard browser [Date Picker](http://www.material-ui.com/#/components/date-picker). + +```jsx +import { DateInput } from 'react-admin'; + + +``` + +![DateInput](./img/date-input.gif) + +## `DisabledInput` Component + +When you want to display a record property in an `` form without letting users update it (such as for auto-incremented primary keys), use the ``: + +```jsx +import { DisabledInput } from 'react-admin'; + + +``` + +![DisabledInput](https://marmelab.com/react-admin/img/disabled-input.png) + +**Tip**: To add non-editable fields to the `` view, you can also use one of react-admin `Field` components: + +```jsx +// in src/posts.js +import { Edit, LongTextInput, SimpleForm, TextField } from 'react-admin'; + +export const PostEdit = (props) => ( + + + {/* NOT EDITABLE */} + + + +); +``` + +**Tip**: You can even use a component of your own, provided it accepts a `record` prop: + +```jsx +// in src/posts.js +import { Edit, Labeled, LongTextInput, SimpleForm } from 'react-admin'; + +const titleStyle = { textOverflow: 'ellipsis', overflow: 'hidden', maxWidth: '20em' }; +const Title = ({ record, label }) => ( + + {record.title} + +); + +export const PostEdit = (props) => ( + + + + <LongTextInput source="body" /> + </SimpleForm> + </Edit> +); +``` + +## `ImageInput` Component + +`<ImageInput>` allows to upload some pictures using [react-dropzone](https://github.com/okonet/react-dropzone). + +![ImageInput](https://marmelab.com/react-admin/img/image-input.png) + +Previews are enabled using `<ImageInput>` children, as following: + +```jsx +<ImageInput source="pictures" label="Related pictures" accept="image/*"> + <ImageField source="src" title="title" /> +</ImageInput> +``` + +Writing a custom field component for displaying the current value(s) is easy: it's a standard [field](./Fields.md#writing_your_own_field_component). + +When receiving **new** files, `ImageInput` will add a `rawFile` property to the object passed as the `record` prop of children. This `rawFile` is the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) instance of the newly added file. This can be useful to display informations about size or mimetype inside a custom field. + +The `ImageInput` component accepts all [react-dropzone properties](https://github.com/okonet/react-dropzone#features), in addition to those of react-admin. For instance, if you need to upload several images at once, just add the `multiple` DropZone attribute to your `<ImageInput />` field. + +If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` attribute to overwrite it. The attribute can be anything React can render (`PropTypes.node`): + +```jsx +<ImageInput source="pictures" label="Related pictures" accept="image/*" placeholder={<p>Drop your file here</p>}> + <ImageField source="src" title="title" /> +</ImageInput> +``` + +Note that the image upload returns a [File](https://developer.mozilla.org/en/docs/Web/API/File) object. It is your responsibility to handle it depending on your API behavior. You can for instance encode it in base64, or send it as a multi-part form data. Check [this example](./DataProviders.md#decorating-your-rest-client-example-of-file-upload) for base64 encoding data by extending the REST Client. + +## `FileInput` Component + +`<FileInput>` allows to upload some files using [react-dropzone](https://github.com/okonet/react-dropzone). + +![FileInput](https://marmelab.com/react-admin/img/file-input.png) + +Previews (actually a simple list of files names) are enabled using `<FileInput>` children, as following: + +```jsx +<FileInput source="files" label="Related files" accept="application/pdf"> + <FileField source="src" title="title" /> +</FileInput> +``` + +Writing a custom field component for displaying the current value(s) is easy: it's a standard [field](./Fields.md#writing_your_own_field_component). + +When receiving **new** files, `FileInput` will add a `rawFile` property to the object passed as the `record` prop of children. This `rawFile` is the [File](https://developer.mozilla.org/en-US/docs/Web/API/File) instance of the newly added file. This can be useful to display informations about size or mimetype inside a custom field. + +The `FileInput` component accepts all [react-dropzone properties](https://github.com/okonet/react-dropzone#features), in addition to those of react-admin. For instance, if you need to upload several files at once, just add the `multiple` DropZone attribute to your `<FileInput />` field. + +If the default Dropzone label doesn't fit with your need, you can pass a `placeholder` attribute to overwrite it. The attribute can be anything React can render (`PropTypes.node`): + +```jsx +<FileInput source="files" label="Related files" accept="application/pdf" placeholder={<p>Drop your file here</p>}> + <ImageField source="src" title="title" /> +</FileInput> +``` + +Note that the file upload returns a [File](https://developer.mozilla.org/en/docs/Web/API/File) object. It is your responsibility to handle it depending on your API behavior. You can for instance encode it in base64, or send it as a multi-part form data. Check [this example](./DataProviders.md#decorating-your-rest-client-example-of-file-upload) for base64 encoding data by extending the REST Client. + +## `LongTextInput` Component + +`<LongTextInput>` is the best choice for multiline text values. It renders as an auto expandable textarea. + +```jsx +import { LongTextInput } from 'react-admin'; + +<LongTextInput source="teaser" /> +``` + +![LongTextInput](https://marmelab.com/react-admin/img/long-text-input.png) + +## `NumberInput` Component + +`<NumberInput>` translates to a HTML `<input type="number">`. It is necessary for numeric values because of a [known React bug](https://github.com/facebook/react/issues/1425), which prevents using the more generic [`<TextInput>`](#textinput) in that case. + +```jsx +import { NumberInput } from 'react-admin'; + +<NumberInput source="nb_views" /> +``` + +You can customize the `step` props (which defaults to "any"): + +```jsx +<NumberInput source="nb_views" step={1} /> +``` + +## `RadioButtonGroupInput` Component + +If you want to let the user choose a value among a list of possible values by showing them all (instead of hiding them behind a dropdown list, as in [`<SelectInput>`](#selectinput)), `<RadioButtonGroupInput>` is the right component. Set the `choices` attribute to determine the options (with `id`, `name` tuples): + +```jsx +import { RadioButtonGroupInput } from 'react-admin'; + +<RadioButtonGroupInput source="category" choices={[ + { id: 'programming', name: 'Programming' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'photography', name: 'Photography' }, +]} /> +``` + +![RadioButtonGroupInput](https://marmelab.com/react-admin/img/radio-button-group-input.png) + +You can also customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes: + +```jsx +const choices = [ + { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, + { _id: 456, full_name: 'Jane Austen', sex: 'F' }, +]; +<RadioButtonGroupInput source="author_id" choices={choices} optionText="full_name" optionValue="_id" /> +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; +<RadioButtonGroupInput source="author_id" choices={choices} optionText={optionRenderer} /> +``` + +`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>; +<RadioButtonGroupInput source="gender" choices={choices} optionText={<FullNameField />}/> +``` + +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'M', name: 'myroot.gender.male' }, + { id: 'F', name: 'myroot.gender.female' }, +]; +``` + +However, in some cases (e.g. inside a `<ReferenceInput>`), you may not want the choice to be translated. In that case, set the `translateChoice` prop to false. + +```jsx +<RadioButtonGroupInput source="gender" choices={choices} translateChoice={false}/> +``` + +Lastly, use the `options` attribute if you want to override any of Material UI's `<RadioButtonGroup>` attributes: + +{% raw %} +```jsx +<RadioButtonGroupInput source="category" options={{ + labelPosition: 'right' +}} /> +``` +{% endraw %} + +Refer to [Material UI RadioGroup documentation](http://www.material-ui.com/#/components/radio-button) for more details. + +**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `<RadioButtonGroupInput>` with [`<ReferenceInput>`](#referenceinput), and leave the `choices` empty: + +```jsx +import { RadioButtonGroupInput, ReferenceInput } from 'react-admin' + +<ReferenceInput label="Author" source="author_id" reference="authors"> + <RadioButtonGroupInput optionText="last_name" /> +</ReferenceInput> +``` + +## `ReferenceArrayInput` Component + +Use `<ReferenceArrayInput>` to edit an array of reference values, i.e. to let users choose a list of values (usually foreign keys) from another REST endpoint. + +`<ReferenceArrayInput>` fetches the related resources (using the `CRUD_GET_MANY` REST method) as well as possible resources (using the +`CRUD_GET_MATCHING` REST method) in the reference endpoint. + +For instance, if the post object has many tags, a post resource may look like: + +```js +{ + id: 1234, + tag_ids: [1, 23, 4] +} +``` + +Then `<ReferenceArrayInput>` would fetch a list of tag resources from these two calls: + +``` +http://myapi.com/tags?id=[1,23,4] +http://myapi.com/tags?page=1&perPage=25 +``` + +Once it receives the deduplicated reference resources, this component delegates rendering to a subcomponent, to which it passes the possible choices as the `choices` attribute. + +This means you can use `<ReferenceArrayInput>` with [`<SelectArrayInput>`](#selectarrayinput), or with the component of your choice, provided it supports the `choices` attribute. + +The component expects a `source` and a `reference` attributes. For instance, to make the `tag_ids` for a `post` editable: + +```js +import { ReferenceArrayInput, SelectArrayInput } from 'react-admin' + +<ReferenceArrayInput source="tag_ids" reference="tags"> + <SelectArrayInput optionText="name" /> +</ReferenceArrayInput> +``` + +![SelectArrayInput](https://marmelab.com/react-admin/img/select-array-input.gif) + +**Note**: You **must** add a `<Resource>` for the reference resource - react-admin needs it to fetch the reference data. You can omit the list prop in this reference if you want to hide it in the sidebar menu. + +```js +<Admin dataProvider={myDataProvider}> + <Resource name="posts" list={PostList} edit={PostEdit} /> + <Resource name="tags" /> +</Admin> +``` + +Set the `allowEmpty` prop when you want to add an empty choice with a value of null in the choices list. +Disabling `allowEmpty` does not mean that the input will be required. If you want to make the input required, you must add a validator as indicated in [Validation Documentation](./CreateEdit.html#validation). Enabling the `allowEmpty` props just adds an empty choice (with `null` value) on top of the options, and makes the value nullable. + +```js +import { ReferenceArrayInput, SelectArrayInput } from 'react-admin' + +<ReferenceArrayInput source="tag_ids" reference="tags" allowEmpty> + <SelectArrayInput optionText="name" /> +</ReferenceArrayInput> +``` + +**Tip**: `allowEmpty` is set by default for all Input components children of the `<Filter>` component + +You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props. + +{% raw %} +```js +// by default, fetches only the first 25 values. You can extend this limit +// by setting the `perPage` prop. +<ReferenceArrayInput + source="tag_ids" + reference="tags" + perPage={100}> + <SelectArrayInput optionText="name" /> +</ReferenceArrayInput> + +// by default, orders the possible values by id desc. You can change this order +// by setting the `sort` prop (an object with `field` and `order` properties). +<ReferenceArrayInput + source="tag_ids" + reference="tags" + sort={{ field: 'title', order: 'ASC' }}> + <SelectArrayInput optionText="name" /> +</ReferenceArrayInput> + +// you can filter the query used to populate the possible values. Use the +// `filter` prop for that. +<ReferenceArrayInput + source="tag_ids" + reference="tags" + filter={{ is_published: true }}> + <SelectArrayInput optionText="name" /> +</ReferenceArrayInput> +``` +{% endraw %} + +## `ReferenceInput` Component + +Use `<ReferenceInput>` for foreign-key values, for instance, to edit the `post_id` of a `comment` resource. This component fetches the possible values in the reference resource (using the `GET_LIST` REST method) and the referenced record (using the `GET_ONE` REST method), then delegates rendering to a subcomponent, to which it passes the possible choices as the `choices` attribute. + +This means you can use `<ReferenceInput>` with any of [`<SelectInput>`](#selectinput), [`<AutocompleteInput>`](#autocompleteinput), or [`<RadioButtonGroupInput>`](#radiobuttongroupinput), or even with the component of your choice, provided it supports the `choices` attribute. + +The component expects a `source` and a `reference` attributes. For instance, to make the `post_id` for a `comment` editable: + +```jsx +import { ReferenceInput, SelectInput } from 'react-admin' + +<ReferenceInput label="Post" source="post_id" reference="posts"> + <SelectInput optionText="title" /> +</ReferenceInput> +``` + +![ReferenceInput](https://marmelab.com/react-admin/img/reference-input.gif) + +**Note**: You **must** add a `<Resource>` for the reference resource - react-admin needs it to fetch the reference data. You *can* omit the `list` prop in this reference if you want to hide it in the sidebar menu. + +```jsx +<Admin dataProvider={myDataProvider}> + <Resource name="comments" list={CommentList} /> + <Resource name="posts" /> +</Admin> +``` + +Set the `allowEmpty` prop when you want to add an empty choice with a value of null in the choices list. +Disabling `allowEmpty` does not mean that the input will be required. If you want to make the input required, you must add a validator as indicated in [Validation Documentation](./CreateEdit.html#validation). Enabling the `allowEmpty` props just adds an empty choice (with `null` value) on top of the options, and makes the value nullable. + +```jsx +import { ReferenceInput, SelectInput } from 'react-admin' + +<ReferenceInput label="Post" source="post_id" reference="posts" allowEmpty> + <SelectInput optionText="title" /> +</ReferenceInput> +``` + +**Tip**: `allowEmpty` is set by default for all Input components children of the `<Filter>` component: + +```jsx +const CommentFilter = (props) => ( + <Filter {...props}> + <ReferenceInput label="Post" source="post_id" reference="posts"> // no need for allowEmpty + <SelectInput optionText="title" /> + </ReferenceInput> + </Filter> +); +``` + +You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props. + +{% raw %} +```jsx +// by default, fetches only the first 25 values. You can extend this limit +// by setting the `perPage` prop. +<ReferenceInput + source="post_id" + reference="posts" + perPage={100}> + <SelectInput optionText="title" /> +</ReferenceInput> + +// by default, orders the possible values by id desc. You can change this order +// by setting the `sort` prop (an object with `field` and `order` properties). +<ReferenceInput + source="post_id" + reference="posts" + sort={{ field: 'title', order: 'ASC' }}> + <SelectInput optionText="title" /> +</ReferenceInput> + +// you can filter the query used to populate the possible values. Use the +// `filter` prop for that. +<ReferenceInput + source="post_id" + reference="posts" + filter={{ is_published: true }}> + <SelectInput optionText="title" /> +</ReferenceInput> +``` +{% endraw %} + +The child component may further filter results (that's the case, for instance, for `<AutocompleteInput>`). ReferenceInput passes a `setFilter` function as prop to its child component. It uses the value to create a filter for the query - by default `{ q: [searchText] }`. You can customize the mapping +`searchText => searchQuery` by setting a custom `filterToQuery` function prop: + +```jsx +<ReferenceInput + source="post_id" + reference="posts" + filterToQuery={searchText => ({ title: searchText })}> + <SelectInput optionText="title" /> +</ReferenceInput> +``` + +The child component receives the following props from `<ReferenceInput>`: + +- `isLoading`: whether the request for possible values is loading or not +- `filter`: the current filter of the request for possible values. Defaults to `{}`. +- `pagination`: the current pagination of the request for possible values. Defaults to `{ page: 1, perPage: 25 }`. +- `sort`: the current sorting of the request for possible values. Defaults to `{ field: 'id', order: 'DESC' }`. +- `error`: the error message if the form validation failed for that input +- `warning`: the warning message if the form validation failed for that input +- `onChange`: function to call when the value changes +- `setFilter`: function to call to update the filter of the request for possible values +- `setPagination`: : function to call to update the pagination of the request for possible values +- `setSort`: function to call to update the sorting of the request for possible values + +## `RichTextInput` Component + +`<RichTextInput>` is the ideal component if you want to allow your users to edit some HTML contents. It +is powered by [Quill](https://quilljs.com/). + +**Note**: Due to its size, `<RichTextInput>` is not bundled by default with react-admin. You must install it first, using npm: + +```sh +npm install ra-input-rich-text +``` + +Then use it as a normal input component: + +```jsx +import RichTextInput from 'ra-input-rich-text'; + +<RichTextInput source="body" /> +``` + +![RichTextInput](https://marmelab.com/react-admin/img/rich-text-input.png) + +You can customize the rich text editor toolbar using the `toolbar` attribute, as described on the [Quill official toolbar documentation](https://quilljs.com/docs/modules/toolbar/). + +```jsx +<RichTextInput source="body" toolbar={[ ['bold', 'italic', 'underline', 'link'] ]} /> +``` + +## `SelectInput` Component + +To let users choose a value in a list using a dropdown, use `<SelectInput>`. It renders using [Material ui's `<SelectField>`](http://www.material-ui.com/#/components/select-field). Set the `choices` attribute to determine the options (with `id`, `name` tuples): + +```jsx +import { SelectInput } from 'react-admin'; + +<SelectInput source="category" choices={[ + { id: 'programming', name: 'Programming' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'photography', name: 'Photography' }, +]} /> +``` + +![SelectInput](https://marmelab.com/react-admin/img/select-input.gif) + +You can also customize the properties to use for the option name and value, thanks to the `optionText` and `optionValue` attributes: + +```jsx +const choices = [ + { _id: 123, full_name: 'Leo Tolstoi', sex: 'M' }, + { _id: 456, full_name: 'Jane Austen', sex: 'F' }, +]; +<SelectInput source="author_id" choices={choices} optionText="full_name" optionValue="_id" /> +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const optionRenderer = choice => `${choice.first_name} ${choice.last_name}`; +<SelectInput source="author_id" choices={choices} optionText={optionRenderer} /> +``` + +`optionText` also accepts a React Element, that will be cloned and receive the related choice as the `record` prop. You can use Field components there. + +```jsx +const choices = [ + { id: 123, first_name: 'Leo', last_name: 'Tolstoi' }, + { id: 456, first_name: 'Jane', last_name: 'Austen' }, +]; +const FullNameField = ({ record }) => <span>{record.first_name} {record.last_name}</span>; +<SelectInput source="gender" choices={choices} optionText={<FullNameField />}/> +``` + +Enabling the `allowEmpty` props adds an empty choice (with `null` value) on top of the options, and makes the value nullable: + +```jsx +<SelectInput source="category" allowEmpty choices={[ + { id: 'programming', name: 'Programming' }, + { id: 'lifestyle', name: 'Lifestyle' }, + { id: 'photography', name: 'Photography' }, +]} /> +``` + +The choices are translated by default, so you can use translation identifiers as choices: + +```jsx +const choices = [ + { id: 'M', name: 'myroot.gender.male' }, + { id: 'F', name: 'myroot.gender.female' }, +]; +``` + +However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to false. + +```jsx +<SelectInput source="gender" choices={choices} translateChoice={false}/> +``` + +Note that `translateChoice` is set to false when `<SelectInput>` is a child of `<ReferenceInput>`. + +Lastly, use the `options` attribute if you want to override any of Material UI's `<SelectField>` attributes: + +{% raw %} +```jsx +<SelectInput source="category" options={{ + maxHeight: 200 +}} /> +``` +{% endraw %} + +Refer to [Material UI SelectField documentation](http://www.material-ui.com/#/components/select-field) for more details. + +**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `<SelectInput>` with [`<ReferenceInput>`](#referenceinput), and leave the `choices` empty: + +```jsx +import { SelectInput, ReferenceInput } from 'react-admin' + +<ReferenceInput label="Author" source="author_id" reference="authors"> + <SelectInput optionText="last_name" /> +</ReferenceInput> +``` + +If, instead of showing choices as a dropdown list, you prefer to display them as a list of radio buttons, try the [`<RadioButtonGroupInput>`](#radiobuttongroupinput). And if the list is too big, prefer the [`<AutocompleteInput>`](#autocompleteinput). + +## `SelectArrayInput` Component + +To let users choose several values in a list using a dropdown, use `<SelectArrayInput>`. It renders using [Material ui's `<Select>`](http://www.material-ui.com/#/components/select). Set the `choices` attribute to determine the options (with `id`, `name` tuples): + +```js +import { SelectArrayInput } from 'react-admin'; + +<SelectArrayInput label="Tags" source="categories" choices={[ + { id: 'music', name: 'Music' }, + { id: 'photography', name: 'Photo' }, + { id: 'programming', name: 'Code' }, + { id: 'tech', name: 'Technology' }, + { id: 'sport', name: 'Sport' }, +]} /> +``` + +![SelectArrayInput](https://marmelab.com/react-admin/img/select-array-input.gif) + +You can also customize the properties to use for the option name and value, +thanks to the `optionText` and `optionValue` attributes. + +```js +const choices = [ + { _id: '1', name: 'Book', plural_name: 'Books' }, + { _id: '2', name: 'Video', plural_name: 'Videos' }, + { _id: '3', name: 'Audio', plural_name: 'Audios' }, +]; +<SelectArrayInput source="categories" choices={choices} optionText="plural_name" optionValue="_id" /> +``` + +`optionText` also accepts a function, so you can shape the option text at will: + +```js +const choices = [ + { id: '1', name: 'Book', quantity: 23 }, + { id: '2', name: 'Video', quantity: 56 }, + { id: '3', name: 'Audio', quantity: 12 }, +]; +const optionRenderer = choice => `${choice.name} (${choice.quantity})`; +<SelectArrayInput source="categories" choices={choices} optionText={optionRenderer} /> +``` + +The choices are translated by default, so you can use translation identifiers as choices: + +```js +const choices = [ + { id: 'books', name: 'myroot.category.books' }, + { id: 'sport', name: 'myroot.category.sport' }, +]; +``` + +Lastly, use the `options` attribute if you want to override any of the `<Select>` attributes: + +{% raw %} +```js +<SelectArrayInput source="category" options={{ fullWidth: true }} /> +``` +{% endraw %} + +Refer to [the Select documentation](http://www.material-ui.com/#/components/select) for more details. + +The `SelectArrayInput` component **cannot** be used inside a `ReferenceInput` but can be used inside a `ReferenceArrayInput`. + +```jsx +import React from 'react'; +import { + ChipField, + Create, + DateInput, + LongTextInput, + ReferenceArrayInput, + SelectArrayInput, + TextInput, +} from 'react-admin'; + +export const PostCreate = props => ( + <Create {...props}> + <SimpleForm> + <TextInput source="title" /> + <LongTextInput source="body" /> + <DateInput source="published_at" /> + + <ReferenceArrayInput reference="tags" source="tags"> + <SelectArrayInput> + <ChipField source="name" /> + </SelectArrayInput> + </ReferenceArrayInput> + </SimpleForm> + </Create> +); +``` + +**Tip**: As it does not provide autocompletion, the `SelectArrayInput` might not be suited when the referenced resource has a lot of items. + +## `TextInput` Component + +`<TextInput>` is the most common input. It is used for texts, emails, URL or passwords. In translates to an HTML `<input>` tag. + +```jsx +import { TextInput } from 'react-admin'; + +<TextInput source="title" /> +``` + +![TextInput](https://marmelab.com/react-admin/img/text-input.png) + +You can choose a specific input type using the `type` attribute, for instance `text` (the default), `email`, `url`, or `password`: + +```jsx +<TextInput label="Email Address" source="email" type="email" /> +``` + +**Warning**: Do not use `type="number"`, or you'll receive a string as value (this is a [known React bug](https://github.com/facebook/react/issues/1425)). Instead, use [`<NumberInput>`](#numberinput). + +## Transforming Input Value to/from Record + +The data format returned by the input component may not be what your API desires. Since React-admin uses Redux Form, we can use its `parse()` and `format()` functions to transform the input value when saving to and loading from the record. It's better to understand the [input value's lifecycle](http://redux-form.com/6.5.0/docs/ValueLifecycle.md/) before you start. + +Mnemonic for the two functions: +- `parse()`: input -> record +- `format()`: record -> input + +Say the user would like to input values of 0-100 to a percentage field but your API (hence record) expects 0-1.0. You can use simple `parse()` and `format()` functions to archive the transform: + +```jsx +<NumberInput source="percent" format={v => v*100} parse={v => v/100} label="Formatted number" /> +``` + +`<DateInput>` stores and returns a string. If you would like to store a JavaScript Date object in your record instead: + +```jsx +const dateFormatter = v => { + // v is a `Date` object + if (!(v instanceof Date) || isNaN(v)) return; + const pad = '00'; + const yy = v.getFullYear().toString(); + const mm = (v.getMonth() + 1).toString(); + const dd = v.getDate().toString(); + return `${yy}-${(pad + mm).slice(-2)}-${(pad + dd).slice(-2)}`; +}; + +const dateParser = v => { + // v is a string of "YYYY-MM-DD" format + const match = /(\d{4})-(\d{2})-(\d{2})/.exec(v); + if (match === null) return; + const d = new Date(match[1], parseInt(match[2], 10) - 1, match[3]); + if (isNaN(d)) return; + return d; +}; + +<DateInput source="isodate" format={dateFormatter} parse={dateParser} /> +``` + +## Third-Party Components + +You can find components for react-admin in third-party repositories. + +* [vascofg/react-admin-color-input](https://github.com/vascofg/react-admin-color-input): a color input using [React Color](http://casesandberg.github.io/react-color/), a collection of color pickers. +* [LoicMahieu/aor-tinymce-input](https://github.com/LoicMahieu/aor-tinymce-input): a TinyMCE component, useful for editing HTML +* [vascofg/react-admin-date-inputs](https://github.com/vascofg/react-admin-date-inputs): a collection of Date Inputs, based on [material-ui-pickers](https://material-ui-pickers.firebaseapp.com/) + +## Writing Your Own Input Component + +If you need a more specific input type, you can also write it yourself. You'll have to rely on redux-form's [`<Field>`](http://redux-form.com/6.5.0/docs/api/Field.md/) component, so as to handle the value update cycle. + +For instance, let's write a component to edit the latitude and longitude of the current record: + +```jsx +// in LatLongInput.js +import { Field } from 'redux-form'; +const LatLngInput = () => ( + <span> + <Field name="lat" component="input" type="number" placeholder="latitude" /> +   + <Field name="lng" component="input" type="number" placeholder="longitude" /> + </span> +); +export default LatLngInput; + +// in ItemEdit.js +const ItemEdit = (props) => ( + <Edit {...props}> + <SimpleForm> + <LatLngInput /> + </SimpleForm> + </Edit> +); +``` + +`LatLngInput` takes no props, because the `<Field>` component can access the current record via its context. The `name` prop serves as a selector for the record property to edit. All `Field` props except `name` and `component` are passed to the child component/element (an `<input>` in that example). Executing this component will render roughly the following code: + +```html +<span> + <input type="number" placeholder="longitude" value={record.lat} /> + <input type="number" placeholder="longitude" value={record.lng} /> +</span> +``` + +**Tip**: The `<Field>` component supports dot notation in the `name` prop, to edit nested props: + +```jsx +const LatLongInput = () => ( + <span> + <Field name="position.lat" component="input" type="number" placeholder="latitude" /> +   + <Field name="position.lng" component="input" type="number" placeholder="longitude" /> + </span> +); +``` + +This component lacks a label. React-admin provides the `<Labeled>` component for that: + +```jsx +// in LatLongInput.js +import { Field } from 'redux-form'; +import { Labeled } from 'react-admin'; + +const LatLngInput = () => ( + <Labeled label="position"> + <span> + <Field name="lat" component="input" type="number" placeholder="latitude" /> +   + <Field name="lng" component="input" type="number" placeholder="longitude" /> + </span> + </Labeled> +); +export default LatLngInput; +``` + +Now the component will render with a label: + +```html +<label>Position</label> +<span> + <input type="number" placeholder="longitude" value={record.lat} /> + <input type="number" placeholder="longitude" value={record.lng} /> +</span> +``` + +Instead of HTML `input` elements, you can use a material-ui component. To compose material-ui and `Field`, use a [field renderer function](http://redux-form.com/6.5.0/examples/material-ui/) to map the props: + +```jsx +// in LatLongInput.js +import TextField from '@material-ui/core/TextField'; +import { Field } from 'redux-form'; +const renderTextField = ({ input, label, meta: { touched, error }, ...custom }) => ( + <TextField + hintText={label} + floatingLabelText={label} + errorText={touched && error} + {...input} + {...custom} + /> +); +const LatLngInput = () => ( + <span> + <Field name="lat" component={renderTextField} label="latitude" /> +   + <Field name="lng" component={renderTextField} label="longitude" /> + </span> +); +``` + +Material-ui's `<TextField>` component already includes a label, so you don't need to use `<Labeled>` in this case. `<Field>` injects two props to its child component: `input` and `meta`. To learn more about these props, please refer to [the `<Field>` component documentation](http://redux-form.com/6.5.0/docs/api/Field.md/#props) in the redux-form website. + +**Tip**: If you only need one `<Field>` component in a custom input, you can let react-admin do the `<Field>` decoration for you by using the `addField` Higher-order component: + +```jsx +// in SexInput.js +import SelectField from '@material-ui/core/SelectField'; +import MenuItem from '@material-ui/core/MenuItem'; +import { addField } from 'react-admin'; + +const SexInput = ({ input, meta: { touched, error } }) => ( + <SelectField + floatingLabelText="Sex" + errorText={touched && error} + {...input} + > + <MenuItem value="M" primaryText="Male" /> + <MenuItem value="F" primaryText="Female" /> + </SelectField> +); +export default addField(SexInput); // decorate with redux-form's <Field> + +// equivalent of +import SelectField from '@material-ui/core/SelectField'; +import MenuItem from '@material-ui/core/MenuItem'; +import { Field } from 'redux-form'; + +const renderSexInput = ({ input, meta: { touched, error } }) => ( + <SelectField + floatingLabelText="Sex" + errorText={touched && error} + {...input} + > + <MenuItem value="M" primaryText="Male" /> + <MenuItem value="F" primaryText="Female" /> + </SelectField> +); +const SexInput = ({ source }) => <Field name={source} component={renderSexInput} /> +export default SexInput; +``` + +For more details on how to use redux-form's `<Field>` component, please refer to [the redux-form doc](http://redux-form.com/6.5.0/docs/api/Field.md/). + +Instead of HTML `input` elements or material-ui components, you can use react-admin input components, like `<NumberInput>` for instance. React-admin components are already decorated by `<Field>`, and already include a label, so you don't need either `<Field>` or `<Labeled>` when using them: + +```jsx +// in LatLongInput.js +import { NumberInput } from 'react-admin'; +const LatLngInput = () => ( + <span> + <NumberInput source="lat" label="latitude" /> +   + <NumberInput source="lng" label="longitude" /> + </span> +); +export default LatLngInput; + +// in ItemEdit.js +const ItemEdit = (props) => ( + <Edit {...props}> + <SimpleForm> + <DisabledInput source="id" /> + <LatLngInput /> + </SimpleForm> + </Edit> +); +``` + +## Linking Two Inputs + +Edition forms often contain linked inputs, e.g. country and city (the choices of the latter depending on the value of the former). + +React-admin relies on redux-form, so you can grab the current form values using redux-form [formValueSelector()](https://redux-form.com/7.3.0/docs/api/formvalueselector.md/). Alternatively, you can use the react-admin `<FormDataConsumer>` component, which grabs the form values, and passes them to a child function. + +This facilitates the implementation of linked inputs: + +```jsx +import { FormDataConsumer } from 'react-admin'; + +const OrderEdit = (props) => ( + <Edit {...props}> + <SimpleForm> + <SelectInput source="country" choices={countries} /> + <FormDataConsumer> + {({ formData, ...rest }) => + <SelectInput + source="city" + choices={getCitiesFor(formData.country)} + {...rest} + /> + } + </FormDataConsumer> + </SimpleForm> + </Edit> +); +``` + +## Hiding Inputs Based On Other Inputs + +You may want to display or hide inputs base on the value of another input - for instance, show an `email` input only if the `hasEmail` boolean input is ticked to `true`. + +For such cases, you can use the approach described above, using the `<FormDataConsumer>` component. + +```jsx +import { FormDataConsumer } from 'react-admin'; + + const PostEdit = (props) => ( + <Edit {...props}> + <SimpleForm> + <BooleanInput source="hasEmail" /> + <FormDataConsumer> + {({ formData, ...rest }) => formData.hasEmail && + <TextInput source="email" {...rest} /> + } + </FormDataConsumer> + </SimpleForm> + </Edit> + ); +``` diff --git a/docs/intro.md b/docs/intro.md new file mode 100644 index 0000000..f207042 --- /dev/null +++ b/docs/intro.md @@ -0,0 +1,188 @@ +--- +id: intro +title: Introduction +--- + +A frontend Framework for building admin applications running in the browser on top of REST/GraphQL APIs, using ES6, [React](https://facebook.github.io/react/) and [Material Design](https://material.io/). Previously named [admin-on-rest](https://github.com/marmelab/admin-on-rest). Open sourced and maintained by [marmelab](https://marmelab.com/). + +[Demo](https://marmelab.com/react-admin-demo/) - [Source](https://github.com/marmelab/react-admin) - [Releases](https://github.com/marmelab/react-admin/releases) - [Support](http://stackoverflow.com/questions/tagged/react-admin) + +<iframe src="https://player.vimeo.com/video/268958716?byline=0&portrait=0" width="640" height="360" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen style="display:block;margin:0 auto"></iframe> + +## Features + +* Adapts to any backend (REST, GraphQL, SOAP, etc.) +* Complete documentation +* Super-fast UI thanks to optimistic rendering (renders before the server returns) +* Undo updates and deletes for a few seconds +* Supports relationships (many to one, one to many) +* Internationalization (i18n) +* Conditional formatting +* Themeable +* Supports any authentication provider (REST API, OAuth, Basic Auth, ...) +* Full-featured Datagrid (sort, pagination, filters) +* Filter-as-you-type +* Supports any form layout (simple, tabbed, etc.) +* Data Validation +* Custom actions +* Large library of components for various data types: boolean, number, rich text, etc. +* WYSIWYG editor +* Customize dashboard, menu, layout +* Super easy to extend and override (it's just React components) +* Highly customizable interface +* Can connect to multiple backends +* Leverages the best libraries in the React ecosystem (Redux, redux-form, redux-saga, material-ui, recompose) +* Can be included in another React app +* Inspired by the popular [ng-admin](https://github.com/marmelab/ng-admin) library (also by marmelab) + +## Installation + +React-admin is available from npm. You can install it (and its required dependencies) +using: + +```sh +npm install react-admin +``` + +## Usage + +Read the [Tutorial](./Tutorial.md) for a 15 minutes introduction. After that, head to the [Documentation](./index.md), or checkout the [source code of the demo](https://github.com/marmelab/react-admin/tree/master/examples/demo) for an example usage. + +## At a Glance + +```jsx +// in app.js +import React from 'react'; +import { render } from 'react-dom'; +import { Admin, Resource } from 'react-admin'; +import simpleRestProvider from 'ra-data-simple-rest'; + +import { PostList, PostEdit, PostCreate, PostIcon } from './posts'; + +render( + <Admin dataProvider={simpleRestProvider('http://localhost:3000')}> + <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} icon={PostIcon}/> + </Admin>, + document.getElementById('root') +); +``` + +The `<Resource>` component is a configuration component that allows to define sub components for each of the admin view: `list`, `edit`, and `create`. These components use Material UI and custom components from react-admin: + +```jsx +// in posts.js +import React from 'react'; +import { List, Datagrid, Edit, Create, SimpleForm, DateField, TextField, EditButton, DisabledInput, TextInput, LongTextInput, DateInput } from 'react-admin'; +import BookIcon from '@material-ui/icons/Book'; +export const PostIcon = BookIcon; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="title" /> + <DateField source="published_at" /> + <TextField source="average_note" /> + <TextField source="views" /> + <EditButton basePath="/posts" /> + </Datagrid> + </List> +); + +const PostTitle = ({ record }) => { + return <span>Post {record ? `"${record.title}"` : ''}</span>; +}; + +export const PostEdit = (props) => ( + <Edit title={<PostTitle />} {...props}> + <SimpleForm> + <DisabledInput source="id" /> + <TextInput source="title" /> + <TextInput source="teaser" options={{ multiLine: true }} /> + <LongTextInput source="body" /> + <DateInput label="Publication date" source="published_at" /> + <TextInput source="average_note" /> + <DisabledInput label="Nb views" source="views" /> + </SimpleForm> + </Edit> +); + +export const PostCreate = (props) => ( + <Create title="Create a Post" {...props}> + <SimpleForm> + <TextInput source="title" /> + <TextInput source="teaser" options={{ multiLine: true }} /> + <LongTextInput source="body" /> + <TextInput label="Publication date" source="published_at" /> + <TextInput source="average_note" /> + </SimpleForm> + </Create> +); +``` + +## Does It Work With My API? + +Yes. + +React-admin uses an adapter approach, with a concept called *Data Providers*. Existing providers can be used as a blueprint to design your API, or you can write your own Data Provider to query an existing API. Writing a custom Data Provider is a matter of hours. + +![Data Provider architecture](https://marmelab.com/react-admin/img/data-provider.png) + +See the [Data Providers documentation](./DataProviders.md) for details. + +## Batteries Included But Removable + +React-admin is designed as a library of loosely coupled React components built on top of [material-ui](http://www.material-ui.com/#/), in addition to controller functions implemented the Redux way. It is very easy to replace one part of react-admin with your own, e.g. to use a custom datagrid, GraphQL instead of REST, or bootstrap instead of Material Design. + +## Contributing + +Pull requests are welcome on the [GitHub repository](https://github.com/marmelab/react-admin). Try to follow the coding style of the existing files, and include unit tests and documentation. Be prepared for a thorough code review, and be patient for the merge - this is an open-source initiative. + +You can run the example app by calling: + +```sh +make run +``` + +And then browse to [http://localhost:8080/](http://localhost:8080/). + +If you want to contribute to the documentation, install jekyll, then call + +```sh +make doc +``` + +And then browse to [http://localhost:4000/](http://localhost:4000/) + +You can run the unit tests by calling + +```sh +make test +``` + +If you are using react-admin as a dependency, and if you want to try and hack it, here is the advised process: + +```sh +# in myapp +# install react-admin from GitHub in another directory +$ cd .. +$ git clone git@github.com:marmelab/react-admin.git && cd react-admin && make install +# replace your node_modules/react-admin by a symbolic link to the github checkout +$ cd ../myapp +$ npm link ../react-admin +# go back to the checkout, and replace the version of react by the one in your app +$ cd ../react-admin +$ npm link ../myapp/node_modules/react +$ make watch +# in another terminal, go back to your app, and start it as usual +$ cd ../myapp +$ npm run +``` + +## License + +React-admin is licensed under the [MIT Licence](https://github.com/marmelab/react-admin/blob/master/LICENSE.md), sponsored and supported by [marmelab](http://marmelab.com). + +## Donate + +This library is free to use, even for commercial purpose. If you want to give back, please talk about it, help newcomers, or contribute code. But the best way to give back is to **donate to a charity**. We recommend [Doctors Without Borders](http://www.doctorswithoutborders.org/). diff --git a/docs/list-view-component.md b/docs/list-view-component.md new file mode 100644 index 0000000..ba739ac --- /dev/null +++ b/docs/list-view-component.md @@ -0,0 +1,796 @@ +--- +layout: list-view-component +title: <List> View +--- + +The List view displays a list of records fetched from the REST API. The entry point for this view is the `<List>` component, which takes care of fetching the data. Then, it passes the data to an iterator view - usually `<Datagrid>`, which then delegates the rendering of each record property to [`<Field>`](./Fields.html) components. + +![The List View](https://marmelab.com/react-admin/img/list-view.png) + +## The `List` Component + +The `<List>` component renders the list layout (title, buttons, filters, pagination), and fetches the list of records from the REST API. It then delegates the rendering of the list of records to its child component. Usually, it's a `<Datagrid>`, responsible for displaying a table with one row for each post. + +**Tip**: In Redux terms, `<List>` is a connected component, and `<Datagrid>` is a dumb component. + +Here are all the props accepted by the `<List>` component: + +* [`title`](#page-title) +* [`actions`](#actions) +* [`bulkActions`](#bulk-actions) +* [`filters`](#filters) (a React element used to display the filter form) +* [`perPage`](#records-per-page) +* [`sort`](#default-sort-field) +* [`filter`](#permanent-filter) (the permanent filter used in the REST request) +* [`filterDefaultValues`](#filter-default-values) (the default values for `alwaysOn` filters) +* [`pagination`](#pagination) + +Here is the minimal code necessary to display a list of posts: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostList } from './posts'; + +const App = () => ( + <Admin dataProvider={jsonServerProvider('http://jsonplaceholder.typicode.com')}> + <Resource name="posts" list={PostList} /> + </Admin> +); + +export default App; + +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="title" /> + <TextField source="body" /> + </Datagrid> + </List> +); +``` + +That's enough to display the post list: + +![Simple posts list](https://marmelab.com/react-admin/img/simple-post-list.png) + +### Page Title + +The default title for a list view is "[resource] list" (e.g. "Posts list"). Use the `title` prop to customize the List view title: + +```jsx +// in src/posts.js +export const PostList = (props) => ( + <List {...props} title="List of posts"> + ... + </List> +); +``` + +The title can be either a string, or an element of your own. + +### Actions + +You can replace the list of default actions by your own element using the `actions` prop: + +```jsx +import Button from '@material-ui/core/Button'; +import { CardActions, CreateButton, RefreshButton } from 'react-admin'; + +const PostActions = ({ resource, filters, displayedFilters, filterValues, basePath, showFilter }) => ( + <CardActions> + {filters && React.cloneElement(filters, { + resource, + showFilter, + displayedFilters, + filterValues, + context: 'button', + }) } + <CreateButton basePath={basePath} /> + <RefreshButton /> + {/* Add your custom actions */} + <Button primary onClick={customAction}>Custom Action</Button> + </CardActions> +); + +export const PostList = (props) => ( + <List {...props} actions={<PostActions />}> + ... + </List> +); +``` + +### Bulk Actions + +Bulk actions are actions that affect several records at once, like mass deletion for instance. In the `<Datagrid>` component, bulk actions are triggered by ticking the checkboxes in the first column of the table, then choosing an action from the bulk action menu. By default, all list views have a single bulk action, the bulk delete action. You can add other bulk actions by passing a custom element as the `bulkActions` prop of the `<List>` component: + +```jsx +import Button from '@material-ui/core/Button'; +import { BulkActions, BulkDeleteAction } from 'react-admin'; +import ResetViewsAction from './ResetViewsAction'; + +const PostBulkActions = props => ( + <BulkActions {...props}> + <ResetViewsAction label="Reset Views" /> + {/* Add the default bulk delete action */} + <BulkDeleteAction /> + </BulkActions> +); + +export const PostList = (props) => ( + <List {...props} bulkActions={<PostBulkActions />}> + ... + </List> +); +``` + +**Tip**: You can also disable bulk actions altogether by passing `false` to the `bulkActions` prop. When using a `Datagrid` inside a `List` with disabled bulk actions, the checkboxes column won't be added. + +React-admin uses the `label` prop of the bulk action components to display the bulk action menu items. + +Bulk action components are regular React component that gets mounted when the related menu item is clicked. The component receives several props allowing it to perform its job: + +* `resource`: the currently displayed resource (eg `posts`, `comments`, etc.) +* `basePath`: the current router base path for the resource (eg `/posts`, `/comments`, etc.) +* `filterValues`: the filter values. This can be useful if you want to apply your action on all items matching the filter. +* `selectedIds`: the identifiers of the currently selected items. +* `onExit`: an event handler you should call when the bulk action ends. + +Here is an example leveraging the `UPDATE_MANY` crud action, which will set the `views` property of all posts to `0`: + +```jsx +// in ./ResetViewsAction.js +import { Component } from 'react'; +import { connect } from 'react-redux'; +import { crudUpdateMany } from 'react-admin'; + +class ResetViewsAction extends Component { + componentDidMount = () => { + const { + resource, + basePath, + selectedIds, + onExit, + crudUpdateMany, + } = this.props; + + crudUpdateMany(resource, selectedIds, { views: 0 }, basePath); + onExit(); + }; + + render() { + return null; + } +} + +export default connect(undefined, { crudUpdateMany })(ResetViewsAction); +``` + +This component renders nothing - it just dispatches an action when mounted. Once finished, it also calls the `onExit()` method passed by the main bulk actions component, which has the effect of unmounting the `ResetViewsAction` component. + +But most of the time, bulk actions are mini-applications with a standalone user interface (in a Dialog), so the `render()` method is useful. Here is the same `ResetViewsAction` implemented behind a confirmation dialog: + +```jsx +// in ./ResetViewsAction.js +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import { Confirm } from 'react-admin'; +import { crudUpdateMany } from 'ra-core'; + +class ResetViewsAction extends Component { + handleDialogClose = () => { + this.props.onExit(); + }; + + handleConfirm = () => { + const { basePath, crudUpdateMany, resource, selectedIds } = this.props; + crudUpdateMany(resource, selectedIds, { views: 0 }, basePath); + this.props.onExit(); + }; + + render() { + return ( + <Confirm + isOpen={true} + title="Update View Count" + content="Are you sure you want to reset the views for these items?" + onConfirm={this.handleConfirm} + onClose={this.handleDialogClose} + /> + ); + } +} + +export default connect(undefined, { crudUpdateMany })(ResetViewsAction); +``` + +**Tip**: `<Confirm>` leverages material-ui's `<Dialog>` component to implement a confirmation popup. Feel free to use it in your admins! + +**Tip**: React-admin doesn't use the `<Confirm>` component internally, because deletes and updates are applied locally immediately, then dispatched to the server after a few seconds, unless the user chooses to undo the modification. That's what we call optimistic rendering. You can do the same for the `ResetViewsAction` by wrapping the `crudUpdateMany()` action creator inside a `startUndoable()` action creator, as follows: + +```jsx +import { Component } from 'react'; +import { connect } from 'react-redux'; +import { startUndoable, crudUpdateMany } from 'ra-core'; + +class ResetViewsAction extends Component { + componentDidMount = () => { + const { basePath, startUndoable, resource, selectedIds } = this.props; + startUndoable( + crudUpdateMany(resource, selectedIds, { views: 0 }, basePath) + ); + this.props.onExit(); + }; + + render() { + return null; + } +} + +export default connect(undefined, { startUndoable })(ResetViewsAction); +``` + +Note that the `crudUpdateMany` action creator is *not* present in the `mapDispatchToProps` argument of `connect()` in that case. Only `startUndoable` needs to be dispatched in this case, using the result of the `crudUpdateMany()` call as parameter. + +### Filters + +You can add a filter element to the list using the `filters` prop: + +```jsx +const PostFilter = (props) => ( + <Filter {...props}> + <TextInput label="Search" source="q" alwaysOn /> + <TextInput label="Title" source="title" defaultValue="Hello, World!" /> + </Filter> +); + +export const PostList = (props) => ( + <List {...props} filters={<PostFilter />}> + ... + </List> +); +``` + +The filter component must be a `<Filter>` with `<Input>` children. + +**Tip**: `<Filter>` is a special component, which renders in two ways: + +- as a filter button (to add new filters) +- as a filter form (to enter filter values) + +It does so by inspecting its `context` prop. + +**Tip**: Don't mix up this `filters` prop, expecting a React element, with the `filter` props, which expects an object to define permanent filters (see below). + +The `Filter` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/overrides/#overriding-with-classes)). This property accepts the following keys: + +* `form`: applied to the root element when rendering as a form. +* `button`: applied to the root element when rendering as a button. + +Children of the `<Filter>` form are regular inputs. `<Filter>` hides them all by default, except those that have the `alwaysOn` prop. + +**Tip**: For technical reasons, react-admin does not accept children of `<Filter>` having both a `defaultValue` and `alwaysOn`. To set default values for always on filters, use the `filterDefaultValues` prop of the `<List>` component instead (see below). + +### Records Per Page + +By default, the list paginates results by groups of 10. You can override this setting by specifying the `perPage` prop: + +```jsx +// in src/posts.js +export const PostList = (props) => ( + <List {...props} perPage={25}> + ... + </List> +); +``` + +### Default Sort Field + +Pass an object literal as the `sort` prop to determine the default `field` and `order` used for sorting: + +```jsx +// in src/posts.js +export const PostList = (props) => ( + <List {...props} sort={{ field: 'published_at', order: 'DESC' }}> + ... + </List> +); +``` + +`sort` defines the *default* sort order ; the list remains sortable by clicking on column headers. + +### Disabling Sorting + +It is possible to disable sorting for a specific field by passing a `sortable` property set to `false`: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" sortable={false} /> + <TextField source="title" /> + <TextField source="body" /> + </Datagrid> + </List> +); +``` + +### Specify Sort Field + +By default, a column is sorted by the `source` property. To define another attribute to sort by, set it via the `sortBy` property: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <ReferenceField label="Post" source="id" reference="posts" sortBy="title"> + <TextField source="title" /> + </ReferenceField> + <FunctionField + label="Author" + sortBy="last_name" + render={record => `${record.author.first_name} ${record.author.last_name}`} + /> + <TextField source="body" /> + </Datagrid> + </List> +); +``` + +### Permanent Filter + +You can choose to always filter the list, without letting the user disable this filter - for instance to display only published posts. Write the filter to be passed to the REST client in the `filter` props: + +```jsx +// in src/posts.js +export const PostList = (props) => ( + <List {...props} filter={{ is_published: true }}> + ... + </List> +); +``` + +The actual filter parameter sent to the REST client is the result of the combination of the *user* filters (the ones set through the `filters` component form), and the *permanent* filter. The user cannot override the permanent filters set by way of `filter`. + +### Filter Default Values + +To set default values to filters, you can either pass an object literal as the `filterDefaultValues` prop of the `<List>` element, or use the `defaultValue` prop of any input component. + +There is one exception: inputs with `alwaysOn` don't accept `defaultValue`. You have to use the `filterDefaultValues` for those. + +```jsx +// in src/posts.js +const PostFilter = (props) => ( + <Filter {...props}> + <TextInput label="Search" source="q" alwaysOn /> + <BooleanInput source="is_published" alwaysOn /> + <TextInput source="title" defaultValue="Hello, World!" /> + </Filter> +); + +export const PostList = (props) => ( + <List {...props} filters={<PostFilter />} filterDefaultValues={{ is_published: true }}> + ... + </List> +); +``` + +**Tip**: The `filter` and `filterDefaultValues` props have one key difference: the `filterDefaultValues` can be overriddent by the user, while the `filter` values are always sent to the data provider. Or, to put it otherwise: + +```js +const filterSentToDataProvider = { ...filterDefaultValues, ...filterChosenByUser, ...filters }; +``` + +### Pagination + +You can replace the default pagination element by your own, using the `pagination` prop. The pagination element receives the current page, the number of records per page, the total number of records, as well as a `setPage()` function that changes the page. + +So if you want to replace the default pagination by a "<previous - next>" pagination, create a pagination component like the following: + +```jsx +import Button from '@material-ui/core/Button'; +import ChevronLeft from '@material-ui/icons/ChevronLeft'; +import ChevronRight from '@material-ui/icons/ChevronRight'; +import Toolbar from '@material-ui/core/Toolbar'; + +const PostPagination = ({ page, perPage, total, setPage }) => { + const nbPages = Math.ceil(total / perPage) || 1; + return ( + nbPages > 1 && + <Toolbar> + {page > 1 && + <Button primary key="prev" icon={<ChevronLeft />} onClick={() => setPage(page - 1)}> + Prev + </Button> + } + {page !== nbPages && + <Button primary key="next" icon={<ChevronRight />} onClick={() => setPage(page + 1)} labelPosition="before"> + Next + </Button> + } + </Toolbar> + ); +} + +export const PostList = (props) => ( + <List {...props} pagination={<PostPagination />}> + ... + </List> +); +``` + +### CSS API + +The `List` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/overrides/#overriding-with-classes)). This property accepts the following keys: + +* `root`: alternative to using `className`. Applied to the root element. +* `header`: applied to the page header +* `actions`: applied to the actions container +* `noResults`: applied to the component shown when there is no result + +Here is an example of how you can override some of these classes: + +You can customize the list styles by passing a `classes` object as prop, through `withStyles()`. Here is an example: + +```jsx +const styles = { + header: { + backgroundColor: '#ccc', + }, +}; + +const PostList = ({ classes, ...props) => ( + <List {...props} classes={{ header: classes.header }}> + <Datagrid> + ... + </Datagrid> + </List> +); + +export withStyles(styles)(PostList); +``` + +## The `Datagrid` component + +The datagrid component renders a list of records as a table. It is usually used as a child of the [`<List>`](#the-list-component) and [`<ReferenceManyField>`](./Fields.md#referencemanyfield) components. + +Here are all the props accepted by the component: + +* [`rowStyle`](#row-style-function) + +It renders as many columns as it receives `<Field>` children. + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField, EditButton } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="title" /> + <TextField source="body" /> + <EditButton /> + </Datagrid> + </List> +); +``` + +The datagrid is an *iterator* component: it receives an array of ids, and a data store, and is supposed to iterate over the ids to display each record. Another example of iterator component is [`<SingleFieldList>`](#the-singlefieldlist-component). + +### Row Style Function + +You can customize the datagrid row style (applied to the `<tr>` element) based on the record, thanks to the `rowStyle` prop, which expects a function. + +For instance, this allows to apply a custom background to the entire row if one value of the record - like its number of views - passes a certain threshold. + +```jsx +const postRowStyle = (record, index) => ({ + backgroundColor: record.nb_views >= 500 ? '#efe' : 'white', +}); +export const PostList = (props) => ( + <List {...props}> + <Datagrid rowStyle={postRowStyle}> + ... + </Datagrid> + </List> +); +``` + +### CSS API + +The `Datagrid` component accepts the usual `className` prop but you can override many class names injected to the inner components by React-admin thanks to the `classes` property (as most Material UI components, see their [documentation about it](https://material-ui.com/customization/overrides/#overriding-with-classes)). This property accepts the following keys: + +* `table`: alternative to using `className`. Applied to the root element. +* `tbody`: applied to the tbody +* `headerCell`: applied to each header cell +* `row`: applied to each row +* `rowEven`: applied to each even row +* `rowOdd`: applied to each odd row +* `rowCell`: applied to each row cell + +Here is an example of how you can override some of these classes: + +You can customize the datagrid styles by passing a `classes` object as prop, through `withStyles()`. Here is an example: + +```jsx +const styles = { + row: { + backgroundColor: '#ccc', + }, +}; + +const PostList = ({ classes, ...props) => ( + <List {...props}> + <Datagrid classes={{ row: classes.row }}> + ... + </Datagrid> + </List> +); + +export withStyles(styles)(PostList); +``` + +**Tip**: If you want to override the `header` and `cell` styles independently for each column, use the `headerClassName` and `cellClassName` props in `<Field>` components. For instance, to hide a certain column on small screens: + +```jsx +import { withStyles } from '@material-ui/core/styles'; + +const styles = theme => ({ + hiddenOnSmallScreens: { + [theme.breakpoints.down('md')]: { + display: 'none', + }, + }, +}); + +const PostList = ({ classes, ...props }) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="title" /> + <TextField + source="views" + headerClassName={classes.hiddenOnSmallScreens} + cellClassName={classes.hiddenOnSmallScreens} + /> + </Datagrid> + </List> +); + +export default withStyles(styles)(PostList); +``` + +## The `SimpleList` component + +For mobile devices, a `<Datagrid>` is often unusable - there is simply not enough space to display several columns. The convention in that case is to use a simple list, with only one column per row. The `<SimpleList>` component serves that purpose, leveraging [material-ui's `<List>` and `<ListItem>` components](http://www.material-ui.com/#/components/list). You can use it as `<List>` or `<ReferenceManyField>` child: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, SimpleList } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + /> + </List> +); +``` + +`<SimpleList>` iterates over the list data. For each record, it executes the `primaryText`, `secondaryText`, `leftAvatar`, `leftIcon`, `rightAvatar`, and `rightIcon` props function, and passes the result as the corresponding `<ListItem>` prop. + +**Tip**: To use a `<SimpleList>` on small screens and a `<Datagrid>` on larger screens, use the `<Responsive>` component: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Responsive, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Responsive + small={ + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + /> + } + medium={ + <Datagrid> + ... + </Datagrid> + } + /> + </List> +); +``` + +**Tip**: The `<SimpleList>` items link to the edition page by default. You can set the `linkType` prop to `show` to link to the `<Show>` page instead. + +```jsx +// in src/posts.js +import React from 'react'; +import { List, SimpleList } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + linkType="show" + /> + </List> +); +``` + +Setting the `linkType` prop to `false` (boolean, not string) removes the link in all list items. + +## The `SingleFieldList` component + +When you want to display only one property of a list of records, instead of using a `<Datagrid>`, use the `<SingleFieldList>`. It expects a single `<Field>` as child. It's especially useful for `<ReferenceManyField>` or `<ReferenceArrayField>` components: + +```jsx +// Display all the tags for the current post +<ReferenceArrayField + label="Tags" + reference="tags" + source="tags" +> + <SingleFieldList> + <ChipField source="name" /> + </SingleFieldList> +</ReferenceArrayField> +``` + +![ReferenceManyFieldSingleFieldList](https://marmelab.com/react-admin/img/reference-many-field-single-field-list.png) + +**Tip**: The `<SingleFieldList>` items link to the edition page by default. You can set the `linkType` prop to `show` to link to the `<Show>` page instead. + +```jsx +// Display all the tags for the current post +<ReferenceArrayField + label="Tags" + reference="tags" + source="tags" +> + <SingleFieldList linkType="show"> + <ChipField source="name" /> + </SingleFieldList> +</ReferenceArrayField> +``` + +## Using a Custom Iterator + +A `<List>` can delegate to any iterator component - `<Datagrid>` is just one example. An iterator component must accept at least two props: + +- `ids` is an array of the ids currently displayed in the list +- `data` is an object of all the fetched data for this resource, indexed by id. + +For instance, what if you prefer to show a list of cards rather than a datagrid? + +![Custom iterator](https://marmelab.com/react-admin/img/custom-iterator.png) + +You'll need to create your own iterator component as follows: + +```jsx +// in src/comments.js +import Card from '@material-ui/core/Card'; +import CardActions from '@material-ui/core/CardActions'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; + +const cardStyle = { + width: 300, + minHeight: 300, + margin: '0.5em', + display: 'inline-block', + verticalAlign: 'top' +}; +const CommentGrid = ({ ids, data, basePath }) => ( + <div style={{ margin: '1em' }}> + {ids.map(id => + <Card key={id} style={cardStyle}> + <CardHeader + title={<TextField record={data[id]} source="author.name" />} + subheader={<DateField record={data[id]} source="created_at" />} + avatar={<Avatar icon={<PersonIcon />} />} + /> + <CardContent> + <TextField record={data[id]} source="body" /> + </CardContent> + <CardContent> + about  + <ReferenceField label="Post" resource="comments" record={data[id]} source="post_id" reference="posts" basePath={basePath}> + <TextField source="title" /> + </ReferenceField> + </CardContent> + <CardActions style={{ textAlign: 'right' }}> + <EditButton resource="posts" basePath={basePath} record={data[id]} /> + </CardActions> + </Card> + )} + </div> +); +CommentGrid.defaultProps = { + data: {}, + ids: [], +}; + +export const CommentList = (props) => ( + <List title="All comments" {...props}> + <CommentGrid /> + </List> +); +``` + +As you can see, nothing prevents you from using `<Field>` components inside your own components... provided you inject the current `record`. Also, notice that components building links require the `basePath` component, which is also injected. + +## Displaying Fields depending on the user permissions + +You might want to display some fields or filters only to users with specific permissions. Those permissions are retrieved for each route and will provided to your component as a `permissions` prop. + +Each route will call the `authProvider` with the `AUTH_GET_PERMISSIONS` type and some parameters including the current location and route parameters. It's up to you to return whatever you need to check inside your component such as the user's role, etc. + +```jsx +const UserFilter = ({ permissions, ...props }) => + <Filter {...props}> + <TextInput + label="user.list.search" + source="q" + alwaysOn + /> + <TextInput source="name" /> + {permissions === 'admin' ? <TextInput source="role" /> : null} + </Filter>; + +export const UserList = ({ permissions, ...props }) => + <List + {...props} + filters={<UserFilter permissions={permissions} />} + sort={{ field: 'name', order: 'ASC' }} + > + <Responsive + small={ + <SimpleList + primaryText={record => record.name} + secondaryText={record => + permissions === 'admin' ? record.role : null} + /> + } + medium={ + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + {permissions === 'admin' && <TextField source="role" />} + {permissions === 'admin' && <EditButton />} + <ShowButton /> + </Datagrid> + } + /> + </List>; +``` + +**Tip** Note how the `permissions` prop is passed down to the custom `filters` component. diff --git a/docs/reference.md b/docs/reference.md new file mode 100644 index 0000000..230bbe0 --- /dev/null +++ b/docs/reference.md @@ -0,0 +1,97 @@ +--- +id: reference +title: Reference +--- + +<div style="column-count:3" markdown="1"> + +* `<Actions>` +* [`<Admin>`](./Admin.html) +* `<AdminRoutes>` +* `<AppBar>` +* `<AppBarMobile>` +* [`<ArrayField>`](./Fields.html#arrayfield) +* [`<ArrayInput>`](./Inputs.html#arrayinput) +* [`<Authenticated>`](./Authentication.html#restricting-access-to-a-custom-page) +* [`<AutocompleteInput>`](./Inputs.html#autocompleteinput) +* [`<BooleanField>`](./Fields.html#booleanfield) +* [`<BooleanInput>`](./Inputs.html#booleaninput-and-nullablebooleaninput) +* [`<CheckboxGroupInput>`](./Inputs.html#checkboxgroupinput) +* [`<ChipField>`](./Fields.html#chipfield) +* `<Confirm>` +* [`<Create>`](./CreateEdit.html#the-create-and-edit-components) +* `<CreateActions>` +* `<CreateButton>` +* `<DashboardMenuItem>` +* [`<Datagrid>`](./List.html#the-datagrid-component) +* `<DatagridBody>` +* `<DatagridCell>` +* `<DatagridHeaderCell>` +* [`<DateField>`](./Fields.html#datefield) +* [`<DateInput>`](./Inputs.html#dateinput) +* `<DeleteButton>` +* [`<DisabledInput>`](./Inputs.html#disabledinput) +* [`<Edit>`](./CreateEdit.html#the-create-and-edit-components) +* `<EditActions>` +* `<EditButton>` +* [`<EmailField>`](./Fields.html#emailfield) +* [`<FileField>`](./Fields.html#filefield) +* [`<FileInput>`](./Inputs.html#fileinput) +* [`<Filter>`](./List.html#filters) +* `<FilterButton>` +* `<FilterForm>` +* [`<FormDataConsumer>`](./Inputs.html#linking-two-inputs) +* [`<FormTab>`](./CreateEdit.html#the-tabbedform-component) +* [`<FunctionField>`](./Fields.html#functionfield) +* `<EditButton>` +* [`<ImageField>`](./Fields.html#imagefield) +* [`<ImageInput>`](./Inputs.html#imageinput) +* [`<ImageInputPreview>`](./Inputs.html#imageinput) +* [`<FunctionField>`](./Fields.html#functionfield) +* `<Labeled>` +* [`<Layout>`](./Theming.html#using-a-custom-layout) +* [`<Loading>`](./Theming.html#Loading) +* [`<List>`](./List.html#the-list-component) +* `<ListButton>` +* [`<LongTextInput>`](./Inputs.html#longtextinput) +* [`<Menu>`](./Theming.html#using-a-custom-menu) +* `<Notification>` +* [`<NullableBooleanInput>`](./Inputs.html#booleaninput-and-nullablebooleaninput) +* [`<NumberField>`](./Fields.html#numberfield) +* [`<NumberInput>`](./Inputs.html#numberinput) +* [`<Pagination>`](./List.html#pagination) +* [`<RadioButtonGroupInput>`](./Inputs.html#radiobuttongroupinput) +* [`<ReferenceArrayField>`](./Fields.html#referencearrayfield) +* [`<ReferenceField>`](./Fields.html#referencefield) +* [`<ReferenceInput>`](./Inputs.html#referenceinput) +* [`<ReferenceManyField>`](./Fields.html#referencemanyfield) +* `<RefreshButton>` +* [`<Resource>`](./Resource.html#the-resource-component) +* [`<Responsive>`](./Theming.html#responsive-utility) +* [`<RichTextField>`](./Fields.html#richtextfield) +* [`<RichTextInput>`](./Inputs.html#richtextinput) +* `<SaveButton>` +* [`<SelectField>`](./Fields.html#selectfield) +* [`<SelectInput>`](./Inputs.html#selectinput) +* [`<Show>`](./Show.html#the-show-component) +* `<ShowButton>` +* `<Sidebar>` +* `<SimpleList>` +* `<SingleFieldList>` +* [`<SimpleForm>`](./CreateEdit.html#the-simpleform-component) +* [`<SimpleformIterator>`](./Inputs.html#arrayinput) +* [`<SimpleList>`](./List.html#the-simplelist-component) +* [`<SimpleShowLayout>`](./Show.html#the-simpleshowlayout-component) +* [`<SingleFieldList>`](./List.html#the-singlefieldlist-component) +* `<Tab>` +* [`<TabbedForm>`](./CreateEdit.html#the-tabbedform-component) +* [`<TabbedShowLayout>`](./Show.html#the-tabbedshowlayout-component) +* [`<TextField>`](./Fields.html#textfield) +* [`<TextInput>`](./Inputs.html#textinput) +* `<Title>` +* `<Toolbar>` +* [`<UrlField>`](./Fields.html#urlfield) +* `<ViewTitle>` +* [`<WithPermissions>`](./Authorization.html#withpermissions) + +</div> diff --git a/docs/resource-component.md b/docs/resource-component.md new file mode 100644 index 0000000..fea1bae --- /dev/null +++ b/docs/resource-component.md @@ -0,0 +1,115 @@ +--- +id: resource-component +title: <Resource> +--- + +A `<Resource>` component maps one API endpoint to a CRUD interface. For instance, the following admin app offers a read-only interface to the resources exposed by the JSONPlaceholder API at [`http://jsonplaceholder.typicode.com/posts`](http://jsonplaceholder.typicode.com/posts) and [`http://jsonplaceholder.typicode.com/users`](http://jsonplaceholder.typicode.com/users): + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostList } from './posts'; +import { UserList } from './users'; + +const App = () => ( + <Admin dataProvider={jsonServerProvider('http://jsonplaceholder.typicode.com')}> + <Resource name="posts" list={PostList} /> + <Resource name="users" list={UserList} /> + </Admin> +); +``` + +`<Resource>` allows you to define a component for each CRUD operation, using the following prop names: + +* `list` +* `create` +* `edit` +* `show` + +Here is a more complete admin, with components for all the CRUD operations: + +```jsx +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostList, PostCreate, PostEdit, PostShow, PostIcon } from './posts'; +import { UserList } from './posts'; +import { CommentList, CommentEdit, CommentCreate, CommentIcon } from './comments'; + +const App = () => ( + <Admin dataProvider={jsonServerProvider('http://jsonplaceholder.typicode.com')}> + <Resource name="posts" list={PostList} create={PostCreate} edit={PostEdit} show={PostShow} icon={PostIcon} /> + <Resource name="users" list={UserList} /> + <Resource name="comments" list={CommentList} create={CommentCreate} edit={CommentEdit} icon={CommentIcon} /> + <Resource name="tags" /> + </Admin> +); +``` + +**Tip**: Under the hood, the `<Resource>` component uses react-router to create several routes: + +* `/` maps to the `list` component +* `/create` maps to the `create` component +* `/:id` maps to the `edit` component +* `/:id/show` maps to the `show` component + +**Tip**: You must add a `<Resource>` when you declare a reference (via `<ReferenceField>`, `<ReferenceArrayField>`, `<ReferenceManyField>`, `<ReferenceInput>` or `<ReferenceArrayInput>`), because react-admin uses resources to define the data store structure. That's why there is an empty `tag` resource in the example above. + +`<Resource>` also accepts additional props: + +* [`name`](#name) +* [`icon`](#icon) +* [`options`](#icon) + +## `name` + +React-admin uses the `name` prop both to determine the API endpoint (passed to the `dataProvider`), and to form the URL for the resource. + +```jsx +<Resource name="posts" list={PostList} create={PostCreate} edit={PostEdit} show={PostShow} /> +``` + +For this resource react-admin will fetch the `http://jsonplaceholder.typicode.com/posts` endpoint for data. + +The routing will map the component as follows: + +* `/posts/` maps to `PostList` +* `/posts/create` maps to `PostCreate` +* `/posts/:id` maps to `PostEdit` +* `/posts/:id/show` maps to `PostShow` + +**Tip**: If you want to use a special API endpoint (e.g. 'http://jsonplaceholder.typicode.com/my-custom-posts-endpoint') without altering the URL in the react-admin application (so still use `/posts`), write the mapping from the resource `name` (`posts`) to the API endpoint (`my-custom-posts-endpoint`) in your own [`dataProvider`](./Admin.md#dataprovider) + +## `icon` + +React-admin will render the `icon` prop component in the menu: + +```jsx +// in src/App.js +import React from 'react'; +import PostIcon from '@material-ui/icons/Book'; +import UserIcon from '@material-ui/icons/People'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostList } from './posts'; + +const App = () => ( + <Admin dataProvider={jsonServerProvider('http://jsonplaceholder.typicode.com')}> + <Resource name="posts" list={PostList} icon={PostIcon} /> + <Resource name="users" list={UserList} icon={UserIcon} /> + </Admin> +); +``` + +## options + +`options.label` allows to customize the display name of a given resource in the menu. + +```jsx +<Resource name="v2/posts" options={{ label: 'Posts' }} list={PostList} /> +``` diff --git a/docs/show-view-component.md b/docs/show-view-component.md new file mode 100644 index 0000000..81cef33 --- /dev/null +++ b/docs/show-view-component.md @@ -0,0 +1,260 @@ +--- +id: show-view-component +title: <Show> View +--- + +The Show view displays a record fetched from the API in a read-only fashion. It delegates the actual rendering of the record to a layout component - usually `<SimpleShowLayout>`. This layout component uses its children ([`<Fields>`](./Fields.md) components) to render each record field. + +![post show view](https://marmelab.com/react-admin/img/show-view.png) + +## The `Show` component + +The `<Show>` component renders the page title and actions, and fetches the record from the REST API. It is not responsible for rendering the actual record - that's the job of its child component (usually `<SimpleShowLayout>`), to which they pass the `record` as prop. + +Here are all the props accepted by the `<Show>` component: + +* [`title`](#page-title) +* [`actions`](#actions) + +Here is the minimal code necessary to display a view to show a post: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +import { PostCreate, PostEdit, PostShow } from './posts'; + +const App = () => ( + <Admin dataProvider={jsonServerProvider('http://jsonplaceholder.typicode.com')}> + <Resource name="posts" show={PostShow} create={PostCreate} edit={PostEdit} /> + </Admin> +); + +export default App; + +// in src/posts.js +import React from 'react'; +import { Show, SimpleShowLayout, TextField, DateField, EditButton, RichTextField } from 'react-admin'; + +export const PostShow = (props) => ( + <Show {...props}> + <SimpleShowLayout> + <TextField source="title" /> + <TextField source="teaser" /> + <RichTextField source="body" /> + <DateField label="Publication date" source="created_at" /> + </SimpleShowLayout> + </Show> +); +``` + +That's enough to display the post show view: + +![post show view](https://marmelab.com/react-admin/img/post-show.png) + +### Page Title + +By default, the title for the Show view is "[resource_name] #[record_id]". + +You can customize this title by specifying a custom `title` prop: + +```jsx +export const PostShow = (props) => ( + <Show title="Post view" {...props}> + ... + </Show> +); +``` + +More interestingly, you can pass a component as `title`. React-admin clones this component and, in the `<ShowView>`, injects the current `record`. This allows to customize the title according to the current record: + +```jsx +const PostTitle = ({ record }) => { + return <span>Post {record ? `"${record.title}"` : ''}</span>; +}; +export const PostShow = (props) => ( + <Show title={<PostTitle />} {...props}> + ... + </Show> +); +``` + +### Actions + +You can replace the list of default actions by your own element using the `actions` prop: + +```jsx +import CardActions from '@material-ui/core/CardActions'; +import Button from '@material-ui/core/Button'; +import { ListButton, EditButton, DeleteButton, RefreshButton } from 'react-admin'; + +const cardActionStyle = { + zIndex: 2, + display: 'inline-block', + float: 'right', +}; + +const PostShowActions = ({ basePath, data, resource }) => ( + <CardActions style={cardActionStyle}> + <EditButton basePath={basePath} record={data} /> + <ListButton basePath={basePath} /> + <DeleteButton basePath={basePath} record={data} resource={resource} /> + <RefreshButton /> + {/* Add your custom actions */} + <Button color="primary" onClick={customAction}>Custom Action</Button> + </CardActions> +); + +export const PostShow = (props) => ( + <Show actions={<PostShowActions />} {...props}> + ... + </Show> +); +``` + +## The `SimpleShowLayout` component + +The `<SimpleShowLayout>` component receives the `record` as prop from its parent component. It is responsible for rendering the actual view. + +The `<SimpleShowLayout>` renders its child components line by line (within `<div>` components). + +```jsx +export const PostShow = (props) => ( + <Show {...props}> + <SimpleShowLayout> + <TextField source="title" /> + <RichTextField source="body" /> + <NumberField source="nb_views" /> + </SimpleShowLayout> + </Show> +); +``` + +It is possible to override its style by specifying the `style` prop, for example: + +```jsx +const styles = { + container: { + display: 'flex', + }, + item: { + marginRight: '1rem', + }, +}; + +export const PostShow = (props) => ( + <Show {...props}> + <SimpleShowLayout style={styles.container}> + <TextField source="title" style={styles.item} /> + <RichTextField source="body" style={styles.item} /> + <NumberField source="nb_views" style={styles.item} /> + </SimpleShowLayout> + </Show> +); +``` + +## The `TabbedShowLayout` component + +Just like `<SimpleShowLayout>`, `<TabbedShowLayout>` receives the `record` prop and renders the actual view. However, the `<TabbedShowLayout>` component renders fields grouped by tab. The tabs are set by using `<Tab>` components, which expect a `label` and an optional `icon` prop. Switching tabs will update the current url. By default, it uses the tabs indexes and the first tab will be displayed at the root url. You can customize the path by providing a `path` prop to each `Tab` component. If you'd like the first one to act as an index page, just omit the `path` prop. + +![tabbed show](https://marmelab.com/react-admin/img/tabbed-show.gif) + +```jsx +import { TabbedShowLayout, Tab } from 'react-admin' + +export const PostShow = (props) => ( + <Show {...props}> + <TabbedShowLayout> + <Tab label="summary"> + <TextField label="Id" source="id" /> + <TextField source="title" /> + <TextField source="teaser" /> + </Tab> + <Tab label="body" path="body"> + <RichTextField source="body" addLabel={false} /> + </Tab> + <Tab label="Miscellaneous" path="miscellaneous"> + <TextField label="Password (if protected post)" source="password" type="password" /> + <DateField label="Publication date" source="published_at" /> + <NumberField source="average_note" /> + <BooleanField label="Allow comments?" source="commentable" defaultValue /> + <TextField label="Nb views" source="views" /> + </Tab> + <Tab label="comments" path="comments"> + <ReferenceManyField reference="comments" target="post_id" addLabel={false}> + <Datagrid> + <TextField source="body" /> + <DateField source="created_at" /> + <EditButton /> + </Datagrid> + </ReferenceManyField> + </Tab> + </TabbedShowLayout> + </Show> +); +``` + +## Displaying Fields depending on the user permissions + +You might want to display some fields only to users with specific permissions. Those permissions are retrieved for each route and will provided to your component as a `permissions` prop. + +Each route will call the `authProvider` with the `AUTH_GET_PERMISSIONS` type and some parameters including the current location and route parameters. It's up to you to return whatever you need to check inside your component such as the user's role, etc. + +Here's an example inside a `Show` view with a `SimpleShowLayout` and a custom `actions` component: + +```jsx +import CardActions from '@material-ui/core/CardActions'; +import Button from '@material-ui/core/Button'; +import { ListButton, EditButton, DeleteButton } from 'react-admin'; + +const cardActionStyle = { + zIndex: 2, + display: 'inline-block', + float: 'right', +}; + +const PostShowActions = ({ permissions, basePath, data, resource }) => ( + <CardActions style={cardActionStyle}> + <EditButton basePath={basePath} record={data} /> + <ListButton basePath={basePath} /> + {permissions === 'admin' && + <DeleteButton basePath={basePath} record={data} resource={resource} /> + } + </CardActions> +); + +export const PostShow = ({ permissions, ...props }) => ( + <Show actions={<PostShowActions permissions={permissions} />} {...props}> + <SimpleShowLayout> + <TextField source="title" /> + <RichTextField source="body" /> + {permissions === 'admin' && + <NumberField source="nb_views" /> + } + </SimpleShowLayout> + </Show> +); +``` + +**Tip** Note how the `permissions` prop is passed down to the custom `actions` component. + +This also works inside a `TabbedShowLayout`, and you can hide a `Tab` completely: + + +```jsx +export const UserShow = ({ permissions, ...props }) => + <Show {...props}> + <TabbedShowLayout> + <Tab label="user.form.summary"> + {permissions === 'admin' && <TextField source="id" />} + <TextField source="name" /> + </Tab> + {permissions === 'admin' && + <Tab label="user.form.security"> + <TextField source="role" /> + </Tab>} + </TabbedShowLayout> + </Show>; +``` diff --git a/docs/theming.md b/docs/theming.md new file mode 100644 index 0000000..bd58b75 --- /dev/null +++ b/docs/theming.md @@ -0,0 +1,629 @@ +--- +id: theming +title: Theming +--- + +Whether you need to adjust a CSS rule for a single component, or change the color of the labels in the entire app, you're covered! + +## Overriding A Component Style + +Every react-admin component provides a `className` property, which is always applied to the root element. + +Here is an example customizing an `EditButton` component inside a `Datagrid`, using its `className` property and the `withStyles` Higher Order Component from Material-UI: + + +```jsx +import { NumberField, List, Datagrid, EditButton } from 'react-admin'; +import { withStyles } from '@material-ui/core/styles'; + +const styles = { + button: { + fontWeight: 'bold', + // This is JSS syntax to target a deeper element using css selector, here the svg icon for this button + '& svg': { color: 'orange' } + }, +}; + +const MyEditButton = withStyles(styles)(({ classes, ...props }) => ( + <EditButton + className={classes.button} + {...props} + /> +)); + +export const ProductList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="sku" /> + <TextField source="price" /> + <MyEditButton /> + </Datagrid> + </List> +); +``` + + +For some components, you may want to override not only the root component style, but also the style of components inside the root. In this case, the `className` property isn't enough. You can take advantage of the `classes` property to customize the classes that the component uses internally. + +Here is an example using the `classes` property of the `Filter` and `List` components: + + +```jsx +import React from 'react'; +import { + BooleanField, + Datagrid, + DateField, + DateInput, + EditButton, + Filter, + List, + NullableBooleanInput, + NumberField, + TextInput, +} from 'react-admin'; +import Icon from '@material-ui/icons/Person'; +import { withStyles } from '@material-ui/core/styles'; + +export const VisitorIcon = Icon; + +// The Filter component supports the `form` and `button` CSS classes. Here we override the `form` class +const filterStyles = { + form: { + backgroundColor: 'Lavender', + }, +}; + +const VisitorFilter = withStyles(filterStyles)(({ classes, ...props }) => ( + <Filter classes={classes} {...props}> + <TextInput + className={classes.searchInput} + label="pos.search" + source="q" + alwaysOn + /> + <DateInput source="last_seen_gte" /> + <NullableBooleanInput source="has_ordered" /> + <NullableBooleanInput source="has_newsletter" defaultValue /> + </Filter> +)); + +// The List component supports the `root`, `header`, `actions` and `noResults` CSS classes. Here we override the `header` and `actions` classes +const listStyles = { + actions: { + backgroundColor: 'Lavender', + }, + header: { + backgroundColor: 'Lavender', + }, +}; + +export const VisitorList = withStyles(listStyles)(({ classes, ...props }) => ( + <List + classes={classes} + {...props} + filters={<VisitorFilter />} + sort={{ field: 'last_seen', order: 'DESC' }} + perPage={25} + > + <Datagrid classes={classes} {...props}> + <DateField source="last_seen" type="date" /> + <NumberField + source="nb_commands" + label="resources.customers.fields.commands" + /> + <NumberField + source="total_spent" + options={{ style: 'currency', currency: 'USD' }} + /> + <DateField source="latest_purchase" showTime /> + <BooleanField source="has_newsletter" label="News." /> + <EditButton /> + </Datagrid> + </List> +)); +``` + + +This example results in: + +![Visitor List with customized CSS classes](https://marmelab.com/react-admin/img/list_with_customized_css.png) + +Take a look at a component documentation and source code to know which classes are available for styling. For instance, you can have a look at the [Datagrid CSS documentation](./List.md#the-datagrid-component). + +If you need more control over the HTML code, you can also create your own [Field](./Fields.md#writing-your-own-field-component) and [Input](./Inputs.md#writing-your-own-input-component) components. + +## Conditional Formatting + +Sometimes you want the format to depend on the value. The following example shows how to create a new custom `NumberField` component which highlight its text in red when its value is 100 or higher. + + +```jsx +import { NumberField, List, Datagrid, EditButton } from 'react-admin'; +import { withStyles } from '@material-ui/core/styles'; +import classnames from 'classnames'; + +const coloredStyles = { + small: { color: 'black' }, + big: { color: 'red' }, +}; + +const ColoredNumberField = withStyles(coloredStyles)( + ({ classes, ...props }) => ( + <NumberField + className={classnames({ + [classes.small]: props.record[props.source] < 100, + [classes.big]: props.record[props.source] >= 100, + })} + {...props} + /> + )); + +// Ensure the original component defaultProps are still applied as they may be used by its parents (such as the `Show` component): +ColoredNumberField.defaultProps = NumberField.defaultProps; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + ... + <ColoredNumberField source="nb_views" /> + <EditButton /> + </Datagrid> + </List> +); +``` + + +Furthermore, you may extract this highlighting strategy into an Higher Order Component if you'd like to reuse it for other components as well: + + +```jsx +import { NumberField, List, Datagrid, EditButton } from 'react-admin'; +import { withStyles } from '@material-ui/core/styles'; +import classnames from 'classnames'; + +const coloredStyles = { + small: { color: 'black' }, + big: { color: 'red' }, +}; + +const colored = WrappedComponent => withStyles(coloredStyles)( + ({ classes, ...props }) => ( + <WrappedComponent + className={classnames({ + [classes.small]: props.record[props.source] < 500, + [classes.big]: props.record[props.source] >= 500, + })} + {...props} + /> + )); + + +const ColoredNumberField = colored(NumberField); +// Ensure the original component defaultProps are still applied as they may be used by its parents (such as the `Show` component): +ColoredNumberField.defaultProps = NumberField.defaultProps; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + ... + <ColoredNumberField source="nb_views" /> + <EditButton /> + </Datagrid> + </List> +); +``` + + +If you want to read more about higher-order components, check out this SitePoint tutorial: [Higher Order Components: A React Application Design Pattern](https://www.sitepoint.com/react-higher-order-components/) + +## Responsive Utility + +To provide an optimized experience on mobile, tablet, and desktop devices, you often need to display different components depending on the screen size. That's the purpose of the `<Responsive>` component, which offers a declarative approach to responsive web design. + +It expects element props named `small`, `medium`, and `large`. It displays the element that matches the screen size (with breakpoints at 768 and 992 pixels): + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Responsive, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Responsive + small={ + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + /> + } + medium={ + <Datagrid> + <TextField source="id" /> + <ReferenceField label="User" source="userId" reference="users"> + <TextField source="name" /> + </ReferenceField> + <TextField source="title" /> + <TextField source="body" /> + <EditButton /> + </Datagrid> + } + /> + </List> +); +``` + +**Tip**: If you only provide `small` and `medium`, the `medium` element will also be used on large screens. The same kind of smart default exists for when you omit `small` or `medium`. + +**Tip**: You can specify `null` as the value for `small`, `medium` or `large` to avoid rendering something on a specific size without falling back to others. + +**Tip**: You can also use [material-ui's `withWidth()` higher order component](https://github.com/callemall/material-ui/blob/master/src/utils/withWidth.js) to have the `with` prop injected in your own components. + +## Using a Predefined Theme + +Material UI also supports [complete theming](http://www.material-ui.com/#/customization/themes) out of the box. Material UI ships two base themes: light and dark. React-admin uses the light one by default. To use the dark one, pass it to the `<Admin>` component, in the `theme` prop (along with `createMuiTheme()`). + +```jsx +import { createMuiTheme } from '@material-ui/core/styles'; + +const theme = createMuiTheme({ + palette: { + type: 'dark', // Switching the dark mode on is a single property value change. + }, +}); + +const App = () => ( + <Admin theme={theme} dataProvider={simpleRestProvider('http://path.to.my.api')}> + // ... + </Admin> +); +``` + +![Dark theme](https://marmelab.com/react-admin/img/dark-theme.png) + +## Writing a Custom Theme + +If you need more fine tuning, you'll need to write your own `theme` object, following [Material UI themes documentation](https://material-ui.com/customization/themes/). Material UI merges custom theme objects with the default theme. + +```jsx +import { createMuiTheme } from '@material-ui/core/styles'; +import indigo from '@material-ui/core/colors/indigo'; +import pink from '@material-ui/core/colors/pink'; +import red from '@material-ui/core/colors/red'; + +const myTheme = createMuiTheme({ + palette: { + primary: indigo, + secondary: pink, + error: red, + contrastThreshold: 3, + tonalOffset: 0.2, + }, + typography: { + // Use the system font instead of the default Roboto font. + fontFamily: [ + '-apple-system', + 'BlinkMacSystemFont', + '"Segoe UI"', + 'Arial', + 'sans-serif', + ].join(','), + }, + overrides: { + MuiButton: { // override the styles of all instances of this component + root: { // Name of the rule + color: 'white', // Some CSS + }, + }, + }, +}); +``` + +The `muiTheme` object contains the following keys: + +* `breakpoints` +* `direction` +* `mixins` +* `overrides` +* `palette` +* `props` +* `shadows` +* `typography` +* `transitions` +* `spacing` +* `zIndex` + +**Tip**: Check [Material UI default theme documentation](https://material-ui.com/customization/default-theme/) to see the default values and meaning for these keys. + +Once your theme is defined, pass it to the `<Admin>` component, in the `theme` prop. + +```jsx +const App = () => ( + <Admin theme={myTheme} dataProvider={simpleRestProvider('http://path.to.my.api')}> + // ... + </Admin> +); +``` + +## Using a Custom Layout + +Instead of the default layout, you can use your own component as the admin layout. Just use the `appLayout` prop of the `<Admin>` component: + +```jsx +// in src/App.js +import MyLayout from './MyLayout'; + +const App = () => ( + <Admin appLayout={MyLayout} dataProvider={simpleRestProvider('http://path.to.my.api')}> + // ... + </Admin> +); +``` + +Your custom layout can extend the default `<Layout>` component if you only want to override the appBar, the menu, or the notification component. For instance: + +```jsx +// in src/MyLayout.js +import { Layout } from 'react-admin'; +import MyAppBar from './MyAppBar'; +import MyMenu from './MyMenu'; +import MyNotification from './MyNotification'; + +const MyLayout = (props) => <Layout + {...props} + appBar={MyAppBar} + menu={MyMenu} + notification={MyNotification} +/>; + +export default MyLayout; +``` + +For more custom layouts, write a component from scratch. It must contain a `{children}` placeholder, where react-admin will render the resources. Use the [default layout](https://github.com/marmelab/react-admin/blob/master/src/mui/layout/Layout.js) as a starting point. Here is a simplified version (with no responsive support): + +```jsx +// in src/MyLayout.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { withStyles, MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; +import { + AppBar, + Menu, + Notification, + Sidebar, + setSidebarVisibility, +} from 'react-admin'; + +const styles = theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + zIndex: 1, + minHeight: '100vh', + backgroundColor: theme.palette.background.default, + position: 'relative', + }, + appFrame: { + display: 'flex', + flexDirection: 'column', + overflowX: 'auto', + }, + contentWithSidebar: { + display: 'flex', + flexGrow: 1, + }, + content: { + display: 'flex', + flexDirection: 'column', + flexGrow: 2, + padding: theme.spacing.unit * 3, + marginTop: '4em', + paddingLeft: 5, + }, +}); + +class MyLayout extends Component { + componentWillMount() { + this.props.setSidebarVisibility(true); + } + + render() { + const { + children, + classes, + dashboard, + isLoading, + logout, + open, + title, + } = this.props; + return ( + <div className={classes.root}> + <div className={classes.appFrame}> + <AppBar title={title} open={open} logout={logout} /> + <main className={classes.contentWithSidebar}> + <Sidebar> + <Menu logout={logout} hasDashboard={!!dashboard} /> + </Sidebar> + <div className={classes.content}> + {children} + </div> + </main> + <Notification /> + </div> + </div> + ); + } +} + +MyLayout.propTypes = { + children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + dashboard: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + ]), + isLoading: PropTypes.bool.isRequired, + logout: componentPropType, + setSidebarVisibility: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, +}; + +const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 }); +export default connect(mapStateToProps, { setSidebarVisibility })(withStyles(styles)(MyLayout)); +``` + +## Using a Custom Menu + +By default, React-admin uses the list of `<Resource>` components passed as children of `<Admin>` to build a menu to each resource with a `list` component. + +If you want to add or remove menu items, for instance to link to non-resources pages, you can create your own menu component: + +```jsx +// in src/MyMenu.js +import React from 'react'; +import { connect } from 'react-redux'; +import { MenuItemLink, getResources } from 'react-admin'; +import { withRouter } from 'react-router-dom'; +import Responsive from '../layout/Responsive'; + +const MyMenu = ({ resources, onMenuClick, logout }) => ( + <div> + {resources.map(resource => ( + <MenuItemLink to={`/${resource.name}`} primaryText={resource.name} onClick={onMenuClick} /> + ))} + <MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} /> + <Responsive + small={logout} + medium={null} // Pass null to render nothing on larger devices + /> + </div> +); + +const mapStateToProps = state => ({ + resources: getResources(state), +}); + +export default withRouter(connect(mapStateToProps)(MyMenu)); + +``` + +**Tip**: Note the `MenuItemLink` component. It must be used to avoid unwanted side effects in mobile views. + +**Tip**: Note that we include the `logout` item only on small devices. Indeed, the `logout` button is already displayed in the AppBar on larger devices. + +**Tip**: Note that we use React Router [`withRouter`](https://reacttraining.com/react-router/web/api/withRouter) Higher Order Component and that it is used **before** Redux [`connect](https://github.com/reactjs/react-redux/blob/master/docs/api.html#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options). This is required if you want the active menu item to be highlighted. + +To use this custom menu component, pass it to a custom Layout, as explained above: + +```jsx +// in src/MyLayout.js +import { Layout } from 'react-admin'; +import MyMenu from './MyMenu'; + +const MyLayout = (props) => <Layout {...props} menu={MyMenu} />; + +export default MyLayout; +``` + +Then, use this layout in the `<Admin>` `applayout` prop: + +```jsx +// in src/App.js +import MyLayout from './MyLayout'; + +const App = () => ( + <Admin appLayout={MyLayout} dataProvider={simpleRestProvider('http://path.to.my.api')}> + // ... + </Admin> +); +``` + +**Tip**: If you use authentication, don't forget to render the `logout` prop in your custom menu component. Also, the `onMenuClick` function passed as prop is used to close the sidebar on mobile. + +The `MenuItemLink` component make use of the React Router [`NavLink`](https://reacttraining.com/react-router/web/api/NavLink) component, hence allowing to customize its style when it targets the current page. + +If the default active style does not suit your tastes, you can override it by passing your own `classes`: + +```jsx +// in src/MyMenu.js +import React from 'react'; +import { connect } from 'react-redux'; +import { MenuItemLink, getResources } from 'react-admin'; +import { withStyles } from '@material-ui/core/styles'; +import { withRouter } from 'react-router-dom'; + +const styles = { + root: {}, // Style applied to the MenuItem from material-ui + active: { fontWeight: 'bold' }, // Style applied when the menu item is the active one + icon: {}, // Style applied to the icon +}; + +const MyMenu = ({ classes, resources, onMenuClick, logout }) => ( + <div> + {resources.map(resource => ( + <MenuItemLink classes={classes} to={`/${resource.name}`} primaryText={resource.name} onClick={onMenuClick} /> + ))} + <MenuItemLink classes={classes} to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} /> + <Responsive + small={logout} + medium={null} // Pass null to render nothing on larger devices + /> + </div> +); + +const mapStateToProps = state => ({ + resources: getResources(state), +}); + +export default withRouter(connect(mapStateToProps)(withStyles(styles)(Menu))); +``` + +## Notifications + +If you use your own layout (or custom login page), then you probably use the `<Notification>` component. + +You can override the notification duration by setting the `autoHideDuration` prop. It defaults to 4000, i.e. 4 seconds. For instance, to create a custom Notification component with a 5 seconds default: + +```jsx +// in src/MyNotification.js +import { Notification } from 'react-admin'; + +const MyNotification = props => <Notification {...props}autoHideDuration={5000} />; + +export default MyNotification; +``` + +**Tip**: if you use the `showNotification` action, then you can define `autoHideDuration` per message as the third parameter of the `showNotification` action creator. + +## Loading + +Display a circular progress component with optional messages. Display the same loading component as `react-admin` on custom pages for consistency. + +Supported props: + +Prop | Type | Default | Descriptions +---|---|---|--- +`loadingPrimary` |`String` | `ra.page.loading` | Label to use for primary loading message +`loadingSecondary` |`String` | `ra.message.loading` | Label to use for secondary loading message + +Usage: + +```jsx +<Loading loadingPrimary="app.page.loading" loadingSecondary="app.message.loading" /> +``` + +## LinearProgress + +Display a linear progress component. Display the same loading component as `react-admin` on custom inputs for consistency. + +Usage: + +```jsx +({ data, ...props }) => !data? + <LinearProgress /> : + <MyInput data={data} /> +``` diff --git a/docs/translation.md b/docs/translation.md new file mode 100644 index 0000000..e02b941 --- /dev/null +++ b/docs/translation.md @@ -0,0 +1,395 @@ +--- +id: translation +title: Translation +--- + + +The react-admin interface uses English as the default language. But it also supports any other language, thanks to the [polyglot.js](http://airbnb.io/polyglot.js/) library. + +## Changing Locale + +If you want to use another locale, you'll have to install a third-party package. For instance, to change the interface to French, you must install the `ra-language-french` npm package then instruct react-admin to use it. + +The `<Admin>` component has an `i18nProvider` prop, which accepts a function with the following signature: + +```js +const i18nProvider = locale => messages; +``` + +The `messages` should be a dictionary of interface and resource names (see the [Translation Messages section](#translation-messages) below for details about the dictionary format). + +React-admin calls the `i18nProvider` when it starts, passing the `locale` specified on the `Admin` component as parameter. The provider must return the messages synchronously. React-admin also calls the `i18nProvider` whenever the locale changes, passing the new locale as parameter. So the simplest example for a multilingual interface reads as follow: + +```jsx +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import frenchMessages from 'ra-language-french'; +import englishMessages from 'ra-language-english'; + +const messages = { + fr: frenchMessages, + en: englishMessages, +} +const i18nProvider = locale => messages[locale]; + +const App = () => ( + <Admin locale="en" i18nProvider={i18nProvider}> + ... + </Admin> +); + +export default App; +``` + +The `i18nProvider` may return a promise for locale change calls (except the initial call, when the app starts). This can be useful to only load the needed locale. For example: + +```js +import englishMessages from '../en.js'; + +const asyncMessages = { + fr: () => import('../i18n/fr.js').then(messages => messages.default), + it: () => import('../i18n/it.js').then(messages => messages.default), +}; + +const i18nProvider = locale => { + if (locale === 'en') { + // initial call, must return synchronously + return englishMessages; + } + // change of locale after initial call returns a promise + return asyncMessages[params.locale](); +} + +const App = () => ( + <Admin locale="en" i18nProvider={i18nProvider}> + ... + </Admin> +); +``` + +## Available Locales + +You can find translation packages for the following languages: + +- Chinese (`cn`): [chen4w/ra-language-chinese](https://github.com/chen4w/ra-language-chinese) +- Czech (`cs`): [binao/ra-language-czech](https://github.com/binao/ra-language-czech) +- Dutch (`nl`): [pimschaaf/ra-language-dutch](https://github.com/pimschaaf/ra-language-dutch) +- English (`en`): [marmelab/ra-language-english](https://github.com/marmelab/react-admin/tree/master/packages/ra-language-english) +- French (`fr`): [marmelab/ra-language-french](https://github.com/marmelab/react-admin/tree/master/packages/ra-language-french) +- German (`de`): [greenbananaCH/ra-language-german](https://github.com/greenbananaCH/ra-language-german) +- Indonesian (`id`): [ronadi/ra-language-indonesian](https://github.com/ronadi/ra-language-indonesian) +- Italian (`it`): [stefsava/ra-language-italian](https://github.com/stefsava/ra-language-italian) +- Polish (`pl`): [tskorupka/ra-language-polish](https://github.com/tskorupka/ra-language-polish) +- Portuguese (`pt`): [marquesgabriel/ra-language-portuguese](https://github.com/marquesgabriel/ra-language-portuguese) +- Russian (`ru`): [klucherev/ra-language-russian](https://github.com/klucherev/ra-language-russian) +- Slovak (`sk`): [zavadpe/ra-language-slovak](https://github.com/zavadpe/ra-language-slovak) +- Spanish (`es`): [blackboxvision/ra-language-spanish](https://github.com/BlackBoxVision/ra-language-spanish) +- Ukrainian (`ua`): [koresar/ra-language-ukrainian](https://github.com/koresar/ra-language-ukrainian) + +The previous version of react-admin, called admin-on-rest, was translated in the following languages: + +- Arabic ( `ع` ): [aymendhaya/aor-language-arabic](https://github.com/aymendhaya/aor-language-arabic) +- Chinese (`cn`): [downup2u/aor-language-chinese](https://github.com/downup2u/aor-language-chinese) +- Chinese (Traditional) (`cht`): [leesei/aor-language-chinese-traditional](https://github.com/leesei/aor-language-chinese-traditional) +- Croatian (`hr`): [ariskemper/aor-language-croatian](https://github.com/ariskemper/aor-language-croatian) +- Czech (`cs`): [magikMaker/aor-language-czech](https://github.com/magikMaker/aor-language-czech) +- Danish (`da`): [SSA111/aor-language-danish](https://github.com/SSA111/aor-language-danish) +- Farsi (`fa`): [hamidfzm/aor-language-farsi](https://github.com/hamidfzm/aor-language-farsi) +- Finnish (`fi`): [Joni-Aaltonen/aor-language-finnish](https://github.com/Joni-Aaltonen/aor-language-finnish) +- German (`de`): [der-On/aor-language-german](https://github.com/der-On/aor-language-german) +- Greek (`el`): [zifnab87/aor-language-greek](https://github.com/zifnab87/aor-language-greek) +- Hebrew (`he`): [motro/aor-language-hebrew](https://github.com/motro/aor-language-hebrew) +- Hungarian (`hu`): [s33m4nn/aor-language-hungarian](https://github.com/s33m4nn/aor-language-hungarian) +- Indonesian (`id`): [ronadi/aor-language-indonesian](https://github.com/ronadi/aor-language-indonesian) +- Japanese (`ja`): [kuma-guy/aor-language-japanese](https://github.com/kuma-guy/aor-language-japanese) +- Norwegian (`nb`): [zeusbaba/aor-language-norwegian](https://github.com/zeusbaba/aor-language-norwegian) +- Russian (`ru`): [cytomich/aor-language-russian](https://github.com/cytomich/aor-language-russian) +- Slovenian (`sl`): [ariskemper/aor-language-slovenian](https://github.com/ariskemper/aor-language-slovenian) +- Spanish (`es`): [blackboxvision/aor-language-spanish](https://github.com/BlackBoxVision/aor-language-spanish) +- Swedish (`sv`): [StefanWallin/aor-language-swedish](https://github.com/StefanWallin/aor-language-swedish) +- Thai (`th`): [liverbool/aor-language-thai](https://github.com/liverbool/aor-language-thai) +- Turkish (`tr`): [ismailbaskin/aor-language-turkish](https://github.com/ismailbaskin/aor-language-turkish) +- Ukrainian (`uk`): [vitivs/aor-language-ukrainian](https://github.com/vitivs/aor-language-ukrainian) +- Vietnamese (`vi`): [kimkha/aor-language-vietnamese](https://github.com/kimkha/aor-language-vietnamese) + +These packages are not directly interoperable with react-admin, but the upgrade is straightforward; rename the root key from "aor" to "ra". We invite the authors of the packages listed above to republish their translations for react-admin, using a different package name. + +If you want to contribute a new translation, feel free to submit a pull request to update [this page](https://github.com/marmelab/react-admin/blob/master/docs/Translation.md) with a link to your package. + +## Changing Locale At Runtime + +If you want to offer the ability to change locale at runtime, you must provide the messages for all possible translations: + +```jsx +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import englishMessages from 'ra-language-english'; +import frenchMessages from 'ra-language-french'; + +const messages = { + fr: frenchMessages, + en: englishMessages, +}; +const i18nProvider = locale => messages[locale]; + +const App = () => ( + <Admin locale="en" i18nProvider={i18nProvider}> + ... + </Admin> +); + +export default App; +``` + +Then, dispatch the `CHANGE_LOCALE` action, by using the `changeLocale` action creator. For instance, the following component switches language between English and French: + +```jsx +import React, { Component } from 'react'; +import { connect } from 'react-redux'; +import Button from '@material-ui/core/Button'; +import { changeLocale as changeLocaleAction } from 'react-admin'; + +class LocaleSwitcher extends Component { + switchToFrench = () => this.props.changeLocale('fr'); + switchToEnglish = () => this.props.changeLocale('en'); + + render() { + const { changeLocale } = this.props; + return ( + <div> + <div>Language</div> + <Button onClick={this.switchToEnglish}>en</Button> + <Button onClick={this.switchToFrench}>fr</Button> + </div> + ); + } +} + +export default connect(undefined, { changeLocale: changeLocaleAction })(LocaleSwitcher); +``` + +## Using The Browser Locale + +React-admin provides a helper function named `resolveBrowserLocale()`, which helps you to introduce a dynamic locale attribution based on the locale configured in the user's browser. To use it, simply pass the function as `locale` prop. + +```jsx +import React from 'react'; +import { Admin, Resource, resolveBrowserLocale } from 'react-admin'; +import englishMessages from 'ra-language-english'; +import frenchMessages from 'ra-language-french'; + +const messages = { + fr: frenchMessages, + en: englishMessages, +}; +const i18nProvider = locale => messages[locale]; + +const App = () => ( + <Admin locale={resolveBrowserLocale()} i18nProvider={i18nProvider}> + ... + </Admin> +); + +export default App; +``` + +## Translation Messages + +The `message` returned by the `i18nProvider` value should be a dictionary where the keys identify interface components, and values are the translated string. This dictionary is a simple JavaScript object looking like the following: + +```jsx +{ + ra: { + action: { + delete: 'Delete', + show: 'Show', + list: 'List', + save: 'Save', + create: 'Create', + edit: 'Edit', + cancel: 'Cancel', + }, + ... + }, +} +``` + +All core translations are in the `ra` namespace, in order to prevent collisions with your own custom translations. The root key used at runtime is determined by the value of the `locale` prop. + +The default messages are available [here](https://github.com/marmelab/react-admin/blob/master/packages/ra-language-english/index.js). + +## Translating Resource and Field Names + +By default, React-admin uses resource names ("post", "comment", etc) and field names ("title", "first_name", etc) everywhere in the interface. It simply "humanizes" the technical identifiers to make them look better (e.g. "first_name" becomes "First name"). + +However, before humanizing names, react-admin checks the `messages` dictionary for a possible translation, with the following keys: + +- `${locale}.resources.${resourceName}.name` for resource names (used for the menu and page titles) +- `${locale}.resources.${resourceName}.fields.${fieldName}` for field names (used for datagrid header and form input labels) + +This lets you translate your own resource and field names by passing a `messages` object with a `resources` key: + +```jsx +{ + resources: { + shoe: { + name: 'Shoe |||| Shoes', + fields: { + model: 'Model', + stock: 'Nb in stock', + color: 'Color', + }, + }, + customer: { + name: 'Customer |||| Customers', + fields: { + first_name: 'First name', + last_name: 'Last name', + dob: 'Date of birth', + } + } + }, + ... +} +``` + +As you can see, [polyglot pluralization](http://airbnb.io/polyglot.js/#pluralization) is used here, but it is optional. + +Using `resources` keys is an alternative to using the `label` prop in Field and Input components, with the advantage of supporting translation. + +## Mixing Interface and Domain Translations + +When translating an admin, interface messages (e.g. "List", "Page", etc.) usually come from a third-party package, while your domain messages (e.g. "Shoe", "Date of birth", etc.) come from your own code. That means you need to combine these messages before passing them to `<Admin>`. The recipe for combining messages is to use ES6 destructuring: + +```jsx +// interface translations +import englishMessages from 'ra-language-english'; +import frenchMessages from 'ra-language-french'; + +// domain translations +import * as domainMessages from './i18n'; + +const messages = { + fr: { ...frenchMessages, ...domainMessages.fr }, + en: { ...englishMessages, ...domainMessages.en }, +}; +const i18nProvider = locale => messages[locale]; + +const App = () => ( + <Admin i18nProvider={i18nProvider}> + ... + </Admin> +); +``` + +## Translating Your Own Components + +The translation system use the React `context` to pass translations down the component tree. To translate a sentence, use the `translate` function from the context. Of course, this assumes that you've previously added the corresponding translation to the `messages` props of the `Admin` component. + +```jsx +// in src/MyHelloButton.js +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +class MyHelloButton { + render() { + const { translate } = this.context; + return <button>{translate('myroot.hello.world')}</button>; + } +} +MyHelloButton.contextTypes = { + translate: PropTypes.func, +}; + +// in src/App.js +const messages = { + en: { + myroot: { + hello: { + world: 'Hello, World!', + }, + }, + }, +}; +``` + +However, using the context makes components harder to test. That's why react-admin provides a `translate` Higher-Order Component, which simply passes the `translate` function from context to props: + +```jsx +// in src/MyHelloButton.js +import React from 'react'; +import { translate } from 'react-admin'; + +const MyHelloButton = ({ translate }) => ( + <button>{translate('myroot.hello.world')}</button> +); + +export default translate(MyHelloButton); +``` + +**Tip**: For your message identifiers, choose a different root name than `ra` and `resources`, which are reserved. + +**Tip**: Don't use `translate` for Field and Input labels, or for page titles, as they are already translated: + +```jsx +// don't do this +<TextField source="first_name" label={translate('myroot.first_name')} /> + +// do this instead +<TextField source="first_name" label="myroot.first_name" /> + +// or even better, use the default translation key +<TextField source="first_name" /> +// and translate the `resources.customers.fields.first_name` key +``` + +## Using Specific Polyglot Features + +Polyglot.js is a fantastic library: in addition to being small, fully maintained, and totally framework agnostic, it provides some nice features such as interpolation and pluralization, that you can use in react-admin. + +```jsx +const messages = { + 'hello_name': 'Hello, %{name}', + 'count_beer': 'One beer |||| %{smart_count} beers', +} + +// interpolation +translate('hello_name', { name: 'John Doe' }); +=> 'Hello, John Doe.' + +// pluralization +translate('count_beer', { smart_count: 1 }); +=> 'One beer' + +translate('count_beer', { smart_count: 2 }); +=> '2 beers' + +// default value +translate('not_yet_translated', { _: 'Default translation' }) +=> 'Default translation' +``` + +To find more detailed examples, please refer to [http://airbnb.io/polyglot.js/](http://airbnb.io/polyglot.js/) + +## Notifications With Variables + +It is possible to pass variables for polyglot interpolation with custom notifications. For example: + +```js +showNotification('myroot.hello.world', 'info', { messageArgs: { name: 'Planet Earth' } }); +``` + +Assuming you have the following in your custom messages: + +```js +// in src/App.js +const messages = { + en: { + myroot: { + hello: { + world: 'Hello, %{name}!', + }, + }, + }, +}; +``` diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 0000000..2ff84dc --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,697 @@ +--- +id: tutorial +title: Tutorial +--- + +This 15 minutes tutorial will expose how to create a new admin app based on an existing REST API. + +<video width="800" height="600" controls> + <source src="http://static.marmelab.com/react-admin/react-admin.mp4" type="video/mp4"> +Your browser does not support the video tag. +</video> + +## Setting Up + +React-admin uses React. We'll use Facebook's [create-react-app](https://github.com/facebookincubator/create-react-app) to create an empty React app, and install the `react-admin` package: + +```sh +npm install -g create-react-app +create-react-app test-admin +cd test-admin/ +yarn add react-admin +yarn start +``` + +You should be up and running with an empty React application on port 3000. + +## Using an API As Data Source + +React-admin runs in the browser, and uses APIs for fetching and storing data. + +We'll be using [JSONPlaceholder](http://jsonplaceholder.typicode.com/), a fake REST API designed for testing and prototyping, as the datasource for the admin. Here is what it looks like: + +``` +curl http://jsonplaceholder.typicode.com/posts/12 +``` + +```json +{ + "id": 12, + "title": "in quibusdam tempore odit est dolorem", + "body": "itaque id aut magnam\npraesentium quia et ea odit et ea voluptas et\nsapiente quia nihil amet occaecati quia id voluptatem\nincidunt ea est distinctio odio", + "userId": 2 +} +``` + +JSONPlaceholder provides endpoints for posts, comments, and users. The admin we'll build should allow to Create, Retrieve, Update, and Delete (CRUD) these resources. + +## Making Contact With The API Using a Data Provider + +Bootstrap the admin app by replacing the `src/App.js` by the following code: + +```jsx +// in src/App.js +import React from 'react'; +import { Admin, Resource } from 'react-admin'; +import jsonServerProvider from 'ra-data-json-server'; + +const dataProvider = jsonServerProvider('http://jsonplaceholder.typicode.com'); +const App = () => <Admin dataProvider={dataProvider} />; + +export default App; +``` + +The `App` component now renders an `<Admin>` component, which is the root component of a react-admin application. This component expects a `dataProvider` prop - a function capable of fetching data from an API. Since there is no standard for data exchanges between computers, you will probably have to write a custom provider to connect react-admin to your own APIs - but we'll dive into Data Providers later. For now, let's take advantage of the `ra-data-json-server` data provider, which speaks the same REST dialect as JSONPlaceholder. + +```sh +yarn add ra-data-json-server +``` + +That's enough for react-admin to run an empty app. Now it's time to add features! + +## Mapping API Endpoints With Resources + +The `<Admin>` component expects one or more `<Resource>` child components. Each resource maps a name to an endpoint in the API. Edit the `App.js` file to add a `posts` resource: + +```jsx +// in src/App.js +import { PostList } from './posts'; + +const App = () => ( + <Admin dataProvider={dataProvider}> + <Resource name="posts" list={PostList} /> + </Admin> +); +``` + +**Tip**: We'll define the `<PostList>` component in the next section. + +The line `<Resource name="posts" />` informs react-admin to fetch the "posts" records from the [http://jsonplaceholder.typicode.com/posts](http://jsonplaceholder.typicode.com/posts) URL. + + +## Displaying A List Of Records + +`<Resource>` also defines the React components to use for each CRUD operation (`list`, `create`, `edit`, and `show`). The `list={PostList}` prop means that react-admin should use the `<PostList>` component to display the list of posts. Create that component as follows: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="title" /> + <TextField source="body" /> + </Datagrid> + </List> +); +``` + +The main component of the post list is a `<List>` component, responsible for grabbing the information from the API, displaying the page title, and handling pagination. This list then delegates the display of the actual list of posts to its child. In this case, that's a `<Datagrid>` component, which renders a table with one row for each record. The datagrid uses its child components (here, a list of `<TextField>`) to determine the columns to render. Each Field component maps a different field in the API response, specified by the `source` prop. + +That's enough to display the post list: + +![Simple posts list](https://marmelab.com/react-admin/img/simple-post-list.png) + +If you look at your browser network tab in the developer tools, you'll notice that the application fetched the `http://jsonplaceholder.typicode.com/posts` URL, then used the results to build the datagrid. That's basically how react-admin works. + +The list is already functional: you can reorder it by clicking on column headers, or change pages by using the bottom pagination controls. The `ra-data-json-server` data provider translates these actions to a query string that JSONPlaceholder understands. + +## Using Field Types + +You've just met the `<TextField>` component. React-admin provides more Field components, mapping various data types: number, date, image, HTML, array, reference, etc. + +For instance, [the `/users` endpoint in JSONPlaceholder](http://jsonplaceholder.typicode.com/users) contains emails. + +``` +curl http://jsonplaceholder.typicode.com/users/2 +``` + +```json +{ + "id": 2, + "name": "Ervin Howell", + "username": "Antonette", + "email": "Shanna@melissa.tv", +} +``` + +Let's create a new `users` resource to fetch that endpoint. Add it in `src/App.js`: + +```jsx +// in src/App.js +import { PostList } from './posts'; +import { UserList } from './users'; + +const App = () => ( + <Admin dataProvider={dataProvider}> + <Resource name="posts" list={PostList} /> + <Resource name="users" list={UserList} /> + </Admin> +); +``` + +Now, create a `users.js` file exporting a `UserList`, using `<EmailField>` to map the `email` field: + +```jsx +// in src/users.js +import React from 'react'; +import { List, Datagrid, EmailField, TextField } from 'react-admin'; + +export const UserList = (props) => ( + <List title="All users" {...props}> + <Datagrid> + <TextField source="id" /> + <TextField source="name" /> + <TextField source="username" /> + <EmailField source="email" /> + </Datagrid> + </List> +); +``` + +![Simple user datagrid](https://marmelab.com/react-admin/img/simple-user-list.png) + +The sidebar now gives access to the second resource, "users". You can click on it, it's working! The users list shows email addresses as a `<a href="mailto:">` tags. + +In react-admin, fields are simple React components. At runtime, they receive the `record` fetched from the API (e.g. `{ "id": 2, "name": "Ervin Howell", "username": "Antonette", "email": "Shanna@melissa.tv" }`), and the `source` field they should display (e.g. `email`). + +That means that writing a custom Field component is really simple. For instance, to create an `UrlField`: + +```jsx +// in src/MyUrlField.js +import React from 'react'; +import PropTypes from 'prop-types'; + +const UrlField = ({ record = {}, source }) => + <a href={record[source]}> + {record[source]} + </a>; + +UrlField.propTypes = { + record: PropTypes.object, + source: PropTypes.string.isRequired, +}; + +export default UrlField; +``` + +## Handling Relationships + +In JSONPlaceholder, each `post` record includes a `userId` field, which points to a `user`: + +```json +{ + "id": 1, + "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", + "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto", + "userId": 1 +} +``` + +React-admin knows how to take advantage of these foreign keys to fetch references. For instance, to include the user name in the posts list, use the `<ReferenceField>`: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Datagrid, TextField, EmailField, ReferenceField } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <ReferenceField label="User" source="userId" reference="users"> + <TextField source="name" /> + </ReferenceField> + <TextField source="title" /> + <TextField source="body" /> + </Datagrid> + </List> +); +``` + +When displaying the posts list, the app now fetches related user records, and displays their `name` as a `<TextField>`. Notice the `label` property: you can use it on any field component to customize the field label. + +![reference posts in comment list](https://marmelab.com/react-admin/img/reference-posts.png) + +**Tip**: The `<ReferenceField>` component alone doesn't display anything. It just fetches the reference data, and passes it as a `record` to its child component. Just like the `<List>` component, all `<Reference>` components are only responsible for fetching and preparing data, and delegate rendering to their children. + +**Tip**: Look at the network tab of your browser again: react-admin deduplicates requests for users, and aggregates them in order to make only *one* HTTP request to the `/users` endpoint for the whole datagrid. That's one of many optimizations that keep the UI fast and responsive. + +## Adding Creation and Editing Capabilities + +An admin interface isn't just about displaying remote data, it should also allow creating and editing records. React-admin provides `<Create>` and `<Edit>` components for that purpose. Add them to the `posts.js` script: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Edit, Create, Datagrid, ReferenceField, TextField, EditButton, DisabledInput, LongTextInput, ReferenceInput, SelectInput, SimpleForm, TextInput } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Datagrid> + <TextField source="id" /> + <ReferenceField label="User" source="userId" reference="users"> + <TextField source="name" /> + </ReferenceField> + <TextField source="title" /> + <TextField source="body" /> + <EditButton /> + </Datagrid> + </List> +); + +const PostTitle = ({ record }) => { + return <span>Post {record ? `"${record.title}"` : ''}</span>; +}; + +export const PostEdit = (props) => ( + <Edit title={<PostTitle />} {...props}> + <SimpleForm> + <DisabledInput source="id" /> + <ReferenceInput label="User" source="userId" reference="users"> + <SelectInput optionText="name" /> + </ReferenceInput> + <TextInput source="title" /> + <LongTextInput source="body" /> + </SimpleForm> + </Edit> +); + +export const PostCreate = (props) => ( + <Create {...props}> + <SimpleForm> + <ReferenceInput label="User" source="userId" reference="users"> + <SelectInput optionText="name" /> + </ReferenceInput> + <TextInput source="title" /> + <LongTextInput source="body" /> + </SimpleForm> + </Create> +); +``` + +If you've understood the `<List>` component, the `<Edit>` and `<Create>` components will be no surprise. They are responsible for fetching the record (or initializing an empty record in the case of `<Create>`), and displaying the page title. They pass the record down to the `<SimpleForm>` component, which is responsible for the form layout, default values, and validation. Just like `<Datagrid>`, `<SimpleForm>` uses its children to determine the form inputs to display. It expects *input components* as children. `<DisabledInput>`, `<TextInput>`, `<LongTextInput>`, and `<ReferenceInput>` are such inputs. + +As for the `<ReferenceInput>`, it takes the same props as the `<ReferenceField>` (used earlier in the list page). `<ReferenceInput>` uses these props to fetch the API for possible references related to the current record (in this case, possible `users` for the current `post`). It then passes these possible references to the child component (`<SelectInput>`), which is responsible for displaying them (via their `name` in that case), and letting the user select one. `<SelectInput>` renders as a `<select>` tag in HTML. + +**Tip**: The `<Edit>` and the `<Create>` components use almost the same child form, except for the additional `id` input in `<Edit>`. In most cases, the forms for creating and editing a record are a bit different. But if they are the same, you can share a common form component between the two. + +Notice the additional `<EditButton>` field in the `<PostList>` children: that's what gives access to the post editing page. Also, the `<Edit>` component uses a custom `<PostTitle>` component as title, which shows the way to customize the title for a given page. + +To use the new `<PostEdit>` and `<PostCreate>` components in the posts resource, just add them as `edit` and `create` attributes in the `<Resource>` component: + +```jsx +// in src/App.js +import { PostList, PostEdit, PostCreate } from './posts'; +import { UserList } from './users'; + +const App = () => ( + <Admin dataProvider={dataProvider}> + <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} /> + // ... + </Admin> +); +``` + +React-admin automatically adds a "create" button on top of the posts list to give access to the `<PostCreate>` component. And the `<EditButton>` renders in each line of the list to give access to the `<PostEdit>` component. + +![post list with access to edit and create](https://marmelab.com/react-admin/img/editable-post.png) + +The form rendered in the create and edit pages is already functional. It issues `POST` and `PUT` requests to the REST API upon submission. + +![post edition form](https://marmelab.com/react-admin/img/post-edition.png) + +**Note**: JSONPlaceholder is a read-only API; although it seems to accept `POST` and `PUT` requests, it doesn't take into account the creations and edits - that's why, in this particular case, you will see errors after creation, and you won't see your edits after you save them. It's just an artifact of JSONPlaceholder. + +React-admin uses *optimistic rendering*. That means that, when you edit a record and hit the "Save" button, the UI displays a confirmation and displays the updated data *before sending the update query to server*. Not only does this make the interface ultra fast, it also allows the "Undo" feature. It's already functional in the admin at that point. Try editing a record, then hit the "Undo" link in the black confirmation bar before it slides out. You'll see that the app does not send the `UPDATE` query to the API, and displays the non-modified data. + +**Note**: When you add the ability to edit an item, you also add the ability to delete it. The "Delete" button in the edit view is fully working out of the box. + +## Adding Search And Filters To The List + +Let's get back to the post list for a minute. It offers sorting and pagination, but one feature is missing: the ability to search content. + +React-admin can use Input components to create a multi-criteria search engine in the list view. First, create a `<Filter>` component just like you would write a `<SimpleForm>` component, using input components as children. Then, add it to the list using the `filters` prop: + +```jsx +// in src/posts.js +import { Filter, ReferenceInput, SelectInput, TextInput } from 'react-admin'; + +const PostFilter = (props) => ( + <Filter {...props}> + <TextInput label="Search" source="q" alwaysOn /> + <ReferenceInput label="User" source="userId" reference="users" allowEmpty> + <SelectInput optionText="name" /> + </ReferenceInput> + </Filter> +); + +export const PostList = (props) => ( + <List {...props} filters={<PostFilter />}> + // ... + </List> +); +``` + +The first filter, 'q', takes advantage of a full-text functionality offered by JSONPlaceholder. It is `alwaysOn`, so it always appears on the screen. The second filter, 'userId', can be added by way of the "add filter" button, located on the top of the list. As it's a `<ReferenceInput>`, it's already populated with possible users. It can be turned off by the end user. + +![posts search engine](https://marmelab.com/react-admin/img/filters.gif) + +Filters are "search-as-you-type", meaning that when the user enters new values in the filter form, the list refreshes (via an API request) immediately. + +## Customizing the Menu Icons + +The sidebar menu shows the same icon for both posts and users. Customizing the menu icon is just a matter of passing an `icon` attribute to each `<Resource>`: + +```jsx +// in src/App.js +import PostIcon from '@material-ui/icons/Book'; +import UserIcon from '@material-ui/icons/Group'; + +const App = () => ( + <Admin dataProvider={dataProvider}> + <Resource name="posts" list={PostList} edit={PostEdit} create={PostCreate} icon={PostIcon} /> + <Resource name="users" list={UserList} icon={UserIcon} /> + </Admin> +); +``` + +![custom menu icons](https://marmelab.com/react-admin/img/custom-menu.gif) + +## Using a Custom Home Page + +By default, react-admin displays the list page of the first resource as home page. If you want to display a custom component instead, pass it in the `dashboard` prop of the `<Admin>` component. + +```jsx +// in src/Dashboard.js +import React from 'react'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; + +export default () => ( + <Card> + <CardHeader title="Welcome to the administration" /> + <CardContent>Lorem ipsum sic dolor amet...</CardContent> + </Card> +); +``` + +```jsx +// in src/App.js +import Dashboard from './Dashboard'; + +const App = () => ( + <Admin dashboard={Dashboard} dataProvider={dataProvider}> + // ... + </Admin> +); +``` + +![Custom home page](https://marmelab.com/react-admin/img/dashboard.png) + +## Adding a Login Page + +Most admin apps require authentication. React-admin can check user credentials before displaying a page, and redirect to a login form when the REST API returns a 403 error code. + +*What* those credentials are, and *how* to get them, are questions that you, as a developer, must answer. React-admin makes no assumption about your authentication strategy (basic auth, OAuth, custom route, etc), but gives you the hooks to plug your logic at the right place - by calling an `authProvider` function. + +For this tutorial, since there is no public authentication API we can use, let's use a fake authentication provider that accepts every login request, and stores the `username` in `localStorage`. Each page change will require that `localStorage` contains a `username` item. + +The `authProvider` is a simple function, which must return a `Promise`: + +```jsx +// in src/authProvider.js +import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin'; + +export default (type, params) => { + // called when the user attempts to log in + if (type === AUTH_LOGIN) { + const { username } = params; + localStorage.setItem('username', username); + // accept all username/password combinations + return Promise.resolve(); + } + // called when the user clicks on the logout button + if (type === AUTH_LOGOUT) { + localStorage.removeItem('username'); + return Promise.resolve(); + } + // called when the API returns an error + if (type === AUTH_ERROR) { + const { status } = params; + if (status === 401 || status === 403) { + localStorage.removeItem('username'); + return Promise.reject(); + } + return Promise.resolve(); + } + // called when the user navigates to a new location + if (type === AUTH_CHECK) { + return localStorage.getItem('username') + ? Promise.resolve() + : Promise.reject(); + } + return Promise.reject('Unknown method'); +}; +``` + +**Tip**: As the `dataProvider` response is asynchronous, you can easily fetch an authentication server in there. + +To enable this authentication strategy, pass the client as the `authProvider` prop in the `<Admin>` component: + +```jsx +// in src/App.js +import Dashboard from './Dashboard'; +import authProvider from './authProvider'; + +const App = () => ( + <Admin dashboard={Dashboard} authProvider={authProvider} dataProvider={dataProvider}> + // ... + </Admin> +); +``` + +Once the app reloads, it's now behind a login form that accepts everyone: + +![Login form](https://marmelab.com/react-admin/img/login.gif) + +## Supporting Mobile Devices + +The react-admin layout is already responsive. Try to resize your browser to see how the sidebar switches to a drawer on smaller screens. + +But a responsive layout is not enough to make a responsive app. Datagrid components work well on desktop, but are absolutely not adapted to mobile devices. If your admin must be used on mobile devices, you'll have to provide an alternative component for small screens. + +First, you should know that you don't have to use the `<Datagrid>` component as `<List>` child. You can use any other component you like. For instance, the `<SimpleList>` component: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, SimpleList } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + /> + </List> +); +``` + +The `<SimpleList>` component uses [material-ui's `<List>` and `<ListItem>` components](http://www.material-ui.com/#/components/list), and expects functions as `primaryText`, `secondaryText`, and `tertiaryText` props. + +<img src="https://marmelab.com/react-admin/img/mobile-post-list.png" alt="Mobile post list" style="display:block;margin:2em auto;box-shadow:none;filter:drop-shadow(13px 12px 7px rgba(0,0,0,0.5));" /> + +**Note:** We switched to a custom API for those screenshots in order to demonstrate how to use some of the `SimpleList` component props. + +That works fine on mobile, but now the desktop user experience is worse. The best compromise would be to use `<SimpleList>` on small screens, and `<Datagrid>` on other screens. That's where the `<Responsive>` component comes in: + +```jsx +// in src/posts.js +import React from 'react'; +import { List, Responsive, SimpleList, Datagrid, TextField, ReferenceField, EditButton } from 'react-admin'; + +export const PostList = (props) => ( + <List {...props}> + <Responsive + small={ + <SimpleList + primaryText={record => record.title} + secondaryText={record => `${record.views} views`} + tertiaryText={record => new Date(record.published_at).toLocaleDateString()} + /> + } + medium={ + <Datagrid> + <TextField source="id" /> + <ReferenceField label="User" source="userId" reference="users"> + <TextField source="name" /> + </ReferenceField> + <TextField source="title" /> + <TextField source="body" /> + <EditButton /> + </Datagrid> + } + /> + </List> +); +``` + +This works exactly the way you expect. The lesson here is that react-admin takes care of responsive web design for the layout, but it's your job to use `<Responsive>` in pages. + +![Responsive List](https://marmelab.com/react-admin/img/responsive-list.gif) + +## Connecting To A Real API + +Here is the elephant in the room of this tutorial. In real world projects, the dialect of your API (REST? GraphQL? Something else?) won't match the JSONPLaceholder dialect. Writing a Data Provider is probably the first thing you'll have to do to make react-admin work. Depending on your API, this can require a few hours of additional work. + +React-admin delegates every data query to a Data Provider function. This function must simply return a promise for the result. This gives extreme freedom to map any API dialect, add authentication headers, use endpoints from several domains, etc. + +For instance, let's imagine you have to use the `my.api.url` REST API, which expects the following parameters: + +| Action | Expected API request | +|---------------------|---------------------- | +| Get list | `GET http://my.api.url/posts?sort=['title','ASC']&range=[0, 24]&filter={title:'bar'}` | +| Get one record | `GET http://my.api.url/posts/123` | +| Get several records | `GET http://my.api.url/posts?filter={ids:[123,456,789]}` | +| Update a record | `PUT http://my.api.url/posts/123` | +| Create a record | `POST http://my.api.url/posts/123` | +| Delete a record | `DELETE http://my.api.url/posts/123` | + +React-admin defines custom verbs for each of the actions of this list. Just like HTTP verbs (`GET`, `POST`, etc.), react-admin verbs qualify a request to a data provider. React-admin verbs are called `GET_LIST`, `GET_ONE`, `GET_MANY`, `CREATE`, `UPDATE`, and `DELETE`. The Data Provider will have to map each of these verbs to one (or many) HTTP request(s). + +The code for a Data Provider for the `my.api.url` API is as follows: + +```jsx +// in src/dataProvider +import { + GET_LIST, + GET_ONE, + GET_MANY, + GET_MANY_REFERENCE, + CREATE, + UPDATE, + DELETE, + fetchUtils, +} from 'react-admin'; +import { stringify } from 'query-string'; + +const API_URL = 'my.api.url'; + +/** + * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE' + * @param {String} resource Name of the resource to fetch, e.g. 'posts' + * @param {Object} params The Data Provider request params, depending on the type + * @returns {Object} { url, options } The HTTP request parameters + */ +const convertDataProviderRequestToHTTP = (type, resource, params) => { + switch (type) { + case GET_LIST: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]), + filter: JSON.stringify(params.filter), + }; + return { url: `${API_URL}/${resource}?${stringify(query)}` }; + } + case GET_ONE: + return { url: `${API_URL}/${resource}/${params.id}` }; + case GET_MANY: { + const query = { + filter: JSON.stringify({ id: params.ids }), + }; + return { url: `${API_URL}/${resource}?${stringify(query)}` }; + } + case GET_MANY_REFERENCE: { + const { page, perPage } = params.pagination; + const { field, order } = params.sort; + const query = { + sort: JSON.stringify([field, order]), + range: JSON.stringify([(page - 1) * perPage, (page * perPage) - 1]), + filter: JSON.stringify({ ...params.filter, [params.target]: params.id }), + }; + return { url: `${API_URL}/${resource}?${stringify(query)}` }; + } + case UPDATE: + return { + url: `${API_URL}/${resource}/${params.id}`, + options: { method: 'PUT', body: JSON.stringify(params.data) }, + }; + case CREATE: + return { + url: `${API_URL}/${resource}`, + options: { method: 'POST', body: JSON.stringify(params.data) }, + }; + case DELETE: + return { + url: `${API_URL}/${resource}/${params.id}`, + options: { method: 'DELETE' }, + }; + default: + throw new Error(`Unsupported fetch action type ${type}`); + } +}; + +/** + * @param {Object} response HTTP response from fetch() + * @param {String} type One of the constants appearing at the top if this file, e.g. 'UPDATE' + * @param {String} resource Name of the resource to fetch, e.g. 'posts' + * @param {Object} params The Data Provider request params, depending on the type + * @returns {Object} Data Provider response + */ +const convertHTTPResponseToDataProvider = (response, type, resource, params) => { + const { headers, json } = response; + switch (type) { + case GET_LIST: + return { + data: json.map(x => x), + total: parseInt(headers.get('content-range').split('/').pop(), 10), + }; + case CREATE: + return { data: { ...params.data, id: json.id } }; + default: + return { data: json }; + } +}; + +/** + * @param {string} type Request type, e.g GET_LIST + * @param {string} resource Resource name, e.g. "posts" + * @param {Object} payload Request parameters. Depends on the request type + * @returns {Promise} the Promise for response + */ +export default (type, resource, params) => { + const { fetchJson } = fetchUtils; + const { url, options } = convertDataProviderRequestToHTTP(type, resource, params); + return fetchJson(url, options) + .then(response => convertHTTPResponseToDataProvider(response, type, resource, params)); +}; +``` + +**Tip**: `fetchJson()` is just a shortcut for `fetch().then(r => r.json())`, plus a control of the HTTP response code to throw an `HTTPError` in case of 4xx or 5xx response. Feel free to use `fetch()` directly if it doesn't suit your needs. + +Using this provider instead of the previous `jsonServerProvider` is just a matter of switching a function: + +```jsx +// in src/app.js +import dataProvider from './dataProvider'; + +const App = () => ( + <Admin dataProvider={dataProvider}> + // ... + </Admin> +); +``` + +## Conclusion + +React-admin was built with customization in mind. You can replace any react-admin component with a component of your own, for instance to display a custom list layout, or a different edit form for a given resource. + +Now that you've completed the tutorial, continue reading the [react-admin documentation](http://marmelab.com/react-admin/), and read the [Material UI components documentation](http://www.material-ui.com/#/). diff --git a/lerna.json b/lerna.json new file mode 100644 index 0000000..9b58d85 --- /dev/null +++ b/lerna.json @@ -0,0 +1,13 @@ +{ + "lerna": "2.11.0", + "packages": [ + "website" + ], + "changelog": { + "labels": { + "enhancement": ":rocket: Enhancement", + "bug": ":bug: Bug Fix" + }, + "cacheDir": ".changelog" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b358cd0 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "name": "react-admin.com", + "version": "1.0.0", + "author": "Kirk Wang <kirk.w.wang@gmail.com>", + "devDependencies": { + "@9renpoto/eslint-config": "4.4.0", + "@9renpoto/eslint-config-react": "4.3.0", + "eslint": "4.19.1", + "husky": "0.14.3", + "lerna": "2.11.0", + "lint-staged": "7.2.0", + "prettier-eslint-cli": "4.7.1" + }, + "license": "MIT", + "lint-staged": { + "*.{js,jsx}": [ + "prettier-eslint --write", + "eslint", + "git add" + ], + "*.{md,css}": [ + "prettier --write", + "git add" + ] + }, + "main": "n/a", + "private": true, + "workspaces": [ + "website" + ], + "repository": "ssh://git@github.com:Kirk-Wang/react-admin.com.git", + "scripts": { + "build": "lerna run build", + "deploy": "cd website && publish-gh-pages", + "postinstall": "cd website && yarn", + "precommit": "lint-staged", + "start": "cd website && npm start", + "test": "eslint . --ext=js.jsx" + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..04b95fd --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "config:base" + ], + "automerge": true +} diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..c430c4d --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,2 @@ +lib/core/metadata.js +lib/core/MetadataBlog.js diff --git a/website/core/Footer.js b/website/core/Footer.js new file mode 100644 index 0000000..5402ba1 --- /dev/null +++ b/website/core/Footer.js @@ -0,0 +1,49 @@ +const React = require('react') + +module.exports = function Footer ({ + language, + config: { baseUrl, footerIcon, title, repoUrl } +}) { + return ( + <footer className='nav-footer' id='footer'> + <section className='sitemap'> + <a href={baseUrl} className='nav-home'> + <img + src={`${baseUrl}${footerIcon}`} + alt={title} + width='66' + height='58' + /> + </a> + <div> + <h5>Docs</h5> + <a href={`${baseUrl}docs/${language}/intro.html`}>intro</a> + </div> + <div> + <h5>Community</h5> + <a href='#'>Project Chat</a> + </div> + <div> + <h5>More</h5> + {/* <a href={`${baseUrl}blog`}>Blog</a> */} + {/* <a href={repoUrl}>GitHub</a> */} + <a + className='github-button' + href={repoUrl} + data-icon='octicon-star' + data-count-href='#' + data-show-count + data-count-aria-label='# stargazers on GitHub' + aria-label='Star this project on GitHub' + > + Star + </a> + </div> + </section> + + <section className='copyright'> + Copyright © {new Date().getFullYear()} react-admin.com. + </section> + </footer> + ) +} diff --git a/website/i18n/en.json b/website/i18n/en.json new file mode 100644 index 0000000..51933c7 --- /dev/null +++ b/website/i18n/en.json @@ -0,0 +1,41 @@ +{ + "_comment": "This file is auto-generated by write-translations.js", + "localized-strings": { + "next": "Next", + "previous": "Previous", + "tagline": "A frontend framework for building admin SPAs on top of REST services, using React and Material Design", + "actions": "Writing Actions", + "admin-component": "<Admin>", + "authentication": "Authentication", + "authorization": "Authorization", + "creat-edit-view-components": "<Create> and <Edit> Views", + "custom-app": "Including the Admin in Another App", + "data-providers": "Data Providers", + "ecosystem": "Ecosystem", + "faq": "FAQ", + "field-components": "<Field> Components", + "input-components": "<Input> components", + "intro": "Introduction", + "list-view-component": "<List> View", + "reference": "Reference", + "resource-component": "<Resource>", + "show-view-component": "<Show> View", + "theming": "Theming", + "translation": "Translation", + "tutorial": "Tutorial", + "Doc": "Doc", + "Knowledge": "Knowledge" + }, + "pages-strings": { + "Need help?|no description given": "Need help?", + "This project is maintained by a dedicated group of people.|no description given": "This project is maintained by a dedicated group of people.", + "Docs|no description given": "Docs", + "Who's Using This?|no description given": "Who's Using This?", + "This project is used by many folks|no description given": "This project is used by many folks", + "Are you using this project?|no description given": "Are you using this project?", + "Add your profile|no description given": "Add your profile", + "Help Translate|recruit community translators for your project": "Help Translate", + "Edit this Doc|recruitment message asking to edit the doc source": "Edit", + "Translate this Doc|recruitment message asking to translate the docs": "Translate" + } +} diff --git a/website/languages.js b/website/languages.js new file mode 100644 index 0000000..1dc45f0 --- /dev/null +++ b/website/languages.js @@ -0,0 +1,13 @@ +const languages = [ + { + enabled: true, + name: 'English', + tag: 'en' + }, + { + enabled: true, + name: '中文', + tag: 'zh-CN' + } +] +module.exports = languages diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..8bea2a5 --- /dev/null +++ b/website/package.json @@ -0,0 +1,19 @@ +{ + "name": "bonfire-website", + "version": "0.0.1", + "devDependencies": { + "docusaurus": "1.3.2" + }, + "license": "MIT", + "scripts": { + "build": "docusaurus-build", + "crowdin-download": "crowdin --config ../crowdin.yaml download -b master", + "crowdin-upload": "crowdin --config ../crowdin.yaml upload sources --auto-update -b master", + "examples": "docusaurus-examples", + "publish-gh-pages": "docusaurus-publish", + "rename-version": "docusaurus-rename-version", + "start": "docusaurus-start", + "version": "docusaurus-version", + "write-translations": "docusaurus-write-translations" + } +} diff --git a/website/pages/en/help.js b/website/pages/en/help.js new file mode 100755 index 0000000..6e02d06 --- /dev/null +++ b/website/pages/en/help.js @@ -0,0 +1,36 @@ +const React = require('react') +const { translate } = require('../../server/translate.js') +const { Container, GridBlock } = require('../../core/CompLibrary.js') + +module.exports = function Help () { + const supportLinks = [ + { + content: 'Ask questions about the documentation and project', + title: 'Join the community' + }, + { + content: "Find out what's new with this project", + title: 'Stay up to date' + } + ] + + return ( + <div className='docMainWrapper wrapper'> + <Container className='mainContainer documentContainer postContainer'> + <div className='post'> + <header className='postHeader'> + <h2> + <translate>Need help?</translate> + </h2> + </header> + <p> + <translate> + This project is maintained by a dedicated group of people. + </translate> + </p> + <GridBlock contents={supportLinks} layout='threeColumn' /> + </div> + </Container> + </div> + ) +} diff --git a/website/pages/en/index.js b/website/pages/en/index.js new file mode 100755 index 0000000..8aa354f --- /dev/null +++ b/website/pages/en/index.js @@ -0,0 +1,61 @@ +const React = require('react') +const { translate } = require('../../server/translate.js') +const siteConfig = require(process.cwd() + '/siteConfig.js') + +class Button extends React.Component { + render () { + return ( + <div className='pluginWrapper buttonWrapper'> + <a className='button' href={this.props.href} target={this.props.target}> + {this.props.children} + </a> + </div> + ) + } +} + +Button.defaultProps = { + target: '_self' +} + +function HomeSplash ({ language }) { + return ( + <div className='homeContainer'> + <div className='homeSplashFade'> + <div className='wrapper homeWrapper'> + <div className='projectLogo'> + <img src={siteConfig.baseUrl + 'img/bg_home.png'} /> + </div> + <div className='inner'> + <h2 className='projectTitle'> + {siteConfig.title} + <small>{siteConfig.tagline}</small> + </h2> + <div className='section promoSection'> + <div className='promoRow'> + <div className='pluginRowBlock'> + <Button + href={`${siteConfig.baseUrl}docs/${language}/intro.html`} + class='red' + > + <translate>Docs</translate> + </Button> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + ) +} + +module.exports = function Index ({ language }) { + language = language || 'zh-CN' + + return ( + <div> + <HomeSplash language={language} /> + </div> + ) +} diff --git a/website/pages/en/users.js b/website/pages/en/users.js new file mode 100644 index 0000000..8742048 --- /dev/null +++ b/website/pages/en/users.js @@ -0,0 +1,39 @@ +const React = require('react') +const { Container } = require('../../core/CompLibrary.js') +const { translate } = require('../../server/translate.js') +const siteConfig = require(process.cwd() + '/siteConfig.js') + +module.exports = function Users () { + const showcase = siteConfig.users.map((user, i) => ( + <a href={user.infoLink} key={i}> + <img src={user.image} title={user.caption} /> + </a> + )) + + return ( + <div className='mainContainer'> + <Container padding={['bottom', 'top']}> + <div className='showcaseSection'> + <div className='prose'> + <h1> + <translate>Who's Using This?</translate> + </h1> + <p> + <translate>This project is used by many folks</translate> + </p> + </div> + <div className='logos'>{showcase}</div> + <p> + <translate>Are you using this project?</translate> + </p> + <a + href={`${siteConfig.repoUrl}edit/master/website/siteConfig.js`} + className='button' + > + <translate>Add your profile</translate> + </a> + </div> + </Container> + </div> + ) +} diff --git a/website/sidebars.json b/website/sidebars.json new file mode 100644 index 0000000..7c1b8cb --- /dev/null +++ b/website/sidebars.json @@ -0,0 +1,25 @@ +{ + "docs": { + "Knowledge": [ + "intro", + "tutorial", + "data-providers", + "admin-component", + "resource-component", + "list-view-component", + "show-view-component", + "field-components", + "creat-edit-view-components", + "input-components", + "authentication", + "authorization", + "theming", + "actions", + "translation", + "custom-app", + "reference", + "faq", + "ecosystem" + ] + } +} diff --git a/website/siteConfig.js b/website/siteConfig.js new file mode 100644 index 0000000..ddb3ae6 --- /dev/null +++ b/website/siteConfig.js @@ -0,0 +1,37 @@ +const siteConfig = { + title: 'React-Admin', + tagline: 'A frontend framework for building admin SPAs on top of REST services, using React and Material Design', + // url: 'https://kanto-riders.github.io/bonfire/', + url: '/', + baseUrl: '/', + projectName: 'react-admin', + headerLinks: [ + { doc: 'intro', label: 'Doc' }, + // { page: 'help', label: 'Help' }, + // { blog: true, label: 'Blog' }, + // { href: 'https://github.com/kanto-riders/bonfire/', label: 'GitHub' }, + { search: true } + ], + + /* On page navigation for the current documentation page */ + onPageNav: 'separate', + + // users, + headerIcon: 'img/icon_common.png', + footerIcon: 'img/icon_common.png', + favicon: 'img/favicon/favicon.ico', + colors: { + primaryColor: '#272e37', + secondaryColor: '#232930' + }, + copyright: 'Copyright © ' + new Date().getFullYear() + 'React-Admin', + organizationName: 'Kirk-Wang', + highlight: { + theme: 'default' + }, + scripts: ['https://buttons.github.io/buttons.js'], + repoUrl: 'https://github.com/Kirk-Wang/react-admin.com.git' + // gaTrackingId: 'UA-113151047-1' +} + +module.exports = siteConfig diff --git a/website/static/css/custom.css b/website/static/css/custom.css new file mode 100644 index 0000000..a88c521 --- /dev/null +++ b/website/static/css/custom.css @@ -0,0 +1,108 @@ +/* your custom css */ +body { + background: #fff; + font-family: "Apple SD Gothic Neo", "AppleSDGothicNeo", -apple-system, + "Hiragino Kaku Gothic ProN", "メイリオ", Helvetica, Arial, sans-serif; +} +.mainContainer { + background: #ffffff; +} + +.homeContainer { + background: #272e37; +} + +.homeContainer .homeWrapper { + padding: 80px 0; +} + +.homeContainer h2 { + color: #fff; +} + +.imageAlignTop .blockImage { + max-width: 160px; +} + +.productShowcaseSection { + background: #232930; + color: #fff; + padding: 80px 0; +} + +.productShowcaseSection.lightBackground { + background: #272e37; +} + +.promoSection .buttonWrapper:nth-of-type(1) .button { + background: #e85f61; +} + +.promoSection .buttonWrapper:nth-of-type(2) .button { + background: #edae53; +} + +.container.introSection h2 { + color: #fff; +} + +.userSection h2 { + color: #be5baf; + margin: 0; +} + +.userSection p { + margin-top: 30px; +} + +.userSection .button { + color: #be5baf; + border: 1px solid #be5baf; + margin-top: 30px; +} + +.userSection .button:hover { + color: #fff; + background: #be5baf; +} + +footer.nav-footer { + background: #272e37; + box-shadow: none; +} + +footer .sitemap .nav-home { + width: 30px; +} + + + +@media only screen and (min-device-width: 360px) and (max-device-width: 736px) { +} + +@media only screen and (max-width: 736px) { + .introSection .blockElement + .blockElement { + margin-top: 50px; + } +} + +@media only screen and (min-width: 1024px) { + .homeContainer .inner { + position: relative; + z-index: 1; + } + + .homeContainer .homeWrapper .projectLogo { + justify-content: center; + z-index: 1; + } +} + +@media only screen and (max-width: 1023px) { +} + +@media only screen and (min-width: 1400px) { +} + +@media only screen and (min-width: 1500px) { +} diff --git a/website/static/img/bg_home.png b/website/static/img/bg_home.png new file mode 100644 index 0000000000000000000000000000000000000000..f37d6ec65ec3d071c6f81d703b54c8339bafab8c GIT binary patch literal 11936 zcmW+6cOca7`|db<oV__`%PiUBva*SitVEgFJL^Q8>=81HqsY#<%!{*9viCSt2#2pt z^!xPt<MYSoeV^xj-sgFr_gU|gU}kcgmWq=K1On0O>uH;VKwvcR^-z!jBkBvu2_O(0 zq_2JJZb-q-qDz+D<m+GiQTO;MpD?Gzq9F`Ie%D9Y8Inl)`qn0PW}5qNjNcgl_-yKH z&e_uV4IVXOkV@DB-4HuBnGb?pMmXPrGARL}hZZeGr`^5D*DRG~pitnyyL7P_R=nPl z&00pa-kDWN^#4w`%d70ttHL0A%-|s4!?xG%D~V_rvY&#YK`JQfpvzEhEG?FP#_d6t z-nbZC5L%lQc2Q>+B?XO4rcV6Es*@mB2+=g(^UFW^Mz#dCjpcgQ8od~!tqrk2>QS|- z?VFMflMRE2q%~XnXNc7V0Jg5Pdp!x{0(n>Z29}@+<`<hGO#*ANs0W9eEr9=U&%kQr z(tnu)*t)*_vLE}V0a)-mkOIjx$^4%j;W67DU~k(E72j*~pXU!;@nJ(>iU;!}%%g#& zWjAC-B?wx$C@38VmXqNQQtx_mNb4{d_YlO_`yvi~sP7NUcsZ?V??IuUP)~#9Y?<}? zx^|?)c`F!}K~)SMz0Pt9+AEIk1=mQM%Ryr?2yJAe#OH6u?IbPd`r6`VNIh{)GE%Gq zmYt26oHSA*9U%%AWKlt=4LcH~M;BxDOsOc`N**mN;vYgTIHnO968{!CgjuLutZA-# zy2ca^@G=9WK0JA!i5fyh8FpCmA$l#4XvNzke|Z$Yw8GNwio&&*<d}j*d}TTP81Ep_ zMlwk@D~C!Balpxx1IRBY<~q5de-swZW)R63RN7sm2B|m6v$!bGaJ`f^{y9Vw)5bu! z#F3BoDZ8-)^c@=l!D<DRf0V4LuF;kN2pD<t(c!mPdlPbNHUwh~Ah`OLKrqWrj0az2 zdmumJEpX5*<5kq}A2IIfJG=l!`2USqR8zlqqrfsNgjO#Lh-F0939)>FY7f1}z0;4% zu*1mzV|5w@3O+t6CK<1$X4uyU^H&VG(Ula&ME{MQw-qrb1wcw-MW%Fs13CByaSJ&9 zT;05C`Eyye7Mh@_Elq8*GE^FKR<lYd>mHvk*Oqp}zyEC~J0!>=I*MGPNlF{@kcvY8 zv&0qz@;XTcrN__z;Geb_w8yx*y}&a}y+BX=L;3nq3SKveb1~D?a|WdLJffN!{f33{ zHo&s^D}d#ZtO$}!Ur!}D5&j2eNOY5aR=s{rC4k_oQmd0uj)Hj{_!i#De@x{?StUQN zKgJAmtQPArWHN?cseQZrn?oZC$InGhLD}=7sS`4C&J6kh2+#UJaNn<$WBnfsL|dvd zU=ica7}p0h;Z*Fv**sQ-##+r$TRGTiBT_)>ng_z$LXW5@>KkE~G%3MUds15nZR^7! zvFtqczazs?-0YMN8U@jO%Hv>%ERiD5wqfHhLBR~?j2D(<_GAO%h5nIz!oDL&bf)q; z+oIXh#C-ZUB3mP!9?(p0eI*_T{Oa@=PI8mAFSk;-2?O+%23k=`1)sK3_3i?s8}O~D zukCz_6TmDhFq@k&FDNpZ42p{5ywKMM0NyZ7p5Gt|MidHH3sC_8ieCYA9*Xv90D%nn zAK}3}41-PTq%^<0L#54>lO$FpisIsZT=dbTrN98)ZOQ70+?Vs(EjfYw=2FmDd{9;( z7^(!qer=@?%YkS*S|Cr$&L}8#!Thq4kY848cizy0{i_qx{sXR-@S<9*EXy>nZP)kJ zSCMZ7%S)@NMQ0cZh%E$0f{G&H$@Mun+g$H^!<rD_CRnAA3O``GgMCCO-6){l*9P;~ zb9>k!wYI6-=K*4XqC2va0a6@=uY+z|vGJb)Hj2j3M^aueg`XgRois*7{2Sr4f9Xx< zQxV9Jp#W^xryvka8%W0Ko&g3n)EHwnedn4M)^5srQZNEAFs>rjY$HAgsNe@|{Gosz z>S?`Cx1}1s#kGN<r=p<TgiUBmL3?oIBfD)A7(RAN{T{<jMUdk6R*Jzyh^FS~p7}GW zTfqV5z~(fS7;`=CVz-^I`+Ne3(?erp{VD^&V^e+(uYrGZ;9mynnPNEm^T+su6TL&# zC%Vfalq=eeO4ptops1@7-@9JlQ<@kWiUrmh*)u=4OzV$561Sy3=)xDeNim4Q@hzbn zKd{_JkR>NOZKxB@6I=Y@@AH)EWCJjNXJLxXs`#b~*vcK43toBd=uP;DEtX9{g89{H zNxbW&G~Gk_HZ|zmsZ?9Hb{-LWum>$dbrVrCg3?1HOYsZaro6QSQ7Xh8egY46(yG;l zRu8}|bhASp{^Y@Q{oV#UT*RIfCbs2|99Y56`(8@>kCHmpID^-L6X~%etjO<b@SKv$ zFgE4I0T)s_uV8ro4EsiJ_6yyv2JfjN73D57Y_*4i)BejM^MaYh7vAn_XZPFu48Ro$ zSByS<7-8W3j)b~8al=tyFCZtCk3O76tv0mO8*}PY69``ImDya@QJq;^6Cn@<4;Ow# zcP3naALAp^ug{Uvf0<wYdvvL|n{vaXn>vEziCJrysG&#dt;!<j?>}pH3Mj&v)%t0* zmsL6NGz7sd1ir0QzomGJj!^NkIE>M&IM#Cd2rzdsid@y4V)woTGnh`CE{vm2;>0ll zR1}-jf^zM58_IADQ#t`=B5=?WRlAxte;J@{T_(5Uu~|gMEr@bRd1~z^(vCM3#$f(S zc0@7LASNn)HRX@dH(}fO>DHzAutx$cVsH%KQ1sc9jvjLb-M6aEHIFz{Y8r9mfgdw$ zEVbnU8+?@8An#h<IVFWpqPRL#;@HZto*8C-ztWVQ(d1?PfvN&-+61Z|PkbH*sH+{D zHGsjR|E%r9L+-sT7n0)7FJ1(^L8U1l-*!=#o79<t8tpa2dq{NCD!56Tl2cJstRWh& zHC1G<6`5Joa|EMD-lX7+ZMW82X`!)qn_$xe#(8e&LspUY)lKDhX+(EP3joI*`s~$Y zz`Km8PkiceudStY!vv23BI3VRinuYex~PMyM0S^t{T{SnJ_y}$$hU2P&6kOz-(M=a zK*DU;>W9d_Pv?_{bI$Z11b!@g_-yludTUskf^jd8aUP^N7$*4<^&OH)VymJ82>a*G z#=3S;u$0@^+n!Vbi<i+FLdkz`Tb%dic(H;+03$m1I@RXb-O~`~!DpJMGEfT0K_M?J z&1Cqa6ljL^^xT^~8ycB}+01#U{2(@k>D6#Hgq(d^2AI*p`d71&Mix8<_Lmu4QcS-| z<X-Eik01wonO}{|HM(RLfiQr(a{}zwk3ypxMT%@=BSG9iP_S+%x0iFvJwJ9Q<33+U zZlyBawDFre%&_aD=^J>-3gXjkdn2Qle?oTg-#X;tAewz#-HU4IyG_zT>z2DLXZvx{ zri9@PSHOcN1*>lI&<Jd*47H-r&a0Xw4BrcrPm8k*bWy<oa}TUjjB}>N$WkeZGmD1F zyei_**v5jG;f+^UG$ijhRBN7eVGdkz1{%;<luc#sTzW=E&JDE21J6(RAc~g5z?4U{ zpT*#$1Wkul!00rc;UBKt5cqG+SdR9T9jcMadZ8iw>RWH%Iy1)vM>?Ok^N)Uybu$9v z7DDq5a-a<V&{XP)atbz{vaDg@P;)!`oNO`+rT3GxYJ!`rz`-t-*@@9Ns>qxY&A)KQ zAhs|XG8v1?jWrCJU4;2?xL5E;_Nqqs*yH<uKVjW_M0FA2u8I8Gom_8IHEuHN=4w%J z8qDr%c}$aTiA1TWl)N{NF{VIsV2{R<HF(?$#NL<1#mZ_ISWv9Px+Zm5c0UJ3Nora^ zAPZE~o}~U#n!$G&O_Cm!Um{d>HH6o)J5#LKR<)QQB}dwdnxBV8-Dh&-QWCTLBoTky z{40P!@9#;lHQNdR;rK`Kr@q|jDb7wx<eQDZnBhhn#nzfhm{KvsI!1vX{wpOcj2(-s zOe5u{<_3(oT(^E~M5K};p(%nLauxeEX9fTgt_e_V`gW1$aDC4naA2~|3|<E_5<yWb zIhCItU#U#vQS9Z>x5-zD1$pdAjv^B><By_ixVmjnPF0B;_cy_^ua4WemNh?G?tkr1 zPI!qAka%S6w;m)&jQ2^tY_NES46sgBeXe5IBDR!zE~y`$NqJ*E@Gi<DPL6B339eE` zj$f7HQ^|K$bt^&(m-h}Q6xFt*ss7><RxYH8&oiJ$dwG<-BYB4x#}6z$R@WDvwTWAP zRKBKWf32C`G1$ocApoMIrXI=r;^uT7!45ONZ=N@JA~WvGTgx)9{x~Q*a1bm@%008@ z&zim*d;cD}xz(D?V0P`J@`%k#mBQAKKJhe9-u^(%<hZ*`6>+q}NP4iZn1udb7^tUw z{!YBit;TC{bYFTo%l>`vACums<y61<(k(u3p-W5BF>VX?KPD0E3Zpj9UpyYOYyE+G z-^vmd>le9)^Nb6N4`e_v(I^M|sCv<KF|U(c06rxV(ybKZC;UMJnfpy25o6Wo(B-rq zS}DLHfL)%%6bBp7qQxfizvh{qf9tALeDz13LI1|rlE2IKLOA{_1N}aJL3MI#ccjGI ztG_lxCWjj%MdbQ6T|GwLC)PUFOTGE=c|z|dP>SuTwMyyjrqQk3EoY{J_l7P`^Lj)t zYC<@PxnqK_IANFE6S9TjQLvn~^W8+uQ~slrOCH3><R=lzue4Prve{u4n^8BCcROWe zje3lO5`H*5NdB(&>iRmbFlYBAoruEoJB~>1TYr}~p8Pmb`$W?;o)&)B(@KFQBzQoR z@gs%ebSpZ+FYJ1iV_K*5xZa^<ZH$ws2Yq~&x7Z}-R7Z|;;Vi`Kx_ZT57bw0c<;Ywo z#KL!xU&pV3tt0rJ^t;#6;m5-DZRKMSN7<O&=C49<snV*#vN7&Od;Ct!*1d_l@60=l z&3wwBv8f^_p90hqq*(e1DaI^=civ4t7r=>#)hbx?Xkmr6^tbDzB;nylFHJu|Pv!D1 zsSN*9NIf31TQ<F6DS6~ak-+V2X^#Bb`;dG*>qN^o#ZprLO86?JqYyuDqJ8Yes2EX$ zLG6W(O{@rFg*m22uP5ARMUZgv!PY@JRxBCsXK_?p2m>mLVwcG&&ttY8qegy{FZIkP zh;7>iyZY@-Pj=}rNg?<z0_Wjb)?7V`Pk5!=9&r<h*Rx`UA2Z2utbMdIphRcx{ng4g zM;7RJutE_^W`FCJO!{{k<nNdxPk&vcU>+X$hVg1+LgQqgT3Kl#CE#DrhHeDe&5z0K z-C?x)21ht&k1jKikzC`4n^1@Bz&=~_zi_p~qOoo04gZ^;$nmg8rq4gx4hG2h)UT{1 z>M7H8?;3nF@S{fyP|l|g{HAlH+LIkyNaRPzOY#3_r%ePi_=QkzLstEM7q$x_e%g>0 za36sE>CkSci)xFTvcSDlOLuNoW=&s`;$J-o8V37{%ZPQa;ca77*><v7(}#aCMgI6f zt3<9{)n{K1ab$1#7+{U*XG&MKGs2t=+LElAt*ZI<KPxaC*uq*c)XTCd3iOfu<b=8C zgjcc0lk<RWVJp7c7#g;f6mMGbepGHs{y_2*d&1Ut{7&}p{<5a_)33ta6&XtEVui*F zm*9`Sb}U-#-QQGln-5viC92_j{*S!CvPPKt7D~-U9b4qwyXgeI)?bXxnc6{MUvA=d z{EQ|ko-rw`BT?^IN{~SOy#QI|F6}?!fsLb5GnY>uYL8v#Q{<oM^?&wU85C^v_Qfo3 z?M>2f(tH(u2C?r@($>TQG5D}T_|sz3U+a+mXYEC3xp^?QuWacjBg`M&))<I*c$rHF zT3Xw*Mj#}wY1+!=pcL8Wts_YBcL|yAUj&=jlTBH(@tc2UAl*<e)Iu8dI=gL!+#vsR z&QCBkquyOGBDtc&vasK%(ZS>#gg=ytM=4UXF=h+P&U_lVHqx$@QobrLdj&cbubXY= zPOmZPJ6O$`AaP{ev)n=iZJtQ2QV<H&sng$Ef8)Flj-pj{w@TDg=m{ZO<UB(b=qh`P zmY?k3{OEf-zWgd8dzcldqTC}CM?Wm#7ttSo9il2C@>8Vd(bJq}le-G<by21=r+;3| z<SBZ~QQ0PP`OR^50W`}=-kr8ueF6Kbx*7kT>WINI2YbEvQ9YhZ6eoD2ZZdc<RIJ)Q zWHV5Lo2&4>9WKOCl3Lw?3%5%8MpB-V6Nq|E=)3jorqU)BwCGZby7FM;4e`h&P1|d< zVp_Ftd#9tlNi;swY9wHALyrS1!M<+;!Jc<N#pKe2zx<qyH?XcOVu%#jyXjzqv(~Ou zj=o}@+7|9bDblo+hcDhebB>A)aj`Pw7PzY+<IdtWu=&d8C;Dy!8?Q-$p)6bqs8Ph& z=N~P_d3K3Xz8lv4)`VT0HYKgp!BpPhs2lQd2m8uw7S&Dd=&$N6R0@e%Og<{lPR3p` z(v!XiV9sb}e%@?CVP{%VQp#%jUvJ0%&8jK3l%9|FhIAd<ywsC)q5rQ}Os#^4f*Z(f z*mjBpg-N2~;s@bkW1>DnKD2Y8lJeQIIOoMI$f_fxsQUM3{NNYM_^Dp`pL*Ic&8}T^ zr&4v}T9SxBnzg*sqsv|~D;kLwsgmdW!3@_ZLRhN=%YKWOdl9=OV-|@k)`-A@sa>{m zMp(tNT4Uoc{uEsv>#|PrXz4Zi=>&Yk-x`gkG8in;CnCpBhSu0%$blE!iRQf2w~biT z!R!g<3@wsiJ5`H6Ux5S$=b3P9BVcXD?}LZ?;ody3!~qZg*^3dAO<}50r_Be116(<1 zn)S7-u$OB)>lO-&4kz-ydDf#YX88WfU<(zc$<OaQ!Ry>WgMNY2k4u%#c;1Ayo3^Q< zPG6NQbr!fYgc@)o-pZ3ayn1mHn>K=Pij_;q%TKR{ID&y7Ivnr1a!F&oD7Z42chE$I zZYgaeAG}>9vhxmA`zC^saaeYO-MN7R4gJyNr~jh~oqD8LGgv(5r$^Bg_gVrFW@$gm zX20mZX+jumx=o+<N-*||^oWzj9S`CyKc|}_KJ<H9rTy6r_jsLO^1#?QRLck8j&#z- z0n|PnD{F?Y#p!SKhBw&bK%Oo-NmeaASh8RR{?+mNd+K7&$PHkP#~nkVG^e~?FEY{6 z{bnl#nO3GV2cwHIKio<K!8khG{Jk82RYxNz-OiDIJw)?bj^KSUQc|qSYiY-*+X6o~ zl)f^r>4&NN7{vB7P_Cp-L6Z&`_W~PlYyR1oUA(D_bOneU1Bo`1DxIwx5TQN-6QSX^ z-xg!nO8`;W7y#)-%g(vL_4lQ%5i$YDsyE$6pY*!T3)wpvgRd73z*~v&6dbURsBsee zhk}jOm@tzb6M_dOG+yA$${>dunDg%0nPD279GZe2)={2{dvR3hX_SM=1J51aqjKpA zST^Pm^%&)LxuqMp)T+d^D#@M0Xfv>{)XmgpCVjKt|26>WYLe1y2n=~gA4`Gmh;AXn z*62O`L`sS~iCzbi4GA8D_+D*($m-Jtd9U<^4xnO0JMWXf5x`kEaq~6Z0jv|SRX#=Q z9xts1cUaDp8xu^<$AfsggOLSfUk}5L8K`ZdX{U4!S4;@%TL?iF&vpKAGQ5mWXa81$ zm9>i<4palO2TA}94YBf}gd6abcefwXrzcf4NjZRQP`iZTan=gQ>vhZc;By)@3vaEE zqvSdY6hgyuU;Kz$ai6CX;^@|vnD|)Y1O+-(+G_+-i)1a~&Yt_og7r;hgcv(?%8G5} zv&n+lc+i%nAa$5T;1L7tAo?e7jX=iBKx_n5$cp?AP%;|qj>LXKE1XiGTfkcXpB#&O z5(!m(Vizmu&93OMszN5q(sFVOBUlE-ivm>}_aH83PC59nL$_pt9@v1I!l$kK=7eBJ z2sN6YQ1IEEL4P>ti`D0e;aZdnAV|!P#+7soDc*14^*BQ4L88s2MadmPm@7$PKr?9O z!%@yH$m+sp4*+Xz3QBq`;gblkQuWAT<+SV232aCA8a8OP2?p<31Y|b_G-Ho+yTHD} z)^p+=KaU@)7)0JYapKw|0VMn&(c1fv36?oBX1VDpvd2Qu+0q1jhR4Ao=?By+Aid8Z z<8)L8=MrmK#U?&tMdo+GPJK;xkKPA@(=RD@lGD^AT{}iikp(ODFtKT-wyO(8A}pU< z?oo(adrmahzx?y7%YFF?T68Fuq(<jisN=%?bVj6iscaq_j9~q|wna)a<2vMP^f?ev z<P~JQ7jd5)lZ{1{oy!s56qbkboYn)36?`(2hR6AU`NxK+aeb7#ZRw3Umg<k~P;W2X zmi{CL+*g5TzY)+W%hPBKUEabhQg569710ZZGYR3eJ$HK9P2#P}XV{*L-EQNga%X`j z@*5wVXnj(Fm6NQ4VVNkVo=F6BR)2lv<sCu}gGa^hla0w6*j`fWV{x+sI!7hqHhH?e z<#<Hz*dkVcD@)bY4vCl|Q2^3h{r7?7Z&!pWD#!QChfKzvF>;n&7U)~p(LZgL(){`j z99Kz-4<Kyddi9i^K}m7mdM*#~Omgo!bXjcl{n^b{Il<1Bl<2iR|DdKU?;CqYFTT%H zEpTD8D~T;q@LOGAd*Hf#y+?&!i^<_H3qk+R>}Yu*fhmzC1W)M*x~jA$!g80c!OJ1H z@$a=?)>Zbct0!OX=a{~||D|w&uHDftHaBnT8a$;pStsGJA?7^)8<FCU9_<N-M&y^E zk~VbtRw%izZPs-RX{uS&>RIIW@NHf#@aRw>&JZa2zUEkjI7-X43r-&SpMKS~t+NW@ zrj<U*_}!<QP`U*hH+L|oG7$7AJ=B=>Xv)*~6sr70-bCVESp)VpYP>EOoC?lA|1<Xr z&*was5|>jsN4WrG-nkpf<n<EP92sr5jv1eRZdymX3GG?`=>Iy)vpw5s_eG(}0{K;^ zx}z+wkfw9CYWP1ZJ|a+%_%u(q@YpU^TcN@N>FQ@)c}@~@LQLR*MPaN}V6%(fW`-H% z96;*-Cfw*)-L#!Wr=@gi>2l`f#ge{%01djQG@Jj^oNm;R&3r=rLLl?Sw@{@vXj{ZJ z#^oPD;ChN&mNv%0u}f^Jc0|a+cwp<9np<&VVe1i&1h@mLJCN1186CamaH3F1_ga(k zC!ubqETZrhL@=*o9*TcEI+VtJnLd0w@pui|ZSuS`mX7SdM>qF#gc8$%#RZ?SWR$Ax zsm!XxZS8S>k#zN&%gL{zP7^)Ojmah3_PCSq_zBJQz_%9hc-kYUYjYQbagz6!x=h>k zFE`;110{@uUwUGdt~q$3Jf7PWRowh@m6XvJZOS;c#YD0S%DgV9@OG*vo&tSGPKl>` z2Wxj|UQ@_K;ur<Yh;UK19GvO=sr}f+MjIbn#~>LhSp?Di-J0oB%N<NO84RZhM9}K1 zd>E3KSBDF~rF7r)V#eOwb86q%x4uTHP|NT2`hXe`fy{g#IX<%S;j#EwL&jduP3f@q zsX)NKhEX-;8IRS?bH~QzU~<?qJ38}@)wE|KSXLL3&u_eP&xdGMS|_R~Nj1_I)g1Vn zPNx&?byOw{n{1F0^4GNdU>+44Z(IyfHcyVS;<xl}&c#}lS2L}{wdZD&mixjNX`YeG zlG(16!YZB$5jU*FhPb$XIuqueq8t6Gi8F)j^0cQ%!6e_$M771|Q|7%9PePs0ZqaTW z84c6X4{wLJ--^p^CYe4L*LFXv28yqXeU?bqzcB%R<dMIim7G67CHbS#NXsJa<%_YW zV?40|H>eia&t!wgmQ8kyaYscS>A(9%WGvj4DA1qEpXgOqowFbLv-#5go^dw@-^oq} z%6x+F)OAhQ;=1uuH%>iYA)4h|AdV@(21@Sz80~kZ!BQ2n=1?u_WUWm;a*%e&4iw7B zk|nrI>D&$AQWj7WatGVT?QC;8<|Uh?XD+INs$|3eq`xd~VZbUs<~N_`{3CD6%(eA7 z(0g(*CP{(pPd7GhkVRwnwyUGE&b4alv}m9NSbZ_*M0~_DSMaKG8K}IHgbBc%x!4e! z>^+^-zn56lU8sF@HZ(Ebrq@G;nCp+Iu&7)wTQp|P-l5Q!_Fg1!R8p6}0KV%^ccG1x zZ#yX!Md&!)*v9j@t#w}(<z5MLDs+1_2^;K25_P)V!k3zP1E<ASDsNCH*9REKfWYDO zb)D_xdXJtp8JWpuR(%}n_I;_QfYumxl1{CKzUZ+oPaG4VLJ#eq+;8K8#)AH21ki=Q zo?W?A0Mp;D(jt2oRjaDD{LCr$x5sdqQ7!DGJ>Gt<DdIu0JpShf{}u!z?JaSaqkOJG zQ*62kOZofm_KlU@{Allz{cUcwc+32s8?#eftue06lbDv-r&rjPew4S<xSuVVLR{Qy zzeC;p!WFwHOsH-&Uz$DJMCjVml@e$9z+&kXR8LYRR|YTFTf-_Fz_sQj#3Y-kz-Fw9 zRKE5B6O{^xvZ=~x!Ka1@&vRq)BJ$s)4!#Vg^>P(!YU8T#R{KrN%UraV6X(v`<Q#i2 zrMwi0joIxoit0}>$dmh;l{&fhw*=){G>HjQT~{R-V$;(6MB)GK|2Xa1esb?cuHI$H zFqiL+97ZB9exQf7&ohH$@s`}T+UR${+kxhaS-D1rZn5;H*G(pSRk~89PkD}NZUG*z zoUJ{9r;NiN_}~q?5~Uj4O*5wYy17=+rd#T-mpxco3-ji%k+>W6P5;Jz2hRQBq9v)- zk=AjXL*?hP)*njLEw)l@Zurg}J|5@RKI=5i3**H+2+CL*jtI4wFB@o$5uIP$*to4P z&+2^v0@2z0>jmhy0EBhhGhbXB{^dw~PYZY<><jH{1-W@J)ip1Y$Bpi3Rn#E=HfOLx zwext(dw$J6a&TI(7JMEyGLnH=hfX+M)48S+;lRFQFPRKfTq^rEWU~03gFs!ohqs{G zp^>P#Y(YDZXHhS;583VHM*uQ@(t9cZnbr{=ZEIyZ>1b+-CQGRvU4j8H_{?hh!a^`Z z_Sx5abZe~=m5aJpHKg)@J09yf=+MwXgl6z6P*mF+ZIxyTyj7)G@V1N9gK~{hzrMRL zzr|#|X#0@H79D$_%xRs`?wUKeB+6ocw;8fpR@bD5)VLjAuV>E;JLWCx=EVZ$!dAxA zBl;omu4bND<h^~_{6(Y*qG|a3zZ-E})?*No!DL;Dr=IUt{X(GHoy6WFAGX0{NcR-U zUDaQ2SztCJk?4Y_B$0q8)+@Fz*Ya0PSUs*RR=hHD_l*ADABYWFLMY8eM@Ck+YbU42 z?}PUn|5^*8j&R0+|E1Y@vIXJ%9pt{ykxY*kxtvCTZDWRyU;Pn-uW~_&l?PC!U3eox z*z>!YZ)l$0YGbfS=klG8N?WI>87kHFChq;6epf%!FK={Y_b*$CY&52)ZE>&1j>v6y zgVGr{a-*?DWvT0_3E+KnF+kL#ni{sR_aS$+wW3(soeh>3ulHS?AO^@=)f|ccm`+t_ zGyj?$W;5zHJ&^>~03s9o$7Gzz7J_qC{``-<DGh~>(_%(S{D`owSSAtC4S4LaUA7i# zH2FoX4g|Z@3Q9)?_?U$RgYwTWSA)R&at6nDRq0e80kVMud7|ioC<!n5h!ni<{S1}m z^R{6C0d*ev<l3u+9+b+|JP<#9xg{lTUq!+T1XqG^TG<yjUdFTK7D85K11?;4SQ|a8 zzm%gW*6j%|Zb^@c!Gp%uTOaH@ULzixVIDtY;OrA6R-H!&Qc<AVB2<Crz@UY<NQ96D zGS-)EuOyW^kQ)7NWE92P_uh9kQ?GBLd8vyBCmn8AWrIw5+eSfcy839_Opv72@Oal! zwb^`^6$T0}Nf?_U)=41+Gp2zj;jbPz=?MF}`^c)>Dj%HY$o{W*g0&CW%yPH(+pkyM zM<V&+jr5jzku8g3+^sQtA_EVO?m<@RT^p3MFRG|v33`jRmy~{%5wB3}J;vdBRyJlL zc5bYTk{9)PNSDd`z-!tiz9mi+xm3s4QIg?23d#MP48G5>5%%CFy!mY#dIX5p5P^%g za9T^1WTd!nu1<!c^Qha^iLmslr;r-DGf*JJQNZhA?CA-G7db{R)AjY1L!=QQE<O+f zA&|+L@OEnfjV<gW+}eM6g*3Ikw}SVnyt=LbjD4a<m`~CXs1T%27g3;=S2q3(SM5pt zOJWEPjs_{X#WYjJLmLfnJvcZw`Sk6LcV!jF%~Ei!?@armtC7Idhi&Y!&gokeEL~hT zdKcz<%diVv&ylLu&t(GUN8DRLsk{@(izI<8$YkbNByv8bCT8EkB*>B9S+rK=5wPxu zgX@`E=<c7NNLc?C`F69n6=k$7_}_^65F8Ayqq>>Ph3DLYXww)2G?}JX?2U94T#+E# zgdp&V2jKV-alngwTJ*cUa06Q&b;v3dA=M_HM1*4o1IR}g<q0<ILA84fFoL|7YeQ4f zokO)u=W%s3v8Ma9PW~adp5#<L&!^f+jWZ7zhgrCsq#^z9J_OP$#oyz1Z)a8ihoH=N z2AU)6aYxqI*Kfc<k<Ygz1v@JKW&mwTj;8a`e+?1+_2+bGPwY`^X;H0B$<aR{TOhYL z$=M~{Jo|%@C@%L_F<hYG0Z;)z01XvwZONp+c>1UrU?2@>5p}?h`O^j|Bt93iYHYf8 zoFyc57x|RD6Wq?baV`Y(0|8A@1;)5({k_IvGcedI`R_e&ZE>6AKPvP6ttir@Rrwwv zB@vG6a1asR{i)*hoU0uQUH^ItDoAi=B)YIE!*SK?lX-vDgzA%w@dJVmm4OU>L^7{N zsgitla6PR;XZaagp~Lwf)+~@*g;AXr1u}HtDJXod<z@VJ*2ZKDjs<+IqLw+N+IzxS zkX0mVi3BZB{}EqZD|_1Zk13$3!r}3?J(lYadf5RPUGeQ*45Yp;`SDN*O)5X#3L;VQ z?b(?vAYoF0>-t9MHk^?q=)sJs(Xxa#oWRtJ2?iB7ZqYF!*~qJ2TM%6i#ov64{jrRI z<9MF$KWS{x$5iAo!&+A9?hU2fL!xYc9ZBYWDsjQLf*7=V<V4^^fwdIIY(QHWTQck1 z0$6M1X1N`tw-AUB($o9b)vs{MaF7>#e7+XEAz|;<@L~$8y(f_%Pxv740tHft_HPSh zNEZ{tRIpmH!Df^RiCC!814AKrpO0F;r8OJlC^MjcNm<-5>9y{+=zDn;K<&r3)jMrg zc?JAFvrO(@g_cni>6P1bRAQPraNM;QRK%IFG2<5CR0j}17f}eysRmXB?TBH85sr1k zDMPwj({KUg{Kj%zxqyju;X+9hj)Pf*dQqTh#GfIY`<k+*`?EGDxAMzs>b)u!I-R1> zOm~2B@SZfbr`>R3!Yh01X(BM^O5$a;8M>vZsjmdby})Z&{3--`GxFYNsi3C{w9<p= zBrC{C+c_9ht=U!|SXk4dlkRIG+aZ4hUc{690B*%E!A?@u$9Cx32hse1H$dbWT!q2y zLF%TDKD<&M6oTUlyN~Yy8R?6V>m0^+4L@@0ElIzeBE-F#vSDL1Gy(Ln7_Wd*sixk4 z+zBWG1J6@C9Ei97MGx7QrsEe$IBo}f#rs6=bpAVF@zH*?=zGj#HWfA8BoN!(@(<B` z87HBPLXu}pn?n64EOjvC7f_p#_`wO2R9G9v4mXw60L(b@$_%xl^CDlU+*Y1H;3m7* z{g&qhoF8op8Bg-4@X%Pm*bN8YLfqp9?kR0G!%MCz>iX;p9_=9kY~bK0Nws{<tD!6H zN}%fmsWb0e%9u<~E-C`3*4X~ZkCv@-7;w0V0e-mrn*7ft7dt(1-~|YkjO}}HV$EjW z6Aj!m(q?)uI>ZxcNYw2!JB)|eBRa>D9bAjgUjkeDRbPP?VBIMtj@sfyeDZyO%0Win z7!hvWhbo3F<C`}1X0J9*?}*K(PeB8GrM;~5S4MOzz@vT|d-T{}))ky&*+8EV;{eQ! z4A1T(>DBl)J&e0jR1FTAf<$S}EjsvHLqyH{BQewdPr1DGHgjAPLhl<>P=xpgc7L0C z5s-GyUai-a(k$ihJjW+<^g|W(;H`wriNd5+YkyT}cv@rMB|X_Onf*tgaqnYk6*c=h zJvMYuvM=y)saP;<?UMD>W=vVwhjdX#FSHyKZw+)p7-I#JSm<ub$pRYHvz##%mz!np zmT>ALcU;OmkBWjuYR80$9}S)JeIJuw*!jhTdxE{cpKZ?dfrVqjw=eatD)CS5deYW_ zWhP0<PgdKolvJlM)c0@)emB~PghLNf#lF4$-JU|O%yaekn45uUw~}|!f0H8;Q-1cP zO0N!?FK|rM?~v9YQjHr1Es*V-H*MMacfqw>lR!U*Ik(4N?pE~oHC-s4gjRNElWrkx zTm&5@xWSL3aj^K&&$92vuBiT>lR9ZQ`6V%=Oa*;BwPLBXXtYq6-7{mloj~I*wr`R; zA8qsY@Ch^Bk{DbW$S$o;bO@8^)G$qaKO^M|MlbaMLZr#fHycWVGs8MA!KcO8z9?C4 zI-}G>D@129%q0XM+@0YfplazP2CtlbZex1M)7{cj$KJ)V|5LQPh_;KRMRU+kR^Le! z{`(X4smB7x41Xc_={e$U0ps4BDdcZrqcWTs$TXL)VTbGPGZI<8KEf>^r&%eVa6Zaz zA%bf9Bp(@{J<VAag`eLb`@<Qmuy2Lhq6eat!SDJw!SB<yF*bKX)P9#S1Aa(G8vGDf z9rRy>0>ginc7n0vYW5z?SUNU34>kMy?jyRNJ}0jn8>*6qvq!z_k?i&?u-VyshTxYZ zqx&zQnl0M9v^c_FfSm7HY#?pf%=3Kw5_(?r$F=al|6ctdKo+R;ZY6=cz@2pMs;;-L zE7HoI-6UI}`ols@aWoT2pBfBC>=-%kt(4{X_B3fAhwf*-RYA+x_Y~TmK)ePjmPO7w zd=9whXavPnXcg<f*Lu>9Mv^Er3Q~@8Yg?T_=t*`zcXV_9UgLCG2}*v6`Ms^)MGRet zYY|bKLhy>gyXT)@2w|B{K}RZh-o~x(<3(GH`ztU4D(~S(rWVc3oVKNGZ8x-7(Gg&5 z_9ulVR8|8>tvE_V)4?5FEzc_|MWUrwp5<43wqKRg=b1KHh%4@b@K)^yiZC>HCF^PY zGK(NSqTY)j@cHf>3X!zN)YY>nBwTV6Sv#gjlLT<ia+cTJKIJZ5ME0S=P_>{`@>;$2 zG49xEa-Yl@Hpu#T#LT9x?Q&QXR~oYsba-Q5X$y7DOy;p6_>ni453N_(*hO@yO864D zEtgMVLl$$vt#y@Upo9VH(##xrV1dM}2@xLpEXpiXfAu&}S_k5&Faey6O88S2AC}*( z)}&DFXO^pRg_eXlwxr#ZPzo<-)-MEptOBRbv~+@yVvpqO;-=KeoN(jpZDQ!+0{Sae zM&}+YkhFkJ@!c|e`b)O=T3E2G!E>wBmOA?EtucD@&j>a+NnF=wPd)3nt;5Q)=Z-6P z7xvcKy$;r!aAe~z1=$yG{a8uG8V7Af$3)NKc8#uO2y>FDM{}8+i~DeSrN3YX-Ym}8 zd#VFk>01PY$m#|g5pH>9xbHq%1#{3v&|kho%h;lTSpGzy^BknFW1?N7>G1r2A=3H4 literal 0 HcmV?d00001 diff --git a/website/static/img/favicon/favicon.ico b/website/static/img/favicon/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1a106f97daaaea0dd9e09f11b984bfdb821e03d0 GIT binary patch literal 4286 zcmc)NTWAwm7zgkXky1p^2bFD+3L^R@-CbD<ZLRd8rWOr0y-2E*#2Z?~ii*Znv`SGC zQORO=m1Q@v>o(Jw1yK>LJ_!0yP>T;Df)_-B6cLe{%Vcu)f5sU+Jw0;?SWHP~=FIud zZ@!sxnM%?W{LP;)(O>DxOi7v}Nz!6`Q6$yiOUoTTQ;aIKn&8_M$<_Og<$724YSTpL z`LcED^{FkY9O+OmH1g*PId0yOFJ8M_KLTHe-&sg(tD0znuZQGgJzEpIiVBIPbz8#u zdGLqe6ZlEr(cG*;+GzhTK0T=RJ%>-=U+Ws!FxzW<+XihB{HO4V>EB&_C^yw>yj3r# zUbt-f1pcG$E50)8oZWVPTXrt|C-4d5-?XdmRNkRPdqeVZnm_dk{NcU*Kh1TDfggiU zIR75<_tf{;w&CyQd;&kg)?xR9fov6gUEqJf^Pk7o^|B9t=R|)gIS?;x7sUsgvr+g& z;1l%!h$zOX41PrHmpqNcrRjVu{1IEfC-<)5q*l;9=um!(iv0cY5^^vmwF>;CgHIYx zX_u{-Wq)N{#{<US4W|Fo{)gwkl$idXR$JB<`M-7XN!_`uF{f4konBSlpI&Fh^4m7` zpHj>}NeBN4ALDZWY5t`4eD)rVgTDg)aMi!*3D1$9Z&l?r@QH)}R^Z3n@`tZ4@5_{% zKB*i?ZRFdb$C&@Gy7=$-IOHGl;FFr)GhKN8`;b5NZ}IK;)!P-wUvu%_@NpRbrSRVi z$6rs*y8qOF2|sE2qm_x&xN)&oy>koe4{`A)Jb!vU@K1a234CJuq*DG|Z{^}sMs~s{ z9{iXU!*18NXBWc%#Ld6NH$H#PPZ?`-o${jIr#v3>=!aOB-G|?2`Wn{1A!Gfw_0b39 zvBqVCqZM_^%lGh!n|~l)@)7f&?ONEjQCkTAld=AF>y7Zw*yo?t;m6>Q!Y3a6OKiTP z{g$wvpuWFbuWkA@R}6o|9RIZcG56odpTH;H{GT)X<7MR{lPwE`^Zw=?ddj~`|ACgn z{{?;u<G&C;hWY0;v;JfL{n0gd>IF9E%i&*TuL*u{0e&W^{;>f53C2(E>8~*IVcQk$ z2e##BG;i0(^^u0t+AR3bJl_9d+HP1r64;vPHs60%O<40pwo@7W4tIZuw5VzqZD*5w zsNVF?I<@QdTx~R41b^h~{3GgrL$Bc*@5^EN=waOdn|S{#`2Ef9n&8v<-<<zM{v9Lt z&cTyKc>mZ2-_tX<^Z4?bi<vU`AHU4My#MGylXAb9kNMWe@NWqG4?gALl5cH^+Jk?J z^R+tVm0whMGRoj@Vf?Y0!BOEm^<>zz&FWaX)AXNV{;!%W$F5!EAA%pP97sF<KV=us KmE-XHef|eB#o`VC literal 0 HcmV?d00001 diff --git a/website/static/img/icon_common.png b/website/static/img/icon_common.png new file mode 100644 index 0000000000000000000000000000000000000000..61e2dcd91c2709e83ea06bd5b03a5cb14ee09495 GIT binary patch literal 1394 zcmV-&1&#WNP)<h;3K|Lk000e1NJLTq001)p0024&1^@s69j;;G000F!Nkl<ZXo0nw zdx#ZP9LGO%=3Y%}H%XGxwy4<h(F1xI7*y7l71BdXA|;^*Xyo=XdME@fg}o@kiY!wo zh$W3JOWdT|ifTd1Kl-O2%aXDuYp!>9-IY20aqi@~bLVm9%$YA-=A7R-zu#}ZbI+W| z?>FA)!X+R8KL7z<CBX9~!VjPdvJyZgfT~3L0aVp5l*_7A*Gd2t3BH^pD{=#ZH}U0W zDI|ChcrfM(4k8%EXT%eXP+=IKA<w5qg<&exJfB+NQ<D{jq)$!y4B;~*1qY;0O$ut# z=YSLpO2K|9*oy~JAaIX;z?HxzU>k5o2|i*`1{r{_>eqR|x4_Dh{4}5}GUFrlr-6+n z`hyaUPmYl-Mg3zfCHX9c%u&FJ32n21e!wrmZz*JE0uwUY`%Ca!3Ym6;KIZ{%xprIz znc0T@ZUXLf>9`Csi*odR5jfSAW7lLlfa7xNe2^f|eQ&PGEVa-%9k?Y%emQVk9z!n4 zRDtU)bzi1=q1_3*2HbCI#3h+WfFmt+CjxgxWPbu%fM<YfO^moA;{&S;+E@*E5!v?o zwFfnwr@|GPl|ZGSjY+`rh^(fSd&&6cU65%39(P~|;GRf*oK`*t_$W;|i_B$pBWZXR zcYCdN;54m%Ql$L?QigLDnO8}lZqEnq$gq(Y`cS(nro9U|D^4+s%*nvQl=e*-`Zicx zzqSEqC-mu#6Q+>~w;30rw)IhTk-XI}f@6Vcz?zgk(}9Pg!Zb4Twel5_wpkRPijxok z-vBpf^m~HHLYqeBKCP_pgsZ4&vMdN-ia|dQcrL0Khq*Kkp{ZKA8#N5_U@T{UQ!EFL z4l7AyqT{huQ?}WE?FUw&wkWRA85ZKmoI~?F@*C=(ddbq^WYid+3H)wpBMA>s=MhKd zg&0`}u%n>UOMrh+Ys60lZRu#CKAlI&%tB4YQM6<BQl?>~e*Fuze(Wh|PsajiCbQ0= zgDC#h%7ef~941&4mjdH-GSh)Mj*Lff2-q8u?FO!MWWN=-SSQov$Z#BfKn2vJ=mnm4 z<YR6~<`m!t2L^`|<X@)f+)MMnW`lD>GRqto16Tn(79)Q@L(e7<62-KT%>2}D=6ILp zVKWA7&CuJ<;WUUXS_SGbG^3y)P&3ruzyR=vCjS^XXrRBUp#O;il@M|PeOlL#x28wx zPf&>|TlfNXT6BS@v~qY;UjU!yuu&wIa3~yRMX{B}O=S2kuCTCUi<%&RY&Z^`k@_6e zbi6aCok7%av%!F%cv|jtV3$_+f%mWv)o!-b+f_7hH$Zb(RZ?t^ml_*O!KVUsaI(b$ znpgdxfIWckP)CKl=n=rrmQH(sK1<y!{F1Z(4q%S}zP5C@#8S@$dG~4?K~14=TR2-s zV^G^Qe$CN$A1d;SK&7$s9%JJ+lP)okqi-km=ufz~#hv?;@EQ4@oDD_|`~3*K5mt0E zrZ>1P$4%M7G^=p6UJ1zrm><qu1|pJRvXDOre6I`iV{<=d@4{e?o3h368SNWr+}Hxm z(V0idp53B($1*TArL7mZHLf8p2)Ky!yh~}HSZQcWkoTd!GK@u1tocUZm4vpmr{)4} zzzH$(Zd5=&5osxnWyB<OC@;1aq9aDO2KC`kDzegmPokcNzNpRvjxfHp{8+4&hfrzA zhfNesQs;fZnZWvbeLMzF$r2M#8E$|NQL*oLOqET)1E>MF1J|H7f$M3^(YwOh`t@$q z2-uxRIqydeTTqGHwIpYPZ7~HElA1xj?bZMI4}N3!J^zT;EC2ui07*qoM6N<$f<}*v A4*&oF literal 0 HcmV?d00001 diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..a51ae06 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,7218 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@9renpoto/eslint-config-react@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@9renpoto/eslint-config-react/-/eslint-config-react-4.3.0.tgz#78b35c7654d5d1309613079fe2416c71000c497b" + dependencies: + eslint-config-standard-react "^6.0.0" + eslint-plugin-react "^7.7.0" + +"@9renpoto/eslint-config@4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@9renpoto/eslint-config/-/eslint-config-4.4.0.tgz#8cf57b5ae09eef9e55afec2c2085fe1cfb1e12ed" + dependencies: + eslint-config-standard "^11.0.0" + eslint-plugin-import "^2.9.0" + eslint-plugin-node "^6.0.1" + eslint-plugin-promise "^3.6.0" + eslint-plugin-standard "^3.0.1" + +"@samverschueren/stream-to-observable@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" + dependencies: + any-observable "^0.3.0" + +JSONStream@^1.0.4: + version "1.3.3" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.2.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" + +acorn@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" + +add-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" + +address@1.0.3, address@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" + +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + +ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-escapes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92" + +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + dependencies: + ansi-wrap "0.1.0" + +ansi-red@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" + dependencies: + ansi-wrap "0.1.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" + dependencies: + color-convert "^1.9.0" + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + +ansi-wrap@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + +any-observable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" + +app-root-path@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +archive-type@^3.0.0, archive-type@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/archive-type/-/archive-type-3.2.0.tgz#9cd9c006957ebe95fadad5bd6098942a813737f6" + dependencies: + file-type "^3.1.0" + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + dependencies: + sprintf-js "~1.0.2" + +argparse@~0.1.15: + version "0.1.16" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c" + dependencies: + underscore "~1.7.0" + underscore.string "~2.4.0" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-filter@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-ify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +array-map@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" + +array-reduce@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.0, array-uniq@^1.0.1, array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +async-each-series@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-1.1.0.tgz#f42fd8155d38f21a5b8ea07c28e063ed1700b138" + +async@^1.4.0, async@^1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +atob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" + +autolinker@~0.15.0: + version "0.15.3" + resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832" + +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + +babel-code-frame@6.26.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + +babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-flow@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-class-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-display-name@^6.23.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-source@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-preset-env@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^3.2.6" + invariant "^2.2.2" + semver "^5.3.0" + +babel-preset-flow@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + dependencies: + babel-plugin-transform-flow-strip-types "^6.22.0" + +babel-preset-react@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" + dependencies: + babel-plugin-syntax-jsx "^6.3.13" + babel-plugin-transform-react-display-name "^6.23.0" + babel-plugin-transform-react-jsx "^6.24.1" + babel-plugin-transform-react-jsx-self "^6.22.0" + babel-plugin-transform-react-jsx-source "^6.22.0" + babel-preset-flow "^6.23.0" + +babel-register@^6.24.1, babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.25.0, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.17.4, babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +bcrypt-pbkdf@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + dependencies: + tweetnacl "^0.14.3" + +beeper@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" + +bin-build@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/bin-build/-/bin-build-2.2.0.tgz#11f8dd61f70ffcfa2bdcaa5b46f5e8fedd4221cc" + dependencies: + archive-type "^3.0.1" + decompress "^3.0.0" + download "^4.1.2" + exec-series "^1.0.0" + rimraf "^2.2.6" + tempfile "^1.0.0" + url-regex "^3.0.0" + +bin-check@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bin-check/-/bin-check-2.0.0.tgz#86f8e6f4253893df60dc316957f5af02acb05930" + dependencies: + executable "^1.0.0" + +bin-version-check@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bin-version-check/-/bin-version-check-2.1.0.tgz#e4e5df290b9069f7d111324031efc13fdd11a5b0" + dependencies: + bin-version "^1.0.0" + minimist "^1.1.0" + semver "^4.0.3" + semver-truncate "^1.0.0" + +bin-version@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/bin-version/-/bin-version-1.0.4.tgz#9eb498ee6fd76f7ab9a7c160436f89579435d78e" + dependencies: + find-versions "^1.0.0" + +bin-wrapper@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/bin-wrapper/-/bin-wrapper-3.0.2.tgz#67d3306262e4b1a5f2f88ee23464f6a655677aeb" + dependencies: + bin-check "^2.0.0" + bin-version-check "^2.1.0" + download "^4.0.0" + each-async "^1.1.1" + lazy-req "^1.0.0" + os-filter-obj "^1.0.0" + +bl@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +body@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069" + dependencies: + continuable-cache "^0.3.1" + error "^7.0.0" + raw-body "~1.1.0" + safe-json-parse "~1.0.1" + +boolbase@^1.0.0, boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + +boolify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/boolify/-/boolify-1.0.1.tgz#b5c09e17cacd113d11b7bb3ed384cc012994d86b" + +brace-expansion@^1.0.0, brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + +browserslist@^3.2.6: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + dependencies: + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + +buffer-alloc@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + +buffer-from@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0" + +buffer-from@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + +buffer-to-vinyl@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-to-vinyl/-/buffer-to-vinyl-1.1.0.tgz#00f15faee3ab7a1dda2cde6d9121bffdd07b2262" + dependencies: + file-type "^3.1.0" + readable-stream "^2.0.2" + uuid "^2.0.1" + vinyl "^1.0.0" + +builtin-modules@^1.0.0, builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + +bytes@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase-keys@^4.0.0, camelcase-keys@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-4.2.0.tgz#a2aa5fb1af688758259c32c141426d78923b9b77" + dependencies: + camelcase "^4.1.0" + map-obj "^2.0.0" + quick-lru "^1.0.0" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000861" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000861.tgz#6f27840a130c10c0b1e00fab7729c1faf8f4ccd3" + +caniuse-lite@^1.0.30000844: + version "1.0.30000861" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000861.tgz#a32bb9607c34e4639b497ff37de746fc8a160410" + +capture-stack-trace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +caw@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/caw/-/caw-1.2.0.tgz#ffb226fe7efc547288dc62ee3e97073c212d1034" + dependencies: + get-proxy "^1.0.1" + is-obj "^1.0.0" + object-assign "^3.0.0" + tunnel-agent "^0.4.0" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + dependencies: + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + +ci-info@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +clap@^1.0.9: + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + dependencies: + chalk "^1.1.3" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +classnames@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + +cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + +cli-truncate@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" + dependencies: + slice-ansi "0.0.4" + string-width "^1.0.1" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +clipboard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.1.tgz#a12481e1c13d8a50f5f036b0560fe5d16d74e46a" + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +clone-stats@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" + +clone@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" + +clone@^1.0.0, clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + +cmd-shim@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" + dependencies: + graceful-fs "^4.1.2" + mkdirp "~0.5.0" + +co@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +coa@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + dependencies: + q "^1.1.2" + +coa@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.1.tgz#f3f8b0b15073e35d70263fb1042cb2c023db38af" + dependencies: + q "^1.1.2" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +coffee-script@^1.12.4: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.3.0, color-convert@^1.9.1: + version "1.9.2" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" + dependencies: + color-name "1.1.1" + +color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" + +color-name@^1.0.0, color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color-string@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9" + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +color@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839" + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +columnify@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" + dependencies: + strip-ansi "^3.0.0" + wcwidth "^1.0.0" + +combined-stream@1.0.6, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + +command-join@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/command-join/-/command-join-2.0.0.tgz#52e8b984f4872d952ff1bdc8b98397d27c7144cf" + +commander@^2.14.1, commander@^2.9.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + +commander@^2.15.1, commander@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" + +commander@~2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" + dependencies: + graceful-readlink ">= 1.0.0" + +common-tags@^1.4.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.5.1.tgz#e2e39931a013cd02253defeed89a1ad615a27f07" + dependencies: + babel-runtime "^6.26.0" + +compare-func@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-1.3.2.tgz#99dd0ba457e1f9bc722b12c08ec33eeab31fa648" + dependencies: + array-ify "^1.0.0" + dot-prop "^3.0.0" + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.4.10, concat-stream@^1.4.6, concat-stream@^1.4.7, concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +console-stream@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/console-stream/-/console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +continuable-cache@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f" + +conventional-changelog-angular@^1.6.6: + version "1.6.6" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" + dependencies: + compare-func "^1.3.1" + q "^1.5.1" + +conventional-changelog-atom@^0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-atom/-/conventional-changelog-atom-0.2.8.tgz#8037693455990e3256f297320a45fa47ee553a14" + dependencies: + q "^1.5.1" + +conventional-changelog-cli@^1.3.13: + version "1.3.22" + resolved "https://registry.yarnpkg.com/conventional-changelog-cli/-/conventional-changelog-cli-1.3.22.tgz#13570fe1728f56f013ff7a88878ff49d5162a405" + dependencies: + add-stream "^1.0.0" + conventional-changelog "^1.1.24" + lodash "^4.2.1" + meow "^4.0.0" + tempfile "^1.1.1" + +conventional-changelog-codemirror@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-codemirror/-/conventional-changelog-codemirror-0.3.8.tgz#a1982c8291f4ee4d6f2f62817c6b2ecd2c4b7b47" + dependencies: + q "^1.5.1" + +conventional-changelog-core@^2.0.11: + version "2.0.11" + resolved "https://registry.yarnpkg.com/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz#19b5fbd55a9697773ed6661f4e32030ed7e30287" + dependencies: + conventional-changelog-writer "^3.0.9" + conventional-commits-parser "^2.1.7" + dateformat "^3.0.0" + get-pkg-repo "^1.0.0" + git-raw-commits "^1.3.6" + git-remote-origin-url "^2.0.0" + git-semver-tags "^1.3.6" + lodash "^4.2.1" + normalize-package-data "^2.3.5" + q "^1.5.1" + read-pkg "^1.1.0" + read-pkg-up "^1.0.1" + through2 "^2.0.0" + +conventional-changelog-ember@^0.3.12: + version "0.3.12" + resolved "https://registry.yarnpkg.com/conventional-changelog-ember/-/conventional-changelog-ember-0.3.12.tgz#b7d31851756d0fcb49b031dffeb6afa93b202400" + dependencies: + q "^1.5.1" + +conventional-changelog-eslint@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/conventional-changelog-eslint/-/conventional-changelog-eslint-1.0.9.tgz#b13cc7e4b472c819450ede031ff1a75c0e3d07d3" + dependencies: + q "^1.5.1" + +conventional-changelog-express@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/conventional-changelog-express/-/conventional-changelog-express-0.3.6.tgz#4a6295cb11785059fb09202180d0e59c358b9c2c" + dependencies: + q "^1.5.1" + +conventional-changelog-jquery@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-jquery/-/conventional-changelog-jquery-0.1.0.tgz#0208397162e3846986e71273b6c79c5b5f80f510" + dependencies: + q "^1.4.1" + +conventional-changelog-jscs@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-jscs/-/conventional-changelog-jscs-0.1.0.tgz#0479eb443cc7d72c58bf0bcf0ef1d444a92f0e5c" + dependencies: + q "^1.4.1" + +conventional-changelog-jshint@^0.3.8: + version "0.3.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-jshint/-/conventional-changelog-jshint-0.3.8.tgz#9051c1ac0767abaf62a31f74d2fe8790e8acc6c8" + dependencies: + compare-func "^1.3.1" + q "^1.5.1" + +conventional-changelog-preset-loader@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz#40bb0f142cd27d16839ec6c74ee8db418099b373" + +conventional-changelog-writer@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz#4aecdfef33ff2a53bb0cf3b8071ce21f0e994634" + dependencies: + compare-func "^1.3.1" + conventional-commits-filter "^1.1.6" + dateformat "^3.0.0" + handlebars "^4.0.2" + json-stringify-safe "^5.0.1" + lodash "^4.2.1" + meow "^4.0.0" + semver "^5.5.0" + split "^1.0.0" + through2 "^2.0.0" + +conventional-changelog@^1.1.24: + version "1.1.24" + resolved "https://registry.yarnpkg.com/conventional-changelog/-/conventional-changelog-1.1.24.tgz#3d94c29c960f5261c002678315b756cdd3d7d1f0" + dependencies: + conventional-changelog-angular "^1.6.6" + conventional-changelog-atom "^0.2.8" + conventional-changelog-codemirror "^0.3.8" + conventional-changelog-core "^2.0.11" + conventional-changelog-ember "^0.3.12" + conventional-changelog-eslint "^1.0.9" + conventional-changelog-express "^0.3.6" + conventional-changelog-jquery "^0.1.0" + conventional-changelog-jscs "^0.1.0" + conventional-changelog-jshint "^0.3.8" + conventional-changelog-preset-loader "^1.1.8" + +conventional-commits-filter@^1.1.1, conventional-commits-filter@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz#4389cd8e58fe89750c0b5fb58f1d7f0cc8ad3831" + dependencies: + is-subset "^0.1.1" + modify-values "^1.0.0" + +conventional-commits-parser@^2.1.1, conventional-commits-parser@^2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz#eca45ed6140d72ba9722ee4132674d639e644e8e" + dependencies: + JSONStream "^1.0.4" + is-text-path "^1.0.0" + lodash "^4.2.1" + meow "^4.0.0" + split2 "^2.0.0" + through2 "^2.0.0" + trim-off-newlines "^1.0.0" + +conventional-recommended-bump@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/conventional-recommended-bump/-/conventional-recommended-bump-1.2.1.tgz#1b7137efb5091f99fe009e2fe9ddb7cc490e9375" + dependencies: + concat-stream "^1.4.10" + conventional-commits-filter "^1.1.1" + conventional-commits-parser "^2.1.1" + git-raw-commits "^1.3.0" + git-semver-tags "^1.3.0" + meow "^3.3.0" + object-assign "^4.0.1" + +convert-source-map@^1.1.1, convert-source-map@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +core-js@^2.4.0: + version "2.5.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e" + +core-js@^2.5.0: + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + +core-util-is@1.0.2, core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cosmiconfig@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.5.tgz#a809e3c2306891ce17ab70359dc8bdf661fe2cd0" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^4.0.0" + +create-error-class@^3.0.0, create-error-class@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + dependencies: + capture-stack-trace "^1.0.0" + +cross-spawn@5.1.0, cross-spawn@^5.0.1, cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +crowdin-cli@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/crowdin-cli/-/crowdin-cli-0.3.0.tgz#eac9989a6fe7feaaf33090397afc187c67b46191" + dependencies: + request "^2.53.0" + yamljs "^0.2.1" + yargs "^2.3.0" + +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-select-base-adapter@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz#0102b3d14630df86c3eb9fa9f5456270106cf990" + +css-select@~1.3.0-rc0: + version "1.3.0-rc0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.3.0-rc0.tgz#6f93196aaae737666ea1036a8cb14a8fcb7a9231" + dependencies: + boolbase "^1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "^1.0.1" + +css-tree@1.0.0-alpha.29: + version "1.0.0-alpha.29" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" + dependencies: + mdn-data "~1.1.0" + source-map "^0.5.3" + +css-tree@1.0.0-alpha25: + version "1.0.0-alpha25" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha25.tgz#1bbfabfbf6eeef4f01d9108ff2edd0be2fe35597" + dependencies: + mdn-data "^1.0.0" + source-map "^0.5.3" + +css-url-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec" + +css-what@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" + +cssnano@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@^3.5.0: + version "3.5.1" + resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b" + dependencies: + css-tree "1.0.0-alpha.29" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +dargs@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17" + dependencies: + number-is-nan "^1.0.0" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-fns@^1.27.2: + version "1.29.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" + +dateformat@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" + +dateformat@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" + +debug@0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +decamelize-keys@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +decompress-tar@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-3.1.0.tgz#217c789f9b94450efaadc5c5e537978fc333c466" + dependencies: + is-tar "^1.0.0" + object-assign "^2.0.0" + strip-dirs "^1.0.0" + tar-stream "^1.1.1" + through2 "^0.6.1" + vinyl "^0.4.3" + +decompress-tarbz2@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-3.1.0.tgz#8b23935681355f9f189d87256a0f8bdd96d9666d" + dependencies: + is-bzip2 "^1.0.0" + object-assign "^2.0.0" + seek-bzip "^1.0.3" + strip-dirs "^1.0.0" + tar-stream "^1.1.1" + through2 "^0.6.1" + vinyl "^0.4.3" + +decompress-targz@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-3.1.0.tgz#b2c13df98166268991b715d6447f642e9696f5a0" + dependencies: + is-gzip "^1.0.0" + object-assign "^2.0.0" + strip-dirs "^1.0.0" + tar-stream "^1.1.1" + through2 "^0.6.1" + vinyl "^0.4.3" + +decompress-unzip@^3.0.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-3.4.0.tgz#61475b4152066bbe3fee12f9d629d15fe6478eeb" + dependencies: + is-zip "^1.0.0" + read-all-stream "^3.0.0" + stat-mode "^0.2.0" + strip-dirs "^1.0.0" + through2 "^2.0.0" + vinyl "^1.0.0" + yauzl "^2.2.1" + +decompress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-3.0.0.tgz#af1dd50d06e3bfc432461d37de11b38c0d991bed" + dependencies: + buffer-to-vinyl "^1.0.0" + concat-stream "^1.4.6" + decompress-tar "^3.0.0" + decompress-tarbz2 "^3.0.0" + decompress-targz "^3.0.0" + decompress-unzip "^3.0.0" + stream-combiner2 "^1.1.1" + vinyl-assign "^1.0.1" + vinyl-fs "^2.2.0" + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + +deep-is@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.2.tgz#9ced65ea0bc0b09f42a6d79c1b1903f9d913cc18" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + dependencies: + clone "^1.0.2" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + +depd@~1.1.1, depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + +detect-port-alt@1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275" + dependencies: + address "^1.0.1" + debug "^2.6.0" + +diacritics-map@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" + +dlv@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.0.tgz#fee1a7c43f63be75f3f679e85262da5f102764a7" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.2, doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + dependencies: + esutils "^2.0.2" + +docusaurus@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/docusaurus/-/docusaurus-1.3.2.tgz#1e5001d1831b028178e496f6b8511e436ac7fda9" + dependencies: + babel-plugin-transform-class-properties "^6.24.1" + babel-plugin-transform-object-rest-spread "^6.26.0" + babel-polyfill "^6.26.0" + babel-preset-env "^1.7.0" + babel-preset-react "^6.24.1" + babel-register "^6.24.1" + babel-traverse "^6.25.0" + babylon "^6.17.4" + chalk "^2.1.0" + classnames "^2.2.6" + color "^2.0.1" + commander "^2.16.0" + crowdin-cli "^0.3.0" + cssnano "^3.10.0" + escape-string-regexp "^1.0.5" + express "^4.15.3" + feed "^1.1.0" + fs-extra "^5.0.0" + gaze "^1.1.2" + glob "^7.1.2" + highlight.js "^9.12.0" + imagemin "^5.3.1" + imagemin-gifsicle "^5.2.0" + imagemin-jpegtran "^5.0.2" + imagemin-optipng "^5.2.1" + imagemin-svgo "^6.0.0" + markdown-toc "^1.2.0" + mkdirp "^0.5.1" + prismjs "^1.15.0" + react "^16.4.1" + react-dev-utils "^5.0.1" + react-dom "^16.4.1" + remarkable "^1.7.1" + request "^2.87.0" + shelljs "^0.7.8" + sitemap "^1.13.0" + tcp-port-used "^0.1.2" + tiny-lr "^1.1.1" + tree-node-cli "^1.2.5" + +dom-serializer@0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + +domelementtype@1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + +dot-prop@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" + dependencies: + is-obj "^1.0.0" + +download@^4.0.0, download@^4.1.2: + version "4.4.3" + resolved "https://registry.yarnpkg.com/download/-/download-4.4.3.tgz#aa55fdad392d95d4b68e8c2be03e0c2aa21ba9ac" + dependencies: + caw "^1.0.1" + concat-stream "^1.4.7" + each-async "^1.0.0" + filenamify "^1.0.1" + got "^5.0.0" + gulp-decompress "^1.2.0" + gulp-rename "^1.2.0" + is-url "^1.2.0" + object-assign "^4.0.1" + read-all-stream "^3.0.0" + readable-stream "^2.0.2" + stream-combiner2 "^1.1.1" + vinyl "^1.0.0" + vinyl-fs "^2.2.0" + ware "^1.2.0" + +duplexer2@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" + dependencies: + readable-stream "~1.1.9" + +duplexer2@^0.1.4, duplexer2@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + +duplexer@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + +duplexify@^3.2.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +each-async@^1.0.0, each-async@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/each-async/-/each-async-1.1.1.tgz#dee5229bdf0ab6ba2012a395e1b869abf8813473" + dependencies: + onetime "^1.0.0" + set-immediate-shim "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47: + version "1.3.50" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.50.tgz#7438b76f92b41b919f3fbdd350fbd0757dacddf7" + +elegant-spinner@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +end-of-stream@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +error@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02" + dependencies: + string-template "~0.2.1" + xtend "~4.0.0" + +es-abstract@^1.5.1, es-abstract@^1.6.1: + version "1.12.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-abstract@^1.7.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +eslint-config-standard-jsx@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-5.0.0.tgz#4abfac554f38668e0078c664569e7b2384e5d2aa" + +eslint-config-standard-react@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard-react/-/eslint-config-standard-react-6.0.0.tgz#d366d6c3c092426fd3ae794a4ca0b3cb131f2964" + dependencies: + eslint-config-standard-jsx "^5.0.0" + +eslint-config-standard@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz#87ee0d3c9d95382dc761958cbb23da9eea31e0ba" + +eslint-import-resolver-node@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc" + dependencies: + debug "^2.6.8" + resolve "^1.2.0" + +eslint-module-utils@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz#abaec824177613b8a95b299639e1b6facf473449" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz#26002efbfca5989b7288ac047508bd24f217b169" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.1.1" + has "^1.0.1" + lodash "^4.17.4" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + +eslint-plugin-node@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz#bf19642298064379315d7a4b2a75937376fa05e4" + dependencies: + ignore "^3.3.6" + minimatch "^3.0.4" + resolve "^1.3.3" + semver "^5.4.1" + +eslint-plugin-promise@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz#54b7658c8f454813dc2a870aff8152ec4969ba75" + +eslint-plugin-react@^7.7.0: + version "7.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz#f606c719dbd8a1a2b3d25c16299813878cca0160" + dependencies: + doctrine "^2.0.2" + has "^1.0.1" + jsx-ast-utils "^2.0.1" + prop-types "^15.6.0" + +eslint-plugin-standard@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz#34d0c915b45edc6f010393c7eef3823b08565cf2" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + +eslint@4.19.1: + version "4.19.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" + dependencies: + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^3.7.1" + eslint-visitor-keys "^1.0.0" + espree "^3.5.4" + esquery "^1.0.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^1.0.1" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "4.0.2" + text-table "~0.2.0" + +eslint@^4.0.0, eslint@^4.5.0: + version "4.18.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.18.2.tgz#0f81267ad1012e7d2051e186a9004cc2267b8d45" + dependencies: + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^3.7.1" + eslint-visitor-keys "^1.0.0" + espree "^3.5.2" + esquery "^1.0.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "4.0.2" + text-table "~0.2.0" + +espree@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" + dependencies: + acorn "^5.2.1" + acorn-jsx "^3.0.0" + +espree@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + dependencies: + acorn "^5.5.0" + acorn-jsx "^3.0.0" + +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +eventsource@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + dependencies: + original ">=0.0.5" + +exec-buffer@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/exec-buffer/-/exec-buffer-3.2.0.tgz#b1686dbd904c7cf982e652c1f5a79b1e5573082b" + dependencies: + execa "^0.7.0" + p-finally "^1.0.0" + pify "^3.0.0" + rimraf "^2.5.4" + tempfile "^2.0.0" + +exec-series@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/exec-series/-/exec-series-1.0.3.tgz#6d257a9beac482a872c7783bc8615839fc77143a" + dependencies: + async-each-series "^1.1.0" + object-assign "^4.1.0" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +executable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/executable/-/executable-1.1.0.tgz#877980e9112f3391066da37265de7ad8434ab4d9" + dependencies: + meow "^3.1.0" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + dependencies: + homedir-polyfill "^1.0.1" + +express@^4.15.3: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@^3.0.0, extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.1.0.tgz#3d026a21b7f95b5726387d4200ac160d372c3b48" + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +fancy-log@^1.1.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + time-stamp "^1.0.0" + +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +faye-websocket@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + dependencies: + websocket-driver ">=0.5.1" + +fbjs@^0.8.16: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + dependencies: + pend "~1.2.0" + +feed@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/feed/-/feed-1.1.1.tgz#914897517e94fa327cc6f73bb585a47c4a9ed321" + dependencies: + xml "^1.0.1" + +figures@^1.3.5, figures@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +file-type@^3.1.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + +file-type@^4.1.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +filename-reserved-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz#e61cf805f0de1c984567d0386dc5df50ee5af7e4" + +filenamify@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-1.2.1.tgz#a9f2ffd11c503bed300015029272378f1f1365a5" + dependencies: + filename-reserved-regex "^1.0.0" + strip-outer "^1.0.0" + trim-repeated "^1.0.0" + +filesize@3.5.11: + version "3.5.11" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-3.5.11.tgz#1919326749433bb3cf77368bd158caabcc19e9ee" + +fill-range@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^3.0.0" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + +find-parent-dir@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +find-versions@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/find-versions/-/find-versions-1.2.1.tgz#cbde9f12e38575a0af1be1b9a2c5d5fd8f186b62" + dependencies: + array-uniq "^1.0.0" + get-stdin "^4.0.1" + meow "^3.5.0" + semver-regex "^1.0.0" + +first-chunk-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +gaze@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" + dependencies: + globule "^1.0.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-own-enumerable-property-symbols@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b" + +get-pkg-repo@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz#c73b489c06d80cc5536c2c853f9e05232056972d" + dependencies: + hosted-git-info "^2.1.4" + meow "^3.3.0" + normalize-package-data "^2.3.0" + parse-github-repo-url "^1.3.0" + through2 "^2.0.0" + +get-port@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" + +get-proxy@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-1.1.0.tgz#894854491bc591b0f147d7ae570f5c678b7256eb" + dependencies: + rc "^1.1.2" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +gifsicle@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/gifsicle/-/gifsicle-3.0.4.tgz#f45cb5ed10165b665dc929e0e9328b6c821dfa3b" + dependencies: + bin-build "^2.0.0" + bin-wrapper "^3.0.0" + logalot "^2.0.0" + +git-raw-commits@^1.3.0, git-raw-commits@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-1.3.6.tgz#27c35a32a67777c1ecd412a239a6c19d71b95aff" + dependencies: + dargs "^4.0.1" + lodash.template "^4.0.2" + meow "^4.0.0" + split2 "^2.0.0" + through2 "^2.0.0" + +git-remote-origin-url@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz#5282659dae2107145a11126112ad3216ec5fa65f" + dependencies: + gitconfiglocal "^1.0.0" + pify "^2.3.0" + +git-semver-tags@^1.3.0, git-semver-tags@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/git-semver-tags/-/git-semver-tags-1.3.6.tgz#357ea01f7280794fe0927f2806bee6414d2caba5" + dependencies: + meow "^4.0.0" + semver "^5.5.0" + +gitconfiglocal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz#41d045f3851a5ea88f03f24ca1c6178114464b9b" + dependencies: + ini "^1.3.2" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob-parent@^3.0.0, glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-stream@^5.3.2: + version "5.3.5" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" + dependencies: + extend "^3.0.0" + glob "^5.0.3" + glob-parent "^3.0.0" + micromatch "^2.3.7" + ordered-read-streams "^0.3.0" + through2 "^0.6.0" + to-absolute-glob "^0.1.1" + unique-stream "^2.0.2" + +glob@^5.0.3: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@~7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.0.6.tgz#211bafaf49e525b8cd93260d14ab136152b3f57a" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.2" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-modules@1.0.0, global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + +globals@^11.0.1: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.1.0.tgz#632644457f5f0e3ae711807183700ebf2e4633e4" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globule@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" + dependencies: + glob "~7.1.1" + lodash "~4.17.10" + minimatch "~3.0.2" + +glogg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" + dependencies: + sparkles "^1.0.0" + +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + dependencies: + delegate "^3.1.2" + +got@^5.0.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" + dependencies: + create-error-class "^3.0.1" + duplexer2 "^0.1.4" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + node-status-codes "^1.0.0" + object-assign "^4.0.1" + parse-json "^2.1.0" + pinkie-promise "^2.0.0" + read-all-stream "^3.0.0" + readable-stream "^2.0.5" + timed-out "^3.0.0" + unzip-response "^1.0.2" + url-parse-lax "^1.0.0" + +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + +graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +gray-matter@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e" + dependencies: + ansi-red "^0.1.1" + coffee-script "^1.12.4" + extend-shallow "^2.0.1" + js-yaml "^3.8.1" + toml "^2.3.2" + +gulp-decompress@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gulp-decompress/-/gulp-decompress-1.2.0.tgz#8eeb65a5e015f8ed8532cafe28454960626f0dc7" + dependencies: + archive-type "^3.0.0" + decompress "^3.0.0" + gulp-util "^3.0.1" + readable-stream "^2.0.2" + +gulp-rename@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.3.0.tgz#2e789d8f563ab0c924eeb62967576f37ff4cb826" + +gulp-sourcemaps@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" + dependencies: + convert-source-map "^1.1.1" + graceful-fs "^4.1.2" + strip-bom "^2.0.0" + through2 "^2.0.0" + vinyl "^1.0.0" + +gulp-util@^3.0.1: + version "3.0.8" + resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" + dependencies: + array-differ "^1.0.0" + array-uniq "^1.0.2" + beeper "^1.0.0" + chalk "^1.0.0" + dateformat "^2.0.0" + fancy-log "^1.1.0" + gulplog "^1.0.0" + has-gulplog "^0.1.0" + lodash._reescape "^3.0.0" + lodash._reevaluate "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.template "^3.0.0" + minimist "^1.1.0" + multipipe "^0.1.2" + object-assign "^3.0.0" + replace-ext "0.0.1" + through2 "^2.0.0" + vinyl "^0.5.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + dependencies: + glogg "^1.0.0" + +gzip-size@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-3.0.0.tgz#546188e9bdc337f673772f81660464b389dce520" + dependencies: + duplexer "^0.1.1" + +handlebars@^4.0.2: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-gulplog@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" + dependencies: + sparkles "^1.0.0" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +highlight.js@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +homedir-polyfill@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" + dependencies: + parse-passwd "^1.0.0" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +hosted-git-info@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.1.tgz#6e4cee78b01bb849dcf93527708c69fdbee410df" + +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + +http-errors@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.4.0: + version "0.4.13" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +husky@0.14.3: + version "0.14.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" + dependencies: + is-ci "^1.0.10" + normalize-path "^1.0.0" + strip-indent "^2.0.0" + +iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +ignore@^3.2.7, ignore@^3.3.3, ignore@^3.3.6: + version "3.3.7" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" + +imagemin-gifsicle@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/imagemin-gifsicle/-/imagemin-gifsicle-5.2.0.tgz#3781524c457612ef04916af34241a2b42bfcb40a" + dependencies: + exec-buffer "^3.0.0" + gifsicle "^3.0.0" + is-gif "^1.0.0" + +imagemin-jpegtran@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/imagemin-jpegtran/-/imagemin-jpegtran-5.0.2.tgz#e6882263b8f7916fddb800640cf75d2e970d2ad6" + dependencies: + exec-buffer "^3.0.0" + is-jpg "^1.0.0" + jpegtran-bin "^3.0.0" + +imagemin-optipng@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/imagemin-optipng/-/imagemin-optipng-5.2.1.tgz#d22da412c09f5ff00a4339960b98a88b1dbe8695" + dependencies: + exec-buffer "^3.0.0" + is-png "^1.0.0" + optipng-bin "^3.0.0" + +imagemin-svgo@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/imagemin-svgo/-/imagemin-svgo-6.0.0.tgz#2dd8c82946be42a8e2cbcae3c5bf007bc2b8b9e8" + dependencies: + buffer-from "^0.1.1" + is-svg "^2.0.0" + svgo "^1.0.0" + +imagemin@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/imagemin/-/imagemin-5.3.1.tgz#f19c2eee1e71ba6c6558c515f9fc96680189a6d4" + dependencies: + file-type "^4.1.0" + globby "^6.1.0" + make-dir "^1.0.0" + p-pipe "^1.1.0" + pify "^2.3.0" + replace-ext "^1.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indent-string@^3.0.0, indent-string@^3.1.0, indent-string@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.2, ini@^1.3.4, ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +inquirer@3.3.0, inquirer@^3.0.6, inquirer@^3.2.2: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +interpret@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +ip-regex@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-1.0.3.tgz#dc589076f659f419c222039a33316f1c7387effd" + +ipaddr.js@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + +is-absolute@^0.1.5: + version "0.1.7" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-0.1.7.tgz#847491119fccb5fb436217cc737f7faad50f603f" + dependencies: + is-relative "^0.1.0" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-bzip2@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-bzip2/-/is-bzip2-1.0.0.tgz#5ee58eaa5a2e9c80e21407bedf23ae5ac091b3fc" + +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-ci@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + dependencies: + ci-info "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-gif@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-gif/-/is-gif-1.0.0.tgz#a6d2ae98893007bffa97a1d8c01d63205832097e" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + +is-gzip@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" + +is-jpg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-1.0.1.tgz#296d57fdd99ce010434a7283e346ab9a1035e975" + +is-natural-number@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-2.1.1.tgz#7d4c5728377ef386c3e194a9911bf57c6dc335e7" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + +is-obj@^1.0.0, is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-observable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" + dependencies: + symbol-observable "^1.1.0" + +is-odd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" + dependencies: + is-number "^4.0.0" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-png@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-png/-/is-png-1.1.0.tgz#d574b12bf275c0350455570b0e5b57ab062077ce" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + +is-relative@^0.1.0: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-0.1.3.tgz#905fee8ae86f45b3ec614bc3c15c869df0876e82" + +is-resolvable@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.1.tgz#acca1cd36dbe44b974b924321555a70ba03b1cf4" + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + +is-root@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-root/-/is-root-1.0.0.tgz#07b6c233bc394cd9d02ba15c966bd6660d6342d5" + +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + +is-tar@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-tar/-/is-tar-1.0.0.tgz#2f6b2e1792c1f5bb36519acaa9d65c0d26fe853d" + +is-text-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" + dependencies: + text-extensions "^1.0.0" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-url@^1.2.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-valid-glob@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +is-zip@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-zip/-/is-zip-1.0.0.tgz#47b0a8ff4d38a76431ccfd99a8e15a4c86ba2325" + +is2@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/is2/-/is2-0.0.9.tgz#119556d1d1651a41ba105af803267c80b299f629" + dependencies: + deep-is "0.1.2" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +jest-get-type@^22.1.0: + version "22.4.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-22.4.3.tgz#e3a8504d8479342dd4420236b322869f18900ce4" + +jest-validate@^23.0.0: + version "23.0.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-23.0.1.tgz#cd9f01a89d26bb885f12a8667715e9c865a5754f" + dependencies: + chalk "^2.0.1" + jest-get-type "^22.1.0" + leven "^2.1.0" + pretty-format "^23.0.1" + +jpegtran-bin@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jpegtran-bin/-/jpegtran-bin-3.2.0.tgz#f60ecf4ae999c0bdad2e9fbcdf2b6f0981e7a29b" + dependencies: + bin-build "^2.0.0" + bin-wrapper "^3.0.0" + logalot "^2.0.0" + +js-base64@^2.1.9: + version "2.4.5" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.8.1: + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.9.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^3.9.1, js-yaml@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + +json-stable-stringify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jsx-ast-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" + dependencies: + array-includes "^3.0.3" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lazy-cache@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" + dependencies: + set-getter "^0.1.0" + +lazy-req@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + dependencies: + readable-stream "^2.0.5" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +lerna@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/lerna/-/lerna-2.11.0.tgz#89b5681e286d388dda5bbbdbbf6b84c8094eff65" + dependencies: + async "^1.5.0" + chalk "^2.1.0" + cmd-shim "^2.0.2" + columnify "^1.5.4" + command-join "^2.0.0" + conventional-changelog-cli "^1.3.13" + conventional-recommended-bump "^1.2.1" + dedent "^0.7.0" + execa "^0.8.0" + find-up "^2.1.0" + fs-extra "^4.0.1" + get-port "^3.2.0" + glob "^7.1.2" + glob-parent "^3.1.0" + globby "^6.1.0" + graceful-fs "^4.1.11" + hosted-git-info "^2.5.0" + inquirer "^3.2.2" + is-ci "^1.0.10" + load-json-file "^4.0.0" + lodash "^4.17.4" + minimatch "^3.0.4" + npmlog "^4.1.2" + p-finally "^1.0.0" + package-json "^4.0.1" + path-exists "^3.0.0" + read-cmd-shim "^1.0.1" + read-pkg "^3.0.0" + rimraf "^2.6.1" + safe-buffer "^5.1.1" + semver "^5.4.1" + signal-exit "^3.0.2" + slash "^1.0.0" + strong-log-transformer "^1.0.6" + temp-write "^3.3.0" + write-file-atomic "^2.3.0" + write-json-file "^2.2.0" + write-pkg "^3.1.0" + yargs "^8.0.2" + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lint-staged@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.2.0.tgz#bdf4bb7f2f37fe689acfaec9999db288a5b26888" + dependencies: + app-root-path "^2.0.1" + chalk "^2.3.1" + commander "^2.14.1" + cosmiconfig "^5.0.2" + debug "^3.1.0" + dedent "^0.7.0" + execa "^0.9.0" + find-parent-dir "^0.3.0" + is-glob "^4.0.0" + is-windows "^1.0.2" + jest-validate "^23.0.0" + listr "^0.14.1" + lodash "^4.17.5" + log-symbols "^2.2.0" + micromatch "^3.1.8" + npm-which "^3.0.1" + p-map "^1.1.1" + path-is-inside "^1.0.2" + pify "^3.0.0" + please-upgrade-node "^3.0.2" + staged-git-files "1.1.1" + string-argv "^0.0.2" + stringify-object "^3.2.2" + +list-item@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/list-item/-/list-item-1.1.1.tgz#0c65d00e287cb663ccb3cb3849a77e89ec268a56" + dependencies: + expand-range "^1.8.1" + extend-shallow "^2.0.1" + is-number "^2.1.0" + repeat-string "^1.5.2" + +listr-silent-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + +listr-update-renderer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz#344d980da2ca2e8b145ba305908f32ae3f4cc8a7" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + strip-ansi "^3.0.1" + +listr-verbose-renderer@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +listr@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.1.tgz#8a7afa4a7135cee4c921d128e0b7dfc6e522d43d" + dependencies: + "@samverschueren/stream-to-observable" "^0.3.0" + cli-truncate "^0.2.1" + figures "^1.7.0" + indent-string "^2.1.0" + is-observable "^1.1.0" + is-promise "^2.1.0" + is-stream "^1.1.0" + listr-silent-renderer "^1.1.1" + listr-update-renderer "^0.4.0" + listr-verbose-renderer "^0.4.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + ora "^0.2.3" + p-map "^1.1.1" + rxjs "^6.1.0" + strip-ansi "^3.0.1" + +livereload-js@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.3.0.tgz#c3ab22e8aaf5bf3505d80d098cbad67726548c9a" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash._basecopy@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" + +lodash._basetostring@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" + +lodash._basevalues@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" + +lodash._getnative@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" + +lodash._isiterateecall@^3.0.0: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" + +lodash._reescape@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" + +lodash._reevaluate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" + +lodash._reinterpolate@^3.0.0, lodash._reinterpolate@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" + +lodash._root@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" + +lodash.escape@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" + dependencies: + lodash._root "^3.0.0" + +lodash.isarguments@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + +lodash.isarray@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" + +lodash.isequal@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + +lodash.keys@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" + dependencies: + lodash._getnative "^3.0.0" + lodash.isarguments "^3.0.0" + lodash.isarray "^3.0.0" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash.restparam@^3.0.0: + version "3.6.1" + resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" + +lodash.template@^3.0.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" + dependencies: + lodash._basecopy "^3.0.0" + lodash._basetostring "^3.0.0" + lodash._basevalues "^3.0.0" + lodash._isiterateecall "^3.0.0" + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + lodash.keys "^3.0.0" + lodash.restparam "^3.0.0" + lodash.templatesettings "^3.0.0" + +lodash.template@^4.0.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + dependencies: + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" + +lodash.templatesettings@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" + dependencies: + lodash._reinterpolate "^3.0.0" + lodash.escape "^3.0.0" + +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + dependencies: + lodash._reinterpolate "~3.0.0" + +lodash.unescape@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + +lodash@^4.17.4, lodash@^4.3.0: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + +lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + +log-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" + dependencies: + chalk "^1.0.0" + +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + dependencies: + chalk "^2.0.1" + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + +logalot@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/logalot/-/logalot-2.1.0.tgz#5f8e8c90d304edf12530951a5554abb8c5e3f552" + dependencies: + figures "^1.3.5" + squeak "^1.0.0" + +loglevel-colored-level-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz#6a40218fdc7ae15fc76c3d0f3e676c465388603e" + dependencies: + chalk "^1.1.3" + loglevel "^1.4.1" + +loglevel@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.0.tgz#ae0caa561111498c5ba13723d6fb631d24003934" + +longest@^1.0.0, longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + +lpad-align@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/lpad-align/-/lpad-align-1.1.2.tgz#21f600ac1c3095c3c6e497ee67271ee08481fe9e" + dependencies: + get-stdin "^4.0.1" + indent-string "^2.1.0" + longest "^1.0.0" + meow "^3.3.0" + +lru-cache@^4.0.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + dependencies: + pify "^3.0.0" + +make-plural@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/make-plural/-/make-plural-4.1.1.tgz#5658ce9d337487077daed221854c8cef9dd75749" + optionalDependencies: + minimist "^1.2.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +map-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +markdown-link@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/markdown-link/-/markdown-link-0.1.1.tgz#32c5c65199a6457316322d1e4229d13407c8c7cf" + +markdown-toc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/markdown-toc/-/markdown-toc-1.2.0.tgz#44a15606844490314afc0444483f9e7b1122c339" + dependencies: + concat-stream "^1.5.2" + diacritics-map "^0.1.0" + gray-matter "^2.1.0" + lazy-cache "^2.0.2" + list-item "^1.1.1" + markdown-link "^0.1.1" + minimist "^1.2.0" + mixin-deep "^1.1.3" + object.pick "^1.2.0" + remarkable "^1.7.1" + repeat-string "^1.6.1" + strip-color "^0.1.0" + +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + +mdn-data@^1.0.0, mdn-data@~1.1.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + +meow@^3.1.0, meow@^3.3.0, meow@^3.5.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +meow@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-4.0.1.tgz#d48598f6f4b1472f35bf6317a95945ace347f975" + dependencies: + camelcase-keys "^4.0.0" + decamelize-keys "^1.0.0" + loud-rejection "^1.0.0" + minimist "^1.1.3" + minimist-options "^3.0.1" + normalize-package-data "^2.3.4" + read-pkg-up "^3.0.0" + redent "^2.0.0" + trim-newlines "^2.0.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +merge-stream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" + dependencies: + readable-stream "^2.0.1" + +messageformat-parser@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/messageformat-parser/-/messageformat-parser-1.1.0.tgz#13ba2250a76bbde8e0fca0dbb3475f95c594a90a" + +messageformat@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/messageformat/-/messageformat-1.1.0.tgz#706c26f901e8219b3aa5308f8b5eaa3781d23a7a" + dependencies: + glob "~7.0.6" + make-plural "^4.0.1" + messageformat-parser "^1.1.0" + nopt "~3.0.6" + reserved-words "^0.1.2" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +micromatch@^2.3.7: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimatch@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" + dependencies: + brace-expansion "^1.0.0" + +minimist-options@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.1.0.tgz#99df657a52574c21c9057497df742790b2b4c0de" + +minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +mixin-deep@^1.1.3, mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +modify-values@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" + +moment@^2.6.0: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multipipe@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" + dependencies: + duplexer2 "0.0.2" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nanomatch@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-odd "^2.0.0" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-status-codes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" + +nopt@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + dependencies: + abbrev "1" + +normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" + +normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +npm-path@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" + dependencies: + which "^1.2.10" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npm-which@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" + dependencies: + commander "^2.9.0" + npm-path "^2.0.2" + which "^1.2.10" + +npmlog@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" + +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + +object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.pick@^1.2.0, object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +object.values@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +opn@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + dependencies: + is-wsl "^1.1.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +optipng-bin@^3.0.0: + version "3.1.4" + resolved "https://registry.yarnpkg.com/optipng-bin/-/optipng-bin-3.1.4.tgz#95d34f2c488704f6fd70606bfea0c659f1d95d84" + dependencies: + bin-build "^2.0.0" + bin-wrapper "^3.0.0" + logalot "^2.0.0" + +ora@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + +ordered-read-streams@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" + dependencies: + is-stream "^1.0.1" + readable-stream "^2.0.1" + +original@>=0.0.5: + version "1.0.1" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.1.tgz#b0a53ff42ba997a8c9cd1fb5daaeb42b9d693190" + dependencies: + url-parse "~1.4.0" + +os-filter-obj@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/os-filter-obj/-/os-filter-obj-1.0.3.tgz#5915330d90eced557d2d938a31c6dd214d9c63ad" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + +p-pipe@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" + +package-json@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + dependencies: + got "^6.7.1" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +parse-github-repo-url@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.1.0, parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +please-upgrade-node@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.0.2.tgz#7b9eaeca35aa4a43d6ebdfd10616c042f9a83acc" + dependencies: + semver-compare "^1.0.0" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" + dependencies: + postcss "^5.0.4" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +prepend-http@^1.0.0, prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +prettier-eslint-cli@4.7.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/prettier-eslint-cli/-/prettier-eslint-cli-4.7.1.tgz#3d103c494baa4e80b99ad53e2b9db7620101859f" + dependencies: + arrify "^1.0.1" + babel-runtime "^6.23.0" + boolify "^1.0.0" + camelcase-keys "^4.1.0" + chalk "2.3.0" + common-tags "^1.4.0" + eslint "^4.5.0" + find-up "^2.1.0" + get-stdin "^5.0.1" + glob "^7.1.1" + ignore "^3.2.7" + indent-string "^3.1.0" + lodash.memoize "^4.1.2" + loglevel-colored-level-prefix "^1.0.0" + messageformat "^1.0.2" + prettier-eslint "^8.5.0" + rxjs "^5.3.0" + yargs "10.0.3" + +prettier-eslint@^8.5.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/prettier-eslint/-/prettier-eslint-8.8.0.tgz#09ded651ac190bd357a00140b96d5b1fb20a2418" + dependencies: + babel-runtime "^6.26.0" + common-tags "^1.4.0" + dlv "^1.1.0" + eslint "^4.0.0" + indent-string "^3.2.0" + lodash.merge "^4.6.0" + loglevel-colored-level-prefix "^1.0.0" + prettier "^1.7.0" + pretty-format "^22.0.3" + require-relative "^0.8.7" + typescript "^2.5.1" + typescript-eslint-parser "^11.0.0" + +prettier@^1.7.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" + +pretty-format@^22.0.3: + version "22.4.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-22.4.0.tgz#237b1f7e1c50ed03bc65c03ccc29d7c8bb7beb94" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + +pretty-format@^23.0.1: + version "23.0.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.0.1.tgz#d61d065268e4c759083bccbca27a01ad7c7601f4" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + +prismjs@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.15.0.tgz#8801d332e472091ba8def94976c8877ad60398d9" + optionalDependencies: + clipboard "^2.0.0" + +private@^0.1.6, private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.6.0: + version "15.6.0" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +proxy-addr@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.6.0" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +q@0.9.7: + version "0.9.7" + resolved "https://registry.yarnpkg.com/q/-/q-0.9.7.tgz#4de2e6cb3b29088c9e4cbc03bf9d42fb96ce2f75" + +q@^1.1.2, q@^1.4.1, q@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + +qs@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@^6.4.0, qs@~6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" + +quick-lru@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-1.1.0.tgz#4360b17c61136ad38078397ff11416e186dcfbb8" + +randomatic@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.0.0.tgz#d35490030eb4f7578de292ce6dfb04a91a128923" + dependencies: + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" + +range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +raw-body@~1.1.0: + version "1.1.7" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425" + dependencies: + bytes "1" + string_decoder "0.10" + +rc@^1.0.1, rc@^1.1.2, rc@^1.1.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-dev-utils@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-5.0.1.tgz#1f396e161fe44b595db1b186a40067289bf06613" + dependencies: + address "1.0.3" + babel-code-frame "6.26.0" + chalk "1.1.3" + cross-spawn "5.1.0" + detect-port-alt "1.1.6" + escape-string-regexp "1.0.5" + filesize "3.5.11" + global-modules "1.0.0" + gzip-size "3.0.0" + inquirer "3.3.0" + is-root "1.0.0" + opn "5.2.0" + react-error-overlay "^4.0.0" + recursive-readdir "2.2.1" + shell-quote "1.6.1" + sockjs-client "1.1.4" + strip-ansi "3.0.1" + text-table "0.2.0" + +react-dom@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.1.tgz#7f8b0223b3a5fbe205116c56deb85de32685dad6" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +react-error-overlay@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" + +react@^16.4.1: + version "16.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +read-all-stream@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" + dependencies: + pinkie-promise "^2.0.0" + readable-stream "^2.0.0" + +read-cmd-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" + dependencies: + graceful-fs "^4.1.2" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" + dependencies: + find-up "^2.0.0" + read-pkg "^3.0.0" + +read-pkg@^1.0.0, read-pkg@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.3.0, readable-stream@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^2.2.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + dependencies: + resolve "^1.1.6" + +recursive-readdir@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99" + dependencies: + minimatch "3.0.3" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + +regenerate@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpp@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.0.1.tgz#d857c3a741dce075c2848dcb019a0a975b190d43" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +registry-auth-token@^3.0.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + dependencies: + rc "^1.0.1" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remarkable@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.1.tgz#aaca4972100b66a642a63a1021ca4bac1be3bff6" + dependencies: + argparse "~0.1.15" + autolinker "~0.15.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +replace-ext@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" + +replace-ext@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" + +request@^2.53.0, request@^2.87.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +require-relative@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +reserved-words@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" + +resolve-dir@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@^1.1.6: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + dependencies: + path-parse "^1.0.5" + +resolve@^1.2.0, resolve@^1.3.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@^2.2.6, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +rxjs@^5.3.0: + version "5.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.5.tgz#e164f11d38eaf29f56f08c3447f74ff02dd84e97" + dependencies: + symbol-observable "1.0.1" + +rxjs@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.0.tgz#e024d0e180b72756a83c2aaea8f25423751ba978" + dependencies: + tslib "^1.9.0" + +safe-buffer@5.1.1, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + +safe-json-parse@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +safer-buffer@^2.0.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +sax@~1.2.1, sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +seek-bzip@^1.0.3: + version "1.0.5" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" + dependencies: + commander "~2.8.1" + +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + +semver-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" + +semver-truncate@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/semver-truncate/-/semver-truncate-1.1.2.tgz#57f41de69707a62709a7e0104ba2117109ea47e8" + dependencies: + semver "^5.3.0" + +"semver@2 || 3 || 4 || 5", semver@5.4.1, semver@^5.3.0, semver@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" + +semver@^4.0.3: + version "4.3.6" + resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" + +semver@^5.1.0, semver@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-getter@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" + dependencies: + to-object-path "^0.3.0" + +set-immediate-shim@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +shell-quote@1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" + dependencies: + array-filter "~0.0.0" + array-map "~0.0.0" + array-reduce "~0.0.0" + jsonify "~0.0.0" + +shelljs@^0.7.8: + version "0.7.8" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3" + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + dependencies: + is-arrayish "^0.3.1" + +sitemap@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-1.13.0.tgz#569cbe2180202926a62a266cd3de09c9ceb43f83" + dependencies: + underscore "^1.7.0" + url-join "^1.1.0" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +sparkles@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +split2@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" + dependencies: + through2 "^2.0.2" + +split@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +squeak@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/squeak/-/squeak-1.3.0.tgz#33045037b64388b567674b84322a6521073916c3" + dependencies: + chalk "^1.0.0" + console-stream "^0.1.1" + lpad-align "^1.0.1" + +sshpk@^1.7.0: + version "1.14.2" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + safer-buffer "^2.0.2" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stable@~0.1.6: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + +staged-git-files@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b" + +stat-mode@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + +string-argv@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" + +string-template@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@0.10, string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" + dependencies: + get-own-enumerable-property-symbols "^2.0.1" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" + dependencies: + first-chunk-stream "^1.0.0" + strip-bom "^2.0.0" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-color@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" + +strip-dirs@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-1.1.1.tgz#960bbd1287844f3975a4558aa103a8255e2456a0" + dependencies: + chalk "^1.0.0" + get-stdin "^4.0.1" + is-absolute "^0.1.5" + is-natural-number "^2.0.0" + minimist "^1.1.0" + sum-up "^1.0.1" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +strip-outer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" + dependencies: + escape-string-regexp "^1.0.2" + +strong-log-transformer@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz#f7fb93758a69a571140181277eea0c2eb1301fa3" + dependencies: + byline "^5.0.0" + duplexer "^0.1.1" + minimist "^0.1.0" + moment "^2.6.0" + through "^2.3.4" + +sum-up@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sum-up/-/sum-up-1.0.3.tgz#1c661f667057f63bcb7875aa1438bc162525156e" + dependencies: + chalk "^1.0.0" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" + dependencies: + has-flag "^2.0.0" + +supports-color@^5.2.0, supports-color@^5.3.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +svgo@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.0.5.tgz#7040364c062a0538abacff4401cea6a26a7a389a" + dependencies: + coa "~2.0.1" + colors "~1.1.2" + css-select "~1.3.0-rc0" + css-select-base-adapter "~0.1.0" + css-tree "1.0.0-alpha25" + css-url-regex "^1.1.0" + csso "^3.5.0" + js-yaml "~3.10.0" + mkdirp "~0.5.1" + object.values "^1.0.4" + sax "~1.2.4" + stable "~0.1.6" + unquote "~1.1.1" + util.promisify "~1.0.0" + +symbol-observable@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + +symbol-observable@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" + +table@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +tar-stream@^1.1.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" + dependencies: + bl "^1.0.0" + buffer-alloc "^1.1.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" + xtend "^4.0.0" + +tcp-port-used@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tcp-port-used/-/tcp-port-used-0.1.2.tgz#9450e8768c83b416fd4d1a6a9449eeccbf496c29" + dependencies: + debug "0.7.4" + is2 "0.0.9" + q "0.9.7" + +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + +temp-write@^3.3.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492" + dependencies: + graceful-fs "^4.1.2" + is-stream "^1.1.0" + make-dir "^1.0.0" + pify "^3.0.0" + temp-dir "^1.0.0" + uuid "^3.0.1" + +tempfile@^1.0.0, tempfile@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-1.1.1.tgz#5bcc4eaecc4ab2c707d8bc11d99ccc9a2cb287f2" + dependencies: + os-tmpdir "^1.0.0" + uuid "^2.0.1" + +tempfile@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" + dependencies: + temp-dir "^1.0.0" + uuid "^3.0.1" + +text-extensions@^1.0.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.7.0.tgz#faaaba2625ed746d568a23e4d0aacd9bf08a8b39" + +text-table@0.2.0, text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +through2-filter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^0.6.0, through2@^0.6.1: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + +through2@^2.0.0, through2@^2.0.2, through2@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + +timed-out@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + +tiny-emitter@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" + +tiny-lr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" + dependencies: + body "^5.1.0" + debug "^3.1.0" + faye-websocket "~0.10.0" + livereload-js "^2.3.0" + object-assign "^4.1.0" + qs "^6.4.0" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +to-absolute-glob@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" + dependencies: + extend-shallow "^2.0.1" + +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toml@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/toml/-/toml-2.3.3.tgz#8d683d729577cb286231dfc7a8affe58d31728fb" + +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + +tree-node-cli@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/tree-node-cli/-/tree-node-cli-1.2.5.tgz#afd75437976bbf2cc0c52b9949798e7530e8fd8c" + dependencies: + commander "^2.15.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim-newlines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-2.0.0.tgz#b403d0b91be50c331dfc4b82eeceb22c3de16d20" + +trim-off-newlines@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" + +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + dependencies: + escape-string-regexp "^1.0.2" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tslib@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.1.tgz#a5d1f0532a49221c87755cfcc89ca37197242ba7" + +tunnel-agent@^0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.15, type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.18" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +typescript-eslint-parser@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-11.0.0.tgz#37dba6a0130dd307504aa4b4b21b0d3dc7d4e9f2" + dependencies: + lodash.unescape "4.0.1" + semver "5.4.1" + +typescript@^2.5.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + +ua-parser-js@^0.7.9: + version "0.7.17" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +underscore.string@~2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b" + +underscore@^1.7.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" + +underscore@~1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + +unique-stream@^2.0.2: + version "2.2.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" + dependencies: + json-stable-stringify "^1.0.0" + through2-filter "^2.0.0" + +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unquote@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +unzip-response@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" + +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +url-join@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + +url-parse@^1.1.8, url-parse@~1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.1.tgz#4dec9dad3dc8585f862fed461d2e19bbf623df30" + dependencies: + querystringify "^2.0.0" + requires-port "^1.0.0" + +url-regex@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/url-regex/-/url-regex-3.2.0.tgz#dbad1e0c9e29e105dd0b1f09f6862f7fdb482724" + dependencies: + ip-regex "^1.0.1" + +use@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" + dependencies: + kind-of "^6.0.2" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util.promisify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" + +uuid@^3.0.1, uuid@^3.1.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" + +vali-date@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vinyl-assign@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/vinyl-assign/-/vinyl-assign-1.2.1.tgz#4d198891b5515911d771a8cd9c5480a46a074a45" + dependencies: + object-assign "^4.0.1" + readable-stream "^2.0.0" + +vinyl-fs@^2.2.0: + version "2.4.4" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" + dependencies: + duplexify "^3.2.0" + glob-stream "^5.3.2" + graceful-fs "^4.0.0" + gulp-sourcemaps "1.6.0" + is-valid-glob "^0.3.0" + lazystream "^1.0.0" + lodash.isequal "^4.0.0" + merge-stream "^1.0.0" + mkdirp "^0.5.0" + object-assign "^4.0.0" + readable-stream "^2.0.4" + strip-bom "^2.0.0" + strip-bom-stream "^1.0.0" + through2 "^2.0.0" + through2-filter "^2.0.0" + vali-date "^1.0.0" + vinyl "^1.0.0" + +vinyl@^0.4.3: + version "0.4.6" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" + dependencies: + clone "^0.2.0" + clone-stats "^0.0.1" + +vinyl@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +vinyl@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" + dependencies: + clone "^1.0.0" + clone-stats "^0.0.1" + replace-ext "0.0.1" + +ware@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ware/-/ware-1.3.0.tgz#d1b14f39d2e2cb4ab8c4098f756fe4b164e473d4" + dependencies: + wrap-fn "^0.1.0" + +wcwidth@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + dependencies: + defaults "^1.0.3" + +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + +whatwg-fetch@>=0.10.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" + +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.2.10, which@^1.2.9: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +which@^1.2.14: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + dependencies: + string-width "^1.0.2 || 2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrap-fn@^0.1.0: + version "0.1.5" + resolved "https://registry.yarnpkg.com/wrap-fn/-/wrap-fn-0.1.5.tgz#f21b6e41016ff4a7e31720dbc63a09016bdf9845" + dependencies: + co "3.1.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^2.0.0, write-file-atomic@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write-json-file@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.3.0.tgz#2b64c8a33004d54b8698c76d585a77ceb61da32f" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^3.0.0" + sort-keys "^2.0.0" + write-file-atomic "^2.0.0" + +write-pkg@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.2.0.tgz#0e178fe97820d389a8928bc79535dbe68c2cff21" + dependencies: + sort-keys "^2.0.0" + write-json-file "^2.2.0" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yamljs@^0.2.1: + version "0.2.10" + resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.2.10.tgz#481cc7c25ca73af59f591f0c96e3ce56c757a40f" + dependencies: + argparse "^1.0.7" + glob "^7.0.5" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + +yargs-parser@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" + dependencies: + camelcase "^4.1.0" + +yargs@10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae" + dependencies: + cliui "^3.2.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^8.0.0" + +yargs@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-2.3.0.tgz#e900c87250ec5cd080db6009fe3dd63156f1d7fb" + dependencies: + wordwrap "0.0.2" + +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0" + +yauzl@^2.2.1: + version "2.9.2" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.2.tgz#4fb1bc7ae1fc2f57037b54af6acc8fe1031c5b77" + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0"