diff --git a/.babelrc b/.babelrc index b16cc73..6464282 100644 --- a/.babelrc +++ b/.babelrc @@ -11,6 +11,7 @@ "react" ], "plugins": [ + "transform-decorators-legacy", "transform-class-properties", ["transform-object-rest-spread", { "useBuiltIns": true }] ] diff --git a/.eslintrc.json b/.eslintrc.json index bd858cc..632602f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "parser": "babel-eslint", "env": { "browser": true, "commonjs": true, @@ -12,15 +13,38 @@ "sourceType": "module" }, "rules": { + "strict": 0, "no-const-assign": "warn", - "no-this-before-super": "warn", - "no-undef": "warn", + "no-this-before-super": "error", + "no-undef": "error", "no-unreachable": "warn", "no-unused-vars": "warn", "constructor-super": "warn", - "valid-typeof": "warn" + "react/jsx-uses-vars": 1, + "react/jsx-uses-react": 1, + "valid-typeof": "warn", + "babel/new-cap": 1, + "babel/no-invalid-this": 1, + "babel/object-curly-spacing": 0, + "babel/quotes": 0, + "babel/semi": 0, + "babel/no-unused-expressions": 1, + "import/order": [ + 1, + { + "groups": ["external", "internal", "builtin", ["parent", "sibling", "index"]], + "newlines-between": "never" + } + ], + "babel/no-invalid-this": 0, + "no-unused-vars": 0 }, "plugins": [ - "class-property" - ] + "babel", + "react", + "import" + ], + "globals": { + "chrome": true + } } diff --git a/README.md b/README.md index 05f20b5..722a26d 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,35 @@ # Gitako -Yet another Chrome extension for GitHub. Inspired by [Octotree](https://github.com/buunguyen/octotree) & [GayHub](https://github.com/jawil/GayHub). Aimed at providing better UX, consistent UI regards to GitHub and more essential features. +Yet another Chrome extension for GitHub. Inspired by [Octotree](https://github.com/buunguyen/octotree) & [GayHub](https://github.com/jawil/GayHub), thanks to them! Gitako is aimed at providing better UX, consistent UI regards to GitHub and more essential features. ### Features - [x] neat file tree - [x] instant file search -- [x] keyboard accessibility - - press 'up' and 'down' to nav between search input and files in the list - - press 'enter' or 'right' to redirect to file or expand folder - - press 'left' to jump to parent folder or collapse current folder +- [x] fast even in gigantic projects +- [x] intuitive keyboard accessibility + - file navigation using arrow keys + - set shortcut to toggle sidebar - [x] redirect smoothly with pjax -- [x] enlargeable when window is wide enough -- [x] copy snippets with one click - - copy code snippets inside markdown preview - - copy all contents of a file -- [x] support access token (for private repo and frequent usage) -- [x] consistent UI with GitHub - - [x] use [octicons](https://octicons.github.com) -- [x] light size (~30kb) & low memory usage +- [x] resize-able when window is wide enough +- [x] copy snippets with single click + - copy code snippets from markdown preview + - copy whole content of a file +- [x] support private repo and frequent usages +- [x] support git submodule +- [x] UI consistent to GitHub's +- [x] light size (~60kb) & low memory usage - [more WIP](https://github.com/EnixCoda/Gitako/projects/1) -### Download +[Change Log](https://github.com/EnixCoda/Gitako/wiki/ChangeLog) -[install from Chrome web store](https://chrome.google.com/webstore/detail/gitako/giljefjcheohhamkjphiebfjnlphnokk) +### Install + +[Please install from Chrome Web Store](https://chrome.google.com/webstore/detail/gitako/giljefjcheohhamkjphiebfjnlphnokk) ### Issues -Any bug report or feature requests are welcomed, feel free to draft a [issue](https://github.com/EnixCoda/Gitako/issues/)! +Any bug report or feature request discussions are welcomed, feel free to draft a [issue](https://github.com/EnixCoda/Gitako/issues/)! ### About diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..b50e39b --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "jsx": "react", + "target": "es2017", + "experimentalDecorators": true, + "baseUrl": "./", + "paths": { + "*" : ["src/*", "packages/*"] + } + }, + "exclude": [ + "node_modules", + ], + "include": [ + "src/**/*", + "packages/**/*", + ] +} diff --git a/package.json b/package.json index 6099958..71d8a6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitako", - "version": "0.2.4", + "version": "0.3.16", "description": "yet another extension for GitHub", "repository": "https://github.com/EnixCoda/Gitako", "author": "EnixCoda", @@ -8,28 +8,41 @@ "private": true, "scripts": { "start": "webpack --watch", - "dev": "webpack", - "prod": "NODE_ENV=production webpack && cd dist && rm -f ./gitako.zip && zip gitako.zip -r *" + "stamp": "node scripts/version.js", + "postversion": "npm run stamp && git add src/manifest.json && git commit --amend --no-edit", + "prod": "npm run stamp && NODE_ENV=production webpack && cd dist && rm -f ./gitako.zip && zip gitako.zip -r *" }, "dependencies": { + "ini": "^1.3.5", "nprogress": "^0.2.0", "octicons": "^7.1.0", - "preact": "^8.2.6", - "preact-portal": "^1.1.3" + "prop-types": "^15.6.1", + "react": "^16.4.0", + "react-dom": "^16.4.0" }, "devDependencies": { "babel-core": "^6.26.0", + "babel-eslint": "^8.2.3", "babel-loader": "^7.1.2", "babel-plugin-react-require": "^3.0.0", "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", "copy-webpack-plugin": "^4.2.0", "css-loader": "^0.28.7", + "eslint": "^4.19.1", + "eslint-plugin-babel": "^5.1.0", + "eslint-plugin-class-property": "^1.1.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-react": "^7.9.1", "file-loader": "^1.1.5", + "fsevents": "^1.2.4", + "json-loader": "^0.5.7", "less": "^2.7.3", "less-loader": "^4.0.5", + "react-life-hook": "^0.2.0", "style-loader": "^0.19.0", "svgr": "^1.6.0", "uglifyjs-webpack-plugin": "^1.1.1", diff --git a/generate-octicon.js b/scripts/generate-octicon.js similarity index 100% rename from generate-octicon.js rename to scripts/generate-octicon.js diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 0000000..3aa7456 --- /dev/null +++ b/scripts/version.js @@ -0,0 +1,10 @@ +const fs = require('fs') +const path = require('path') + +const rootPath = path.resolve(__dirname, '../') +const packageJSON = require(path.resolve(rootPath, 'package.json')) +const manifestPath = path.resolve(rootPath, 'src/manifest.json') +const manifest = require(manifestPath) + +manifest.version = packageJSON.version +fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, ' '), 'utf-8') diff --git a/src/analytics.js b/src/analytics.js new file mode 100644 index 0000000..9cf2a31 --- /dev/null +++ b/src/analytics.js @@ -0,0 +1,34 @@ +// TODO: set this through ENV or something else +const LOG_ENDPOINT = 'https://enix.one/gitako/log' + +export function raiseError(error) { + return reportError(error) +} + +export function withErrorLog(method, args) { + return [ + function() { + try { + method.apply(this, arguments) + } catch (error) { + raiseError(error) + } + }, + args, + ] +} + +function encodeParams(params) { + return Object.keys(params) + .map(key => `${key}=${encodeURIComponent(JSON.stringify(params[key]))}`) + .join('&') +} + +function reportError(error) { + return fetch( + `${LOG_ENDPOINT}?${encodeParams({ + error: (error && error.message) || error, + path: window.location.href, + })}` + ) +} diff --git a/src/components/FileExplorer.js b/src/components/FileExplorer.js index dc1b32f..3dd07c6 100644 --- a/src/components/FileExplorer.js +++ b/src/components/FileExplorer.js @@ -1,203 +1,100 @@ -import preact from 'preact' -/** @jsx preact.h */ -import SearchBar from './SearchBar' -import Node from './Node' +import React from 'react' +import PropTypes from 'prop-types' +import connect from 'driver/connect' +import { FileExplorer as FileExplorerCore } from 'driver/core' +import SearchBar from 'components/SearchBar' +import Node from 'components/Node' +import LoadingIndicator from 'components/LoadingIndicator' +import cx from 'utils/cx' -import cx from '../utils/cx' -import DOMHelper from '../utils/DOMHelper' -import treeParser from '../utils/treeParser' -import URLHelper from '../utils/URLHelper' -import VisibleNodesGenerator from '../utils/VisibleNodesGenerator' +@connect(FileExplorerCore) +export default class FileExplorer extends React.Component { + static propTypes = { + treeData: PropTypes.object, + metaData: PropTypes.object, + freeze: PropTypes.bool, + visibleNodes: PropTypes.object, -export default class List extends preact.Component { - state = { - // generated by this.visibleNodesGenerator - visibleNodes: null, + init: PropTypes.func.isRequired, + execAfterRender: PropTypes.func.isRequired, + handleKeyDown: PropTypes.func.isRequired, + handleSearchKeyChange: PropTypes.func.isRequired, + setExpand: PropTypes.func.isRequired, + toggleNodeExpansion: PropTypes.func.isRequired, + focusNode: PropTypes.func.isRequired, + onNodeClick: PropTypes.func.isRequired, + updateVisibleNodes: PropTypes.func.isRequired, } - props = { + static defaultProps = { treeData: null, metaData: null, freeze: false, + visibleNodes: null, } - tasksAfterRender = [] - visibleNodesGenerator = new VisibleNodesGenerator() - componentWillMount() { - const { treeData, metaData } = this.props - const { root, nodes } = treeParser.parse(treeData, metaData) - this.visibleNodesGenerator.plantTree(root, nodes) - const currentPath = URLHelper.getCurrentPath(true) - this.tasksAfterRender.push(DOMHelper.focusSearchInput) - if (currentPath.length) { - const nodeExpandedTo = this.visibleNodesGenerator.expandTo(currentPath) - if (nodeExpandedTo) { - this.visibleNodesGenerator.focusNode(nodeExpandedTo) - const { nodes } = this.visibleNodesGenerator.visibleNodes - this.tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo))) - } - } - this.updateVisibleNodes() + const { init, setUpTree, treeData } = this.props + init() + setUpTree(treeData) } componentDidMount() { - this.execAfterRender() + const { execAfterRender } = this.props + execAfterRender() } - componentDidUpdate(prevProps, prevState) { - this.execAfterRender() - } - - execAfterRender() { - for (const task of this.tasksAfterRender) { - task() - } - this.tasksAfterRender.length = 0 - } - - updateVisibleNodes() { - const { visibleNodes } = this.visibleNodesGenerator - this.setState({ visibleNodes }) - } - - handleKeyDown = event => { - const { key } = event - const { visibleNodes: { nodes, focusedNode, expandedNodes, depths } } = this.state - let shouldStopPropagation = true // prevent body scrolling - if (focusedNode) { - const focusedNodeIndex = nodes.indexOf(focusedNode) - switch (key) { - case 'ArrowUp': - // focus on previous node - if (focusedNodeIndex === 0) { - this.focusNode(null) - this.tasksAfterRender.push(DOMHelper.focusSearchInput) - } else { - this.focusNode(nodes[focusedNodeIndex - 1]) - } - break - - case 'ArrowDown': - // focus on next node - if (focusedNodeIndex + 1 < nodes.length) { - this.focusNode(nodes[focusedNodeIndex + 1]) - } else { - this.focusNode(null) - this.tasksAfterRender.push(DOMHelper.focusSearchInput) - } - break - - case 'ArrowLeft': - // collapse node or go to parent node - if (expandedNodes.has(focusedNode)) { - this.setExpand(focusedNode, false) - } else { - // go forward to the start of the list, find the closest node with lower depth - let indexOfParentNode = focusedNodeIndex - const focusedNodeDepth = depths.get(nodes[focusedNodeIndex]) - while ( - indexOfParentNode !== -1 && - depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth - ) { - --indexOfParentNode - } - if (indexOfParentNode !== -1) { - this.focusNode(nodes[indexOfParentNode]) - } - } - break - - // consider the two keys as 'confirm' key - case 'ArrowRight': - case 'Enter': - // expand node or redirect to file page - if (focusedNode.type === 'tree') { - this.setExpand(focusedNode, true) - } else { - // simulate click to trigger pjax - DOMHelper.clickOnNodeElement(focusedNodeIndex) - } - break - - default: - shouldStopPropagation = false - } - } else { - // now search input is focused - if (nodes.length) { - switch (key) { - case 'ArrowDown': - this.focusNode(nodes[0]) - break - case 'ArrowUp': - this.focusNode(nodes[nodes.length - 1]) - break - default: - shouldStopPropagation = false - } - } else { - shouldStopPropagation = false - } - } - if (shouldStopPropagation) { - event.stopPropagation() - event.preventDefault() + componentWillReceiveProps(nextProps) { + if (nextProps.treeData !== this.props.treeData) { + const { setUpTree } = nextProps + setUpTree() } } - handleSearchKeyChange = async event => { - const searchKey = event.target.value - await this.visibleNodesGenerator.search(searchKey) - this.updateVisibleNodes() + componentDidUpdate() { + const { execAfterRender } = this.props + execAfterRender() } - setExpand = (node, expand) => { - this.visibleNodesGenerator.setExpand(node, expand) - this.focusNode(node) - this.tasksAfterRender.push(DOMHelper.focusSearchInput) - } - - toggleNodeExpand = node => { - this.visibleNodesGenerator.toggleExpand(node) - this.focusNode(node) - this.tasksAfterRender.push(DOMHelper.focusFileExplorer) - } - - focusNode = node => { - this.visibleNodesGenerator.focusNode(node) - if (node) { - // when focus a node not in viewport(by keyboard), scroll to it - const { visibleNodes: { nodes } } = this.state - const indexOfToBeFocusedNode = nodes.indexOf(node) - this.tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode)) - this.tasksAfterRender.push(DOMHelper.focusSearchInput) + renderFiles(visibleNodes, onNodeClick) { + const { nodes, depths, focusedNode, expandedNodes } = visibleNodes + if (nodes.length === 0) { + return ( + + ) } - this.updateVisibleNodes() + return ( +
+ {nodes.map(node => ( + + ))} +
+ ) } render() { - const { visibleNodes: { nodes, depths, focusedNode, expandedNodes } } = this.state - const { freeze } = this.props + const { stateText, visibleNodes, freeze, handleKeyDown, handleSearchKeyChange, onNodeClick, toggleShowSettings } = this.props return ( -
- - {nodes.length === 0 ? ( - - ) : ( -
- {nodes.map(node => ( - - ))} -
- )} +
+ { + stateText + ? + : visibleNodes && ( + + + {this.renderFiles(visibleNodes, onNodeClick)} + + ) + }
) } diff --git a/src/components/Gitako.js b/src/components/Gitako.js new file mode 100644 index 0000000..aec2575 --- /dev/null +++ b/src/components/Gitako.js @@ -0,0 +1,23 @@ +import React from 'react' +import SideBar from 'components/SideBar' +import { Gitako as GitakoCore } from 'driver/core' +import connect from 'driver/connect' +import { raiseError } from 'analytics' + +@connect(GitakoCore) +export default class Gitako extends React.PureComponent { + componentDidMount() { + const { init } = this.props + init() + } + + componentDidCatch(error) { + raiseError(error) + } + + render() { + return ( + + ) + } +} diff --git a/src/components/Icon.js b/src/components/Icon.js index ca64f43..4f952e4 100644 --- a/src/components/Icon.js +++ b/src/components/Icon.js @@ -1,27 +1,26 @@ -import preact from 'preact' -/** @jsx preact.h */ - -import Grabber from '../assets/icons/octicons/grabber.svg?svgr' -import Octoface from '../assets/icons/octicons/octoface.svg?svgr' -import ChevronDown from '../assets/icons/octicons/chevron-down.svg?svgr' -import TriangleRight from '../assets/icons/octicons/triangle-right.svg?svgr' -import FilePdf from '../assets/icons/octicons/file-pdf.svg?svgr' -import File from '../assets/icons/octicons/file.svg?svgr' -import FileZip from '../assets/icons/octicons/file-zip.svg?svgr' -import Markdown from '../assets/icons/octicons/markdown.svg?svgr' -import FileMedia from '../assets/icons/octicons/file-media.svg?svgr' -import FileCode from '../assets/icons/octicons/file-code.svg?svgr' -// import FileBinary from '../assets/icons/octicons/file-binary.svg?svgr' -// import FileSubmodule from '../assets/icons/octicons/file-submodule.svg?svgr' -// import FileSymlinkDirectory from '../assets/icons/octicons/file-symlink-directory.svg?svgr' -// import FileSymlinkFile from '../assets/icons/octicons/file-symlink-file.svg?svgr' -import X from '../assets/icons/octicons/x.svg?svgr' -import Gear from '../assets/icons/octicons/gear.svg?svgr' - -import cx from '../utils/cx' +import React from 'react' +import Submodule from 'assets/icons/octicons/file-submodule.svg?svgr' +import Grabber from 'assets/icons/octicons/grabber.svg?svgr' +import Octoface from 'assets/icons/octicons/octoface.svg?svgr' +import ChevronDown from 'assets/icons/octicons/chevron-down.svg?svgr' +import TriangleRight from 'assets/icons/octicons/triangle-right.svg?svgr' +import FilePdf from 'assets/icons/octicons/file-pdf.svg?svgr' +import File from 'assets/icons/octicons/file.svg?svgr' +import FileZip from 'assets/icons/octicons/file-zip.svg?svgr' +import Markdown from 'assets/icons/octicons/markdown.svg?svgr' +import FileMedia from 'assets/icons/octicons/file-media.svg?svgr' +import FileCode from 'assets/icons/octicons/file-code.svg?svgr' +// import FileBinary from 'assets/icons/octicons/file-binary.svg?svgr' +// import FileSymlinkDirectory from 'assets/icons/octicons/file-symlink-directory.svg?svgr' +// import FileSymlinkFile from 'assets/icons/octicons/file-symlink-file.svg?svgr' +import X from 'assets/icons/octicons/x.svg?svgr' +import Gear from 'assets/icons/octicons/gear.svg?svgr' +import cx from 'utils/cx' function getSVGIconComponent(type) { switch (type) { + case 'submodule': + return Submodule case 'grabber': return Grabber case 'octoface': @@ -68,10 +67,12 @@ function getSVGIconComponent(type) { } } +const iconStyle = { width: '100%', height: '100%' } + export default function Icon({ type, className, ...otherProps }) { return (
- {preact.h(getSVGIconComponent(type), { width: '100%', height: '100%' })} + {React.createElement(getSVGIconComponent(type), iconStyle)}
) } diff --git a/src/components/LoadingIndicator.js b/src/components/LoadingIndicator.js new file mode 100644 index 0000000..5aca587 --- /dev/null +++ b/src/components/LoadingIndicator.js @@ -0,0 +1,13 @@ +import React from 'react' +import Icon from 'components/Icon'; + +export default function LoadingIndicator({ text }) { + return ( +
+
+ + {text} +
+
+ ) +} diff --git a/src/components/MetaBar.js b/src/components/MetaBar.js index eee74dd..b49b901 100644 --- a/src/components/MetaBar.js +++ b/src/components/MetaBar.js @@ -1,5 +1,4 @@ -import preact from 'preact' -/** @jsx preact.h */ +import React from 'react' export default function MetaBar({ metaData }) { const userUrl = metaData ? metaData.api && metaData.api.owner.html_url : undefined @@ -13,6 +12,8 @@ export default function MetaBar({ metaData }) { {metaData.repoName} +  /  + {metaData.branchName}
) } diff --git a/src/components/Node.js b/src/components/Node.js index 0db724b..3b6130f 100644 --- a/src/components/Node.js +++ b/src/components/Node.js @@ -1,49 +1,53 @@ -import preact from 'preact' -/** @jsx preact.h */ -import PJAX from 'pjax' - -import Icon from './Icon' - -import cx from '../utils/cx' -import general from '../utils/general' -import DOMHelper from '../utils/DOMHelper' +import React from 'react' +import Icon from 'components/Icon' +import cx from 'utils/cx' +import DOMHelper from 'utils/DOMHelper' +import LoadingIndicator from 'components/LoadingIndicator'; function getIconType(node) { switch (node.type) { case 'tree': return 'folder' + case 'commit': + return 'submodule' default: return node.name.replace(/.*\./, '.') } } -export default class Node extends preact.Component { - shouldComponentUpdate(nextProps) { - return !general.shallowEqual(this.props, nextProps) - } - - onNodeClick = (...args) => { - const { node, toggleExpand } = this.props - if (node.type === 'tree') { - toggleExpand(node, ...args) - } else { - DOMHelper.loadWithPJAX(node.url) - } +export default class Node extends React.PureComponent { + onClick = (event) => { + if (event.metaKey) return + event.preventDefault() + const { node, onClick } = this.props + onClick(node) } render() { - const { node, depth, expanded, focused, pjax } = this.props - const { name, path } = node + const { node, depth, expanded, focused } = this.props + const { name, path, virtual } = node + if (virtual) { + // this is not a real node + // for now, all virtual nodes are indicators for pending state + return ( +
+
+ +
+
+ ) + } return (
-

- - {name} -

+ +
+ + {name} +
+
) } diff --git a/src/components/PJAXLink.js b/src/components/PJAXLink.js index 79eaa0b..6e87cc3 100644 --- a/src/components/PJAXLink.js +++ b/src/components/PJAXLink.js @@ -1,9 +1,8 @@ -import preact from 'preact' -/** @jsx preact.h */ -import DOMHelper from '../utils/DOMHelper' +import React from 'react' +import DOMHelper from 'utils/DOMHelper' export default function PJAXLink({ to, children }) { - return preact.cloneElement(children, { + return React.cloneElement(children, { onClick: () => DOMHelper.loadWithPJAX(to), }) } diff --git a/src/components/Portal.js b/src/components/Portal.js new file mode 100644 index 0000000..a321eca --- /dev/null +++ b/src/components/Portal.js @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom' + +class Portal extends React.PureComponent { + render() { + const { into, children } = this.props + if (!(into instanceof window.Element)) return null + return ReactDOM.createPortal( + children, + into, + ) + } +} + +export default Portal diff --git a/src/components/Resizable.js b/src/components/Resizable.js new file mode 100644 index 0000000..1bef6a7 --- /dev/null +++ b/src/components/Resizable.js @@ -0,0 +1,29 @@ +import React from 'react' +import PropTypes from 'prop-types' +import ResizeHandler from 'components/ResizeHandler' +import cx from 'utils/cx'; + +export default class Resizable extends React.PureComponent { + static propTypes = { + baseSize: PropTypes.number.isRequired, + } + + state = { + size: this.props.baseSize, + } + + onResize = size => this.setState({ size: Math.max(this.props.baseSize, size) }) + + render() { + const { className, children } = this.props + const { size } = this.state + return ( +
+ +
+ {children} +
+
+ ) + } +} diff --git a/src/components/ResizeHandler.js b/src/components/ResizeHandler.js index b95b5b0..328742a 100644 --- a/src/components/ResizeHandler.js +++ b/src/components/ResizeHandler.js @@ -1,13 +1,11 @@ -import preact from 'preact' -/** @jsx preact.h */ +import React from 'react' +import Icon from 'components/Icon' -import Icon from './Icon' - -export default class ResizeHandler extends preact.Component { +export default class ResizeHandler extends React.PureComponent { pointerDown = false startX = 0 delta = 0 - baseSize = this.props.baseSize + baseSize = this.props.size subscribeEvents = () => { window.addEventListener('mousemove', this.onPointerMove) @@ -41,7 +39,7 @@ export default class ResizeHandler extends preact.Component { onPointerUp = () => { this.pointerDown = false - this.baseSize += this.delta + this.baseSize = Math.max(this.baseSize + this.delta, this.props.size) this.unsubscribeEvents() } @@ -57,4 +55,4 @@ export default class ResizeHandler extends preact.Component { ) } -} \ No newline at end of file +} diff --git a/src/components/SearchBar.js b/src/components/SearchBar.js index bd50951..9b8508e 100644 --- a/src/components/SearchBar.js +++ b/src/components/SearchBar.js @@ -1,5 +1,4 @@ -import preact from 'preact' -/** @jsx preact.h */ +import React from 'react' export default function SearchBar({ onSearchKeyChange }) { return ( diff --git a/src/components/SettingsBar.js b/src/components/SettingsBar.js index 97b4737..fe19ca1 100644 --- a/src/components/SettingsBar.js +++ b/src/components/SettingsBar.js @@ -1,9 +1,16 @@ -import preact from 'preact' -/** @jsx preact.h */ +import React from 'react' +import PropTypes from 'prop-types' +import Icon from 'components/Icon' +import configHelper, { config } from 'utils/configHelper' +import keyHelper from 'utils/keyHelper' +import { version } from '../../package' -import Icon from './Icon' -import storageHelper from '../utils/storageHelper' -import keyHelper from '../utils/keyHelper' +const wikiLinks = { + compressSingletonFolder: 'https://github.com/EnixCoda/Gitako/wiki/Compress-Singleton-Folder', + changeLog: 'https://github.com/EnixCoda/Gitako/wiki/Change-Log', + copyFileButton: 'https://github.com/EnixCoda/Gitako/wiki/Copy-file-and-snippet', + copySnippet: 'https://github.com/EnixCoda/Gitako/wiki/Copy-file-and-snippet', +} const ACCESS_TOKEN_REGEXP = /^[0-9a-f]{40}$/ @@ -23,7 +30,7 @@ function detectOS() { } function friendlyFormatShortcut(shortcut) { - if (!shortcut) return + if (typeof shortcut !== 'string') return '' const OS = detectOS() if (OS === OperatingSystems.Windows) { return shortcut.replace(/meta/, 'win') @@ -39,54 +46,101 @@ function friendlyFormatShortcut(shortcut) { } } -export default class SettingsBar extends preact.Component { +export default class SettingsBar extends React.PureComponent { + static propTypes = { + accessToken: PropTypes.string.isRequired, + activated: PropTypes.bool.isRequired, + onAccessTokenChange: PropTypes.func.isRequired, + onShortcutChange: PropTypes.func.isRequired, + compressSingletonFolder: PropTypes.bool.isRequired, + copyFileButton: PropTypes.bool.isRequired, + copySnippetButton: PropTypes.bool.isRequired, + setCopyFile: PropTypes.func.isRequired, + setCopySnippet: PropTypes.func.isRequired, + setCompressSingleton: PropTypes.func.isRequired, + toggleShowSettings: PropTypes.func.isRequired, + toggleShowSideBarShortcut: PropTypes.string.isRequired, + } + state = { - accessTokenHint: null, accessToken: '', - shortcutHint: null, + accessTokenHint: '', + shortcutHint: '', toggleShowSideBarShortcut: '', + reloadHint: '', + moreOptions: [ + { + key: 'compress-singleton', + label: 'Compress singleton folder', + onChange: this.createOnChange(config.compressSingletonFolder, this.props.setCompressSingleton), + getValue: () => this.props.compressSingletonFolder, + wikiLink: wikiLinks.compressSingletonFolder, + }, + { + key: 'copy-file', + label: 'Copy File', + onChange: this.createOnChange(config.copyFileButton, this.props.setCopyFile), + getValue: () => this.props.copyFileButton, + wikiLink: wikiLinks.copyFileButton, + }, + { + key: 'copy-snippet', + label: 'Copy Snippet', + onChange: this.createOnChange(config.copySnippetButton, this.props.setCopySnippet), + getValue: () => this.props.copySnippetButton, + wikiLink: wikiLinks.copySnippet, + }, + ] } componentWillMount() { - const { toggleShowSideBarShortcut } = this.props - this.setState({ toggleShowSideBarShortcut }) + const { toggleShowSideBarShortcut, compressSingletonFolder, copyFileButton, copySnippetButton } = this.props + this.setState({ toggleShowSideBarShortcut, compressSingletonFolder, copyFileButton, copySnippetButton }) } - componentWillReceiveProps({ toggleShowSideBarShortcut }) { - this.setState({ toggleShowSideBarShortcut }) + componentWillReceiveProps({ toggleShowSideBarShortcut, compressSingletonFolder, copyFileButton, copySnippetButton }) { + this.setState({ toggleShowSideBarShortcut, compressSingletonFolder, copyFileButton, copySnippetButton }) } onInputAccessToken = event => { const value = event.target.value - this.setState({ accessToken: value }) this.setState({ + accessToken: value, accessTokenHint: ACCESS_TOKEN_REGEXP.test(value) ? '' : 'This token is in unknown format.', }) } saveToken = async () => { - const { onHasAccessTokenChange } = this.props + const { onAccessTokenChange } = this.props const { accessToken } = this.state if (accessToken) { - await storageHelper.setAccessToken(accessToken) - onHasAccessTokenChange(true) + await configHelper.setOne(config.accessToken, accessToken) + onAccessTokenChange(accessToken) this.setState({ - accessTokenHint: 'Your token is saved, will work after reloading the page!', + accessToken: '', + accessTokenHint: ( + + window.location.reload()}> + Reload + {' '} + to activate! + + ), }) } } clearToken = async () => { - const { onHasAccessTokenChange } = this.props - await storageHelper.setAccessToken('') - onHasAccessTokenChange(false) + const { onAccessTokenChange } = this.props + await configHelper.setOne(config.accessToken, '') + onAccessTokenChange('') this.setState({ accessToken: '' }) } saveShortcut = async () => { const { onShortcutChange } = this.props const { toggleShowSideBarShortcut } = this.state - await storageHelper.setShortcut(toggleShowSideBarShortcut) + await configHelper.setOne(config.shortcut, toggleShowSideBarShortcut) onShortcutChange(toggleShowSideBarShortcut) this.setState({ shortcutHint: 'Shortcut is saved!', @@ -102,99 +156,134 @@ export default class SettingsBar extends preact.Component { this.setState({ toggleShowSideBarShortcut: shortcut }) } + setReloadHint = () => { + this.setState({ + reloadHint: ( + + Saved,{' '} + window.location.reload()}> + reload + {' '} + to apply. + + ), + }) + } + + // writing this method as arrow function would be more verbose + createOnChange(configKey, set ) { + return async e => { + const enabled = e.target.checked + await configHelper.setOne(configKey, enabled) + set(enabled) + this.setReloadHint() + } + } + render() { - const { accessTokenHint, accessToken, toggleShowSideBarShortcut, shortcutHint } = this.state - const { hasAccessToken } = this.props - const { toggleShowSettings, activated } = this.props + const { + accessTokenHint, + toggleShowSideBarShortcut, + shortcutHint, + accessToken, + reloadHint, + moreOptions, + } = this.state + const { toggleShowSettings, activated, accessToken: hasAccessToken } = this.props return (
{activated && ( -

- Settings -

- )} - {activated && ( -
-
-
-

Access Token

- With access token provided, Gitako can access more repositories. -
- - Help: how to create access token? - -
- - Gitako stores the token in  - - chrome local storage + +

Settings

+
+
+
+

Access Token

+
+ Why & how to create it? -  locally and safely. - -
-
- - {hasAccessToken ? ( - - ) : ( - + ) : ( + + )} +
+ {accessTokenHint && {accessTokenHint}} +
+
+

Toggle Shortcut

+ Set a combination of keys for toggling Gitako sidebar. +
+
+ + - )} +
+ {shortcutHint && {shortcutHint}}
- {accessTokenHint && {accessTokenHint}} -
-
-

Toggle shortcut

- Set a combination of keys for toggling Gitako sidebar. -
-
- - +
+

More Options

+ {moreOptions.map(option => ( + + +
+
+ ))} + {reloadHint &&
{reloadHint}
}
- {shortcutHint && {shortcutHint}} -
-
-
Position of Gitako (WIP)
- -
-
-
Table of Markdown Content (WIP)
- -
-
-

Issue

- +
+

Contact

- Draft a issue on Github + Bug report / feature request. -  for bug report or feature request. - +
-
+ )}
+ + v{version} + {activated ? ( { + componentDidMount() { + const { onPJAXEnd, onKeyDown } = this.props + window.addEventListener('pjax:complete', onPJAXEnd) + window.addEventListener('keydown', onKeyDown) } - onPJAXEnd = () => { - NProgress.done() - const { metaData } = this.state - const mergedMetaData = { ...metaData, ...URLHelper.parse() } - this.setState({ - metaData: mergedMetaData, - }) - this.setShouldShow(URLHelper.isInCodePage(mergedMetaData)) - DOMHelper.decorateGitHubPageContent() - DOMHelper.focusSearchInput() - } - - setShouldShow = shouldShow => { - this.setState({ shouldShow }) - DOMHelper.setBodyIndent(shouldShow) - } - - toggleShowSideBar = () => { - const { shouldShow } = this.state - this.setShouldShow(!shouldShow) - } - - toggleShowSettings = () => { - const { showSettings } = this.state - this.setState({ showSettings: !showSettings }) - } - - onHasAccessTokenChange = hasAccessToken => { - this.setState({ hasAccessToken }) - } - - onKeyDown = e => { - const { toggleShowSideBarShortcut } = this.state - if (toggleShowSideBarShortcut) { - const keys = keyHelper.parseEvent(e) - if (keys === toggleShowSideBarShortcut) { - this.toggleShowSideBar() - } - } - } - - onShortcutChange = shortcut => { - this.setState({ toggleShowSideBarShortcut: shortcut }) - } - - onResize = size => { - this.setState({ - size, - }) + componentWillUnmount() { + const { onPJAXEnd, onKeyDown } = this.props + window.removeEventListener('pjax:complete', onPJAXEnd) + window.removeEventListener('keydown', onKeyDown) } renderAccessDeniedError() { @@ -143,55 +84,98 @@ export default class SideBar extends preact.Component {
Access Denied

- Gitako needs access token with proper scopes (recommended: repo) to access this - repository. Please save it in the settings below. + Due to{' '} + + limitation of GitHub + {' '} + or{' '} + + auth needs + + , Gitako needs access token to continue. Please follow the instructions in the settings + panel below.

) } renderContent() { - const { errorDueToAuth, metaData, treeData, showSettings } = this.state + const { + errorDueToAuth, + metaData, + treeData, + showSettings, + accessToken, + compressSingletonFolder, + toggleShowSettings, + } = this.props return (
{metaData && } - {errorDueToAuth && this.renderAccessDeniedError()} - {metaData && - treeData && ( - - )} + {errorDueToAuth + ? this.renderAccessDeniedError() + : metaData && ( + + )}
) } render() { const { - size, + baseSize, + error, shouldShow, showSettings, - hasAccessToken, + accessToken, + compressSingletonFolder, + copyFileButton, + copySnippetButton, toggleShowSideBarShortcut, - loading, - } = this.state + logoContainerElement, + toggleShowSideBar, + toggleShowSettings, + onShortcutChange, + onAccessTokenChange, + setCompressSingleton, + setCopyFile, + setCopySnippet, + } = this.props return ( -
- - +
+ + -
- -
+ +
{this.renderContent()}
-
+
) } diff --git a/src/components/ToggleShowButton.js b/src/components/ToggleShowButton.js index a38a445..df15d4a 100644 --- a/src/components/ToggleShowButton.js +++ b/src/components/ToggleShowButton.js @@ -1,13 +1,18 @@ -import preact from 'preact' -/** @jsx preact.h */ -import Icon from './Icon' +import React from 'react' +import Icon from 'components/Icon' +import cx from 'utils/cx' -import cx from '../utils/cx' - -export default function Logo({ shouldShow, toggleShowSideBar }) { +export default function Logo({ error, shouldShow, toggleShowSideBar }) { return ( -
+
+ {error && {error}}
) } diff --git a/src/content.js b/src/content.js index ad6f38b..b86067f 100644 --- a/src/content.js +++ b/src/content.js @@ -1,10 +1,14 @@ -import preact from 'preact' -/** @jsx preact.h */ +import React from 'react' +import ReactDOM from 'react-dom' +import Gitako from 'components/Gitako' +import { addMiddleware } from 'driver/connect' +import { withErrorLog } from 'analytics' import './content.less' -import SideBar from './components/SideBar' +addMiddleware(withErrorLog) const SideBarElement = document.createElement('div') document.body.appendChild(SideBarElement) -preact.render(, SideBarElement) + +ReactDOM.render(, SideBarElement) diff --git a/src/content.less b/src/content.less index 6c88504..c8dd374 100644 --- a/src/content.less +++ b/src/content.less @@ -15,6 +15,11 @@ @media (min-width: @width-with-gitako) { margin-left: 0; } + + .Header > div { + width: 1012px; + margin: 0 auto; + } } .markdown-body { @@ -74,24 +79,27 @@ top: 5px; left: 0; transform: translate((@side-bar-base-width - 30px)); - z-index: 2; + z-index: 32; display: inline-flex; justify-content: center; align-items: center; cursor: pointer; - width: 30px; + min-width: 30px; height: 30px; will-change: transform; border: 1px solid transparent; + transition: all @animation-duration ease; - @media screen and (max-width: @width-with-gitako) { - transition: all @animation-duration ease; - } @media screen and (min-width: @width-with-gitako) { - transition: border-color, border-radius, background @animation-duration ease; transform: translate(~'calc(50vw - (' @github-content-width ~') / 2 - 30px)'); } + &:hover { + .error-message { + display: inline; + } + } + &.collapsed { border-color: #999999; border-radius: 3px; @@ -106,262 +114,319 @@ } } + &.error { + .action-icon { + color: #cb2431; + } + } + .action-icon { color: #666666; width: 16px; height: 20px; + margin: 5px 6px; transition: all @animation-duration ease; } + + .error-message { + display: none; + margin: 0 4px; + } } -.@{name} { +.@{name}-side-bar { .@{name}-position-wrapper { position: fixed; top: 0; left: 0; height: 100vh; - z-index: 2; + z-index: 32; // be more aggressive, against header in guest mode min-width: @side-bar-base-width; + display: flex; + justify-content: flex-end; @media screen and (min-width: @min-screen-width) { width: ~'calc((100vw - ' @github-content-width ~') / 2)'; } - .@{name}-resize-handler { - @resize-handler-width: 16px; - @side-bar-and-handler-width: (@min-screen-width + @resize-handler-width); - position: absolute; - top: 0; - display: none; - width: @resize-handler-width; - height: 100vh; - background: #fafbfc; - cursor: ew-resize; - user-select: none; - border-left: 1px solid #e1e4e8; - - @media screen and (min-width: @min-screen-width) { - display: flex; - align-items: center; - } - - .grabber-icon { - transform: rotate(90deg); - width: 12px; - } + &.hidden { + .hidden; } - .@{name}-side-bar { - position: absolute; - right: 0; - width: @side-bar-base-width; + + .gitako-position-content { max-width: 100%; - height: 100%; + } + } + .@{name}-resize-handler { + @resize-handler-width: 16px; + @side-bar-and-handler-width: (@min-screen-width + @resize-handler-width); + display: none; + width: @resize-handler-width; + background: #fafbfc; + cursor: ew-resize; + user-select: none; + border-left: 1px solid #e1e4e8; + + @media screen and (min-width: (@min-screen-width + @side-bar-base-width + @resize-handler-width * 2)) { display: flex; + align-items: center; + } + + .grabber-icon { + transform: rotate(90deg); + width: 12px; + } + } + .@{name}-side-bar-body { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: #fafbfc; + border-right: 1px solid #e1e4e8; + border-left: 1px solid #e1e4e8; + overflow: hidden; + + &.hidden { + .hidden; + } + + .octicon { + transition: 0.3s ease; + color: rgba(3, 47, 98, 0.55); + vertical-align: initial; + } + + .octicon-wrapper { + display: inline-block; + width: 16px; + height: 16px; + text-align: center; + vertical-align: middle; + } + + .octicon-color { + color: rgba(3, 47, 98, 0.55); + } + + .@{name}-side-bar-content { + display: flex; + flex: 1; flex-direction: column; - background: #fafbfc; - border-right: 1px solid #e1e4e8; - overflow: hidden; - will-change: transform; - border-left: 1px solid #e1e4e8; - &.hidden { - .hidden; + .meta-bar { + position: relative; + padding: 10px; + padding-right: 30px; + font-size: 13px; + line-height: 20px; + color: #586069; + background-color: #f1f8ff; + border-bottom: 1px solid #c8e1ff; + + a { + // fix a weird bug: + // when gitako failed loading repo, cursor hovering in meta bar will be 'text' + cursor: pointer; + } + + .repo-name { + font-weight: bolder; + } } - .octicon { - transition: 0.3s ease; - color: rgba(3, 47, 98, 0.55); - vertical-align: initial; + .description { + padding: 4px 10px; } - .octicon-wrapper { - display: inline-block; - width: 16px; - height: 16px; - text-align: center; - vertical-align: middle; - } - - .octicon-color { - color: rgba(3, 47, 98, 0.55); - } - - .@{name}-side-bar-content { + .loading-indicator-container { + width: 100%; + height: 100%; display: flex; - flex: 1; - flex-direction: column; + justify-content: center; + align-items: center; - .meta-bar { - position: relative; - padding: 10px; - padding-right: 30px; - font-size: 13px; - line-height: 20px; - color: #586069; - background-color: #f1f8ff; - border-bottom: 1px solid #c8e1ff; - - a { - // fix a weird bug: - // when gitako failed loading repo, cursor hovering in meta bar will be 'text' - cursor: pointer; - } - - .repo-name { - font-weight: bolder; - } - } - - .description { - padding: 4px 10px; - } - - .file-explorer { - width: 100%; - height: 100%; - outline: none; + .loading-indicator { display: flex; - flex-direction: column; - flex-wrap: nowrap; - &.freeze { - filter: blur(1.5px) opacity(0.6) grayscale(0.9); - pointer-events: none; - } + align-items: center; + } - /* search input */ - .search-input-wrapper { - input[type='text'].form-control { - box-shadow: none; - width: 100%; - } - } - - .no-results { - padding: 0px 10px; - color: #666; - } - - .files { - flex: 1; - overflow: auto; - } - - .node-item-row { - background: #fff; - &:hover { - background: #f6f8fa; - } - &.focused { - background: #f0f0f6; - } - - .node-item { - word-wrap: normal; - word-break: break-all; - margin: 0; - color: #0366d6; - line-height: 20px; - padding: 6px 0; - cursor: pointer; - border-top: 1px solid #eaecef; - transition: all 0.5s ease; - - // folder icon rotate when expand - &.expanded .octicon.octicon-triangle-right { - transform: rotate(90deg); - } - - .node-item-name { - padding-left: 6px; - vertical-align: middle; - } - - .node-item:hover .node-item-name { - text-decoration: underline; - } - } - } + .loading-indicator-icon { + width: 20px; + height: 20px; + animation: rotate 2s infinite linear; } } - .@{name}-settings-bar { - z-index: 2; - background: #fafbfc; + .file-explorer { + width: 100%; + height: 100%; + outline: none; display: flex; flex-direction: column; - &-title { - border-top: 1px solid #eaecef; - padding: 6px 10px; - box-shadow: 0 1px 8px 0 rgba(0,0,0,.2), 0 3px 4px 0 rgba(0,0,0,.14), 0 3px 3px -2px rgba(0,0,0,.12); - z-index: 1; + flex-wrap: nowrap; + &.freeze { + filter: blur(1.5px) opacity(0.6) grayscale(0.9); + cursor: pointer; + + > * { + pointer-events: none; + user-select: none; + } } - &-content { - padding: 0 10px; + + /* search input */ + .search-input-wrapper { + input[type='text'].form-control { + box-shadow: none; + width: 100%; + } + } + + .no-results { + padding: 0px 10px; + color: #666; + } + + .files { flex: 1; overflow: auto; - position: relative; - - .shadow-shelter { - position: absolute; - width: 100%; - height: 6px; - left: 0; - background: #fafbfc; - z-index: 1; - } - &-section { - padding-bottom: 10px; - } } - .access-token { - border-bottom: none; // prevent overwrite by github style - .hint { - color: #6a737d; - } - } - .access-token-input-control { - display: flex; - margin-top: 8px; - .access-token-input { - flex: 1; - box-shadow: none; - &:disabled { - cursor: not-allowed; + .node-item-row { + background: #fff; + &:hover { + background: #f6f8fa; + } + &.focused { + background: #f0f0f6; + } + + .node-item { + word-wrap: normal; + word-break: break-all; + margin: 0; + color: #0366d6; + line-height: 20px; + padding: 6px 0; + cursor: pointer; + border-top: 1px solid #eaecef; + transition: all 0.5s ease; + + // folder icon rotate when expand + &.expanded .octicon.octicon-triangle-right { + transform: rotate(90deg); + } + + .node-item-name { + padding-left: 6px; + vertical-align: middle; + } + + .node-item:hover .node-item-name { + text-decoration: underline; } } } - .toggle-shortcut { - .hint { - color: #6a737d; - } - } - .toggle-shortcut-input-control { - display: flex; - margin-top: 8px; - .toggle-shortcut-input { - flex: 1; - box-shadow: none; - } - } - .placeholder-row { - display: flex; - justify-content: flex-end; - align-items: center; - padding: 6px 10px; - border-top: 1px solid #eaecef; + } + } - .show-settings-icon { - width: 20px; - height: 20px; - cursor: pointer; - } - .hide-settings-icon { - width: 20px; - height: 20px; - cursor: pointer; + .@{name}-settings-bar { + z-index: 2; + background: #fafbfc; + display: flex; + flex-direction: column; + &-title { + border-top: 1px solid #eaecef; + padding: 6px 10px; + box-shadow: 0 1px 8px 0 rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14), + 0 3px 3px -2px rgba(0, 0, 0, 0.12); + z-index: 1; + } + &-content { + padding: 0 10px; + flex: 1; + overflow: auto; + position: relative; + + .shadow-shelter { + position: absolute; + width: 100%; + height: 6px; + left: 0; + background: #fafbfc; + z-index: 1; + } + &-section { + padding-bottom: 10px; + } + } + .access-token { + border-bottom: none; // prevent overwrite by github style + .hint { + color: #6a737d; + } + } + .access-token-input-control { + display: flex; + margin-top: 8px; + .access-token-input { + flex: 1; + box-shadow: none; + + &:disabled { + cursor: not-allowed; } } } + .toggle-shortcut { + .hint { + color: #6a737d; + } + } + .toggle-shortcut-input-control { + display: flex; + margin-top: 8px; + .toggle-shortcut-input { + flex: 1; + box-shadow: none; + } + } + .singleton { + .hint { + color: #6a737d; + } + } + .placeholder-row { + flex-shrink: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 10px; + border-top: 1px solid #eaecef; + + .version { + color: #999999; + } + .show-settings-icon { + width: 20px; + height: 20px; + cursor: pointer; + } + .hide-settings-icon { + width: 20px; + height: 20px; + cursor: pointer; + } + } } } } + +@keyframes rotate { + from { + transform: rotateZ(0); + } + to { + transform: rotateZ(360deg); + } +} diff --git a/src/driver/connect.js b/src/driver/connect.js new file mode 100644 index 0000000..f0f9f03 --- /dev/null +++ b/src/driver/connect.js @@ -0,0 +1,71 @@ +import React from 'react' + +const middlewares = [] + +export function addMiddleware(middleware) { + if (typeof middleware !== 'function') return null + const m = middleware.bind(null) + middlewares.push(m) + return function removeMiddleware() { + const index = middlewares.indexOf(m) + if (index === -1) return + middlewares.splice(index, 1) + } +} + +function applyMiddlewares(method, args) { + for(const middleware of middlewares) { + [method = method, args = args] = middleware(method, args) || [] + } + return [method, args] +} + +function run([method, args]) { + method.apply(null, args) +} + +function link(instance, sources) { + const wrappedMethods = {/* [keyof sources] -> wrappedMethods.method */} + const map = new Map(/* sources.creator -> wrappedMethods.method */) + + Object.entries(sources).forEach(([key, createMethod]) => { + const method = createMethod(dispatch) + wrappedMethods[key] = method + map.set(createMethod, method) + }) + + const sourcesValues = Object.values(sources) + function dispatch(...args) { + const isFromSource = sourcesValues.includes(args[0]) + if (isFromSource) { + const [createMethod, ...otherArgs] = args + const method = map.get(createMethod) + run(applyMiddlewares(method, otherArgs)) + } else { + // by doing so, no async updater is available anymore + // luckily I don't need them :) + let [updater, callback] = args + if (typeof updater === 'function') { + updater = updater(instance.state, instance.props) + } + instance.setState(updater, callback) + } + } + + return wrappedMethods +} + +export default function connect(mapping) { + return function linkComponent(ComponentClass) { + return class AwesomeApp extends React.PureComponent { + static displayName = `Driven${ComponentClass.name}` + + state = {} + boundCore = link(this, mapping) + + render() { + return + } + } + } +} diff --git a/src/driver/core/FileExplorer.js b/src/driver/core/FileExplorer.js new file mode 100644 index 0000000..6c616b1 --- /dev/null +++ b/src/driver/core/FileExplorer.js @@ -0,0 +1,269 @@ +import ini from 'ini' +import DOMHelper from 'utils/DOMHelper' +import treeParser from 'utils/treeParser' +import URLHelper from 'utils/URLHelper' +import VisibleNodesGenerator from 'utils/VisibleNodesGenerator' +import GitHubHelper from 'utils/GitHubHelper' + +function getVisibleParentNode(nodes, focusedNode, depths) { + const focusedNodeIndex = nodes.indexOf(focusedNode) + const focusedNodeDepth = depths.get(focusedNode) + let indexOfParentNode = focusedNodeIndex - 1 + while ( + indexOfParentNode !== -1 && + depths.get(nodes[indexOfParentNode]) >= focusedNodeDepth + ) { + --indexOfParentNode + } + const parentNode = nodes[indexOfParentNode] + return parentNode +} + +const tasksAfterRender = [] +const visibleNodesGenerator = new VisibleNodesGenerator() + +const init = dispatch => () => dispatch(async () => { + dispatch(setStateText, 'Fetching File List...') +}) + +function resolveGitModules(root, blobData) { + if (blobData) { + if (blobData.encoding === 'base64') { + const content = atob(blobData.content) + const parsed = ini.parse(content) + Object.values(parsed).map(value => { + const { url, path } = value + // for now, handle modules at root only + const node = root.contents.find(node => node.path === path) + node.url = url + }) + } + } +} + +const setUpTree = dispatch => () => dispatch(async (state, { treeData, metaData, compressSingletonFolder, accessToken }) => { + if (!treeData) return + dispatch(setStateText, 'Rendering File List...') + const { root, gitModules } = treeParser.parse(treeData, metaData) + + if (gitModules) { + const blobData = await GitHubHelper.getBlobData({ + userName: metaData.userName, + repoName: metaData.repoName, + fileSHA: gitModules.sha, + accessToken, + }) + + resolveGitModules(root, blobData) + } + + visibleNodesGenerator.setCompress(compressSingletonFolder) + await visibleNodesGenerator.plantTree(root) + + tasksAfterRender.push(DOMHelper.focusSearchInput) + dispatch(setStateText, null) + const currentPath = URLHelper.getCurrentPath(true) + if (currentPath.length) { + const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/')) + if (nodeExpandedTo) { + visibleNodesGenerator.focusNode(nodeExpandedTo) + const { nodes } = visibleNodesGenerator.visibleNodes + tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(nodes.indexOf(nodeExpandedTo))) + } + } + dispatch(updateVisibleNodes) +}) + +const execAfterRender = dispatch => () => { + for (const task of tasksAfterRender) { + task() + } + tasksAfterRender.length = 0 +} + +const setStateText = dispatch => text => dispatch({ + stateText: text, +}) + +const handleKeyDown = dispatch => ({ key }) => dispatch(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => { + if (focusedNode) { + const focusedNodeIndex = nodes.indexOf(focusedNode) + switch (key) { + case 'ArrowUp': + // focus on previous node + if (focusedNodeIndex === 0) { + dispatch(focusNode, nodes[nodes.length - 1]) + } else { + dispatch(focusNode, nodes[focusedNodeIndex - 1]) + } + break + + case 'ArrowDown': + // focus on next node + if (focusedNodeIndex + 1 < nodes.length) { + dispatch(focusNode, nodes[focusedNodeIndex + 1]) + } else { + dispatch(focusNode, nodes[0]) + } + break + + case 'ArrowLeft': + // collapse node or go to parent node + if (expandedNodes.has(focusedNode)) { + dispatch(setExpand, focusedNode, false) + } else { + // go forward to the start of the list, find the closest node with lower depth + const parentNode = getVisibleParentNode(nodes, focusedNode, depths) + if (parentNode) { + dispatch(focusNode, parentNode) + } + } + break + + // consider the two keys as 'confirm' key + case 'ArrowRight': + // expand node or focus on first content node or redirect to file page + if (focusedNode.type === 'tree') { + if (expandedNodes.has(focusedNode)) { + const nextNode = nodes[focusedNodeIndex + 1] + if (depths.get(nextNode) > depths.get(focusedNode)) { + dispatch(focusNode, nextNode) + } + } else { + dispatch(setExpand, focusedNode, true) + } + } else if (focusedNode.type === 'blob') { + DOMHelper.loadWithPJAX(focusedNode.url) + } else if (focusedNode.type === 'commit') { + // redirect to its parent folder + DOMHelper.loadWithPJAX(focusedNode.parent.url) + } + break + case 'Enter': + // expand node or redirect to file page + if (focusedNode.type === 'tree') { + dispatch(setExpand, focusedNode, true) + } else if (focusedNode.type === 'blob') { + DOMHelper.loadWithPJAX(focusedNode.url) + } else if (focusedNode.type === 'commit') { + // redirect to its parent folder + DOMHelper.loadWithPJAX(focusedNode.parent.url) + } + break + + } + } else { + // now search input is focused + if (nodes.length) { + switch (key) { + case 'ArrowDown': + dispatch(focusNode, nodes[0]) + break + case 'ArrowUp': + dispatch(focusNode, nodes[nodes.length - 1]) + break + } + } + } +}) + +const handleSearchKeyChange = dispatch => { + let i = 0 + return async event => { + const searchKey = event.target.value + const j = i += 1 + await visibleNodesGenerator.search(searchKey) + if (i === j) dispatch(updateVisibleNodes) + } +} + +const delayExpandThreshold = 400 +function shouldDelayExpand(node) { + return visibleNodesGenerator.visibleNodes.expandedNodes.has(node) + && node.contents.length > delayExpandThreshold +} + +const setExpand = dispatch => (node, expand) => { + visibleNodesGenerator.setExpand(node, expand) + const applyChanges = () => { + dispatch(focusNode, node) + tasksAfterRender.push(DOMHelper.focusSearchInput) + } + if (shouldDelayExpand(node)) { + dispatch(mountExpandingIndicator, node) + tasksAfterRender.push(() => setTimeout(applyChanges, 0)) + } else { + applyChanges() + } +} + +const toggleNodeExpansion = dispatch => (node, skipScrollToNode) => { + visibleNodesGenerator.toggleExpand(node) + const applyChanges = () => { + dispatch(focusNode, node, skipScrollToNode) + tasksAfterRender.push(DOMHelper.focusFileExplorer) + } + if (shouldDelayExpand(node)) { + dispatch(mountExpandingIndicator, node) + tasksAfterRender.push(() => setTimeout(applyChanges, 0)) + } else { + applyChanges() + } +} + +const focusNode = dispatch => (node, skipScroll) => dispatch(({ visibleNodes: { nodes } }) => { + visibleNodesGenerator.focusNode(node) + if (node && !skipScroll) { + // when focus a node not in viewport(by keyboard), scroll to it + const indexOfToBeFocusedNode = nodes.indexOf(node) + tasksAfterRender.push(() => DOMHelper.scrollToNodeElement(indexOfToBeFocusedNode)) + tasksAfterRender.push(DOMHelper.focusSearchInput) + } + dispatch(updateVisibleNodes) +}) + +const onNodeClick = dispatch => (node) => dispatch((state, { metaData, accessToken }) => { + if (node.type === 'tree') { + dispatch(toggleNodeExpansion, node, true) + } else if (node.type === 'blob') { + dispatch(focusNode, node, true) + DOMHelper.loadWithPJAX(node.url) + } else if (node.type === 'commit') { + window.open(node.url, '_blank') + } +}) + +const mountExpandingIndicator = dispatch => node => dispatch(({ visibleNodes }) => { + const dummyVisibleNodes = { + ...visibleNodes, + nodes: visibleNodes.nodes.slice(), + } + dummyVisibleNodes.nodes.splice( + dummyVisibleNodes.nodes.indexOf(node) + 1, + 0, + { virtual: true, name: 'Loading', path: '-' }, + ) + return { + visibleNodes: dummyVisibleNodes + } +}) + +const updateVisibleNodes = dispatch => () => { + const { visibleNodes } = visibleNodesGenerator + dispatch({ visibleNodes }) +} + +export default { + init, + setUpTree, + execAfterRender, + setStateText, + handleKeyDown, + handleSearchKeyChange, + setExpand, + toggleNodeExpansion, + focusNode, + onNodeClick, + updateVisibleNodes, + mountExpandingIndicator, +} diff --git a/src/driver/core/Gitako.js b/src/driver/core/Gitako.js new file mode 100644 index 0000000..922294c --- /dev/null +++ b/src/driver/core/Gitako.js @@ -0,0 +1,6 @@ +const init = dispatch => () => { +} + +export default { + init, +} diff --git a/src/driver/core/SideBar.js b/src/driver/core/SideBar.js new file mode 100644 index 0000000..f5cbf9d --- /dev/null +++ b/src/driver/core/SideBar.js @@ -0,0 +1,137 @@ +import DOMHelper, { REPO_TYPE_PRIVATE } from 'utils/DOMHelper' +import GitHubHelper, { NOT_FOUND, BAD_CREDENTIALS, API_RATE_LIMIT } from 'utils/GitHubHelper' +import configHelper from 'utils/configHelper' +import URLHelper from 'utils/URLHelper' +import keyHelper from 'utils/keyHelper' + +const init = dispatch => async () => { + try { + if (!URLHelper.isInRepoPage()) return + dispatch({ logoContainerElement: DOMHelper.insertLogoMountPoint() }) + let nothingWentWrong = true + const metaData = URLHelper.parse() + dispatch(setMetaData, metaData) + const { access_token: accessToken, shortcut, compressSingletonFolder, copyFileButton, copySnippetButton } = await configHelper.get() + DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton }) + dispatch({ accessToken, toggleShowSideBarShortcut: shortcut, compressSingletonFolder, copyFileButton, copySnippetButton }) + const detectedBranchName = DOMHelper.getCurrentBranch() || 'master' + let aggressivelyGotTreeData = GitHubHelper.getTreeData({ + ...metaData, + branchName: detectedBranchName, + accessToken, + }).catch(() => { + nothingWentWrong = false + }) + const metaDataFromAPI = await GitHubHelper.getRepoMeta({ ...metaData, accessToken }) + const projectDefaultBranchName = metaDataFromAPI['default_branch'] + if (!metaData.branchName) { + // User accessed repo's homepage(no branch name in URL) and we predicted its default branch to be 'master' + if (projectDefaultBranchName !== detectedBranchName) { + // And the repo do not use {defaultBranchName} as default branch, + aggressivelyGotTreeData = GitHubHelper.getTreeData({ + ...metaData, + branchName: projectDefaultBranchName, + accessToken, + }) + } + } + const branchName = metaData.branchName || projectDefaultBranchName + Object.assign(metaData, { branchName, api: metaDataFromAPI }) + dispatch(setMetaData, metaData) + const shouldShow = URLHelper.isInCodePage(metaData) + dispatch(setShouldShow, nothingWentWrong && shouldShow) + aggressivelyGotTreeData + .then(treeData => { + dispatch({ treeData }) + }) + .catch(err => { + dispatch(handleError, err) + }) + } catch (err) { + dispatch(handleError, err) + } +} + +const handleError = dispatch => async (err) => { + // TODO: detect request time exceeds limit + if (err.message === NOT_FOUND || err.message === BAD_CREDENTIALS || err.message === API_RATE_LIMIT ) { + const repoPageType = await DOMHelper.getRepoPageType() + const errorDueToAuth = repoPageType === REPO_TYPE_PRIVATE || err.message === BAD_CREDENTIALS || err.message === API_RATE_LIMIT + dispatch({ errorDueToAuth }) + dispatch(setShowSettings, true) + dispatch(setShouldShow, errorDueToAuth) + if (!errorDueToAuth) { + dispatch(setError, 'Gitako ate a bug, but it should recovery soon!') + } + } else { + dispatch(setShouldShow, false) + dispatch(setError, 'Gitako ate a bug, but it should recovery soon!') + } +} + +const onPJAXEnd = dispatch => () => { + dispatch(({ metaData, copyFileButton, copySnippetButton }) => { + DOMHelper.unmountTopProgressBar() + DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton }) + DOMHelper.focusSearchInput() + const mergedMetaData = { ...metaData, ...URLHelper.parse() } + dispatch(setShouldShow, URLHelper.isInCodePage(mergedMetaData)) + dispatch(setMetaData, mergedMetaData) + }) +} + +const onKeyDown = dispatch => e => { + dispatch(({ toggleShowSideBarShortcut }) => { + if (toggleShowSideBarShortcut) { + const keys = keyHelper.parseEvent(e) + if (keys === toggleShowSideBarShortcut) { + dispatch(toggleShowSideBar) + } + } + }) +} + +const toggleShowSideBar = dispatch => () => dispatch(({ shouldShow }) => dispatch(setShouldShow, !shouldShow)) + +const setShouldShow = dispatch => shouldShow => { + dispatch({ shouldShow }, shouldShow ? DOMHelper.focusSearchInput : null) + DOMHelper.setBodyIndent(shouldShow) +} + +const setError = dispatch => error => { + dispatch({ error }) +} + +const toggleShowSettings = dispatch => () => dispatch(({ showSettings }) => ({ showSettings: !showSettings })) + +const setShowSettings = dispatch => showSettings => dispatch({ showSettings }) + +const onAccessTokenChange = dispatch => accessToken => dispatch({ accessToken }) + +const onShortcutChange = dispatch => shortcut => dispatch({ toggleShowSideBarShortcut: shortcut }) + +const setMetaData = dispatch => metaData => dispatch({ metaData }) + +const setCompressSingleton = dispatch => compressSingletonFolder => dispatch({ compressSingletonFolder }) + +const setCopyFile = dispatch => copyFileButton => dispatch({ copyFileButton }) + +const setCopySnippet = dispatch => copySnippetButton => dispatch({ copySnippetButton }) + +export default { + init, + onPJAXEnd, + onKeyDown, + setShouldShow, + setShowSettings, + toggleShowSideBar, + toggleShowSettings, + onAccessTokenChange, + onShortcutChange, + setMetaData, + setCompressSingleton, + setCopyFile, + setCopySnippet, + setError, + handleError, +} diff --git a/src/driver/core/index.js b/src/driver/core/index.js new file mode 100644 index 0000000..18fb34f --- /dev/null +++ b/src/driver/core/index.js @@ -0,0 +1,3 @@ +export { default as Gitako } from './Gitako' +export { default as SideBar } from './SideBar' +export { default as FileExplorer } from './FileExplorer' diff --git a/src/manifest.json b/src/manifest.json index d42f148..70a4fab 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,18 +1,26 @@ { "manifest_version": 2, - "name": "Gitako", - "version": "0.2.4", + "name": "Gitako - enhanced Github code tree", + "version": "0.3.16", "author": "EnixCoda", "icons": { "128": "icons/Gitako-128x128.png" }, "description": "yet another GitHub extension, inspired by Octotree and GayHub.", "homepage_url": "https://github.com/EnixCoda/Gitako", - "permissions": ["tabs", "storage", "*://github.com/*"], + "permissions": [ + "tabs", + "storage", + "*://github.com/*" + ], "content_scripts": [ { - "matches": ["https://github.com/*"], - "js": ["content.js"] + "matches": [ + "https://github.com/*" + ], + "js": [ + "content.js" + ] } ] -} +} \ No newline at end of file diff --git a/src/utils/DOMHelper.js b/src/utils/DOMHelper.js index e768b30..dddca68 100644 --- a/src/utils/DOMHelper.js +++ b/src/utils/DOMHelper.js @@ -20,11 +20,29 @@ function setBodyIndent(shouldShowGitako) { } } +function isInCodePage() { + const branchListSelector = '.branch-select-menu' + const listElement = document.querySelector(branchListSelector) + return Boolean(listElement) +} + +function getBranches() { + const branchSelector = '.branch-select-menu .select-menu-list > div .select-menu-item-text' + const branchElements = Array.from(document.querySelectorAll(branchSelector)) + return branchElements.map(element => element.innerHTML.trim()) +} + +function getCurrentBranch() { + const selectedBranchSelector = '.select-menu.branch-select-menu .select-menu-modal .select-menu-list .select-menu-item.selected svg.select-menu-item-icon + span' + const selectedBranchElement = document.querySelector(selectedBranchSelector) + return selectedBranchElement ? selectedBranchElement.textContent.trim() : null +} + /** * add the logo element into DOM * */ -function insertLogo() { +function insertLogoMountPoint() { const logoSelector = '.gitako .gitako-logo' const logoElement = document.querySelector(logoSelector) if (logoElement) { @@ -58,15 +76,18 @@ function scrollToRepoContent() { function scrollToNodeElement(index) { const nodeElementSelector = '.node-item' const nodeElements = document.querySelectorAll(nodeElementSelector) - nodeElements[index].scrollIntoView({ - behavior: 'smooth', - block: 'center', - }) + const targetElement = nodeElements[index] + if (targetElement) { + targetElement.scrollIntoView({ + behavior: 'smooth', + block: 'center', + }) + } } const pjax = new PJAX({ elements: '.pjax-link', - selectors: ['.repository-content'], + selectors: ['.repository-content', 'title'], scrollTo: false, analytics: false, cacheBust: false, @@ -247,7 +268,7 @@ const clippy = createClippy() let currentCodeSnippetElement function attachCopySnippet() { - const readmeSelector = '.repository-content .readme article' + const readmeSelector = '.repository-content #readme article' const readmeElement = document.querySelector(readmeSelector) if (readmeElement) { readmeElement.addEventListener('mouseover', ({ target }) => { @@ -306,11 +327,18 @@ function clickOnNodeElement(index = 0) { /** * a combination of few above functions */ -function decorateGitHubPageContent() { - attachCopyFileBtn() - attachCopySnippet() +function decorateGitHubPageContent({ copyFileButton, copySnippetButton }) { + if (copyFileButton) attachCopyFileBtn() + if (copySnippetButton) attachCopySnippet() } +function mountTopProgressBar() { + NProgress.start() +} + +function unmountTopProgressBar() { + NProgress.done() +} export default { loadWithPJAX, @@ -322,8 +350,13 @@ export default { focusFileExplorer, getCurrentPageType, getRepoPageType, - insertLogo, + insertLogoMountPoint, setBodyIndent, scrollToNodeElement, scrollToRepoContent, + mountTopProgressBar, + unmountTopProgressBar, + isInCodePage, + getBranches, + getCurrentBranch, } diff --git a/src/utils/GitHubHelper.js b/src/utils/GitHubHelper.js index e4760c2..5b11313 100644 --- a/src/utils/GitHubHelper.js +++ b/src/utils/GitHubHelper.js @@ -1,5 +1,11 @@ +import { raiseError } from 'analytics' export const NOT_FOUND = 'Repo Not Found' export const BAD_CREDENTIALS = 'Bad credentials' +export const API_RATE_LIMIT = `API rate limit` + +function apiRateLimitExceeded(content) { + return content && content['documentation_url'] === 'https://developer.github.com/v3/#rate-limiting' +} async function request(url, { accessToken } = {}) { const headers = {} @@ -9,9 +15,13 @@ async function request(url, { accessToken } = {}) { const res = await fetch(url, { headers }) if (res.status === 200) return res.json() // for private repo, GitHub api also responses with 404 when unauthorized - if (res.status === 404) throw new Error(NOT_FOUND) - const content = await res.json() - throw new Error(content.message) + else if (res.status === 404) throw new Error(NOT_FOUND) + else { + const content = await res.json() + if (apiRateLimitExceeded(content)) throw new Error(API_RATE_LIMIT) + else if (!res.ok) raiseError(new Error(`Got ${res.statusText} when requesting ${url}`)) + throw new Error(content && content.message) + } } async function getRepoMeta({ userName, repoName, accessToken }) { @@ -24,12 +34,18 @@ async function getTreeData({ userName, repoName, branchName, accessToken }) { return await request(url, { accessToken }) } -function getUrlForRedirect({ userName, repoName, branchName }, path) { - return `https://github.com/${userName}/${repoName}/tree/${branchName}/${path}` +async function getBlobData({ userName, repoName, accessToken, fileSHA }) { + const url = `https://api.github.com/repos/${userName}/${repoName}/git/blobs/${fileSHA}` + return await request(url, { accessToken }) +} + +function getUrlForRedirect({ userName, repoName, branchName }, type = 'blob', path) { + return `https://github.com/${userName}/${repoName}/${type}/${branchName}/${path}` } export default { getRepoMeta, getTreeData, + getBlobData, getUrlForRedirect, } diff --git a/src/utils/URLHelper.js b/src/utils/URLHelper.js index b5be2a6..d7d1e82 100644 --- a/src/utils/URLHelper.js +++ b/src/utils/URLHelper.js @@ -24,7 +24,11 @@ function parse() { return parsedData } -const RESERVED_NAME = ['blog'] +function isInRepoPage() { + const repoHeaderSelector = '.repohead' + return Boolean(document.querySelector(repoHeaderSelector)) +} + // route types related to determining if sidebar should show const TYPES = { TREE: 'tree', @@ -33,12 +37,12 @@ const TYPES = { // known but not related types: issues, pulls, wiki, insight, // TODO: record more types } + function isInCodePage(metaData = {}) { - const { userName, repoName, type, branchName } = { ...parseRaw(), ...metaData } - return ( - userName && - !RESERVED_NAME.find(_ => _ === userName) && - repoName && + const mergedRepo = { ...parseRaw(), ...metaData } + const { type, branchName } = mergedRepo + return Boolean( + isInRepoPage(mergedRepo) && (!type || type === TYPES.TREE || type === TYPES.BLOB) && type !== TYPES.COMMIT && (branchName || (!type && !branchName)) @@ -52,6 +56,7 @@ function getCurrentPath(decode = false) { export default { getCurrentPath, + isInRepoPage, isInCodePage, parse, } diff --git a/src/utils/configHelper.js b/src/utils/configHelper.js new file mode 100644 index 0000000..160f629 --- /dev/null +++ b/src/utils/configHelper.js @@ -0,0 +1,37 @@ +import storageHelper from 'utils/storageHelper' +import { pick } from 'utils/general' + +export const config = { + shortcut: 'shortcut', + accessToken: 'access_token', + compressSingletonFolder: 'compressSingletonFolder', + copyFileButton: 'copyFileButton', + copySnippetButton: 'copySnippetButton', +} + +const configKeys = Object.values(config) + +function get() { + return storageHelper.get(configKeys) +} + +function getOne(key) { + return get()[key] +} + +function set(partialConfig) { + return storageHelper.set(pick(partialConfig, configKeys)) +} + +function setOne(key, value) { + return set({ + [key]: value, + }) +} + +export default { + get, + getOne, + set, + setOne, +} diff --git a/src/utils/general.js b/src/utils/general.js index 27f4b95..5deebbc 100644 --- a/src/utils/general.js +++ b/src/utils/general.js @@ -1,18 +1,17 @@ -function shallowEqual(a, b) { - if (a === b) return true - if (typeof a === 'object' && typeof a === typeof b) { - if (a === null || b === null) return false - for (const key in a) { - if (a[key] !== b[key]) return false - } - for (const key in b) { - if (!Object.prototype.hasOwnProperty.call(a, key)) return false - } - return true - } - return false -} -export default { - shallowEqual, +/** + * @param {Object} source + * @param {Object|Array} keys + * @returns + */ +export function pick(source, keys) { + if (keys && typeof keys === 'object') { + return ( + Array.isArray(keys) ? keys : Object.keys(keys) + ).reduce((copy, key) => { + copy[key] = source[key] + return copy + }, {}) + } + return {} } diff --git a/src/utils/keyHelper.js b/src/utils/keyHelper.js index 89b4c29..292a0e2 100644 --- a/src/utils/keyHelper.js +++ b/src/utils/keyHelper.js @@ -35,15 +35,12 @@ function parse(keysString) { function parseKeyCode(code) { return code .toLowerCase() - .replace(/(left|right)$/, '') .replace(/^control$/, 'ctrl') - .replace(/^digit/, '') - .replace(/^key/, '') } function parseEvent(e) { const { altKey: alt, shiftKey: shift, metaKey: meta, ctrlKey: ctrl } = e - const code = parseKeyCode(e.code) + const code = parseKeyCode(e.key) const keys = { meta, ctrl, shift, alt, [code]: true } const combination = parse( Object.entries(keys) diff --git a/src/utils/storageHelper.js b/src/utils/storageHelper.js index a5f1f19..8f06782 100644 --- a/src/utils/storageHelper.js +++ b/src/utils/storageHelper.js @@ -1,36 +1,14 @@ const localStorage = chrome.storage.local -const ACCESS_TOKEN_KEY = 'access_token' -const SHORTCUT_KEY = 'shortcut' -function get(key) { - return new Promise(resolve => localStorage.get(key, items => resolve(items[key]))) +function get(mapping) { + return new Promise(resolve => localStorage.get(mapping, resolve)) } -function set(key, value) { - return new Promise(resolve => localStorage.set({ [key]: value }, resolve)) -} - -function getAccessToken() { - return get(ACCESS_TOKEN_KEY) -} - -function setAccessToken(accessToken) { - return set(ACCESS_TOKEN_KEY, accessToken) -} - -function getShortcut() { - return get(SHORTCUT_KEY) -} - -function setShortcut(shortcut) { - return set(SHORTCUT_KEY, shortcut) +function set(value) { + return new Promise(resolve => localStorage.set(value, resolve)) } export default { get, set, - getAccessToken, - setAccessToken, - getShortcut, - setShortcut, } diff --git a/src/utils/treeParser.js b/src/utils/treeParser.js index d3fcc88..69ec360 100644 --- a/src/utils/treeParser.js +++ b/src/utils/treeParser.js @@ -1,4 +1,4 @@ -import GitHubHelper from './GitHubHelper' +import GitHubHelper from 'utils/GitHubHelper' const nodeTemplate = { name: null, @@ -11,16 +11,33 @@ const nodeTemplate = { function sortFoldersToFront(root) { const isFolder = node => node.type === 'tree' - const isNotFolder = (...args) => !isFolder(...args) - function DFS(root) { + const isNotFolder = node => !isFolder(node) + function depthFirstSearch(root) { const nodes = root.contents if (nodes) { nodes.splice(0, Infinity, ...nodes.filter(isFolder), ...nodes.filter(isNotFolder)) - nodes.forEach(DFS) + nodes.forEach(depthFirstSearch) } return root } - return DFS(root) + return depthFirstSearch(root) +} + +function setParentNode(root, parent = null) { + root.parent = parent + if (root.contents) { + root.contents.forEach(node => setParentNode(node, root)) + } +} + +function findGitModules(root) { + if (root.contents) { + const modulesFile = root.contents.find(content => content.name === '.gitmodules') + if (modulesFile) { + return modulesFile + } + } + return null } function parse(treeData, metaData) { @@ -52,7 +69,9 @@ function parse(treeData, metaData) { ...nodeTemplate, ...item, name: item.path.replace(/^.*\//, ''), - url: GitHubHelper.getUrlForRedirect(metaData, item.path), + url: item.url + ? GitHubHelper.getUrlForRedirect(metaData, item.type, item.path) + : null, contents: item.type === 'tree' ? [] : null, } pathToNode.get(path).contents.push(node) @@ -61,9 +80,11 @@ function parse(treeData, metaData) { } }) + setParentNode(root) + return { + gitModules: findGitModules(root), root: sortFoldersToFront(root), - nodes: Array.from(pathToNode.values()), } } diff --git a/src/utils/visibleNodesGenerator.js b/src/utils/visibleNodesGenerator.js index 53a4c66..36723e7 100644 --- a/src/utils/visibleNodesGenerator.js +++ b/src/utils/visibleNodesGenerator.js @@ -26,7 +26,7 @@ function getFilterFunc(keyRegex) { } function search(treeNodes, searchKey) { - if (!searchKey) return + if (!searchKey) return treeNodes /** * if searchKey is 'abcd' * then keyRegex will be /a.*?b.*?c.*?d/i @@ -53,27 +53,83 @@ function debounce(func, delay) { export const debouncedSearch = debounce(search, 250) +function runIdle(func) { + return new Promise(resolve => { + window.requestIdleCallback(async () => resolve(await func())) + }) +} + +function promisifyAsyncFunc(func) { + return (...args) => new Promise(async resolve => resolve(await func(...args))) +} + +async function asyncMap(arr, asyncCallback) { + return await Promise.all(arr.map(promisifyAsyncFunc(asyncCallback))) +} + +async function getNodes(root) { + return await runIdle(async () => { + if (!root.contents) return [] + return [].concat( + ...await asyncMap(root.contents, async node => [node, ...await getNodes(node)]) + ) + }) +} + +function compressTree(root, prefix = []) { + if (root.contents) { + if (root.contents.length === 1) { + const singleton = root.contents[0] + if (singleton.type === 'tree') { + return compressTree(singleton, [...prefix, root.name]) + } + } + } + return { + ...root, + name: [...prefix, root.name].join('/'), + contents: root.contents + ? root.contents.map(node => compressTree(node)) + : undefined, + } +} + export default class VisibleNodesGenerator { // LEVEL 1 root = null nodes = null - plantTree(root, nodes) { - this.root = root - this.nodes = nodes + compressed = false - // a simplified sync 'search' - this.searchedNodes = this.root.contents - this.generateVisibleNodes() + getRoot() { + return this.compress && this.compressed + ? this.compressedRoot + : this.root + } + + async plantTree(root) { + this.root = root + this.nodes = await getNodes(root) + this.compressedRoot = compressTree(root) + + await this.search() } // LEVEL 2 searchedNodes = null async search(searchKey) { - this.searchedNodes = (await debouncedSearch(this.nodes, searchKey)) || this.root.contents + this.compressed = !Boolean(searchKey) + this.searchedNodes = searchKey + ? await debouncedSearch(this.nodes, searchKey) + : this.getRoot().contents + this.expandedNodes.clear() this.generateVisibleNodes() } + setCompress(compress) { + this.compress = compress + } + // LEVEL 3 expandedNodes = new Set() depths = new Map() @@ -92,15 +148,22 @@ export default class VisibleNodesGenerator { } expandTo(path) { - let rootNode = this.root - let targetPath - for (const step of path) { - targetPath = rootNode.path ? `${rootNode.path}/${step}` : step - rootNode = rootNode.contents.find(node => node.path === targetPath) - if (!rootNode) return - this.setExpand(rootNode, true) + let root = this.getRoot() + const findNode = (root) => { + if (path.indexOf(root.path) === 0) { + if (root.path === path) return root + this.setExpand(root, true) + if (root.contents) { + for (const content of root.contents) { + const node = findNode(content) + if (node) return node + } + } + } } - return rootNode + const node = findNode(root) + this.focusNode(node) + return node } visibleNodes = null @@ -108,19 +171,26 @@ export default class VisibleNodesGenerator { this.focusedNode = null this.depths.clear() const nodesSet = new Set() // prevent duplication - const get = (nodes, depth = 0) => { - return [].concat( - ...nodes.map(node => { - if (nodesSet.has(node)) return [] - this.depths.set(node, depth) - nodesSet.add(node) - const children = this.expandedNodes.has(node) ? get(node.contents, depth + 1) : [] - return [node, ...children] - }) - ) + const nodes = [], stack = this.searchedNodes.slice().reverse() + let current, depth = 0 + while (stack.length) { + current = stack.pop() + if (current === null) { + depth -= 1 + continue + } + if (nodesSet.has(current)) continue + nodes.push(current) + nodesSet.add(current) + this.depths.set(current, depth) + if (this.expandedNodes.has(current)) { + stack.push(null) // use null as pop depth flag + stack.push(...current.contents.slice().reverse()) + depth += 1 + } } this.visibleNodes = { - nodes: get(this.searchedNodes), + nodes, depths: this.depths, expandedNodes: this.expandedNodes, } @@ -131,6 +201,9 @@ export default class VisibleNodesGenerator { focusedNode = null focusNode(node) { this.focusedNode = node - this.visibleNodes.focusedNode = node + this.visibleNodes = { + ...this.visibleNodes, + focusedNode: node, + } } } diff --git a/webpack.config.js b/webpack.config.js index fb31c6f..c9b4b95 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin') const UglifyJSWebpackPlugin = require('uglifyjs-webpack-plugin') const srcPath = path.resolve(__dirname, 'src') +const packagesPath = path.resolve(__dirname, 'packages') const plugins = [ new CopyWebpackPlugin([ @@ -46,10 +47,7 @@ module.exports = { filename: '[name].js', }, resolve: { - alias: { - react: 'preact', // for svgr - }, - modules: ['packages', 'node_modules'] + modules: [srcPath, packagesPath, 'node_modules'] }, module: { rules: [ @@ -59,7 +57,7 @@ module.exports = { options: { cacheDirectory: true, }, - include: [srcPath], + include: [srcPath, packagesPath], }, { test: /\.less$/, @@ -78,6 +76,11 @@ module.exports = { loader: ['babel-loader', 'svgr/webpack'], include: [srcPath], }, + { + test: /\.json$/, + loader: ['json-loader'], + include: [srcPath], + }, ], }, plugins, diff --git a/yarn.lock b/yarn.lock index b454fae..ae9c87a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,82 @@ # yarn lockfile v1 +"@babel/code-frame@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9" + dependencies: + "@babel/highlight" "7.0.0-beta.44" + +"@babel/generator@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42" + dependencies: + "@babel/types" "7.0.0-beta.44" + jsesc "^2.5.1" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-function-name@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd" + dependencies: + "@babel/helper-get-function-arity" "7.0.0-beta.44" + "@babel/template" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + +"@babel/helper-get-function-arity@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15" + dependencies: + "@babel/types" "7.0.0-beta.44" + +"@babel/helper-split-export-declaration@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc" + dependencies: + "@babel/types" "7.0.0-beta.44" + +"@babel/highlight@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5" + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +"@babel/template@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f" + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + lodash "^4.2.0" + +"@babel/traverse@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966" + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/generator" "7.0.0-beta.44" + "@babel/helper-function-name" "7.0.0-beta.44" + "@babel/helper-split-export-declaration" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + debug "^3.1.0" + globals "^11.1.0" + invariant "^2.2.0" + lodash "^4.2.0" + +"@babel/types@7.0.0-beta.44": + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757" + dependencies: + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^2.0.0" + "@types/node@*": version "9.3.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" @@ -26,6 +102,16 @@ acorn-globals@^3.1.0: dependencies: acorn "^4.0.4" +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@^4.0.3, acorn@^4.0.4: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" @@ -34,11 +120,23 @@ acorn@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" +acorn@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + ajv-keywords@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" -ajv@^4.9.1: +ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" dependencies: @@ -54,7 +152,7 @@ ajv@^5.0.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" -ajv@^5.1.0: +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: @@ -84,6 +182,14 @@ 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" +ansi-escapes@^1.1.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.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -102,6 +208,12 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.9.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" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -146,6 +258,13 @@ array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" +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-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -160,7 +279,7 @@ 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.1: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -239,7 +358,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.11.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.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: @@ -271,6 +390,17 @@ babel-core@^6.26.0: slash "^1.0.0" source-map "^0.5.6" +babel-eslint@^8.2.3: + version "8.2.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.3.tgz#1a2e6681cc9bc4473c32899e59915e19cd6733cf" + dependencies: + "@babel/code-frame" "7.0.0-beta.44" + "@babel/traverse" "7.0.0-beta.44" + "@babel/types" "7.0.0-beta.44" + babylon "7.0.0-beta.44" + eslint-scope "~3.7.1" + eslint-visitor-keys "^1.0.0" + babel-generator@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" @@ -425,6 +555,10 @@ 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-decorators@^6.1.18: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" + 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" @@ -462,6 +596,14 @@ babel-plugin-transform-class-properties@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" +babel-plugin-transform-decorators-legacy@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators-legacy/-/babel-plugin-transform-decorators-legacy-1.3.5.tgz#0e492dffa0edd70529072887f8aa86d4dd8b40a1" + dependencies: + babel-plugin-syntax-decorators "^6.1.18" + babel-runtime "^6.2.0" + babel-template "^6.3.0" + 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" @@ -757,14 +899,14 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.18.0, babel-runtime@^6.2.0, babel-runtime@^6.22.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: +babel-template@^6.24.1, babel-template@^6.26.0, babel-template@^6.3.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: @@ -797,6 +939,10 @@ babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: lodash "^4.17.4" to-fast-properties "^1.0.3" +babylon@7.0.0-beta.44: + version "7.0.0-beta.44" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d" + babylon@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" @@ -961,6 +1107,10 @@ browserslist@^2.1.2: caniuse-lite "^1.0.30000770" electron-to-chromium "^1.3.27" +buffer-from@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" + buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -1031,6 +1181,16 @@ cache-base@^1.0.1: 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@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" @@ -1067,7 +1227,7 @@ center-align@^0.1.1: 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: @@ -1077,6 +1237,14 @@ chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0: + 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, chalk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" @@ -1085,6 +1253,10 @@ chalk@^2.1.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^4.0.0" +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + chokidar@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" @@ -1114,6 +1286,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +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" @@ -1129,6 +1305,22 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +cli-cursor@^1.0.1: + 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-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -1234,6 +1426,10 @@ commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" +compare-versions@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26" + component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" @@ -1250,6 +1446,15 @@ concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" +concat-stream@^1.5.2, concat-stream@^1.6.0: + 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" + console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" @@ -1264,6 +1469,10 @@ constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + content-type-parser@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" @@ -1300,6 +1509,10 @@ copy-webpack-plugin@^4.2.0: p-limit "^1.0.0" serialize-javascript "^1.4.0" +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.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" @@ -1339,7 +1552,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-spawn@^5.0.1: +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: @@ -1525,12 +1738,18 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8: +debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, 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@^1.0.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" @@ -1539,6 +1758,10 @@ 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" +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + deep-extend@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" @@ -1577,6 +1800,18 @@ 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" @@ -1617,6 +1852,19 @@ dir-glob@^2.0.0: arrify "^1.0.1" path-type "^3.0.0" +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.0, 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" + dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -1678,6 +1926,12 @@ emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" +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, end-of-stream@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" @@ -1719,6 +1973,16 @@ es-abstract@^1.5.1, es-abstract@^1.6.1: is-callable "^1.1.3" is-regex "^1.0.4" +es-abstract@^1.7.0: + 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-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" @@ -1804,6 +2068,161 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-import-resolver-node@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + dependencies: + debug "^2.6.9" + resolve "^1.5.0" + +eslint-module-utils@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-babel@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-babel/-/eslint-plugin-babel-5.1.0.tgz#9c76e476162041e50b6ba69aa4eae3bdd6a4e1c3" + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-plugin-class-property@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-class-property/-/eslint-plugin-class-property-1.1.0.tgz#355784133d4852b8870bd1cb429a1b0403cc7aef" + dependencies: + eslint "^3.19.0" + +eslint-plugin-import@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.14.0.tgz#6b17626d2e3e6ad52cfce8807a845d15e22111a8" + dependencies: + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.2.0" + has "^1.0.1" + lodash "^4.17.4" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + resolve "^1.6.0" + +eslint-plugin-react@^7.9.1: + version "7.9.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.9.1.tgz#101aadd15e7c7b431ed025303ac7b421a8e3dc15" + dependencies: + doctrine "^2.1.0" + has "^1.0.2" + jsx-ast-utils "^2.0.1" + prop-types "^15.6.1" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + +eslint-scope@^3.7.1, 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@^3.19.0: + version "3.19.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" + dependencies: + babel-code-frame "^6.16.0" + chalk "^1.1.3" + concat-stream "^1.5.2" + debug "^2.1.1" + doctrine "^2.0.0" + escope "^3.6.0" + espree "^3.4.0" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.0.3" + globals "^9.14.0" + ignore "^3.2.0" + imurmurhash "^0.1.4" + inquirer "^0.12.0" + is-my-json-valid "^2.10.0" + is-resolvable "^1.0.0" + js-yaml "^3.5.1" + json-stable-stringify "^1.0.0" + levn "^0.3.0" + lodash "^4.0.0" + mkdirp "^0.5.0" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.1" + pluralize "^1.2.1" + progress "^1.1.8" + require-uncached "^1.0.2" + shelljs "^0.7.5" + strip-bom "^3.0.0" + strip-json-comments "~2.0.1" + table "^3.7.8" + text-table "~0.2.0" + user-home "^2.0.0" + +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" + +espree@^3.4.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" @@ -1816,6 +2235,12 @@ 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.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + 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" @@ -1823,7 +2248,7 @@ esrecurse@^4.1.0: estraverse "^4.1.0" object-assign "^4.0.1" -estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -1861,6 +2286,10 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.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@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -1890,6 +2319,14 @@ 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.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" @@ -1923,6 +2360,38 @@ fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" +fbjs@^0.8.16: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + 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.18" + +figures@^1.3.5: + 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-loader@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.6.tgz#7b9a8f2c58f00a77fddf49e940f7ac978a3ea0e8" @@ -1947,12 +2416,28 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" +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" +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" @@ -2031,10 +2516,21 @@ fsevents@^1.1.2: nan "^2.9.2" node-pre-gyp "^0.9.0" +fsevents@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + 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" @@ -2048,6 +2544,16 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^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" @@ -2073,7 +2579,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.5, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -2084,10 +2590,25 @@ glob@^7.0.5, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.18.0: +globals@^11.0.1, globals@^11.1.0: + version "11.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.5.0.tgz#6bc840de6771173b191f13d3a9c94d441ee92642" + +globals@^9.14.0, 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@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" @@ -2175,6 +2696,10 @@ 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-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2212,6 +2737,12 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" +has@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + dependencies: + function-bind "^1.1.1" + hash-base@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" @@ -2311,6 +2842,12 @@ iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@^0.4.17, iconv-lite@~0.4.13: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@^0.4.4: version "0.4.22" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.22.tgz#c6b16b9d05bc6c307dc9303a820412995d2eea95" @@ -2341,7 +2878,7 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.3.5: +ignore@^3.2.0, ignore@^3.3.3, ignore@^3.3.5: version "3.3.8" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" @@ -2376,14 +2913,57 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@~1.3.0: +ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" +inquirer@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" + dependencies: + ansi-escapes "^1.1.0" + ansi-regex "^2.0.0" + chalk "^1.0.0" + cli-cursor "^1.0.1" + cli-width "^2.0.0" + figures "^1.3.5" + lodash "^4.3.0" + readline2 "^1.0.1" + run-async "^0.1.0" + rx-lite "^3.1.2" + string-width "^1.0.1" + strip-ansi "^3.0.0" + through "^2.3.6" + +inquirer@^3.0.6: + 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.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + dependencies: + loose-envify "^1.0.0" + invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" @@ -2508,6 +3088,20 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + +is-my-json-valid@^2.10.0: + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -2524,6 +3118,22 @@ is-odd@^2.0.0: 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.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" + 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" @@ -2534,13 +3144,25 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + 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-stream@^1.1.0: +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + +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" @@ -2580,6 +3202,13 @@ 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" @@ -2592,6 +3221,13 @@ 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.5.1, js-yaml@^3.9.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.10.0: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" @@ -2640,11 +3276,15 @@ jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" +jsesc@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe" + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-loader@^0.5.4: +json-loader@^0.5.4, json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" @@ -2656,7 +3296,11 @@ 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@^1.0.1: +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, json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" dependencies: @@ -2674,6 +3318,10 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2683,6 +3331,12 @@ jsprim@^1.2.2: 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" @@ -2734,7 +3388,7 @@ less@^2.7.3: request "2.81.0" source-map "^0.5.3" -levn@~0.3.0: +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: @@ -2785,19 +3439,19 @@ 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.0.0, lodash@^4.14.0, lodash@^4.2.0, lodash@^4.3.0: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + lodash@^4.13.1, lodash@^4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^4.14.0: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - 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.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: @@ -2922,7 +3576,7 @@ minimatch@3.0.3: dependencies: brace-expansion "^1.0.0" -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -3007,6 +3661,14 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +mute-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + mz@^2.6.0: version "2.7.0" resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -3036,6 +3698,10 @@ nanomatch@^1.2.9: 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" + needle@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" @@ -3044,6 +3710,14 @@ needle@^2.2.0: iconv-lite "^0.4.4" sax "^1.2.4" +needle@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.3.tgz#c1b04da378cd634d8befe2de965dc2cfb0fd65ca" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + neo-async@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" @@ -3052,6 +3726,13 @@ next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" +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-libs-browser@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" @@ -3080,6 +3761,21 @@ node-libs-browser@^2.0.0: util "^0.10.3" vm-browserify "0.0.4" +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + node-pre-gyp@^0.9.0: version "0.9.1" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz#f11c07516dd92f87199dbc7e1838eab7cd56c9e0" @@ -3238,7 +3934,17 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -optionator@^0.8.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" + +optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" dependencies: @@ -3265,7 +3971,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: +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" @@ -3354,6 +4060,12 @@ 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" @@ -3362,10 +4074,18 @@ 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-type@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" @@ -3404,12 +4124,36 @@ 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" + pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" dependencies: find-up "^2.1.0" +pluralize@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + pn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -3664,14 +4408,6 @@ postcss@^6.0.1: source-map "^0.6.1" supports-color "^4.4.0" -preact-portal@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/preact-portal/-/preact-portal-1.1.3.tgz#22cdd3ecf6ad9aaa3f830607a9c6591de90aedb7" - -preact@^8.2.6: - version "8.2.6" - resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.6.tgz#0028b426ef98fcca741a3c617ff5b813b9a947c7" - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -3696,6 +4432,14 @@ process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" +progress@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -3706,6 +4450,14 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.6.0, prop-types@^15.6.1: + version "15.6.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" @@ -3807,6 +4559,39 @@ rc@^1.1.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +rc@^1.2.7: + 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-dom@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.4.0.tgz#099f067dd5827ce36a29eaf9a6cdc7cbf6216b1e" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.0" + +react-life-hook@^0.2.0: + version "0.2.8" + resolved "https://registry.yarnpkg.com/react-life-hook/-/react-life-hook-0.2.8.tgz#d38bdeb82a30010c11c9a5e45f27805fdd25e7e7" + dependencies: + compare-versions "^3.4.0" + +react@^16.4.0: + version "16.4.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.4.0.tgz#402c2db83335336fba1962c08b98c6272617d585" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.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" @@ -3843,6 +4628,20 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" +readline2@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + mute-stream "0.0.5" + +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" @@ -3886,6 +4685,10 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regexpp@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + regexpu-core@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" @@ -4006,10 +4809,47 @@ 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-uncached@^1.0.2, 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" + +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.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.0.tgz#a7f2ac27b78480ecc09c83782741d9f26e4f0c3e" + dependencies: + path-parse "^1.0.5" + +resolve@^1.5.0, resolve@^1.6.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + 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" @@ -4020,7 +4860,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -4033,12 +4873,38 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^2.0.0" inherits "^2.0.1" +run-async@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" + dependencies: + once "^1.3.0" + +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" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" dependencies: aproba "^1.1.1" +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" + +rx-lite@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -4105,7 +4971,7 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -4126,7 +4992,15 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -signal-exit@^3.0.0: +shelljs@^0.7.5: + 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" @@ -4134,6 +5008,16 @@ 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" @@ -4203,7 +5087,7 @@ 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.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -4324,7 +5208,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: +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: @@ -4388,6 +5272,12 @@ supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: dependencies: has-flag "^2.0.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" @@ -4440,6 +5330,28 @@ symbol-tree@^3.2.1: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" +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" + +table@^3.7.8: + version "3.8.3" + resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" @@ -4456,6 +5368,10 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -4475,12 +5391,22 @@ through2@^2.0.0: readable-stream "^2.1.5" xtend "~4.0.1" +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + timers-browserify@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" dependencies: setimmediate "^1.0.4" +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-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -4489,6 +5415,10 @@ 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-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + 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" @@ -4551,6 +5481,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +ua-parser-js@^0.7.18: + version "0.7.18" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" + uglify-es@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.2.0.tgz#fbbfb9dc465ec7e5065701b9720d0de977d0bc24" @@ -4672,6 +5606,12 @@ use@^3.1.0: dependencies: kind-of "^6.0.2" +user-home@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" + dependencies: + os-homedir "^1.0.0" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4770,6 +5710,10 @@ whatwg-encoding@^1.0.1: dependencies: iconv-lite "0.4.19" +whatwg-fetch@>=0.10.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + whatwg-url@^6.1.0: version "6.4.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" @@ -4828,6 +5772,12 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" +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-name-validator@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"