Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1533c2261f | ||
|
|
d8398fca5f | ||
|
|
02a0bec738 | ||
|
|
0f5172a820 | ||
|
|
a3f94738b6 | ||
|
|
c153eb6c91 | ||
|
|
87aaa99276 | ||
|
|
e6b6f67814 | ||
|
|
1f8bc33830 | ||
|
|
8e55c13d72 | ||
|
|
b02be37bdd | ||
|
|
76422d7343 | ||
|
|
22ac8f4984 | ||
|
|
1b1fae6f7a | ||
|
|
f49c0bdc3d | ||
|
|
b9f417140d | ||
|
|
29db681e1c | ||
|
|
dda92746fa |
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
BUGSNAG_API_KEY=
|
||||||
|
APPLICATION_ID=
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
build/
|
build/
|
||||||
|
.env
|
||||||
|
|||||||
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
- Add support for starting/stopping a timer
|
||||||
|
- Show hours as HH:MM or decimal in the Bubble, depending on setting in MOCO
|
||||||
|
|
||||||
|
|
||||||
|
## [1.0.18] - 2019-03-23
|
||||||
|
### Added
|
||||||
|
- First release of version 1
|
||||||
|
|
||||||
|
## [1.0.19] - 2019-03-26
|
||||||
|
### Changed
|
||||||
|
- Position Bubble in the bottom right by default
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Set default value of subdomain to `__unset__` to prevent network error if it is empty
|
||||||
|
|
||||||
|
## [1.0.20] - 2019-03-26
|
||||||
|
### Added
|
||||||
|
- Add support for tags in description
|
||||||
|
|
||||||
|
## [1.0.21] - 2019-03-26
|
||||||
|
### Changed
|
||||||
|
- Update README with example configuration and instructions for local installation
|
||||||
|
|
||||||
|
## [1.0.22] - 2019-03-28
|
||||||
|
### Changed
|
||||||
|
- Change the default value of subdomain to `unset` to have a well-formed URL.
|
||||||
|
|
||||||
|
## [1.1.0] - 2019-03-30
|
||||||
|
### Added
|
||||||
|
- Read project identifier from Asana project title
|
||||||
|
- Add support for meistertask.com
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Link logo in modal to MOCO activities page
|
||||||
|
- Set full url on service, including query params
|
||||||
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2019, hundertzehn GmbH
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
116
README.md
116
README.md
@@ -1,15 +1,6 @@
|
|||||||
mocoapp-browser-extension
|
# MOCO Browser Extension
|
||||||
=========================
|
|
||||||
|
|
||||||
Documentation
|
## Development
|
||||||
-------------
|
|
||||||
|
|
||||||
* https://checklyhq.com/blog/2018/08/creating-a-chrome-extension-in-2018-the-good-the-bad-and-the-meh/
|
|
||||||
* https://developer.chrome.com/extensions
|
|
||||||
* https://developer.chrome.com/extensions/api_index
|
|
||||||
|
|
||||||
Development
|
|
||||||
-----------
|
|
||||||
|
|
||||||
* run `yarn`
|
* run `yarn`
|
||||||
* run `yarn start:chrome` or `yarn start:firefox` (`yarn start` is an alias for `yarn start:chrome`)
|
* run `yarn start:chrome` or `yarn start:firefox` (`yarn start` is an alias for `yarn start:chrome`)
|
||||||
@@ -18,9 +9,106 @@ Development
|
|||||||
* Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox`
|
* Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox`
|
||||||
* reload browser extension after change
|
* reload browser extension after change
|
||||||
|
|
||||||
Release
|
## Production Build
|
||||||
-------
|
|
||||||
|
|
||||||
* bump version in `package.json`
|
* bump version in `package.json`
|
||||||
* run `yarn build`
|
* run `yarn build`
|
||||||
* upload Chrome and Firefox extensions in `build/chrome` and `build/firefox` respectively
|
* The Chrome and Firefox extensions are available as ZIP-files in `build/chrome` and `build/firefox` respectively
|
||||||
|
|
||||||
|
## Install Local Builds
|
||||||
|
|
||||||
|
### Chrome
|
||||||
|
|
||||||
|
1. `yarn build:chrome`
|
||||||
|
1. Visit `chrome://extensions`
|
||||||
|
2. Enable `Developer mode`
|
||||||
|
3. `Load unpacked` and select the `build/chrome` folder.
|
||||||
|
|
||||||
|
### Firefox
|
||||||
|
|
||||||
|
1. `yarn build:firefox`
|
||||||
|
1. Visit `about:debugging`
|
||||||
|
2. Click on `Load temporary Add-on` and select the ZIP-file in `build/firefox`
|
||||||
|
|
||||||
|
Only signed extensions can be permantly installed in Firefox (unless you are using <em>Firefox Developer Edition</em>). To sign the build, proceed as described in [Getting started with web-ext](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Getting_started_with_web-ext).
|
||||||
|
|
||||||
|
You can keep the extension settings between builds by providing a stable `APPLICATION_ID` between builds. You can set an `APPLICATION_ID` in a file named `.env` or at build time as follows:
|
||||||
|
|
||||||
|
`APPLICATION_ID=my-custom-moco-extension@mycompany.com yarn build:firefox`
|
||||||
|
|
||||||
|
## Remote Service Configuration
|
||||||
|
|
||||||
|
Remote services are configured in `src/remoteServices.js`.
|
||||||
|
|
||||||
|
A remote service is configured as follows:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
service_key: {
|
||||||
|
name: "service_name",
|
||||||
|
urlPatterns: [
|
||||||
|
"https:\\://:subdomain.example.com/card/:id",
|
||||||
|
[/^https:\/\/(\w+).example.com\/card\/(\d+), ["subdomain", "id"]],
|
||||||
|
],
|
||||||
|
queryParams: {
|
||||||
|
projectId: "currentList"
|
||||||
|
},
|
||||||
|
description: (document, service, { subdomain, id, projectId }) => {
|
||||||
|
const title = document
|
||||||
|
.querySelector('.title')
|
||||||
|
?.textContent
|
||||||
|
?.trim()
|
||||||
|
return `#${id} ${service.key} ${title || ""}`
|
||||||
|
},
|
||||||
|
projectId: (document, service, { subdomain, id, projectId }) => {
|
||||||
|
return projectId
|
||||||
|
},
|
||||||
|
position: { left: "50%", transform: "translate(-50%)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|--------------|:-------------|
|
||||||
|
| service_key | `string` — Unique identifier for the service |
|
||||||
|
| service_name | `string` — Must be one of the registered services `trello`, `jira`, `asana`, `wunderlist`, `github` or `youtrack` |
|
||||||
|
| urlPatterns | `string` \| `RegEx` — A valid URL pattern or regular expression, as described in the [url-pattern](https://www.npmjs.com/package/url-pattern) package. |
|
||||||
|
| queryParams | `Object` — The object value is the name of the query parameter and the key the property it will available on, e.g. the value of the query parameter `currentList` will be available under `projectId`. Matches in `urlPatterns` have precedence over matches in `queryParams`. |
|
||||||
|
| description | `undefined` \| `string` \| `function` — The default description of the service. If it is a function, it will receive `window.document`, the current `service` and an object with the URL `matches` as arguments, and the return value set as the default description. |
|
||||||
|
| projectId | `undefined` \| `string` \| `function` — The pre-selected project of the service matching the MOCO project identifier. If it is a function, it will receive `window.document`, the current `service` and an object with the URL `matches` as arguments, and must return the MOCO project identifier or `undefined`. |
|
||||||
|
| position | `Object` — CSS properties as object styles for position the bubble. Defaults to `{ right: calc(4rem + 5px)` |
|
||||||
|
|
||||||
|
## Adding a Custom Service
|
||||||
|
|
||||||
|
1. Fork and clone this repository
|
||||||
|
2. Add your service to `src/removeServices.js`, e.g. for self-hosted Jira copy the entry with the key `jira` and update the `urlPatterns`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
"self-hosted-jira": {
|
||||||
|
name: "jira",
|
||||||
|
urlPatterns: [
|
||||||
|
"https\\://jira.my-company.com/secure/RapidBoard.jspa",
|
||||||
|
"https\\://jira.my-company.net/browse/:id",
|
||||||
|
"https\\://jira.my-company.net/jira/software/projects/:projectId/boards/:board",
|
||||||
|
"https\\://jira.my-company.net/jira/software/projects/:projectId/boards/:board/backlog"
|
||||||
|
],
|
||||||
|
queryParams: {
|
||||||
|
id: "selectedIssue",
|
||||||
|
projectId: "projectKey"
|
||||||
|
},
|
||||||
|
description: (document, service, { id }) => {
|
||||||
|
const title =
|
||||||
|
document
|
||||||
|
.querySelector('[aria-label="Edit Summary"]')
|
||||||
|
?.parentNode?.querySelector("h1")
|
||||||
|
?.textContent?.trim() ||
|
||||||
|
document
|
||||||
|
.querySelector(".ghx-selected .ghx-summary")
|
||||||
|
?.textContent?.trim()
|
||||||
|
return `#${id} ${title || ""}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the extension (see [Production Build](#production-build)).
|
||||||
|
4. Install the extension locally (see [Install Local Builds](#install-local-builds)).
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -1,24 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "moco-browser-extensions",
|
"name": "moco-browser-extensions",
|
||||||
"description": "Browser plugin for MOCO",
|
"description": "Browser plugin for MOCO",
|
||||||
"version": "1.0.18",
|
"version": "1.1.0",
|
||||||
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn start:chrome",
|
"start": "yarn start:chrome",
|
||||||
"start:chrome": "node_modules/.bin/webpack --config webpack.chrome.config.js --watch --env.browser chrome --env.NODE_ENV development",
|
"start:chrome": "node_modules/.bin/webpack --config webpack.chrome.config.js --watch --env.browser chrome --env.NODE_ENV development",
|
||||||
"start:firefox": "node_modules/.bin/webpack --config webpack.firefox.config.js --watch --env.browser firefox --env.NODE_ENV development",
|
"start:firefox": "node_modules/.bin/webpack --config webpack.firefox.config.js --watch --env.browser firefox --env.NODE_ENV development",
|
||||||
|
"zip:chrome": "zip -qr build/chrome/moco-bx-source.zip . -x .git/\\* build/\\* node_modules/\\* test/\\* .DS_Store",
|
||||||
|
"zip:firefox": "zip -qr build/firefox/moco-bx-source.zip . -x .git/\\* build/\\* node_modules/\\* test/\\* .DS_Store",
|
||||||
"build:chrome": "node_modules/.bin/webpack -p --config webpack.chrome.config.js --env.browser chrome --env.NODE_ENV production",
|
"build:chrome": "node_modules/.bin/webpack -p --config webpack.chrome.config.js --env.browser chrome --env.NODE_ENV production",
|
||||||
"build:firefox": "node_modules/.bin/webpack -p --config webpack.firefox.config.js --env.browser firefox --env.NODE_ENV production",
|
"build:firefox": "node_modules/.bin/webpack -p --config webpack.firefox.config.js --env.browser firefox --env.NODE_ENV production",
|
||||||
"build": "yarn run build:firefox && yarn run build:chrome",
|
"build": "yarn build:firefox && yarn zip:firefox && yarn build:chrome && yarn zip:chrome",
|
||||||
"test": "node_modules/.bin/jest",
|
"test": "node_modules/.bin/jest",
|
||||||
"test:watch": "node_modules/.bin/jest --watch",
|
"test:watch": "node_modules/.bin/jest --watch"
|
||||||
"release": "copyfiles main.css main.min.js background.min.js manifest.json popup.html options.html node_modules/jquery/dist/jquery.min.js node_modules/select2/select2.js src/images/* release"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/polyfill": "^7.4.0",
|
||||||
"@bugsnag/js": "^5.2.0",
|
"@bugsnag/js": "^5.2.0",
|
||||||
"@bugsnag/plugin-react": "^5.2.0",
|
"@bugsnag/plugin-react": "^5.2.0",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"date-fns": "^1.30.1",
|
"date-fns": "^1.30.1",
|
||||||
|
"dotenv": "^7.0.0",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"mobx": "^5.5.0",
|
"mobx": "^5.5.0",
|
||||||
"mobx-react": "^5.2.8",
|
"mobx-react": "^5.2.8",
|
||||||
@@ -55,6 +59,7 @@
|
|||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
|
"uuid": "^3.3.2",
|
||||||
"webpack": "^4.15.0",
|
"webpack": "^4.15.0",
|
||||||
"webpack-bugsnag-plugins": "^1.3.0",
|
"webpack-bugsnag-plugins": "^1.3.0",
|
||||||
"webpack-cli": "^3.0.8",
|
"webpack-cli": "^3.0.8",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#moco-bx-root {
|
#moco-bx-root {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
color: $font-color;
|
color: $font-color;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
.moco-bx-bubble {
|
.moco-bx-bubble {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
#moco-bx-popup-root {
|
#moco-bx-popup-root {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
color: $font-color;
|
color: $font-color;
|
||||||
|
pointer-events: all;
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
@@ -58,16 +60,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.moco-bx-popup {
|
.moco-bx-popup {
|
||||||
position: fixed; /* Stay in place */
|
position: fixed;
|
||||||
z-index: 2000; /* Sit on top */
|
z-index: 2000;
|
||||||
padding-top: 100px; /* Location of the box */
|
padding-top: 100px;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%; /* Full width */
|
width: 100%;
|
||||||
height: 100%; /* Full height */
|
height: 100%;
|
||||||
overflow: auto; /* Enable scroll if needed */
|
overflow: auto;
|
||||||
background-color: rgb(0, 0, 0); /* Fallback color */
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
|
|
||||||
|
|
||||||
.moco-bx-popup-content {
|
.moco-bx-popup-content {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "@babel/polyfill"
|
||||||
import ApiClient from "api/Client"
|
import ApiClient from "api/Client"
|
||||||
import {
|
import {
|
||||||
isChrome,
|
isChrome,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
ERROR_UNKNOWN,
|
ERROR_UNKNOWN,
|
||||||
ERROR_UNAUTHORIZED,
|
ERROR_UNAUTHORIZED,
|
||||||
ERROR_UPGRADE_REQUIRED,
|
ERROR_UPGRADE_REQUIRED,
|
||||||
|
extractAndSetTag,
|
||||||
findProjectByValue,
|
findProjectByValue,
|
||||||
findProjectByIdentifier,
|
findProjectByIdentifier,
|
||||||
findTask,
|
findTask,
|
||||||
@@ -35,6 +36,7 @@ class App extends Component {
|
|||||||
projectId: PropTypes.string,
|
projectId: PropTypes.string,
|
||||||
taskId: PropTypes.string
|
taskId: PropTypes.string
|
||||||
}),
|
}),
|
||||||
|
subdomain: PropTypes.string,
|
||||||
activities: PropTypes.array,
|
activities: PropTypes.array,
|
||||||
schedules: PropTypes.array,
|
schedules: PropTypes.array,
|
||||||
projects: PropTypes.array,
|
projects: PropTypes.array,
|
||||||
@@ -61,12 +63,14 @@ class App extends Component {
|
|||||||
const { service, projects, lastProjectId, lastTaskId } = this.props
|
const { service, projects, lastProjectId, lastTaskId } = this.props
|
||||||
|
|
||||||
const project =
|
const project =
|
||||||
|
findProjectByValue(this.changeset.assignment_id)(projects) ||
|
||||||
findProjectByIdentifier(service?.projectId)(projects) ||
|
findProjectByIdentifier(service?.projectId)(projects) ||
|
||||||
findProjectByValue(Number(lastProjectId))(projects) ||
|
findProjectByValue(Number(lastProjectId))(projects) ||
|
||||||
head(projects)
|
head(projects)
|
||||||
|
|
||||||
const task =
|
const task =
|
||||||
findTask(service?.taskId || lastTaskId)(project) || head(project?.tasks)
|
findTask(this.changeset.task_id || service?.taskId || lastTaskId)(project) ||
|
||||||
|
head(project?.tasks)
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
remote_service: service?.name,
|
remote_service: service?.name,
|
||||||
@@ -80,13 +84,11 @@ class App extends Component {
|
|||||||
seconds:
|
seconds:
|
||||||
this.changeset.hours &&
|
this.changeset.hours &&
|
||||||
new TimeInputParser(this.changeset.hours).parseSeconds(),
|
new TimeInputParser(this.changeset.hours).parseSeconds(),
|
||||||
description: service?.description
|
description: service?.description,
|
||||||
|
tag: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { ...defaults, ...this.changeset }
|
||||||
...defaults,
|
|
||||||
...this.changeset
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -109,7 +111,7 @@ class App extends Component {
|
|||||||
|
|
||||||
if (name === "assignment_id") {
|
if (name === "assignment_id") {
|
||||||
const project = findProjectByValue(value)(projects)
|
const project = findProjectByValue(value)(projects)
|
||||||
this.changeset.task_id = head(project?.tasks).value || null
|
this.changeset.task_id = head(project?.tasks)?.value
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,7 +126,7 @@ class App extends Component {
|
|||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
type: "createActivity",
|
type: "createActivity",
|
||||||
payload: {
|
payload: {
|
||||||
activity: this.changesetWithDefaults,
|
activity: extractAndSetTag(this.changesetWithDefaults),
|
||||||
service
|
service
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -146,6 +148,7 @@ class App extends Component {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
|
subdomain,
|
||||||
projects,
|
projects,
|
||||||
activities,
|
activities,
|
||||||
schedules,
|
schedules,
|
||||||
@@ -180,7 +183,7 @@ class App extends Component {
|
|||||||
>
|
>
|
||||||
{props => (
|
{props => (
|
||||||
<animated.div className="moco-bx-app-container" style={props}>
|
<animated.div className="moco-bx-app-container" style={props}>
|
||||||
<Header />
|
<Header subdomain={subdomain} />
|
||||||
<Observer>
|
<Observer>
|
||||||
{() => (
|
{() => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import React from "react"
|
|||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import logoUrl from "images/logo.png"
|
import logoUrl from "images/logo.png"
|
||||||
|
|
||||||
const Bubble = ({ bookedHours, onClick }) => (
|
const Bubble = ({ bookedHours }) => (
|
||||||
<div className="moco-bx-bubble-inner" onClick={onClick}>
|
<div className="moco-bx-bubble-inner">
|
||||||
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
|
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
|
||||||
{bookedHours > 0 ? (
|
{bookedHours > 0 ? (
|
||||||
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span>
|
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span>
|
||||||
@@ -12,8 +12,7 @@ const Bubble = ({ bookedHours, onClick }) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
Bubble.propTypes = {
|
Bubble.propTypes = {
|
||||||
bookedHours: PropTypes.number,
|
bookedHours: PropTypes.number
|
||||||
onClick: PropTypes.func.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bubble.defaultProps = {
|
Bubble.defaultProps = {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Options extends Component {
|
|||||||
@observable isSuccess = false;
|
@observable isSuccess = false;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
getSettings().then(({ subdomain, apiKey }) => {
|
getSettings(false).then(({ subdomain, apiKey }) => {
|
||||||
this.subdomain = subdomain || ""
|
this.subdomain = subdomain || ""
|
||||||
this.apiKey = apiKey || ""
|
this.apiKey = apiKey || ""
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Popup extends Component {
|
|||||||
const serializedProps = serializeProps([
|
const serializedProps = serializeProps([
|
||||||
"loading",
|
"loading",
|
||||||
"service",
|
"service",
|
||||||
|
"subdomain",
|
||||||
"lastProjectId",
|
"lastProjectId",
|
||||||
"lastTaskId",
|
"lastTaskId",
|
||||||
"roundTimeEntries",
|
"roundTimeEntries",
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
import logoUrl from "images/logo.png"
|
import logoUrl from "images/logo.png"
|
||||||
|
|
||||||
const Header = () => (
|
const Header = ({ subdomain }) => (
|
||||||
<div className="moco-bx-logo__container">
|
<div className="moco-bx-logo__container">
|
||||||
|
<a href={`https://${subdomain}.mocoapp.com/activities`} target="_blank" rel="noopener noreferrer">
|
||||||
<img
|
<img
|
||||||
className="moco-bx-logo"
|
className="moco-bx-logo"
|
||||||
src={chrome.extension.getURL(logoUrl)}
|
src={chrome.extension.getURL(logoUrl)}
|
||||||
/>
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Header.propTypes = {
|
||||||
|
subdomain: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
export default Header
|
export default Header
|
||||||
|
|||||||
@@ -43,15 +43,12 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
<animated.div
|
<animated.div
|
||||||
className="moco-bx-bubble"
|
className="moco-bx-bubble"
|
||||||
style={{ ...props, ...service.position }}
|
style={{ ...props, ...service.position }}
|
||||||
>
|
|
||||||
<Bubble
|
|
||||||
key={service.url}
|
|
||||||
bookedHours={bookedHours}
|
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
messenger.postMessage({ type: "togglePopup" })
|
messenger.postMessage({ type: "togglePopup" })
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
<Bubble key={service.url} bookedHours={bookedHours} />
|
||||||
</animated.div>
|
</animated.div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import "../css/popup.scss"
|
|||||||
const parsedProps = parseProps([
|
const parsedProps = parseProps([
|
||||||
"loading",
|
"loading",
|
||||||
"service",
|
"service",
|
||||||
|
"subdomain",
|
||||||
"projects",
|
"projects",
|
||||||
"activities",
|
"activities",
|
||||||
"schedules",
|
"schedules",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
const projectRegex = /\[([\w-]+)\]/
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
asana: {
|
asana: {
|
||||||
name: "asana",
|
name: "asana",
|
||||||
@@ -15,7 +17,14 @@ export default {
|
|||||||
document
|
document
|
||||||
.querySelector(".ItemRow--focused textarea")
|
.querySelector(".ItemRow--focused textarea")
|
||||||
?.textContent?.trim() ||
|
?.textContent?.trim() ||
|
||||||
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim()
|
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim(),
|
||||||
|
projectId: document => {
|
||||||
|
const match = document
|
||||||
|
.querySelector(".ProjectPageHeader-projectName")
|
||||||
|
?.textContent?.trim()
|
||||||
|
?.match(projectRegex)
|
||||||
|
return match && match[1]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"github-pr": {
|
"github-pr": {
|
||||||
@@ -29,10 +38,9 @@ export default {
|
|||||||
const match = document
|
const match = document
|
||||||
.querySelector(".js-issue-title")
|
.querySelector(".js-issue-title")
|
||||||
?.textContent.trim()
|
?.textContent.trim()
|
||||||
?.match(/^\[(\d+)\]/)
|
?.match(projectRegex)
|
||||||
return match && match[1]
|
return match && match[1]
|
||||||
},
|
}
|
||||||
position: { right: "2rem" }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"github-issue": {
|
"github-issue": {
|
||||||
@@ -41,8 +49,7 @@ export default {
|
|||||||
id: (document, service, { org, repo, id }) =>
|
id: (document, service, { org, repo, id }) =>
|
||||||
[service.key, org, repo, id].join("."),
|
[service.key, org, repo, id].join("."),
|
||||||
description: (document, service, { org, repo, id }) =>
|
description: (document, service, { org, repo, id }) =>
|
||||||
document.querySelector(".js-issue-title")?.textContent?.trim(),
|
document.querySelector(".js-issue-title")?.textContent?.trim()
|
||||||
position: { right: "2rem" }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
jira: {
|
jira: {
|
||||||
@@ -66,7 +73,25 @@ export default {
|
|||||||
document
|
document
|
||||||
.querySelector(".ghx-selected .ghx-summary")
|
.querySelector(".ghx-selected .ghx-summary")
|
||||||
?.textContent?.trim()
|
?.textContent?.trim()
|
||||||
return `[${id}] ${title || ""}`
|
return `#${id} ${title || ""}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
meistertask: {
|
||||||
|
name: "meistertask",
|
||||||
|
urlPatterns: ["https\\://www.meistertask.com/app/task/:id/:slug"],
|
||||||
|
description: document => {
|
||||||
|
const json =
|
||||||
|
document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
||||||
|
const data = JSON.parse(json)
|
||||||
|
return data.taskName
|
||||||
|
},
|
||||||
|
projectId: document => {
|
||||||
|
const json =
|
||||||
|
document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
|
||||||
|
const data = JSON.parse(json)
|
||||||
|
const match = data.projectName?.match(projectRegex)
|
||||||
|
return match && match[1]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -74,8 +99,7 @@ export default {
|
|||||||
name: "trello",
|
name: "trello",
|
||||||
urlPatterns: ["https\\://trello.com/c/:id/:title"],
|
urlPatterns: ["https\\://trello.com/c/:id/:title"],
|
||||||
description: (document, service, { title }) =>
|
description: (document, service, { title }) =>
|
||||||
document.querySelector(".js-title-helper")?.textContent?.trim() || title,
|
document.querySelector(".js-title-helper")?.textContent?.trim() || title
|
||||||
position: { right: "calc(2rem + 4px)" }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
youtrack: {
|
youtrack: {
|
||||||
@@ -91,7 +115,6 @@ export default {
|
|||||||
description: document =>
|
description: document =>
|
||||||
document
|
document
|
||||||
.querySelector(".taskItem.selected .taskItem-titleWrapper-title")
|
.querySelector(".taskItem.selected .taskItem-titleWrapper-title")
|
||||||
?.textContent?.trim(),
|
?.textContent?.trim()
|
||||||
position: { right: "calc(2rem + 4px)" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
|
import { head } from "lodash/fp"
|
||||||
export const isChrome = () => typeof browser === "undefined" && chrome
|
export const isChrome = () => typeof browser === "undefined" && chrome
|
||||||
export const isFirefox = () => typeof browser !== "undefined" && chrome
|
export const isFirefox = () => typeof browser !== "undefined" && chrome
|
||||||
import { head } from "lodash/fp"
|
|
||||||
|
|
||||||
export const getSettings = () => {
|
const DEFAULT_SUBDOMAIN = "unset"
|
||||||
|
|
||||||
|
export const getSettings = (withDefaultSubdomain = true) => {
|
||||||
const keys = ["subdomain", "apiKey"]
|
const keys = ["subdomain", "apiKey"]
|
||||||
const { version } = chrome.runtime.getManifest()
|
const { version } = chrome.runtime.getManifest()
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
chrome.storage.sync.get(keys, data => {
|
chrome.storage.sync.get(keys, data => {
|
||||||
|
if (withDefaultSubdomain) {
|
||||||
|
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
||||||
|
}
|
||||||
resolve({ ...data, version })
|
resolve({ ...data, version })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return browser.storage.sync.get(keys).then(data => ({ ...data, version }))
|
return browser.storage.sync.get(keys).then(data => {
|
||||||
|
if (withDefaultSubdomain) {
|
||||||
|
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN
|
||||||
|
}
|
||||||
|
return { ...data, version }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,3 +86,16 @@ export const formatDate = date => format(date, "YYYY-MM-DD")
|
|||||||
|
|
||||||
export const extensionSettingsUrl = () =>
|
export const extensionSettingsUrl = () =>
|
||||||
`chrome://extensions/?id=${chrome.runtime.id}`
|
`chrome://extensions/?id=${chrome.runtime.id}`
|
||||||
|
|
||||||
|
export const extractAndSetTag = changeset => {
|
||||||
|
let { description } = changeset
|
||||||
|
const match = description.match(/^#(\S+)/)
|
||||||
|
if (!match) {
|
||||||
|
return changeset
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...changeset,
|
||||||
|
description: description.replace(/^#\S+\s/, ""),
|
||||||
|
tag: match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,26 +76,25 @@ export function togglePopup(tab, { messenger }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPopup(tab, { service, messenger }) {
|
async function openPopup(tab, { service, messenger }) {
|
||||||
messenger.postMessage(tab, { type: "openPopup", payload: { loading: true } })
|
messenger.postMessage(tab, { type: "openPopup", payload: { loading: true } })
|
||||||
|
|
||||||
const fromDate = getStartOfWeek()
|
const fromDate = getStartOfWeek()
|
||||||
const toDate = getEndOfWeek()
|
const toDate = getEndOfWeek()
|
||||||
getSettings()
|
const settings = await getSettings()
|
||||||
.then(settings => new ApiClient(settings))
|
const apiClient = new ApiClient(settings)
|
||||||
.then(apiClient =>
|
try {
|
||||||
Promise.all([
|
const responses = await Promise.all([
|
||||||
apiClient.login(service),
|
apiClient.login(service),
|
||||||
apiClient.projects(),
|
apiClient.projects(),
|
||||||
apiClient.activities(fromDate, toDate),
|
apiClient.activities(fromDate, toDate),
|
||||||
apiClient.schedules(fromDate, toDate)
|
apiClient.schedules(fromDate, toDate)
|
||||||
])
|
])
|
||||||
)
|
|
||||||
.then(responses => {
|
|
||||||
const action = {
|
const action = {
|
||||||
type: "openPopup",
|
type: "openPopup",
|
||||||
payload: {
|
payload: {
|
||||||
service,
|
service,
|
||||||
|
subdomain: settings.subdomain,
|
||||||
lastProjectId: get("[0].data.last_project_id", responses),
|
lastProjectId: get("[0].data.last_project_id", responses),
|
||||||
lastTaskId: get("[0].data.last_task_id", responses),
|
lastTaskId: get("[0].data.last_task_id", responses),
|
||||||
roundTimeEntries: get("[0].data.round_time_entries", responses),
|
roundTimeEntries: get("[0].data.round_time_entries", responses),
|
||||||
@@ -108,8 +107,7 @@ function openPopup(tab, { service, messenger }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
messenger.postMessage(tab, action)
|
messenger.postMessage(tab, action)
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => {
|
|
||||||
let errorType, errorMessage
|
let errorType, errorMessage
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
errorType = ERROR_UNAUTHORIZED
|
errorType = ERROR_UNAUTHORIZED
|
||||||
@@ -123,5 +121,5 @@ function openPopup(tab, { service, messenger }) {
|
|||||||
type: "openPopup",
|
type: "openPopup",
|
||||||
payload: { errorType, errorMessage }
|
payload: { errorType, errorMessage }
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const filterReport = report => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bugsnagClient = bugsnag({
|
const bugsnagClient = bugsnag({
|
||||||
apiKey: "da6caac4af70af3e4683454b40fe5ef5",
|
apiKey: process.env.BUGSNAG_API_KEY,
|
||||||
appVersion: getAppVersion(),
|
appVersion: getAppVersion(),
|
||||||
collectUserIp: false,
|
collectUserIp: false,
|
||||||
beforeSend: filterReport,
|
beforeSend: filterReport,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const createEnhancer = document => service => {
|
|||||||
description: evaluate(service.description),
|
description: evaluate(service.description),
|
||||||
projectId: evaluate(service.projectId),
|
projectId: evaluate(service.projectId),
|
||||||
taskId: evaluate(service.taskId),
|
taskId: evaluate(service.taskId),
|
||||||
position: service.position || { left: "50%", transform: "translateX(-50%)" }
|
position: service.position || { right: "calc(2rem + 5px)" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ export const createMatcher = remoteServices => {
|
|||||||
return {
|
return {
|
||||||
...match,
|
...match,
|
||||||
...service,
|
...service,
|
||||||
url,
|
url: tabUrl,
|
||||||
match
|
match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import {
|
|||||||
findProjectByValue,
|
findProjectByValue,
|
||||||
findProjectByIdentifier,
|
findProjectByIdentifier,
|
||||||
findTask,
|
findTask,
|
||||||
groupedProjectOptions
|
groupedProjectOptions,
|
||||||
|
extractAndSetTag
|
||||||
} from "../../src/js/utils"
|
} from "../../src/js/utils"
|
||||||
import { map } from "lodash/fp"
|
import { map } from "lodash/fp"
|
||||||
|
|
||||||
@@ -86,4 +87,36 @@ describe("utils", () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("extractAndSetTag", () => {
|
||||||
|
it("sets the correct tag and updates description", () => {
|
||||||
|
const changeset = {
|
||||||
|
description: "#meeting Lorem ipsum",
|
||||||
|
tag: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(extractAndSetTag(changeset)).toEqual({
|
||||||
|
description: "Lorem ipsum",
|
||||||
|
tag: "meeting"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("only matches tag at the beginning", () => {
|
||||||
|
const changeset = {
|
||||||
|
description: "Lorem #meeting ipsum",
|
||||||
|
tag: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(extractAndSetTag(changeset)).toEqual(changeset)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the changeset if not tag is set", () => {
|
||||||
|
const changeset = {
|
||||||
|
description: "Without tag",
|
||||||
|
tag: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(extractAndSetTag(changeset)).toEqual(changeset)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
require("dotenv").config()
|
||||||
|
|
||||||
const path = require("path")
|
const path = require("path")
|
||||||
const webpack = require("webpack")
|
const webpack = require("webpack")
|
||||||
const CleanWebpackPlugin = require("clean-webpack-plugin")
|
const CleanWebpackPlugin = require("clean-webpack-plugin")
|
||||||
@@ -60,7 +62,10 @@ module.exports = env => {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new CleanWebpackPlugin([`build/${env.browser}`]),
|
new CleanWebpackPlugin([`build/${env.browser}`]),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV)
|
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
|
||||||
|
"process.env.BUGSNAG_API_KEY": JSON.stringify(
|
||||||
|
process.env.BUGSNAG_API_KEY
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: "[name].css",
|
filename: "[name].css",
|
||||||
@@ -95,22 +100,27 @@ module.exports = env => {
|
|||||||
if (env.NODE_ENV === "production") {
|
if (env.NODE_ENV === "production") {
|
||||||
config.devtool = "source-maps"
|
config.devtool = "source-maps"
|
||||||
|
|
||||||
|
if (process.env.BUGSNAG_API_KEY) {
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new BugsnagBuildReporterPlugin({
|
new BugsnagBuildReporterPlugin({
|
||||||
apiKey: "da6caac4af70af3e4683454b40fe5ef5",
|
apiKey: process.env.BUGSNAG_API_KEY,
|
||||||
appVersion: process.env.npm_package_version,
|
appVersion: process.env.npm_package_version,
|
||||||
releaseStage: "production"
|
releaseStage: "production"
|
||||||
}),
|
}),
|
||||||
// important: upload sourcemaps before removing source mapping url
|
// important: upload sourcemaps before removing source mapping url
|
||||||
new BugsnagSourceMapUploaderPlugin({
|
new BugsnagSourceMapUploaderPlugin({
|
||||||
apiKey: "da6caac4af70af3e4683454b40fe5ef5",
|
apiKey: process.env.BUGSNAG_API_KEY,
|
||||||
appVersion: process.env.npm_package_version,
|
appVersion: process.env.npm_package_version,
|
||||||
publicPath:
|
publicPath:
|
||||||
env.browser === "firefox"
|
env.browser === "firefox"
|
||||||
? "moz-extension*://*/"
|
? "moz-extension*://*/"
|
||||||
: "chrome-extension*://*/", // extra asterisk after protocol needed
|
: "chrome-extension*://*/", // extra asterisk after protocol needed
|
||||||
overwrite: true
|
overwrite: true
|
||||||
}),
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.plugins.push(
|
||||||
new RemoveSourceMapPlugin(),
|
new RemoveSourceMapPlugin(),
|
||||||
new ZipPlugin({
|
new ZipPlugin({
|
||||||
filename: `moco-bx-${env.browser}-v${
|
filename: `moco-bx-${env.browser}-v${
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const uuidv4 = require("uuid/v4")
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin")
|
const CopyWebpackPlugin = require("copy-webpack-plugin")
|
||||||
const { compact } = require("lodash/fp")
|
const { compact } = require("lodash/fp")
|
||||||
|
|
||||||
@@ -30,7 +31,9 @@ module.exports = env => {
|
|||||||
browser_style: true
|
browser_style: true
|
||||||
},
|
},
|
||||||
browser_specific_settings: {
|
browser_specific_settings: {
|
||||||
gecko: { id: "browser-extension@mocoapp.com" }
|
gecko: {
|
||||||
|
id: process.env.APPLICATION_ID || `{${uuidv4()}}`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
description: process.env.npm_package_description,
|
description: process.env.npm_package_description,
|
||||||
version: process.env.npm_package_version
|
version: process.env.npm_package_version
|
||||||
|
|||||||
23
yarn.lock
23
yarn.lock
@@ -607,6 +607,14 @@
|
|||||||
"@babel/helper-regex" "^7.0.0"
|
"@babel/helper-regex" "^7.0.0"
|
||||||
regexpu-core "^4.1.3"
|
regexpu-core "^4.1.3"
|
||||||
|
|
||||||
|
"@babel/polyfill@^7.4.0":
|
||||||
|
version "7.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.4.0.tgz#90f9d68ae34ac42ab4b4aa03151848f536960218"
|
||||||
|
integrity sha512-bVsjsrtsDflIHp5I6caaAa2V25Kzn50HKPL6g3X0P0ni1ks+58cPB8Mz6AOKVuRPgaVdq/OwEUc/1vKqX+Mo4A==
|
||||||
|
dependencies:
|
||||||
|
core-js "^2.6.5"
|
||||||
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/preset-env@^7.2.2":
|
"@babel/preset-env@^7.2.2":
|
||||||
version "7.3.1"
|
version "7.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
|
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.1.tgz#389e8ca6b17ae67aaf9a2111665030be923515db"
|
||||||
@@ -2021,6 +2029,11 @@ copyfiles@^2.1.0:
|
|||||||
through2 "^2.0.1"
|
through2 "^2.0.1"
|
||||||
yargs "^11.0.0"
|
yargs "^11.0.0"
|
||||||
|
|
||||||
|
core-js@^2.6.5:
|
||||||
|
version "2.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
|
||||||
|
integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
|
||||||
|
|
||||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
@@ -2437,6 +2450,11 @@ domutils@1.5.1:
|
|||||||
dom-serializer "0"
|
dom-serializer "0"
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
|
dotenv@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c"
|
||||||
|
integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==
|
||||||
|
|
||||||
duplexify@^3.4.2, duplexify@^3.6.0:
|
duplexify@^3.4.2, duplexify@^3.6.0:
|
||||||
version "3.7.1"
|
version "3.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||||
@@ -6155,6 +6173,11 @@ regenerator-runtime@^0.12.0:
|
|||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||||
|
|
||||||
|
regenerator-runtime@^0.13.2:
|
||||||
|
version "0.13.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
|
||||||
|
integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
|
||||||
|
|
||||||
regenerator-transform@^0.13.3:
|
regenerator-transform@^0.13.3:
|
||||||
version "0.13.3"
|
version "0.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb"
|
||||||
|
|||||||
Reference in New Issue
Block a user