Compare commits

...

148 Commits

Author SHA1 Message Date
dependabot-preview[bot]
fb1a4ddd48 Bump mini-css-extract-plugin from 0.9.0 to 0.11.2
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 0.9.0 to 0.11.2.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v0.9.0...v0.11.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-02 01:14:49 +00:00
Manuel Bouza
261db3f9db Remove batchingForReactDom 2020-09-11 09:31:18 +02:00
Manuel Bouza
0b53743298 Pump version 2020-09-11 09:31:18 +02:00
Manuel Bouza
5b63b77102 Upgrade packages 2020-09-10 14:51:58 +02:00
Nicola Piccinini
7589e04207 Select first the project with tracked hours (#217) 2020-09-10 14:08:30 +02:00
pic
1aecaec925 Ignore .idea 2020-09-09 10:53:55 +02:00
Manuel Bouza
6f47cf9cd6 feature/monday-service (#198)
* Upgrade packages

* Add monday as service (#197)

* Add monday as service

* Replaced static subdomain with placeholder

* Only use pulse title for description

* Update changelog und pump version

Co-authored-by: markusNahketing <69197867+markusNahketing@users.noreply.github.com>
2020-08-06 11:50:14 +02:00
dependabot-preview[bot]
dcef794fe4 Bump css-loader from 3.5.3 to 4.2.0 (#185)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.5.3 to 4.2.0.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.5.3...v4.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-04 11:52:32 +02:00
dependabot-preview[bot]
c5230e6e74 Bump sass-loader from 8.0.2 to 9.0.2 (#186)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 8.0.2 to 9.0.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v8.0.2...v9.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-04 11:51:57 +02:00
dependabot-preview[bot]
7370915c27 Bump uuid from 8.1.0 to 8.3.0 (#194)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.1.0 to 8.3.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.1.0...v8.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-04 11:44:11 +02:00
dependabot-preview[bot]
33fe6ad910 Bump date-fns from 2.14.0 to 2.15.0 (#195)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.14.0 to 2.15.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.14.0...v2.15.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-04 11:42:07 +02:00
dependabot-preview[bot]
a8de22b796 Bump @babel/preset-env from 7.10.2 to 7.11.0 (#196)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.10.2 to 7.11.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.11.0/packages/babel-preset-env)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-08-04 11:41:38 +02:00
dependabot-preview[bot]
f38e93bcd6 [Security] Bump elliptic from 6.5.1 to 6.5.3 (#182)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.1 to 6.5.3. **This update includes a security fix.**
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.1...v6.5.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-07-30 09:09:33 +02:00
Manuel Bouza
061a3d9a89 feature/host-overrides (#161)
* configurable host overrides

* base host overrides on name of service instead of key and hide the options by default

* added unit tests

* review changes

* Refactor options

* Refactor

* Update Readme

* Pump version and update Changelog

Co-authored-by: Tobias Jacksteit <me@xtj7.de>
2020-06-15 17:14:31 +02:00
Manuel Bouza
a13e30784c Use mobx batching 2020-06-05 09:53:28 +02:00
Manuel Bouza
f4c747dd7e Fix html webpack plugin
Html files got deleted after a rebuild
2020-06-05 09:52:44 +02:00
dependabot-preview[bot]
46aa91b736 Bump eslint-plugin-prettier from 3.1.2 to 3.1.3 (#152)
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.1.2 to 3.1.3.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v3.1.2...v3.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Manuel Bouza <manuel@bouza.ch>
2020-06-04 23:38:58 +02:00
dependabot-preview[bot]
956e40fc4b Bump eslint-plugin-jest from 23.8.2 to 23.13.2 (#153)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 23.8.2 to 23.13.2.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v23.8.2...v23.13.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:37:49 +02:00
dependabot-preview[bot]
7b0a8276a4 Bump css-loader from 3.4.2 to 3.5.3 (#156)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.4.2 to 3.5.3.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.4.2...v3.5.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:37:35 +02:00
Manuel Bouza
3848055634 Fix uuid 2020-06-04 23:36:54 +02:00
Manuel Bouza
d7e4a01adc Fix copy-webpack-plugin 2020-06-04 23:35:41 +02:00
dependabot-preview[bot]
97e47ad769 Bump copy-webpack-plugin from 5.1.1 to 6.0.2 (#160)
* Bump copy-webpack-plugin from 5.1.1 to 6.0.2

Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 5.1.1 to 6.0.2.
- [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v5.1.1...v6.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

* Update copy-webpack-plugin

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Manuel Bouza <manuel@bouza.ch>
2020-06-04 23:29:06 +02:00
dependabot-preview[bot]
5311e6ea2f Bump @babel/plugin-proposal-decorators from 7.8.3 to 7.10.1 (#145)
Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel/tree/HEAD/packages/babel-plugin-proposal-decorators) from 7.8.3 to 7.10.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.1/packages/babel-plugin-proposal-decorators)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:24:03 +02:00
dependabot-preview[bot]
86191f792b Bump style-loader from 1.1.3 to 1.2.1 (#157)
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 1.1.3 to 1.2.1.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v1.1.3...v1.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:23:13 +02:00
dependabot-preview[bot]
97022dbf97 Bump eslint-config-prettier from 6.10.1 to 6.11.0 (#154)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.10.1 to 6.11.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v6.10.1...v6.11.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:22:42 +02:00
dependabot-preview[bot]
446ad01343 Bump prettier from 2.0.2 to 2.0.5 (#149)
Bumps [prettier](https://github.com/prettier/prettier) from 2.0.2 to 2.0.5.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.0.2...2.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:22:22 +02:00
dependabot-preview[bot]
5c531b4917 Bump @babel/preset-env from 7.9.0 to 7.10.2 (#144)
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.9.0 to 7.10.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.2/packages/babel-preset-env)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:21:56 +02:00
dependabot-preview[bot]
616ab6fb0f Bump mobx-react from 6.1.8 to 6.2.2 (#139)
Bumps [mobx-react](https://github.com/mobxjs/mobx-react) from 6.1.8 to 6.2.2.
- [Release notes](https://github.com/mobxjs/mobx-react/releases)
- [Changelog](https://github.com/mobxjs/mobx-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx-react/compare/6.1.8...v6.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:21:43 +02:00
dependabot-preview[bot]
4fb256fca7 Bump webpack from 4.42.1 to 4.43.0 (#140)
Bumps [webpack](https://github.com/webpack/webpack) from 4.42.1 to 4.43.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.42.1...v4.43.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:21:30 +02:00
dependabot-preview[bot]
3d3d268bca Bump @babel/polyfill from 7.8.7 to 7.10.1 (#151)
Bumps [@babel/polyfill](https://github.com/babel/babel/tree/HEAD/packages/babel-polyfill) from 7.8.7 to 7.10.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.1/packages/babel-polyfill)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:21:09 +02:00
dependabot-preview[bot]
b05d00e823 Bump eslint-plugin-react from 7.19.0 to 7.20.0 (#146)
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.19.0 to 7.20.0.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.19.0...v7.20.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:20:46 +02:00
dependabot-preview[bot]
eac2e86733 Bump date-fns from 2.11.1 to 2.14.0 (#143)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.11.1 to 2.14.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.11.1...v2.14.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:20:32 +02:00
dependabot-preview[bot]
38e33f1c55 Bump html-webpack-plugin from 4.0.4 to 4.3.0 (#142)
Bumps [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) from 4.0.4 to 4.3.0.
- [Release notes](https://github.com/jantimon/html-webpack-plugin/releases)
- [Changelog](https://github.com/jantimon/html-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jantimon/html-webpack-plugin/compare/v4.0.4...v4.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:20:16 +02:00
dependabot-preview[bot]
409bb9bab5 Bump query-string from 6.11.1 to 6.12.1 (#147)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.11.1 to 6.12.1.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.11.1...v6.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:19:49 +02:00
dependabot-preview[bot]
cf1a696fec Bump @babel/preset-react from 7.9.4 to 7.10.1 (#141)
Bumps [@babel/preset-react](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-react) from 7.9.4 to 7.10.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.1/packages/babel-preset-react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:19:34 +02:00
dependabot-preview[bot]
f26ed7ce02 Bump copyfiles from 2.2.0 to 2.3.0 (#138)
Bumps [copyfiles](https://github.com/calvinmetcalf/copyfiles) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/calvinmetcalf/copyfiles/releases)
- [Commits](https://github.com/calvinmetcalf/copyfiles/compare/v2.2.0...v2.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:18:55 +02:00
dependabot-preview[bot]
3756848ba7 Bump uuid from 7.0.3 to 8.1.0 (#137)
Bumps [uuid](https://github.com/uuidjs/uuid) from 7.0.3 to 8.1.0.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v7.0.3...v8.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:15:03 +02:00
dependabot-preview[bot]
0ba606d5d8 Bump jest from 25.2.6 to 26.0.1 (#136)
Bumps [jest](https://github.com/facebook/jest) from 25.2.6 to 26.0.1.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v25.2.6...v26.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 23:14:03 +02:00
dependabot-preview[bot]
5f0bd963be Bump @babel/core from 7.9.0 to 7.10.2 (#135)
Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.9.0 to 7.10.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.10.2/packages/babel-core)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 22:44:31 +02:00
dependabot-preview[bot]
13bbaf1ad3 Bump node-sass from 4.13.1 to 4.14.1 (#134)
Bumps [node-sass](https://github.com/sass/node-sass) from 4.13.1 to 4.14.1.
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v4.13.1...v4.14.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 22:44:14 +02:00
Thomas Ritter
dc4918ad91 Bump version 2020-06-02 09:05:15 +02:00
Adrian Görisch
f763553739 Add support for Gitlab merge-request and issues (#133)
Co-authored-by: Adrian Görisch <adrian.goerisch@10m.de>
2020-04-27 10:57:20 +02:00
dependabot-preview[bot]
c412b1711c [Security] Bump minimist from 1.2.0 to 1.2.5 (#131)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.5. **This update includes security fixes.**
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-05 09:10:32 +02:00
dependabot-preview[bot]
342134a039 [Security] Bump acorn from 6.3.0 to 6.4.1 (#130)
Bumps [acorn](https://github.com/acornjs/acorn) from 6.3.0 to 6.4.1. **This update includes security fixes.**
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/6.3.0...6.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-05 09:10:19 +02:00
Manuel Bouza
97dd2651ce Merge branch 'dependabot/npm_and_yarn/babel/preset-env-7.9.0' 2020-04-03 08:54:57 +02:00
dependabot-preview[bot]
46610a1842 Bump @babel/preset-env from 7.7.7 to 7.9.0
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.7.7 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.7...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-03 08:53:27 +02:00
dependabot-preview[bot]
768cf84080 Bump @babel/plugin-proposal-nullish-coalescing-operator (#80)
Bumps [@babel/plugin-proposal-nullish-coalescing-operator](https://github.com/babel/babel) from 7.7.4 to 7.8.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.4...v7.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Manuel Bouza <manuel@bouza.ch>
2020-04-03 08:50:03 +02:00
dependabot-preview[bot]
0797e764f7 Bump @babel/plugin-proposal-decorators from 7.7.4 to 7.8.3 (#78)
Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel) from 7.7.4 to 7.8.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.4...v7.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:49:16 +02:00
dependabot-preview[bot]
3e9f739b17 Bump @babel/core from 7.7.7 to 7.9.0 (#115)
Bumps [@babel/core](https://github.com/babel/babel) from 7.7.7 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.7...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:49:01 +02:00
dependabot-preview[bot]
a4fc429476 Bump html-webpack-plugin from 3.2.0 to 4.0.4 (#126)
Bumps [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) from 3.2.0 to 4.0.4.
- [Release notes](https://github.com/jantimon/html-webpack-plugin/releases)
- [Changelog](https://github.com/jantimon/html-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jantimon/html-webpack-plugin/compare/v3.2.0...v4.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:47:35 +02:00
dependabot-preview[bot]
fdaad0660a Bump prettier from 1.19.1 to 2.0.2 (#109)
Bumps [prettier](https://github.com/prettier/prettier) from 1.19.1 to 2.0.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.19.1...2.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:47:20 +02:00
dependabot-preview[bot]
1d67790f47 Bump @babel/preset-react from 7.7.4 to 7.9.4 (#117)
Bumps [@babel/preset-react](https://github.com/babel/babel) from 7.7.4 to 7.9.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.4...v7.9.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:47:02 +02:00
dependabot-preview[bot]
4a2b00d984 Bump uuid from 3.3.3 to 7.0.3 (#114)
Bumps [uuid](https://github.com/uuidjs/uuid) from 3.3.3 to 7.0.3.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v3.3.3...v7.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:46:07 +02:00
dependabot-preview[bot]
455e3f26d2 Bump copyfiles from 2.1.1 to 2.2.0 (#74)
Bumps [copyfiles](https://github.com/calvinmetcalf/copyfiles) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/calvinmetcalf/copyfiles/releases)
- [Commits](https://github.com/calvinmetcalf/copyfiles/compare/v2.1.1...v2.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Co-authored-by: Manuel Bouza <manuel@bouza.ch>
2020-04-03 08:45:51 +02:00
dependabot-preview[bot]
ee80ade613 Bump css-loader from 3.4.0 to 3.4.2 (#88)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.4.0 to 3.4.2.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.4.0...v3.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:44:56 +02:00
dependabot-preview[bot]
84d758167f Bump webpack-cli from 3.3.10 to 3.3.11 (#94)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.3.10 to 3.3.11.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/next/CHANGELOG_v3.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v3.3.10...v3.3.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:44:30 +02:00
dependabot-preview[bot]
2e37b5b9ee Bump eslint-config-prettier from 6.9.0 to 6.10.1 (#110)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.9.0 to 6.10.1.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v6.9.0...v6.10.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:44:13 +02:00
dependabot-preview[bot]
63714129cc Bump svg-inline-loader from 0.8.0 to 0.8.2 (#101)
Bumps [svg-inline-loader](https://github.com/sairion/svg-inline-loader) from 0.8.0 to 0.8.2.
- [Release notes](https://github.com/sairion/svg-inline-loader/releases)
- [Changelog](https://github.com/webpack-contrib/svg-inline-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sairion/svg-inline-loader/compare/v0.8.0...v0.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:43:54 +02:00
dependabot-preview[bot]
cb5d81b65c Bump sass-loader from 8.0.0 to 8.0.2 (#89)
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 8.0.0 to 8.0.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v8.0.0...v8.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:43:38 +02:00
dependabot-preview[bot]
ff61028b88 Bump babel-eslint from 10.0.3 to 10.1.0 (#104)
Bumps [babel-eslint](https://github.com/babel/babel-eslint) from 10.0.3 to 10.1.0.
- [Release notes](https://github.com/babel/babel-eslint/releases)
- [Commits](https://github.com/babel/babel-eslint/compare/v10.0.3...v10.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:43:22 +02:00
dependabot-preview[bot]
6486419419 Bump query-string from 6.9.0 to 6.11.1 (#111)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.9.0 to 6.11.1.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.9.0...v6.11.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:43:06 +02:00
dependabot-preview[bot]
c58f6fba6a Bump @babel/polyfill from 7.7.0 to 7.8.7 (#107)
Bumps [@babel/polyfill](https://github.com/babel/babel) from 7.7.0 to 7.8.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.0...v7.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:42:45 +02:00
dependabot-preview[bot]
d6316032db Bump @babel/plugin-proposal-optional-chaining from 7.7.5 to 7.9.0 (#108)
Bumps [@babel/plugin-proposal-optional-chaining](https://github.com/babel/babel) from 7.7.5 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.5...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:42:18 +02:00
dependabot-preview[bot]
b283288337 Bump react-select from 3.0.8 to 3.1.0 (#112)
Bumps [react-select](https://github.com/JedWatson/react-select) from 3.0.8 to 3.1.0.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/.sweet-changelogs.js)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@3.0.8...react-select@3.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:42:00 +02:00
dependabot-preview[bot]
0b4896d613 Bump @babel/plugin-proposal-class-properties from 7.7.4 to 7.8.3 (#73)
Bumps [@babel/plugin-proposal-class-properties](https://github.com/babel/babel) from 7.7.4 to 7.8.3.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.4...v7.8.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:41:37 +02:00
dependabot-preview[bot]
ca07be7856 Bump node-sass from 4.13.0 to 4.13.1 (#72)
Bumps [node-sass](https://github.com/sass/node-sass) from 4.13.0 to 4.13.1.
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v4.13.0...v4.13.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:41:21 +02:00
dependabot-preview[bot]
8b20feb255 Bump file-loader from 5.0.2 to 6.0.0 (#113)
Bumps [file-loader](https://github.com/webpack-contrib/file-loader) from 5.0.2 to 6.0.0.
- [Release notes](https://github.com/webpack-contrib/file-loader/releases)
- [Changelog](https://github.com/webpack-contrib/file-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/file-loader/compare/v5.0.2...v6.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:40:50 +02:00
dependabot-preview[bot]
213c21d19c Bump webpack from 4.41.5 to 4.42.1 (#116)
Bumps [webpack](https://github.com/webpack/webpack) from 4.41.5 to 4.42.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.41.5...v4.42.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:39:36 +02:00
dependabot-preview[bot]
cb490a391a Bump jest from 24.9.0 to 25.2.6 (#127)
Bumps [jest](https://github.com/facebook/jest) from 24.9.0 to 25.2.6.
- [Release notes](https://github.com/facebook/jest/releases)
- [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/jest/compare/v24.9.0...v25.2.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:39:04 +02:00
dependabot-preview[bot]
abc63d4361 Bump mobx-react from 6.1.4 to 6.1.8 (#129)
Bumps [mobx-react](https://github.com/mobxjs/mobx-react) from 6.1.4 to 6.1.8.
- [Release notes](https://github.com/mobxjs/mobx-react/releases)
- [Changelog](https://github.com/mobxjs/mobx-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx-react/compare/6.1.4...6.1.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:38:26 +02:00
dependabot-preview[bot]
7e64ed6599 Bump eslint-plugin-jest from 23.2.0 to 23.8.2 (#120)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 23.2.0 to 23.8.2.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v23.2.0...v23.8.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:38:11 +02:00
dependabot-preview[bot]
c6e14d77d5 Bump style-loader from 1.1.2 to 1.1.3 (#79)
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v1.1.2...v1.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:37:55 +02:00
dependabot-preview[bot]
67640613e2 Bump react-dom from 16.12.0 to 16.13.1 (#128)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 16.12.0 to 16.13.1.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v16.13.1/packages/react-dom)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:35:56 +02:00
dependabot-preview[bot]
5334358c95 Bump mobx from 5.15.1 to 5.15.4 (#84)
Bumps [mobx](https://github.com/mobxjs/mobx) from 5.15.1 to 5.15.4.
- [Release notes](https://github.com/mobxjs/mobx/releases)
- [Changelog](https://github.com/mobxjs/mobx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx/compare/5.15.1...5.15.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:35:40 +02:00
dependabot-preview[bot]
43e700cbf9 Bump @babel/preset-env from 7.7.7 to 7.9.0
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.7.7 to 7.9.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.7...v7.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-04-03 06:29:49 +00:00
dependabot-preview[bot]
4da136e0d7 Bump axios from 0.19.0 to 0.19.2 (#82)
Bumps [axios](https://github.com/axios/axios) from 0.19.0 to 0.19.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.0...v0.19.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:29:22 +02:00
dependabot-preview[bot]
72828a6e9d Bump react from 16.12.0 to 16.13.1 (#121)
Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) from 16.12.0 to 16.13.1.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v16.13.1/packages/react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:28:47 +02:00
dependabot-preview[bot]
c8fc9f9af9 Bump eslint-plugin-react from 7.17.0 to 7.19.0 (#122)
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.17.0 to 7.19.0.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.17.0...v7.19.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:28:06 +02:00
dependabot-preview[bot]
da98ffcb6f Bump babel-loader from 8.0.6 to 8.1.0 (#124)
Bumps [babel-loader](https://github.com/babel/babel-loader) from 8.0.6 to 8.1.0.
- [Release notes](https://github.com/babel/babel-loader/releases)
- [Changelog](https://github.com/babel/babel-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel-loader/compare/v8.0.6...v8.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:27:44 +02:00
dependabot-preview[bot]
2940776284 Bump date-fns from 2.8.1 to 2.11.1 (#125)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.8.1 to 2.11.1.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.8.1...v2.11.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-04-03 08:27:30 +02:00
Manuel Bouza
33dd3299d9 Asana: read task title from single task pane 2020-01-09 19:20:06 +01:00
dependabot-preview[bot]
58bc2ff7fa Bump eslint-config-prettier from 6.3.0 to 6.9.0 (#61)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 6.3.0 to 6.9.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v6.3.0...v6.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:07:56 +01:00
dependabot-preview[bot]
41650f0eab Bump @babel/preset-env from 7.7.6 to 7.7.7 (#62)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.7.6 to 7.7.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.7.6...v7.7.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:07:43 +01:00
dependabot-preview[bot]
0c7a32ac3e Bump webpack from 4.41.4 to 4.41.5 (#63)
Bumps [webpack](https://github.com/webpack/webpack) from 4.41.4 to 4.41.5.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.41.4...v4.41.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:07:28 +01:00
dependabot-preview[bot]
96f5d69028 Bump date-fns from 2.4.1 to 2.8.1 (#64)
Bumps [date-fns](https://github.com/date-fns/date-fns) from 2.4.1 to 2.8.1.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Changelog](https://github.com/date-fns/date-fns/blob/master/CHANGELOG.md)
- [Commits](https://github.com/date-fns/date-fns/compare/v2.4.1...v2.8.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:07:10 +01:00
dependabot-preview[bot]
78a102a6e6 Bump @babel/core from 7.6.2 to 7.7.7 (#65)
Bumps [@babel/core](https://github.com/babel/babel) from 7.6.2 to 7.7.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.2...v7.7.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:06:34 +01:00
dependabot-preview[bot]
400e251d88 Bump mini-css-extract-plugin from 0.8.0 to 0.9.0 (#66)
Bumps [mini-css-extract-plugin](https://github.com/webpack-contrib/mini-css-extract-plugin) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/webpack-contrib/mini-css-extract-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/mini-css-extract-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/mini-css-extract-plugin/compare/v0.8.0...v0.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:06:06 +01:00
dependabot-preview[bot]
f92db975fe Bump style-loader from 1.0.1 to 1.1.2 (#67)
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 1.0.1 to 1.1.2.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v1.0.1...v1.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:05:50 +01:00
dependabot-preview[bot]
1abfdf0e33 Bump eslint-plugin-jest from 23.1.1 to 23.2.0 (#68)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 23.1.1 to 23.2.0.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v23.1.1...v23.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-08 09:05:26 +01:00
Manuel Bouza
f787f88337 Update placeholder text and color 2019-12-24 11:42:43 +01:00
dependabot-preview[bot]
77653eff13 Bump eslint-plugin-react from 7.15.1 to 7.17.0 (#48)
Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.15.1 to 7.17.0.
- [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases)
- [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.15.1...v7.17.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:45:28 +01:00
dependabot-preview[bot]
6a1725e655 Bump eslint from 6.5.1 to 6.8.0 (#53)
Bumps [eslint](https://github.com/eslint/eslint) from 6.5.1 to 6.8.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v6.5.1...v6.8.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:45:12 +01:00
dependabot-preview[bot]
a7e3199eb6 Bump @babel/polyfill from 7.6.0 to 7.7.0 (#52)
Bumps [@babel/polyfill](https://github.com/babel/babel) from 7.6.0 to 7.7.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.0...v7.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:44:52 +01:00
dependabot-preview[bot]
4f5531548d Bump prettier from 1.18.2 to 1.19.1 (#51)
Bumps [prettier](https://github.com/prettier/prettier) from 1.18.2 to 1.19.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/1.18.2...1.19.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:44:37 +01:00
dependabot-preview[bot]
df70dbe047 Bump react-dom from 16.10.1 to 16.12.0 (#50)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 16.10.1 to 16.12.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v16.12.0/packages/react-dom)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:44:20 +01:00
dependabot-preview[bot]
51c9730d1a Bump query-string from 6.8.3 to 6.9.0 (#47)
Bumps [query-string](https://github.com/sindresorhus/query-string) from 6.8.3 to 6.9.0.
- [Release notes](https://github.com/sindresorhus/query-string/releases)
- [Commits](https://github.com/sindresorhus/query-string/compare/v6.8.3...v6.9.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:43:55 +01:00
dependabot-preview[bot]
4b29fe3aaa Bump copy-webpack-plugin from 5.0.5 to 5.1.1 (#46)
Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 5.0.5 to 5.1.1.
- [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v5.0.5...v5.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:43:40 +01:00
dependabot-preview[bot]
ac4dcb259a Bump eslint-plugin-jest from 22.17.0 to 23.1.1 (#45)
Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 22.17.0 to 23.1.1.
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v22.17.0...v23.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:43:28 +01:00
dependabot-preview[bot]
88f7f04fa8 Bump babel-plugin-module-resolver from 3.2.0 to 4.0.0 (#44)
Bumps [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver) from 3.2.0 to 4.0.0.
- [Release notes](https://github.com/tleunen/babel-plugin-module-resolver/releases)
- [Changelog](https://github.com/tleunen/babel-plugin-module-resolver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tleunen/babel-plugin-module-resolver/compare/v3.2.0...v4.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:43:15 +01:00
dependabot-preview[bot]
c310f48ae9 Bump @babel/preset-react from 7.0.0 to 7.7.4 (#43)
Bumps [@babel/preset-react](https://github.com/babel/babel) from 7.0.0 to 7.7.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.0.0...v7.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:42:55 +01:00
dependabot-preview[bot]
f061b81fc8 Bump @babel/plugin-proposal-decorators from 7.6.0 to 7.7.4 (#42)
Bumps [@babel/plugin-proposal-decorators](https://github.com/babel/babel) from 7.6.0 to 7.7.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.0...v7.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:42:20 +01:00
dependabot-preview[bot]
3387c00d07 Bump @babel/plugin-proposal-optional-chaining from 7.6.0 to 7.7.5 (#41)
Bumps [@babel/plugin-proposal-optional-chaining](https://github.com/babel/babel) from 7.6.0 to 7.7.5.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.0...v7.7.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:41:48 +01:00
dependabot-preview[bot]
cfb71d2877 Bump webpack from 4.41.2 to 4.41.4 (#54)
Bumps [webpack](https://github.com/webpack/webpack) from 4.41.2 to 4.41.4.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.41.2...v4.41.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: Manuel Bouza <manuel@bouza.ch>
2019-12-23 15:41:14 +01:00
dependabot-preview[bot]
1ed1d29c99 Bump node-sass from 4.12.0 to 4.13.0 (#55)
Bumps [node-sass](https://github.com/sass/node-sass) from 4.12.0 to 4.13.0.
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v4.12.0...v4.13.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:37:14 +01:00
dependabot-preview[bot]
aa8abf381d Bump webpack-cli from 3.3.9 to 3.3.10 (#56)
Bumps [webpack-cli](https://github.com/webpack/webpack-cli) from 3.3.9 to 3.3.10.
- [Release notes](https://github.com/webpack/webpack-cli/releases)
- [Changelog](https://github.com/webpack/webpack-cli/blob/v3.3.10/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-cli/compare/v3.3.9...v3.3.10)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:36:52 +01:00
dependabot-preview[bot]
9e1a5a713c Bump mobx from 5.14.0 to 5.15.1 (#57)
Bumps [mobx](https://github.com/mobxjs/mobx) from 5.14.0 to 5.15.1.
- [Release notes](https://github.com/mobxjs/mobx/releases)
- [Changelog](https://github.com/mobxjs/mobx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx/compare/5.14.0...5.15.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:36:40 +01:00
dependabot-preview[bot]
f881e2fb3d Bump eslint-plugin-prettier from 3.1.1 to 3.1.2 (#58)
Bumps [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/prettier/eslint-plugin-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-plugin-prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-plugin-prettier/compare/v3.1.1...v3.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 15:36:18 +01:00
dependabot-preview[bot]
2a5f3db563 Bump mobx-react from 6.1.3 to 6.1.4 (#59)
Bumps [mobx-react](https://github.com/mobxjs/mobx-react) from 6.1.3 to 6.1.4.
- [Release notes](https://github.com/mobxjs/mobx-react/releases)
- [Changelog](https://github.com/mobxjs/mobx-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx-react/compare/6.1.3...6.1.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 11:08:27 +01:00
dependabot-preview[bot]
eb2422ba7a Bump css-loader from 3.3.0 to 3.4.0 (#60)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.3.0...v3.4.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-23 11:08:02 +01:00
dependabot-preview[bot]
a2f3c16aca Bump css-loader from 3.2.0 to 3.3.0 (#39)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.2.0...v3.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:52:05 +01:00
dependabot-preview[bot]
36d5bf2a7e Bump @babel/preset-env from 7.6.2 to 7.7.6 (#40)
Bumps [@babel/preset-env](https://github.com/babel/babel) from 7.6.2 to 7.7.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.6.2...v7.7.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:51:43 +01:00
dependabot-preview[bot]
9ecc561d4a Bump @babel/plugin-proposal-nullish-coalescing-operator (#30)
Bumps [@babel/plugin-proposal-nullish-coalescing-operator](https://github.com/babel/babel) from 7.4.4 to 7.7.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.4.4...v7.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:48:11 +01:00
dependabot-preview[bot]
df1a21242b Bump react from 16.10.1 to 16.12.0 (#32)
Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) from 16.10.1 to 16.12.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v16.12.0/packages/react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:47:58 +01:00
dependabot-preview[bot]
e46263657e Bump file-loader from 4.2.0 to 5.0.2 (#31)
Bumps [file-loader](https://github.com/webpack-contrib/file-loader) from 4.2.0 to 5.0.2.
- [Release notes](https://github.com/webpack-contrib/file-loader/releases)
- [Changelog](https://github.com/webpack-contrib/file-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/file-loader/compare/v4.2.0...v5.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:47:22 +01:00
dependabot-preview[bot]
0461b31c36 Bump style-loader from 1.0.0 to 1.0.1 (#28)
Bumps [style-loader](https://github.com/webpack-contrib/style-loader) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/webpack-contrib/style-loader/releases)
- [Changelog](https://github.com/webpack-contrib/style-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/style-loader/compare/v1.0.0...v1.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:46:54 +01:00
dependabot-preview[bot]
ad772b7900 Bump dotenv from 8.1.0 to 8.2.0 (#29)
Bumps [dotenv](https://github.com/motdotla/dotenv) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/motdotla/dotenv/releases)
- [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md)
- [Commits](https://github.com/motdotla/dotenv/compare/v8.1.0...v8.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:46:39 +01:00
dependabot-preview[bot]
fd4911ec72 Bump @babel/plugin-proposal-class-properties from 7.5.5 to 7.7.4 (#34)
Bumps [@babel/plugin-proposal-class-properties](https://github.com/babel/babel) from 7.5.5 to 7.7.4.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/compare/v7.5.5...v7.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:46:07 +01:00
dependabot-preview[bot]
3b3816067e Bump css-loader from 3.2.0 to 3.2.1 (#35)
Bumps [css-loader](https://github.com/webpack-contrib/css-loader) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/webpack-contrib/css-loader/releases)
- [Changelog](https://github.com/webpack-contrib/css-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/css-loader/compare/v3.2.0...v3.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:45:04 +01:00
dependabot-preview[bot]
31eb6094e8 Bump webpack from 4.41.0 to 4.41.2 (#36)
Bumps [webpack](https://github.com/webpack/webpack) from 4.41.0 to 4.41.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.41.0...v4.41.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:44:34 +01:00
dependabot-preview[bot]
0e29686b2d Bump copy-webpack-plugin from 5.0.4 to 5.0.5 (#37)
Bumps [copy-webpack-plugin](https://github.com/webpack-contrib/copy-webpack-plugin) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/webpack-contrib/copy-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/copy-webpack-plugin/compare/v5.0.4...v5.0.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-12-11 08:44:15 +01:00
Manuel Bouza
1447fd6116 fix/trello-and-asana (#38)
* Attach click event listener to window

* Update development server port

* Asana: Read project identifier from topbar page header

* Pump version and update changelog
2019-12-10 21:00:22 +01:00
Manuel Bouza
1dcda94483 feature/project-identifier-in-trello-board-title (#27)
* Read project identifier from Trello board title

* Refactor

* Update changelog and pump version
2019-10-25 11:13:50 +02:00
manubo
7e249202e5 Remove focused button border in firefox 2019-10-17 18:51:53 +02:00
Manuel Bouza
12c8b8e3eb fix/focus-timer-view (#26)
* Auto focus button in timer view

* Revert find projects by identifier without alphanumerical characters

* Update changelog
2019-10-17 11:47:05 +02:00
manubo
76d57729f4 Update changelog 2019-10-10 15:18:59 +02:00
Manuel Bouza
72626a6c42 qw/timer (#23)
* Rename logo and add 32x32 version

* Set timer icon if a timer is running

* Do not query activities on initialization

* Show timer in bubble if timed activity exists

* Pass timed activity to App

* Code cleanup

* Show timer view and stop timer

* Make hours optional

* Use booked seconds instead of hours

* Add type submit to form button

* Define colors as sass variables⎄

* Style timer view

* Show start timer submit label

* Update view layouts and content

* Update version and changelog

* Dyanically set iframe height

* Reduce h1 font size

* Add svg webpack loader

* Parse empty string (TimeInputParser)

* Forward ref in Popup component

* Start time on current day only, format buttons

* Improve styling

* Set standard height as iframe default height, validate form

* Upgrade packages to supress react warning

* Show activity form in popup after timer was stoped

* Use stop-watch icon in timer view

* Fix empty description

* Close TimerView if timer stopped for current service

* Style timerview

* Improve timer view styling

* qw/setting-time-tracking-hh-mm (#24)

* Format duration depending on settingTimeTrackingHHMM

* Fix formatDuation without second argument

* Fix time format after updating bubble

* Add tests for formatDuration
2019-10-10 14:57:01 +02:00
Manuel Bouza
7023b4b482 feature/strip-identifier (#25)
* Ignore non-alphanumeric chars finding project by identifier

* Add babel plugin nullish coallescing operator

* Refactor

* Add projectId to remote services
2019-10-10 14:38:28 +02:00
manubo
53be150788 Update .env.example 2019-09-20 10:28:48 +02:00
manubo
6980df91d7 Update changelog and pump version 2019-09-20 10:18:43 +02:00
manubo
83faab7fd4 Fix deletion of manifest.json after every build 2019-09-19 09:19:48 +02:00
Manuel Bouza
8a72f242f9 Preselect default task (#22) 2019-09-18 12:54:54 +02:00
Manuel Bouza
5e62e16751 Upgrade packages (#21) 2019-09-18 05:53:17 +02:00
Manuel Bouza
986fc64998 Make description of activity optional (#20) 2019-06-26 10:13:59 +02:00
Manuel Bouza
8b2e21c3cf Update eslint cofig 2019-06-26 09:27:31 +02:00
Manuel Bouza
fd04d6bf6c Remove bugsnag (#19) 2019-05-24 13:34:15 +02:00
Manuel Bouza
23c9af90b3 Support EU-hosted wrike.com 2019-05-03 08:47:19 +02:00
Manuel Bouza
a9d1726707 feature/wrike (#17)
* Fix code styles

* Add support for WRIKE

* Add tests
2019-04-26 13:05:14 +02:00
Manuel Bouza
25773cc661 fix/annoying-closing-of-trello-card (#16)
* Fix unexpected closing of trello card when clicking on bubble

* Have latest change at the top in changelog
2019-04-24 15:21:36 +02:00
Manuel Bouza
cd9f94423c Fix path of remoteServices in README 2019-04-18 18:42:48 +02:00
Manuel Bouza
505e3a32ab feature/show-customer-in-project-select (#15)
* Fix code styles

* Show customer name in select control if props.data.customerName is defined.

* Pump version and update changelock
2019-04-12 05:40:44 +02:00
Manuel Bouza
81c7d0ca5d fix/asana-refactor (#14)
* Add packages eslint-plugin-prettier and eslint-config-prettier

These packages add better code formatting support in VS Code

* Fix code styles

* Update projectId query selector for asana service

* Extract constants to own computed getter methods

* Update changelog, bump version
2019-04-10 07:45:05 +02:00
Manuel Bouza
173a1d8e62 Update changelog 2019-04-06 12:44:07 +02:00
Manuel Bouza
4bebae9abe fix/hours-in-brackets-unbillable (#13)
* Set billable to false if hours are entere in brackets

* Fix code style

* Add TODO comment for refactoring
2019-04-06 12:42:54 +02:00
Manuel Bouza
97cea77b7a fix/missing-bugsnag-key-error (#12)
* Only instantiate bugsnag client if BUGSNAG_API_KEY is defined

* Pump version, update CHANGELOG
2019-04-04 20:15:17 +02:00
Manuel Bouza
e57caa8563 feature/meistertask-project-from-title (#11)
* Add .prettierrc

* Read project identifier from card title in the meistertask service
2019-04-04 16:08:08 +02:00
Manuel Bouza
e582f99a94 Add .prettierrc 2019-04-04 09:51:24 +02:00
Manuel Bouza
16d41fc2d4 Update changelog 2019-04-01 17:43:31 +02:00
Manuel Bouza
1d2e336e3d Fix project preselection when project identifier is not defined (#9) 2019-04-01 17:41:54 +02:00
59 changed files with 6293 additions and 4986 deletions

View File

@@ -3,6 +3,7 @@
"plugins": [ "plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }],
["@babel/plugin-proposal-optional-chaining"] "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator"
] ]
} }

View File

@@ -1,2 +1 @@
BUGSNAG_API_KEY=
APPLICATION_ID= APPLICATION_ID=

View File

@@ -1,5 +1,5 @@
{ {
"extends": ["eslint:recommended", "plugin:react/recommended"], "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"],
"env": { "env": {
"browser": true, "browser": true,
"commonjs": true, "commonjs": true,
@@ -22,6 +22,11 @@
}, },
"sourceType": "module" "sourceType": "module"
}, },
"settings": {
"react": {
"version": "detect"
}
},
"rules": { "rules": {
"strict": 0, "strict": 0,
"semi": ["error", "never"], "semi": ["error", "never"],
@@ -68,6 +73,7 @@
"userStore" "userStore"
] ]
} }
] ],
"prettier/prettier": "error"
} }
} }

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/ node_modules/
build/ build/
.env .env
/.idea

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
14

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"semi": false,
"trailingComma": "all",
"printWidth": 100
}

View File

@@ -1,42 +1,179 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [1.5.2] - 2020-09-10
- 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 ### Fixed
- Set default value of subdomain to `__unset__` to prevent network error if it is empty
## [1.0.20] - 2019-03-26 - Remember last tracked project and task on card
## [1.5.1] - 2020-08-04
### Added ### Added
- Add support for tags in description
## [1.0.21] - 2019-03-26 - Add support for Monday
### Changed
- Update README with example configuration and instructions for local installation ## [1.5.0] - 2020-06-15
### Added
- Allow to override hosts for Jira, Youtrack and Gitlab in options (implemented by yay-digital.de)
## [1.4.0] - 2020-04-27
### Added
- Add support for Gitlab merge requests and issues
## [1.3.4] - 2020-01-09
### Added
- Asana: read task title from single task pane
## [1.3.3] - 2019-10-17
### Fixed
- Fix an issue on Trello where the card closes when clicking the MOCO bubble
- Asana: read project title from page heading
## [1.3.2] - 2019-10-24
### Added
- Read project identifier from Trello board title
## [1.3.1] - 2019-10-17
### Fixed
- Set propper focus on timer view
### Removed
- Find projects by identifier without alphanumerical characters
## [1.3.0] - 2019-10-11
### Added
- Start a new timer or stop a running timer
- Format time as set in time tracking
- Add support for project identifier in Github Issue, Trello, Wunderlist, Youtrack
- Find projects by identifier without alphanumerical characters
## [1.2.4] - 2019-09-20
## [1.0.22] - 2019-03-28
### Changed ### Changed
- Change the default value of subdomain to `unset` to have a well-formed URL.
- Preselect last used task per project
## [1.2.3] - 2019-06-26
### Changed
- Description of activities are optional
## [1.2.2] - 2019-05-24
### Removed
- Bugsnag client
## [1.2.1] - 2019-05-03
### Fixed
- Support EU-hosted wrike.com (app-eu.wrike.com)
## [1.2.0] - 2019-04-26
### Added
- Add support for wrike.com
## [1.1.5] - 2019-04-24
### Fixed
- Unexpected closing of Trello card when clicking on Bubble
## [1.1.4] - 2019-04-11
### Added
- Show customer name in the project select box
## [1.1.3] - 2019-04-10
### Fixed
- Read projected identifier in Asana's "My tasks"-view
## [1.1.2] - 2019-04-06
### Fixed
- Allow production build without BUGSNAG_API_KEY
- Hours entered in brackets must be non-billable
### Changed
- Read project identifier also from card title in the meistertask service
## [1.1.1] - 2019-04-01
### Fixed
- Discard projects with undefined identifier for preselecting
## [1.1.0] - 2019-03-30 ## [1.1.0] - 2019-03-30
### Added ### Added
- Read project identifier from Asana project title - Read project identifier from Asana project title
- Add support for meistertask.com - Add support for meistertask.com
### Fixed ### Fixed
- Link logo in modal to MOCO activities page - Link logo in modal to MOCO activities page
- Set full url on service, including query params - Set full url on service, including query params
## [1.0.22] - 2019-03-28
### Changed
- Change the default value of subdomain to `unset` to have a well-formed URL.
## [1.0.21] - 2019-03-26
### Changed
- Update README with example configuration and instructions for local installation
## [1.0.20] - 2019-03-26
### Added
- Add support for tags in description
## [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.18] - 2019-03-23
### Added
- First release of version 1

View File

@@ -2,33 +2,34 @@
## Development ## 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`)
* load extension into browser: - load extension into browser:
* Chrome: visit `chrome://extensions` and load unpacked extension from `build/chrome` - Chrome: visit `chrome://extensions` and load unpacked extension from `build/chrome`
* Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox` - Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox/manifest.json`
* reload browser extension after change - the browser should automatically pick up your changes but from time to time it may be useful to reload the extension
## Production Build ## Production Build
* bump version in `package.json` - bump version in `package.json`
* run `yarn build` - Update `CHANGELOG.md`
* The Chrome and Firefox extensions are available as ZIP-files in `build/chrome` and `build/firefox` respectively - run `yarn build`
- The Chrome and Firefox extensions are available as ZIP-files in `build/chrome` and `build/firefox` respectively
## Install Local Builds ## Install Local Builds
### Chrome ### Chrome
1. `yarn build:chrome` - `yarn build:chrome`
1. Visit `chrome://extensions` - Visit `chrome://extensions`
2. Enable `Developer mode` - Enable `Developer mode`
3. `Load unpacked` and select the `build/chrome` folder. - `Load unpacked` and select the `build/chrome` folder.
### Firefox ### Firefox
1. `yarn build:firefox` - `yarn build:firefox`
1. Visit `about:debugging` - Visit `about:debugging`
2. Click on `Load temporary Add-on` and select the ZIP-file in `build/firefox` - 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). 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).
@@ -38,7 +39,7 @@ You can keep the extension settings between builds by providing a stable `APPLIC
## Remote Service Configuration ## Remote Service Configuration
Remote services are configured in `src/remoteServices.js`. Remote services are configured in `src/js/remoteServices.js`.
A remote service is configured as follows: A remote service is configured as follows:
@@ -46,9 +47,10 @@ A remote service is configured as follows:
{ {
service_key: { service_key: {
name: "service_name", name: "service_name",
host: "https://:subdomain.example.com",
urlPatterns: [ urlPatterns: [
"https:\\://:subdomain.example.com/card/:id", ":host:/card/:id",
[/^https:\/\/(\w+).example.com\/card\/(\d+), ["subdomain", "id"]], [/^:host:\/card\/(\d+), ["subdomain", "id"]],
], ],
queryParams: { queryParams: {
projectId: "currentList" projectId: "currentList"
@@ -63,16 +65,17 @@ A remote service is configured as follows:
projectId: (document, service, { subdomain, id, projectId }) => { projectId: (document, service, { subdomain, id, projectId }) => {
return projectId return projectId
}, },
position: { left: "50%", transform: "translate(-50%)" } position: { left: "50%", transform: "translate(-50%)" },
allowHostOverride: false,
} }
} }
``` ```
| Parameter | Description | | Parameter | Description |
|--------------|:-------------| | ------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| service_key | `string` &mdash; Unique identifier for the service | | service_key | `string` &mdash; Unique identifier for the service |
| service_name | `string` &mdash; Must be one of the registered services `trello`, `jira`, `asana`, `wunderlist`, `github` or `youtrack` | | service_name | `string` &mdash; Must be one of the registered services `trello`, `jira`, `asana`, `wunderlist`, `github` or `youtrack` |
| urlPatterns | `string` \| `RegEx` &mdash; A valid URL pattern or regular expression, as described in the [url-pattern](https://www.npmjs.com/package/url-pattern) package. | | urlPatterns | `string` \| `RegEx` &mdash; A valid URL pattern or regular expression, as described in the [url-pattern](https://www.npmjs.com/package/url-pattern) package. `:host:` will be replaced with the configured host before applying the pattern (can be configured in the settings if `allowHostOverride` is true. |
| queryParams | `Object` &mdash; 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`. | | queryParams | `Object` &mdash; 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` &mdash; 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. | | description | `undefined` \| `string` \| `function` &mdash; 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` &mdash; 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`. | | projectId | `undefined` \| `string` \| `function` &mdash; 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`. |

View File

@@ -1,7 +1,7 @@
{ {
"name": "moco-browser-extensions", "name": "moco-browser-extensions",
"description": "Browser plugin for MOCO", "description": "Browser plugin for MOCO",
"version": "1.1.0", "version": "1.5.2",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"start": "yarn start:chrome", "start": "yarn start:chrome",
@@ -16,53 +16,54 @@
"test:watch": "node_modules/.bin/jest --watch" "test:watch": "node_modules/.bin/jest --watch"
}, },
"dependencies": { "dependencies": {
"@babel/polyfill": "^7.4.0", "@babel/polyfill": "^7.10.1",
"@bugsnag/js": "^5.2.0", "axios": "^0.19.2",
"@bugsnag/plugin-react": "^5.2.0",
"axios": "^0.18.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"date-fns": "^1.30.1", "date-fns": "^2.15.0",
"dotenv": "^7.0.0", "dotenv": "^8.2.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"mobx": "^5.5.0", "mobx": "^5.15.4",
"mobx-react": "^5.2.8", "mobx-react": "^6.2.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"query-string": "^6.2.0", "query-string": "^6.12.1",
"react": "^16.8.0", "react": "^16.13.1",
"react-dom": "^16.8.0", "react-dom": "^16.13.1",
"react-select": "^2.3.0", "react-select": "^3.1.0",
"react-spring": "^8.0.7", "react-spring": "^8.0.7",
"url-pattern": "^1.0.3" "url-pattern": "^1.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.2.2", "@babel/core": "^7.10.2",
"@babel/plugin-proposal-class-properties": "^7.2.2", "@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-decorators": "^7.2.2", "@babel/plugin-proposal-decorators": "^7.10.1",
"@babel/plugin-proposal-optional-chaining": "^7.2.0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
"@babel/preset-env": "^7.2.2", "@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-env": "^7.11.0",
"babel-eslint": "^10.0.1", "@babel/preset-react": "^7.10.1",
"babel-loader": "^8.0.4", "babel-eslint": "^10.1.0",
"babel-plugin-module-resolver": "^3.1.1", "babel-loader": "^8.1.0",
"clean-webpack-plugin": "^1.0.1", "babel-plugin-module-resolver": "^4.0.0",
"copy-webpack-plugin": "^4.6.0", "clean-webpack-plugin": "^3.0.0",
"copyfiles": "^2.1.0", "copy-webpack-plugin": "^6.0.2",
"css-loader": "^2.1.0", "copyfiles": "^2.3.0",
"eslint": "^5.7.0", "css-loader": "^4.2.0",
"eslint-plugin-jest": "^22.2.2", "eslint": "7.6.0",
"eslint-plugin-react": "^7.11.1", "eslint-config-prettier": "^6.11.0",
"file-loader": "^3.0.1", "eslint-plugin-jest": "^23.13.2",
"html-webpack-plugin": "^3.2.0", "eslint-plugin-prettier": "^3.1.3",
"jest": "^24.1.0", "eslint-plugin-react": "^7.20.0",
"mini-css-extract-plugin": "^0.5.0", "file-loader": "^6.0.0",
"node-sass": "^4.11.0", "html-webpack-plugin": "^4.3.0",
"prettier": "^1.16.4", "jest": "^26.0.1",
"sass-loader": "^7.1.0", "mini-css-extract-plugin": "^0.11.2",
"style-loader": "^0.23.1", "node-sass": "^4.14.1",
"uuid": "^3.3.2", "prettier": "^2.0.5",
"webpack": "^4.15.0", "sass-loader": "^9.0.2",
"webpack-bugsnag-plugins": "^1.3.0", "style-loader": "^1.2.1",
"webpack-cli": "^3.0.8", "svg-inline-loader": "^0.8.2",
"uuid": "^8.3.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11",
"zip-webpack-plugin": "^3.0.0" "zip-webpack-plugin": "^3.0.0"
} }
} }

View File

@@ -7,17 +7,25 @@ button.moco-bx-btn {
white-space: nowrap; white-space: nowrap;
color: white; color: white;
background-image: none; background-image: none;
background-color: #7dc332; background-color: $green;
border-color: #7dc332; border-color: $green;
border-radius: 0; border-radius: 0;
border-style: solid; border-style: solid;
box-shadow: none; box-shadow: none;
font-size: 100%; font-size: 100%;
cursor: pointer; cursor: pointer;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
&:hover:not(:disabled) { &:hover:not(:disabled) {
background-color: #639a28; background-color: $green-dark;
border-color: #639a28; border-color: $green-dark;
} }
&:disabled { &:disabled {
@@ -40,3 +48,17 @@ button.moco-bx-btn {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
} }
.moco-bx-btn__secondary {
color: $blue;
border: none;
background: none;
text-decoration: none;
&:hover {
cursor: pointer;
color: $blue;
border: none;
background-color: transparent;
}
}

View File

@@ -1,3 +1,4 @@
@import "variables";
@import "button"; @import "button";
input { input {
@@ -15,7 +16,8 @@ input {
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
input, textarea { input,
textarea {
padding: 6px 12px; padding: 6px 12px;
background-color: white; background-color: white;
border-color: #cccccc; border-color: #cccccc;
@@ -24,6 +26,10 @@ input {
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
min-height: 20px; min-height: 20px;
&::placeholder {
color: #ccc;
}
} }
.text-muted { .text-muted {
@@ -31,13 +37,14 @@ input {
} }
&.has-error { &.has-error {
input, textarea { input,
border-color: #FB3A2F; textarea {
border-color: $red;
} }
} }
.form-error { .form-error {
color: #FB3A2F; color: $red;
} }
.input-group { .input-group {
@@ -56,8 +63,13 @@ input {
text-align: center; text-align: center;
background-color: #eeeeee; background-color: #eeeeee;
border: 1px solid #cccccc; border: 1px solid #cccccc;
border-left: none;
line-height: 18px; line-height: 18px;
&--right {
border-left: none;
}
&--left {
border-right: none;
}
} }
} }
} }
@@ -71,8 +83,8 @@ input[name="hours"] {
outline: 0 !important; outline: 0 !important;
&:focus { &:focus {
border: 1px solid #38b5eb; border: 1px solid $blue;
box-shadow: 0 0 0 1px #38b5eb; box-shadow: 0 0 0 1px $blue;
} }
} }
@@ -84,8 +96,7 @@ textarea[name="description"] {
outline: 0 !important; outline: 0 !important;
&:focus { &:focus {
border: 1px solid #38b5eb; border: 1px solid $blue;
box-shadow: 0 0 0 1px #38b5eb; box-shadow: 0 0 0 1px $blue;
} }
} }

View File

@@ -1,5 +1,10 @@
$font-family: Arial, sans-serif; $font-family: Roboto, Arial, sans-serif;
$font-color: #191919; $font-color: #191919;
$popup-width: 420px; $popup-width: 420px;
$popup-height: 463px; $popup-height: 463px;
$green: #7dc332;
$green-dark: #639a28;
$blue: #38b5eb;
$red: #fb3a2f;
$gray-base: #a3a3a3;

View File

@@ -6,6 +6,10 @@
color: $font-color; color: $font-color;
pointer-events: all; pointer-events: all;
.text-red {
color: $red;
}
.moco-bx-bubble { .moco-bx-bubble {
box-sizing: content-box; box-sizing: content-box;
position: fixed; position: fixed;
@@ -14,8 +18,7 @@
width: 60px; width: 60px;
background-color: white; background-color: white;
border-radius: 50%; border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05), box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05), 2px 2px 15px 4px rgba(0, 0, 0, 0.05);
2px 2px 15px 4px rgba(0, 0, 0, 0.05);
padding: 5px; padding: 5px;
z-index: 9999; z-index: 9999;
@@ -69,6 +72,7 @@
height: 100%; height: 100%;
overflow: auto; overflow: auto;
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
z-index: 9999;
.moco-bx-popup-content { .moco-bx-popup-content {
background-color: white; background-color: white;

View File

@@ -11,6 +11,11 @@
.moco-bx-options { .moco-bx-options {
padding: 0rem 2rem 2rem; padding: 0rem 2rem 2rem;
a {
color: $blue;
text-decoration: none;
}
p { p {
margin: 0.5rem 0; margin: 0.5rem 0;
} }
@@ -20,6 +25,11 @@
margin: 1rem 0 2rem; margin: 1rem 0 2rem;
} }
h3 {
font-size: 1.1rem;
margin: 1rem 0 1rem;
}
label { label {
font-weight: normal; font-weight: normal;
margin-bottom: 5px; margin-bottom: 5px;
@@ -32,11 +42,21 @@
} }
.text-success { .text-success {
color: #7DC332; color: $green;
} }
.text-danger { .text-danger {
color: #FB3A2F; color: $red;
}
&__host-overrides {
margin-bottom: 1.5rem;
text-align: center;
font-weight: normal;
}
small {
font-size: 0.8rem;
} }
} }
} }

View File

@@ -1,6 +1,6 @@
@import "variables";
@import "form"; @import "form";
@import "spinner"; @import "spinner";
@import "variables";
html { html {
overflow: hidden; overflow: hidden;
@@ -14,33 +14,48 @@ html {
#moco-bx-root { #moco-bx-root {
min-width: 516px; min-width: 516px;
h1 {
font-size: 24px;
font-weight: normal;
line-height: 1.5;
margin-top: 1rem;
margin-bottom: 3rem;
}
h2 {
font-size: 20px;
font-weight: normal;
line-height: 1.5;
margin-top: 1rem;
margin-bottom: 3rem;
}
.text-red {
color: $red;
}
.text-secondary {
color: $gray-base;
}
.moco-bx-app-container { .moco-bx-app-container {
width: 324px; width: 324px;
padding: 3rem 6rem; padding: 3rem 6rem;
.moco-bx-logo__container { .moco-bx-logo__container {
display: flex;
justify-content: center;
margin-bottom: 3rem; margin-bottom: 3rem;
text-align: center; text-align: center;
img.moco-bx-logo { img.moco-bx-logo {
flex: 0 0 48px;
width: 48px; width: 48px;
height: 48px; height: 48px;
}
h1 {
line-height: 48px;
margin: 0;
} }
} }
.moco-bx-calendar { .moco-bx-calendar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 3rem; margin-bottom: 2rem;
.moco-bx-calendar__day { .moco-bx-calendar__day {
display: flex; display: flex;
@@ -66,12 +81,11 @@ html {
flex: 0 0 42px; flex: 0 0 42px;
color: white; color: white;
background-color: #eee; background-color: #eee;
} }
&.moco-bx-calendar__day--filled { &.moco-bx-calendar__day--filled {
.moco-bx-calendar__hours { .moco-bx-calendar__hours {
background-color: #7dc332; background-color: $green;
} }
} }
@@ -84,43 +98,73 @@ html {
&.moco-bx-calendar__day--active { &.moco-bx-calendar__day--active {
.moco-bx-calendar__hours { .moco-bx-calendar__hours {
background-color: #38b5eb; background-color: $blue;
} }
} }
} }
} }
.moco-bx-timer-view {
text-align: center;
margin-top: 3rem;
h2 {
margin-top: 2rem;
max-height: 90px;
overflow: hidden;
}
p {
margin-top: 1rem;
margin-bottom: 1rem;
line-height: 1.2rem;
}
span.moco-bx-single-line {
display: inline-block;
max-height: 19px;
overflow: hidden;
}
.timer {
margin-top: 2rem;
}
.btn-stop-timer {
margin-top: 1.5rem;
background-color: $red;
border: none;
border-radius: 50%;
width: 60px;
height: 60px;
}
}
} }
.moco-bx-error-container { .moco-bx-error-container {
font-size: 18px;
line-height: 1.5; line-height: 1.5;
width: 420px; width: 420px;
padding: 3rem; padding: 3rem;
text-align: center; text-align: center;
h1 {
font-size: 35px;
font-weight: normal;
margin-top: 0;
line-height: 1.3;
}
img { img {
width: auto; margin-top: 1.5rem;
max-width: 100%; margin-bottom: 2rem;
&.moco-bx-logo { &.moco-bx-logo {
width: 48px; width: 48px;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
}
ol { &.firefox-addons {
text-align: left; margin-top: 0;
width: auto;
}
} }
button { button {
margin-top: 1.5rem; margin-top: 1.5rem;
margin-bottom: 1.5rem;
} }
} }
} }

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="stopwatch" class="svg-inline--fa fa-stopwatch fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M393.3 141.3l17.5-17.5c4.7-4.7 4.7-12.3 0-17l-5.7-5.7c-4.7-4.7-12.3-4.7-17 0l-17.5 17.5c-35.8-31-81.5-50.9-131.7-54.2V32h25c6.6 0 12-5.4 12-12v-8c0-6.6-5.4-12-12-12h-80c-6.6 0-12 5.4-12 12v8c0 6.6 5.4 12 12 12h23v32.6C91.2 73.3 0 170 0 288c0 123.7 100.3 224 224 224s224-100.3 224-224c0-56.1-20.6-107.4-54.7-146.7zM224 480c-106.1 0-192-85.9-192-192S117.9 96 224 96s192 85.9 192 192-85.9 192-192 192zm4-128h-8c-6.6 0-12-5.4-12-12V172c0-6.6 5.4-12 12-12h8c6.6 0 12 5.4 12 12v168c0 6.6-5.4 12-12 12z"></path></svg>

After

Width:  |  Height:  |  Size: 733 B

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
src/images/moco-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -3,19 +3,15 @@ import { formatDate } from "utils"
const baseURL = subdomain => { const baseURL = subdomain => {
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
return `https://${encodeURIComponent( return `https://${encodeURIComponent(subdomain)}.mocoapp.com/api/browser_extensions`
subdomain
)}.mocoapp.com/api/browser_extensions`
} else { } else {
return `http://${encodeURIComponent( return `http://${encodeURIComponent(subdomain)}.mocoapp.localhost:3000/api/browser_extensions`
subdomain
)}.mocoapp.localhost:3001/api/browser_extensions`
} }
} }
export default class Client { export default class Client {
#client; #client
#apiKey; #apiKey
constructor({ subdomain, apiKey, version }) { constructor({ subdomain, apiKey, version }) {
this.#apiKey = apiKey this.#apiKey = apiKey
@@ -25,9 +21,9 @@ export default class Client {
headers: { headers: {
common: { common: {
"x-api-key": apiKey, "x-api-key": apiKey,
"x-extension-version": version "x-extension-version": version,
} },
} },
}) })
} }
@@ -35,29 +31,31 @@ export default class Client {
this.#client.post("session", { this.#client.post("session", {
api_key: this.#apiKey, api_key: this.#apiKey,
remote_service: service?.name, remote_service: service?.name,
remote_id: service?.id remote_id: service?.id,
}); })
projects = () => this.#client.get("projects"); projects = () => this.#client.get("projects")
schedules = (fromDate, toDate) => schedules = (fromDate, toDate) =>
this.#client.get("schedules", { this.#client.get("schedules", {
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` } params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
}); })
activities = (fromDate, toDate) => activities = (fromDate, toDate) =>
this.#client.get("activities", { this.#client.get("activities", {
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` } params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
}); })
bookedHours = service => { activitiesStatus = service => {
if (!service) { if (!service) {
return Promise.resolve({ data: { hours: 0 } }) return Promise.resolve({ data: { hours: 0 } })
} }
return this.#client.get("activities/tags", { return this.#client.get("activities/status", {
params: { selection: [service.id], remote_service: service.name } params: { remote_id: service.id, remote_service: service.name },
}) })
}; }
createActivity = activity => this.#client.post("activities", { activity }); createActivity = activity => this.#client.post("activities", { activity })
stopTimer = timedActivity => this.#client.get(`activities/${timedActivity.id}/stop_timer`)
} }

View File

@@ -1,20 +1,40 @@
import "@babel/polyfill" import "@babel/polyfill"
import ApiClient from "api/Client" import ApiClient from "api/Client"
import { import { isChrome, getCurrentTab, getSettings, isBrowserTab } from "utils/browser"
isChrome,
getCurrentTab,
getSettings,
isBrowserTab
} from "utils/browser"
import { BackgroundMessenger } from "utils/messaging" import { BackgroundMessenger } from "utils/messaging"
import { import { tabUpdated, settingsChanged, togglePopup, openPopup } from "utils/messageHandlers"
tabUpdated, import { isNil } from "lodash"
settingsChanged,
togglePopup
} from "utils/messageHandlers"
const messenger = new BackgroundMessenger() const messenger = new BackgroundMessenger()
function timerStoppedForCurrentService(service, timedActivity) {
return timedActivity.service_id && timedActivity.service_id === service?.id
}
function resetBubble({ tab, settings, service, timedActivity }) {
const apiClient = new ApiClient(settings)
apiClient
.activitiesStatus(service)
.then(({ data }) => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedSeconds: data.seconds,
timedActivity: data.timed_activity,
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
service,
},
})
})
.then(() => {
if (isNil(timedActivity) || timerStoppedForCurrentService(service, timedActivity)) {
messenger.postMessage(tab, { type: "closePopup" })
} else {
openPopup(tab, { service, messenger })
}
})
}
messenger.on("togglePopup", () => { messenger.on("togglePopup", () => {
getCurrentTab().then(tab => { getCurrentTab().then(tab => {
if (tab && !isBrowserTab(tab)) { if (tab && !isBrowserTab(tab)) {
@@ -40,23 +60,12 @@ chrome.runtime.onMessage.addListener(action => {
const apiClient = new ApiClient(settings) const apiClient = new ApiClient(settings)
apiClient apiClient
.createActivity(activity) .createActivity(activity)
.then(() => { .then(() => resetBubble({ tab, settings, service }))
messenger.postMessage(tab, { type: "closePopup" })
apiClient.bookedHours(service).then(({ data }) => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: parseFloat(data[0]?.hours) || 0,
service
}
})
})
})
.catch(error => { .catch(error => {
if (error.response?.status === 422) { if (error.response?.status === 422) {
chrome.runtime.sendMessage({ chrome.runtime.sendMessage({
type: "setFormErrors", type: "setFormErrors",
payload: error.response.data payload: error.response.data,
}) })
} }
}) })
@@ -64,6 +73,19 @@ chrome.runtime.onMessage.addListener(action => {
}) })
} }
if (action.type === "stopTimer") {
const { timedActivity, service } = action.payload
getCurrentTab().then(tab => {
getSettings().then(settings => {
const apiClient = new ApiClient(settings)
apiClient
.stopTimer(timedActivity)
.then(() => resetBubble({ tab, settings, service, timedActivity }))
.catch(() => null)
})
})
}
if (action.type === "openOptions") { if (action.type === "openOptions") {
let url let url
if (isChrome()) { if (isChrome()) {

View File

@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
import Spinner from "components/Spinner" import Spinner from "components/Spinner"
import Form from "components/Form" import Form from "components/Form"
import Calendar from "components/Calendar" import Calendar from "components/Calendar"
import TimerView from "components/App/TimerView"
import { observable, computed } from "mobx" import { observable, computed } from "mobx"
import { Observer, observer } from "mobx-react" import { Observer, observer } from "mobx-react"
import { Spring, animated, config } from "react-spring/renderprops" import { Spring, animated, config } from "react-spring/renderprops"
@@ -14,15 +15,17 @@ import {
findProjectByValue, findProjectByValue,
findProjectByIdentifier, findProjectByIdentifier,
findTask, findTask,
formatDate defaultTask,
formatDate,
} from "utils" } from "utils"
import { parseISO } from "date-fns"
import InvalidConfigurationError from "components/Errors/InvalidConfigurationError" import InvalidConfigurationError from "components/Errors/InvalidConfigurationError"
import UpgradeRequiredError from "components/Errors/UpgradeRequiredError" import UpgradeRequiredError from "components/Errors/UpgradeRequiredError"
import UnknownError from "components/Errors/UnknownError" import UnknownError from "components/Errors/UnknownError"
import { parse } from "date-fns"
import Header from "./shared/Header" import Header from "./shared/Header"
import { head } from "lodash" import { head } from "lodash"
import TimeInputParser from "utils/TimeInputParser" import TimeInputParser from "utils/TimeInputParser"
import {get} from "lodash/fp";
@observer @observer
class App extends Component { class App extends Component {
@@ -34,58 +37,74 @@ class App extends Component {
name: PropTypes.string, name: PropTypes.string,
description: PropTypes.string, description: PropTypes.string,
projectId: PropTypes.string, projectId: PropTypes.string,
taskId: PropTypes.string taskId: PropTypes.string,
}), }),
subdomain: PropTypes.string, subdomain: PropTypes.string,
activities: PropTypes.array, activities: PropTypes.array,
schedules: PropTypes.array, schedules: PropTypes.array,
projects: PropTypes.array, projects: PropTypes.array,
timedActivity: PropTypes.shape({
customer_name: PropTypes.string.isRequired,
assignment_name: PropTypes.string.isRequired,
task_name: PropTypes.string.isRequired,
timer_started_at: PropTypes.string.isRequired,
seconds: PropTypes.number.isRequired,
}),
lastProjectId: PropTypes.number, lastProjectId: PropTypes.number,
lastTaskId: PropTypes.number, lastTaskId: PropTypes.number,
roundTimeEntries: PropTypes.bool,
fromDate: PropTypes.string, fromDate: PropTypes.string,
toDate: PropTypes.string, toDate: PropTypes.string,
errorType: PropTypes.string, errorType: PropTypes.string,
errorMessage: PropTypes.string errorMessage: PropTypes.string,
}; }
static defaultProps = { static defaultProps = {
activities: [], activities: [],
schedules: [], schedules: [],
projects: [], projects: [],
roundTimeEntries: false }
};
@observable changeset = {}; @observable changeset = {}
@observable formErrors = {}; @observable formErrors = {}
@computed get project() {
const { service, projects, lastProjectId } = this.props
return (
findProjectByValue(this.changeset.assignment_id)(projects) ||
findProjectByValue(Number(lastProjectId))(projects) ||
findProjectByIdentifier(service?.projectId)(projects) ||
head(projects.flatMap(get("options")))
)
}
@computed get task() {
const { service, lastTaskId } = this.props
return (
findTask(this.changeset.task_id || service?.taskId || lastTaskId)(this.project) ||
defaultTask(this.project?.tasks)
)
}
@computed get billable() {
return /\(.+\)/.test(this.changeset.hours) === true ? false : !!this.task?.billable
}
@computed get changesetWithDefaults() { @computed get changesetWithDefaults() {
const { service, projects, lastProjectId, lastTaskId } = this.props const { service } = this.props
const project =
findProjectByValue(this.changeset.assignment_id)(projects) ||
findProjectByIdentifier(service?.projectId)(projects) ||
findProjectByValue(Number(lastProjectId))(projects) ||
head(projects)
const task =
findTask(this.changeset.task_id || service?.taskId || lastTaskId)(project) ||
head(project?.tasks)
const defaults = { const defaults = {
remote_service: service?.name, remote_service: service?.name,
remote_id: service?.id, remote_id: service?.id,
remote_url: service?.url, remote_url: service?.url,
date: formatDate(new Date()), date: formatDate(new Date()),
assignment_id: project?.value, assignment_id: this.project?.value,
task_id: task?.value, task_id: this.task?.value,
billable: task?.billable, billable: this.billable,
hours: "", hours: "",
seconds: seconds: new TimeInputParser(this.changeset.hours).parseSeconds(),
this.changeset.hours && description: service?.description || "",
new TimeInputParser(this.changeset.hours).parseSeconds(), tag: "",
description: service?.description,
tag: ""
} }
return { ...defaults, ...this.changeset } return { ...defaults, ...this.changeset }
@@ -93,6 +112,7 @@ class App extends Component {
componentDidMount() { componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown) window.addEventListener("keydown", this.handleKeyDown)
parent.postMessage({ __mocoBX: { iFrameHeight: window.document.body.scrollHeight } }, "*")
chrome.runtime.onMessage.addListener(this.handleSetFormErrors) chrome.runtime.onMessage.addListener(this.handleSetFormErrors)
} }
@@ -101,25 +121,34 @@ class App extends Component {
chrome.runtime.onMessage.removeListener(this.handleSetFormErrors) chrome.runtime.onMessage.removeListener(this.handleSetFormErrors)
} }
handleChange = event => { handleChange = (event) => {
const { projects } = this.props const { projects } = this.props
const { const {
target: { name, value } target: { name, value },
} = event } = event
this.changeset[name] = value this.changeset[name] = value
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 this.changeset.task_id = defaultTask(project?.tasks)?.value
}
} }
};
handleSelectDate = date => { handleSelectDate = (date) => {
this.changeset.date = formatDate(date) this.changeset.date = formatDate(date)
}; }
handleSubmit = event => { handleStopTimer = (timedActivity) => {
const { service } = this.props
chrome.runtime.sendMessage({
type: "stopTimer",
payload: { timedActivity, service },
})
}
handleSubmit = (event) => {
event.preventDefault() event.preventDefault()
const { service } = this.props const { service } = this.props
@@ -127,35 +156,36 @@ class App extends Component {
type: "createActivity", type: "createActivity",
payload: { payload: {
activity: extractAndSetTag(this.changesetWithDefaults), activity: extractAndSetTag(this.changesetWithDefaults),
service service,
} },
}) })
}; }
handleKeyDown = event => { handleKeyDown = (event) => {
if (event.keyCode === 27) { if (event.keyCode === 27) {
event.stopPropagation() event.stopPropagation()
chrome.runtime.sendMessage({ type: "closePopup" }) chrome.runtime.sendMessage({ type: "closePopup" })
} }
}; }
handleSetFormErrors = ({ type, payload }) => { handleSetFormErrors = ({ type, payload }) => {
if (type === "setFormErrors") { if (type === "setFormErrors") {
this.formErrors = payload this.formErrors = payload
} }
}; }
render() { render() {
const { const {
loading, loading,
subdomain, subdomain,
projects, projects,
timedActivity,
activities, activities,
schedules, schedules,
fromDate, fromDate,
toDate, toDate,
errorType, errorType,
errorMessage errorMessage,
} = this.props } = this.props
if (loading) { if (loading) {
@@ -175,21 +205,19 @@ class App extends Component {
} }
return ( return (
<Spring <Spring native from={{ opacity: 0 }} to={{ opacity: 1 }} config={config.stiff}>
native {(props) => (
from={{ opacity: 0 }}
to={{ opacity: 1 }}
config={config.stiff}
>
{props => (
<animated.div className="moco-bx-app-container" style={props}> <animated.div className="moco-bx-app-container" style={props}>
<Header subdomain={subdomain} /> <Header subdomain={subdomain} />
<Observer> <Observer>
{() => ( {() =>
timedActivity ? (
<TimerView timedActivity={timedActivity} onStopTimer={this.handleStopTimer} />
) : (
<> <>
<Calendar <Calendar
fromDate={parse(fromDate)} fromDate={parseISO(fromDate)}
toDate={parse(toDate)} toDate={parseISO(toDate)}
activities={activities} activities={activities}
schedules={schedules} schedules={schedules}
selectedDate={new Date(this.changesetWithDefaults.date)} selectedDate={new Date(this.changesetWithDefaults.date)}
@@ -203,7 +231,8 @@ class App extends Component {
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
/> />
</> </>
)} )
}
</Observer> </Observer>
</animated.div> </animated.div>
)} )}

View File

@@ -0,0 +1,45 @@
import React from "react"
import PropTypes from "prop-types"
import Timer from "components/shared/Timer"
import { parseISO } from "date-fns"
import StopWatch from "components/shared/StopWatch"
export default function TimerView({ timedActivity, onStopTimer }) {
const handleStopTimer = () => {
onStopTimer(timedActivity)
}
return (
<div className="moco-bx-timer-view">
<p>
<span className="moco-bx-single-line text-secondary">{timedActivity.customer_name}</span>
<br />
<span className="moco-bx-single-line">{timedActivity.assignment_name}</span>
<br />
<span className="moco-bx-single-line">{timedActivity.task_name}</span>
</p>
<h2>{timedActivity.description}</h2>
<Timer
className="timer text-red"
startedAt={parseISO(timedActivity.timer_started_at)}
offset={timedActivity.seconds}
style={{ fontSize: "36px", display: "inline-block" }}
/>
<button className="moco-bx-btn btn-stop-timer" onClick={handleStopTimer} autoFocus>
<StopWatch />
</button>
</div>
)
}
TimerView.propTypes = {
timedActivity: PropTypes.shape({
customer_name: PropTypes.string.isRequired,
assignment_name: PropTypes.string.isRequired,
task_name: PropTypes.string.isRequired,
description: PropTypes.string,
timer_started_at: PropTypes.string.isRequired,
seconds: PropTypes.number.isRequired,
}).isRequired,
onStopTimer: PropTypes.func.isRequired,
}

View File

@@ -1,22 +1,46 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import logoUrl from "images/logo.png" import mocoLogo from "images/moco-32x32.png"
import mocoTimerLogo from "images/moco-timer-32x32.png"
import { parseISO } from "date-fns"
import { formatDuration } from "utils"
import Timer from "./shared/Timer"
const Bubble = ({ bookedHours }) => ( const Bubble = ({ bookedSeconds, timedActivity, settingTimeTrackingHHMM }) => {
const logo = timedActivity ? mocoTimerLogo : mocoLogo
return (
<div className="moco-bx-bubble-inner"> <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(logo)} />
{bookedHours > 0 ? ( {!timedActivity && bookedSeconds > 0 && (
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span> <span className="moco-bx-booked-hours">
) : null} {formatDuration(bookedSeconds, { settingTimeTrackingHHMM, showSeconds: false })}
</span>
)}
{timedActivity && (
<Timer
className="text-red"
startedAt={parseISO(timedActivity.timer_started_at)}
offset={timedActivity.seconds}
style={{ marginBottom: "3px", fontSize: "12px" }}
/>
)}
</div> </div>
) )
}
Bubble.propTypes = { Bubble.propTypes = {
bookedHours: PropTypes.number bookedSeconds: PropTypes.number,
timedActivity: PropTypes.shape({
timer_started_at: PropTypes.string.isRequired,
seconds: PropTypes.number.isRequired,
}),
settingTimeTrackingHHMM: PropTypes.bool,
} }
Bubble.defaultProps = { Bubble.defaultProps = {
bookedHours: 0 bookedSeconds: 0,
settingTimeTrackingHHMM: false,
} }
export default Bubble export default Bubble

View File

@@ -10,15 +10,11 @@ const Day = ({ date, hours, absence, active, onClick }) => {
return ( return (
<div <div
className={cn( className={cn("moco-bx-calendar__day", `moco-bx-calendar__day--week-day-${getDay(date)}`, {
"moco-bx-calendar__day",
`moco-bx-calendar__day--week-day-${getDay(date)}`,
{
"moco-bx-calendar__day--active": active, "moco-bx-calendar__day--active": active,
"moco-bx-calendar__day--filled": hours > 0, "moco-bx-calendar__day--filled": hours > 0,
"moco-bx-calendar__day--absence": absence "moco-bx-calendar__day--absence": absence,
} })}
)}
onClick={handleClick} onClick={handleClick}
> >
<span className="moco-bx-calendar__day-of-week"> <span className="moco-bx-calendar__day-of-week">
@@ -34,7 +30,7 @@ Day.propTypes = {
hours: PropTypes.number.isRequired, hours: PropTypes.number.isRequired,
absence: PropTypes.object, absence: PropTypes.object,
active: PropTypes.bool.isRequired, active: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired onClick: PropTypes.func.isRequired,
} }
export default Day export default Day

View File

@@ -35,9 +35,9 @@ Hours.propTypes = {
hours: PropTypes.number.isRequired, hours: PropTypes.number.isRequired,
absence: PropTypes.shape({ absence: PropTypes.shape({
assignment_code: PropTypes.string, assignment_code: PropTypes.string,
assignment_color: PropTypes.string assignment_color: PropTypes.string,
}), }),
active: PropTypes.bool.isRequired active: PropTypes.bool.isRequired,
} }
export default Hours export default Hours

View File

@@ -2,27 +2,19 @@ import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import Day from "./Day" import Day from "./Day"
import { formatDate } from "utils" import { formatDate } from "utils"
import { eachDay } from "date-fns" import { eachDayOfInterval } from "date-fns"
import { pathEq } from "lodash/fp" import { pathEq } from "lodash/fp"
const findAbsence = (date, schedules) => const findAbsence = (date, schedules) => schedules.find(pathEq("date", formatDate(date)))
schedules.find(pathEq("date", formatDate(date)))
const hoursAtDate = (date, activities) => const hoursAtDate = (date, activities) =>
activities activities
.filter(pathEq("date", formatDate(date))) .filter(pathEq("date", formatDate(date)))
.reduce((acc, activity) => acc + activity.hours, 0) .reduce((acc, activity) => acc + activity.hours, 0)
const Calendar = ({ const Calendar = ({ fromDate, toDate, selectedDate, activities, schedules, onChange }) => (
fromDate,
toDate,
selectedDate,
activities,
schedules,
onChange
}) => (
<div className="moco-bx-calendar"> <div className="moco-bx-calendar">
{eachDay(fromDate, toDate).map(date => ( {eachDayOfInterval({ start: fromDate, end: toDate }).map(date => (
<Day <Day
key={date} key={date}
date={date} date={date}
@@ -44,17 +36,17 @@ Calendar.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
hours: PropTypes.number.isRequired, hours: PropTypes.number.isRequired,
timer_started_at: PropTypes.string timer_started_at: PropTypes.string,
}).isRequired }).isRequired,
), ),
schedules: PropTypes.arrayOf( schedules: PropTypes.arrayOf(
PropTypes.shape({ PropTypes.shape({
date: PropTypes.string, date: PropTypes.string,
assignment_code: PropTypes.string, assignment_code: PropTypes.string,
assignment_color: PropTypes.string assignment_color: PropTypes.string,
}) }),
).isRequired, ).isRequired,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
} }
export default Calendar export default Calendar

View File

@@ -3,25 +3,23 @@ import settingsUrl from "images/settings.png"
const InvalidConfigurationError = () => ( const InvalidConfigurationError = () => (
<div className="moco-bx-error-container"> <div className="moco-bx-error-container">
<h1>Bitte Einstellungen aktualisieren</h1> <h1>MOCO verbinden</h1>
<ol> <p>
<li>Internetadresse eintragen</li> Dazu trägst Du in den Einstellungen Deine Account-Internetadresse und Deinen API-Schlüssel
<li>Persönlichen API-Schlüssel eintragen</li> ein.
</ol> </p>
<img
src={chrome.extension.getURL(settingsUrl)}
alt="Browser extension configuration settings"
style={{ cursor: "pointer", width: "185px", height: "195px" }}
onClick={() => chrome.runtime.sendMessage({ type: "openOptions" })}
/>
<button <button
className="moco-bx-btn" className="moco-bx-btn"
onClick={() => chrome.runtime.sendMessage({ type: "openOptions" })} onClick={() => chrome.runtime.sendMessage({ type: "openOptions" })}
> >
Einstellungen öffnen Weiter zu den Einstellungen
</button> </button>
<br />
<br />
<img
src={chrome.extension.getURL(settingsUrl)}
alt="Browser extension configuration settings"
style={{ cursor: "pointer" }}
onClick={() => chrome.runtime.sendMessage({ type: "openOptions" })}
/>
</div> </div>
) )

View File

@@ -1,21 +1,24 @@
import React from "react" import React from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import logo from "images/logo.png" import logo from "images/moco-159x159.png"
const UnknownError = ({ message = "Unbekannter Fehler" }) => ( const UnknownError = ({ message = "Unbekannter Fehler" }) => (
<div className="moco-bx-error-container"> <div className="moco-bx-error-container">
<img className="moco-bx-logo" src={logo} alt="MOCO logo" /> <img
className="moco-bx-logo"
src={logo}
style={{ width: "48px", height: "48px" }}
alt="MOCO logo"
/>
<h1>Ups, es ist ein Fehler passiert!</h1> <h1>Ups, es ist ein Fehler passiert!</h1>
<p>Bitte überprüfe deine Internetverbindung.</p> <p>Bitte überprüfe deine Internetverbindung.</p>
<p>Wir wurden per Email benachrichtigt und untersuchen den Vorfall.</p>
<br />
<p>Fehlermeldung:</p> <p>Fehlermeldung:</p>
<pre>{message}</pre> <pre>{message}</pre>
</div> </div>
) )
UnknownError.propTypes = { UnknownError.propTypes = {
message: PropTypes.string message: PropTypes.string,
} }
export default UnknownError export default UnknownError

View File

@@ -1,16 +1,18 @@
import React from "react" import React from "react"
import { isChrome } from "utils/browser" import { isChrome } from "utils/browser"
import logo from "images/logo.png" import logo from "images/moco-159x159.png"
import firefoxAddons from "images/firefox_addons.png" import firefoxAddons from "images/firefox_addons.png"
const UpgradeRequiredError = () => ( const UpgradeRequiredError = () => (
<div className="moco-bx-error-container"> <div className="moco-bx-error-container">
<img className="moco-bx-logo" src={logo} alt="MOCO logo" /> <img
<h1>Upgrade erforderlich</h1> className="moco-bx-logo"
<p> src={logo}
Die installierte MOCO Browser-Erweiterung ist veraltet &mdash; bitte style={{ width: "48px", height: "48px" }}
aktualisieren. alt="MOCO logo"
</p> />
<h1>Bitte aktualisieren</h1>
<p>Die installierte MOCO Browser-Erweiterung ist veraltet &mdash; bitte aktualisieren.</p>
{isChrome() ? ( {isChrome() ? (
<button <button
className="moco-bx-btn" className="moco-bx-btn"
@@ -20,11 +22,11 @@ const UpgradeRequiredError = () => (
</button> </button>
) : ( ) : (
<> <>
<br />
<p>Unter folgender URL:</p> <p>Unter folgender URL:</p>
<img <img
className="firefox-addons" className="firefox-addons"
src={firefoxAddons} src={firefoxAddons}
style={{ width: "292px", height: "40px" }}
alt="about:addons" alt="about:addons"
/> />
</> </>

View File

@@ -1,31 +1,71 @@
import React, { Component } from "react" import React, { Component } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import Select from "components/Select" import Select from "components/Select"
import { formatDate } from "utils"
import cn from "classnames" import cn from "classnames"
import StopWatch from "components/shared/StopWatch"
class Form extends Component { class Form extends Component {
static propTypes = { static propTypes = {
changeset: PropTypes.shape({ changeset: PropTypes.shape({
project: PropTypes.object, assignment_id: PropTypes.number.isRequired,
task: PropTypes.object, billable: PropTypes.bool.isRequired,
hours: PropTypes.string date: PropTypes.string.isRequired,
task_id: PropTypes.number.isRequired,
description: PropTypes.string,
remote_id: PropTypes.string,
remote_service: PropTypes.string,
remote_url: PropTypes.string,
seconds: PropTypes.number,
hours: PropTypes.string,
}).isRequired, }).isRequired,
errors: PropTypes.object, errors: PropTypes.object,
projects: PropTypes.array.isRequired, projects: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired onSubmit: PropTypes.func.isRequired,
}; }
static defaultProps = { static defaultProps = {
inline: true inline: true,
}; }
isValid = () => { isValid() {
const { changeset } = this.props const { changeset } = this.props
return ["assignment_id", "task_id", "hours", "description"] return (
.map(prop => changeset[prop]) ["assignment_id", "task_id"].map(prop => changeset[prop]).every(Boolean) &&
.every(Boolean) (changeset.date === formatDate(new Date()) || changeset.seconds > 0)
}; )
}
get isTimerStartable() {
const {
changeset: { seconds, date },
} = this.props
return date === formatDate(new Date()) && seconds === 0
}
buttonStyle() {
const styleMap = {
true: {
border: "none",
borderRadius: "50%",
width: "60px",
height: "60px",
marginTop: "22px",
transition: "all 0.2s ease-in-out",
},
false: {
border: "none",
width: "50px",
height: "36px",
marginTop: "35px",
transition: "all 0.2s ease-in-out",
},
}
return styleMap[this.isTimerStartable]
}
handleTextareaKeyDown = event => { handleTextareaKeyDown = event => {
const { onSubmit } = this.props const { onSubmit } = this.props
@@ -34,7 +74,7 @@ class Form extends Component {
event.preventDefault() event.preventDefault()
this.isValid() && onSubmit(event) this.isValid() && onSubmit(event)
} }
}; }
render() { render() {
const { projects, changeset, errors, onChange, onSubmit } = this.props const { projects, changeset, errors, onChange, onSubmit } = this.props
@@ -44,7 +84,7 @@ class Form extends Component {
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<div <div
className={cn("form-group", { className={cn("form-group", {
"has-error": errors.assignment_id || errors.task_id "has-error": errors.assignment_id || errors.task_id,
})} })}
> >
<Select <Select
@@ -69,9 +109,7 @@ class Form extends Component {
{errors.assignment_id ? ( {errors.assignment_id ? (
<div className="form-error">{errors.assignment_id.join("; ")}</div> <div className="form-error">{errors.assignment_id.join("; ")}</div>
) : null} ) : null}
{errors.task_id ? ( {errors.task_id ? <div className="form-error">{errors.task_id.join("; ")}</div> : null}
<div className="form-error">{errors.task_id.join("; ")}</div>
) : null}
</div> </div>
<div className={cn("form-group", { "has-error": errors.hours })}> <div className={cn("form-group", { "has-error": errors.hours })}>
<input <input
@@ -83,16 +121,14 @@ class Form extends Component {
autoComplete="off" autoComplete="off"
autoFocus autoFocus
/> />
{errors.hours ? ( {errors.hours ? <div className="form-error">{errors.hours.join("; ")}</div> : null}
<div className="form-error">{errors.hours.join("; ")}</div>
) : null}
</div> </div>
<div className={cn("form-group", { "has-error": errors.description })}> <div className={cn("form-group", { "has-error": errors.description })}>
<textarea <textarea
name="description" name="description"
onChange={onChange} onChange={onChange}
value={changeset.description} value={changeset.description}
placeholder="Beschreibung der Tätigkeit - mind. 3 Zeichen" placeholder="Beschreibung der Tätigkeit optional"
maxLength={255} maxLength={255}
rows={3} rows={3}
onKeyDown={this.handleTextareaKeyDown} onKeyDown={this.handleTextareaKeyDown}
@@ -102,8 +138,13 @@ class Form extends Component {
) : null} ) : null}
</div> </div>
<button className="moco-bx-btn" disabled={!this.isValid()}> <button
OK type="submit"
className="moco-bx-btn"
disabled={!this.isValid()}
style={this.buttonStyle()}
>
{this.isTimerStartable ? <StopWatch /> : "OK"}
</button> </button>
</form> </form>
) )

View File

@@ -3,68 +3,96 @@ import { observable } from "mobx"
import { observer } from "mobx-react" import { observer } from "mobx-react"
import { isChrome, getSettings, setStorage } from "utils/browser" import { isChrome, getSettings, setStorage } from "utils/browser"
import ApiClient from "api/Client" import ApiClient from "api/Client"
import { pipe, toPairs, fromPairs, map } from "lodash/fp"
function upperCaseFirstLetter(input) {
return input[0].toUpperCase() + input.slice(1)
}
function removePathFromUrl(url) {
return url.replace(/(\.[a-z]+)\/.*$/, "$1")
}
@observer @observer
class Options extends Component { class Options extends Component {
@observable subdomain = ""; @observable subdomain = ""
@observable apiKey = ""; @observable apiKey = ""
@observable errorMessage = null; @observable hostOverrides = {}
@observable isSuccess = false; @observable errorMessage = null
@observable isSuccess = false
@observable showHostOverrideOptions = false
componentDidMount() { componentDidMount() {
getSettings(false).then(({ subdomain, apiKey }) => { getSettings(false).then((settings) => {
this.subdomain = subdomain || "" this.subdomain = settings.subdomain || ""
this.apiKey = apiKey || "" this.apiKey = settings.apiKey || ""
this.hostOverrides = settings.hostOverrides
}) })
} }
onChange = event => { handleChange = (event) => {
this[event.target.name] = event.target.value.trim() this[event.target.name] = event.target.value.trim()
}; }
handleSubmit = _event => { handleChangeHostOverrides = (event) => {
this.hostOverrides[event.target.name] = event.target.value.trim()
}
toggleHostOverrideOptions = () => {
this.showHostOverrideOptions = !this.showHostOverrideOptions
}
handleSubmit = (_event) => {
this.isSuccess = false this.isSuccess = false
this.errorMessage = null this.errorMessage = null
setStorage({ subdomain: this.subdomain, apiKey: this.apiKey }).then(() => {
setStorage({
subdomain: this.subdomain,
apiKey: this.apiKey,
settingTimeTrackingHHMM: false,
hostOverrides: pipe(
toPairs,
map(([key, url]) => [key, removePathFromUrl(url)]),
fromPairs,
)(this.hostOverrides),
}).then(() => {
const { version } = chrome.runtime.getManifest() const { version } = chrome.runtime.getManifest()
const apiClient = new ApiClient({ const apiClient = new ApiClient({
subdomain: this.subdomain, subdomain: this.subdomain,
apiKey: this.apiKey, apiKey: this.apiKey,
version version,
}) })
apiClient apiClient
.login() .login()
.then(({ data }) =>
setStorage({ settingTimeTrackingHHMM: data.setting_time_tracking_hh_mm }),
)
.then(() => { .then(() => {
this.isSuccess = true this.isSuccess = true
this.closeWindow() this.closeWindow()
}) })
.catch(error => { .catch((error) => {
this.errorMessage = this.errorMessage = error.response?.data?.message || "Anmeldung fehlgeschlagen"
error.response?.data?.message || "Anmeldung fehlgeschlagen"
}) })
}) })
}; }
handleInputKeyDown = event => { handleInputKeyDown = (event) => {
if (event.key === "Enter") { if (event.key === "Enter") {
this.handleSubmit() this.handleSubmit()
} }
}; }
closeWindow = () => { closeWindow = () => {
isChrome() && window.close() isChrome() && window.close()
}; }
render() { render() {
return ( return (
<div className="moco-bx-options"> <div className="moco-bx-options">
<h2 style={{ textAlign: "center" }}>Einstellungen</h2> <h2 style={{ textAlign: "center" }}>Einstellungen</h2>
{this.errorMessage && ( {this.errorMessage && <div className="text-danger">{this.errorMessage}</div>}
<div className="text-danger">{this.errorMessage}</div> {this.isSuccess && <div className="text-success">Anmeldung erfolgreich</div>}
)}
{this.isSuccess && (
<div className="text-success">Anmeldung erfolgreich</div>
)}
<div className="form-group"> <div className="form-group">
<label>Internetadresse</label> <label>Internetadresse</label>
<div className="input-group"> <div className="input-group">
@@ -73,9 +101,9 @@ class Options extends Component {
name="subdomain" name="subdomain"
value={this.subdomain} value={this.subdomain}
onKeyDown={this.handleInputKeyDown} onKeyDown={this.handleInputKeyDown}
onChange={this.onChange} onChange={this.handleChange}
/> />
<span className="input-group-addon">.mocoapp.com</span> <span className="input-group-addon input-group-addon--right">.mocoapp.com</span>
</div> </div>
</div> </div>
<div className="form-group"> <div className="form-group">
@@ -85,13 +113,59 @@ class Options extends Component {
name="apiKey" name="apiKey"
value={this.apiKey} value={this.apiKey}
onKeyDown={this.handleInputKeyDown} onKeyDown={this.handleInputKeyDown}
onChange={this.onChange} onChange={this.handleChange}
/> />
<p className="text-muted"> <p className="text-muted">
Den API-Schlüssel findest du in deinem Profil unter Den API-Schlüssel findest du in deinem Profil unter &quot;Integrationen&quot;.
&quot;Integrationen&quot;.
</p> </p>
</div> </div>
{!this.showHostOverrideOptions && (
<div className="moco-bx-options__host-overrides">
<a href="#" className="moco-bx-btn__secondary" onClick={this.toggleHostOverrideOptions}>
Service-URLs überschreiben?
</a>
</div>
)}
{this.showHostOverrideOptions && (
<div style={{ marginBottom: "1.5rem" }}>
<h3 style={{ marginBottom: 0 }}>Service-URLs</h3>
<small>
Doppelpunkt für Platzhalter verwenden, z.B.{" "}
<span style={{ backgroundColor: "rgba(100, 100, 100, 0.1)" }}>:org</span>. Siehe{" "}
<a
href="https://github.com/hundertzehn/mocoapp-browser-extension#remote-service-configuration"
target="_blank"
rel="noopener noreferrer"
>
Online-Doku
</a>
.
</small>
<br />
{pipe(
Object.entries,
Array.from,
)(this.hostOverrides).map(([name, host]) => (
<div className="form-group" key={name} style={{ margin: "0.5rem 0" }}>
<div className="input-group">
<span
className="input-group-addon input-group-addon--left"
style={{ display: "inline-block", width: "70px", textAlign: "left" }}
>
{upperCaseFirstLetter(name)}
</span>
<input
type="text"
name={name}
value={host}
onKeyDown={this.handleInputKeyDown}
onChange={this.handleChangeHostOverrides}
/>
</div>
</div>
))}
</div>
)}
<button className="moco-bx-btn" onClick={this.handleSubmit}> <button className="moco-bx-btn" onClick={this.handleSubmit}>
OK OK
</button> </button>

View File

@@ -1,88 +1,65 @@
import React, { Component } from "react" import React, { useEffect, useRef, forwardRef } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import queryString from "query-string" import queryString from "query-string"
import { import { serializeProps } from "utils"
ERROR_UNKNOWN,
ERROR_UNAUTHORIZED,
ERROR_UPGRADE_REQUIRED,
serializeProps
} from "utils"
import { isChrome } from "utils/browser"
function getStyles(errorType) { const Popup = forwardRef((props, ref) => {
return { const iFrameRef = useRef()
width: "516px",
height:
errorType === ERROR_UNAUTHORIZED
? "834px"
: errorType === ERROR_UPGRADE_REQUIRED
? isChrome()
? "369px"
: "461px"
: errorType === ERROR_UNKNOWN
? "550px"
: "558px"
}
}
class Popup extends Component { const handleRequestClose = event => {
static propTypes = {
service: PropTypes.object,
errorType: PropTypes.string,
onRequestClose: PropTypes.func.isRequired
};
handleRequestClose = event => {
if (event.target.classList.contains("moco-bx-popup")) { if (event.target.classList.contains("moco-bx-popup")) {
this.props.onRequestClose() props.onRequestClose()
} }
};
componentDidMount() {
// Document might lose focus when clicking the browser action.
// Document might be out of focus when hitting the shortcut key.
// This puts the focus back to the document and ensures that:
// - the autofocus on the hours input field is triggered
// - the ESC key closes the popup without closing anything else
window.focus()
document.activeElement?.blur()
} }
render() { const handleMessage = event => {
if (iFrameRef.current && event.data?.__mocoBX?.iFrameHeight > 300) {
iFrameRef.current.style.height = `${event.data.__mocoBX.iFrameHeight}px`
}
}
useEffect(() => {
window.addEventListener("message", handleMessage)
return () => {
window.removeEventListener("message", handleMessage)
}
}, [])
const serializedProps = serializeProps([ const serializedProps = serializeProps([
"loading", "loading",
"service", "service",
"subdomain", "subdomain",
"lastProjectId",
"lastTaskId",
"roundTimeEntries",
"projects", "projects",
"activities", "activities",
"schedules", "schedules",
"timedActivity",
"lastProjectId", "lastProjectId",
"lastTaskId", "lastTaskId",
"fromDate", "fromDate",
"toDate", "toDate",
"errorType", "errorType",
"errorMessage" "errorMessage",
])(this.props) ])(props)
const styles = getStyles(this.props.errorType)
return ( return (
<div className="moco-bx-popup" onClick={this.handleRequestClose}> <div ref={ref} className="moco-bx-popup" onClick={handleRequestClose}>
<div className="moco-bx-popup-content" style={styles}> <div className="moco-bx-popup-content" style={{ width: "516px" }}>
<iframe <iframe
src={chrome.extension.getURL( ref={iFrameRef}
`popup.html?${queryString.stringify(serializedProps)}` src={chrome.extension.getURL(`popup.html?${queryString.stringify(serializedProps)}`)}
)} style={{ width: "516px", height: "576px", transition: "height 0.1s ease-in-out" }}
width={styles.width}
height={styles.height}
/> />
</div> </div>
</div> </div>
) )
} })
Popup.displayName = "Popup"
Popup.propTypes = {
service: PropTypes.object,
errorType: PropTypes.string,
onRequestClose: PropTypes.func.isRequired,
} }
export default Popup export default Popup

View File

@@ -1,6 +1,6 @@
import React, { Component } from "react" import React, { Component } from "react"
import PropTypes from "prop-types" import PropTypes from "prop-types"
import ReactSelect, { createFilter } from "react-select" import ReactSelect, { createFilter, components } from "react-select"
import { import {
values, values,
isString, isString,
@@ -10,11 +10,11 @@ import {
compose, compose,
property, property,
flatMap, flatMap,
pathEq pathEq,
isNil,
} from "lodash/fp" } from "lodash/fp"
const hasOptionGroups = options => const hasOptionGroups = options => options.some(option => Boolean(option.options))
options.some(option => Boolean(option.options))
const customTheme = theme => ({ const customTheme = theme => ({
...theme, ...theme,
@@ -22,30 +22,30 @@ const customTheme = theme => ({
spacing: { spacing: {
...theme.spacing, ...theme.spacing,
baseUnit: 3, baseUnit: 3,
controlHeight: 32 controlHeight: 32,
}, },
colors: { colors: {
...theme.colors, ...theme.colors,
primary: "#38b5eb", primary: "#38b5eb",
primary75: "rgba(56, 181, 235, 0.25)", primary75: "rgba(56, 181, 235, 0.25)",
primary50: "#38b5eb", primary50: "#38b5eb",
primary25: "#38b5eb" primary25: "#38b5eb",
} },
}) })
const customStyles = props => ({ const customStyles = props => ({
control: (base, _state) => ({ control: (base, _state) => ({
...base, ...base,
borderColor: props.hasError ? "#FB3A2F" : base.borderColor borderColor: props.hasError ? "#fb3a2f" : base.borderColor,
}), }),
valueContainer: base => ({ valueContainer: base => ({
...base, ...base,
padding: "4px 12px" padding: "4px 12px",
}), }),
input: base => ({ input: base => ({
...base, ...base,
border: "0 !important", border: "0 !important",
boxShadow: "0 !important" boxShadow: "0 !important",
}), }),
groupHeading: (base, _state) => ({ groupHeading: (base, _state) => ({
...base, ...base,
@@ -53,16 +53,14 @@ const customStyles = props => ({
textTransform: "none", textTransform: "none",
fontWeight: "bold", fontWeight: "bold",
fontSize: "100%", fontSize: "100%",
padding: "2px 7px 4px" padding: "2px 7px 4px",
}), }),
option: (base, state) => ({ option: (base, state) => ({
...base, ...base,
padding: hasOptionGroups(state.options) padding: hasOptionGroups(state.options) ? "4px 7px 4px 20px" : "4px 7px 4px",
? "4px 7px 4px 20px"
: "4px 7px 4px",
backgroundColor: state.isFocused ? "#38b5eb" : "none", backgroundColor: state.isFocused ? "#38b5eb" : "none",
color: state.isFocused ? "white" : "hsl(0, 0%, 20%)" color: state.isFocused ? "white" : "hsl(0, 0%, 20%)",
}) }),
}) })
const filterOption = createFilter({ const filterOption = createFilter({
@@ -70,27 +68,38 @@ const filterOption = createFilter({
join(" "), join(" "),
filter(value => isString(value) || isNumber(value)), filter(value => isString(value) || isNumber(value)),
values, values,
property("data") property("data"),
) ),
}) })
SingleValue.propTypes = {
children: PropTypes.string.isRequired,
data: PropTypes.shape({
customerName: PropTypes.string,
}).isRequired,
}
function SingleValue({ children, ...props }) {
const label = isNil(props.data.customerName)
? children
: `${props.data.customerName}: ${children}`
return <components.SingleValue {...props}>{label}</components.SingleValue>
}
export default class Select extends Component { export default class Select extends Component {
static propTypes = { static propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
options: PropTypes.array, options: PropTypes.array,
hasError: PropTypes.bool, hasError: PropTypes.bool,
onChange: PropTypes.func.isRequired onChange: PropTypes.func.isRequired,
}; }
static findOptionByValue = (selectOptions, value) => { static findOptionByValue = (selectOptions, value) => {
const options = flatMap( const options = flatMap(option => (option.options ? option.options : option), selectOptions)
option => (option.options ? option.options : option),
selectOptions
)
return options.find(pathEq("value", value)) || null return options.find(pathEq("value", value)) || null
}; }
constructor(props) { constructor(props) {
super(props) super(props)
@@ -101,7 +110,7 @@ export default class Select extends Component {
const { name, onChange } = this.props const { name, onChange } = this.props
const { value } = option const { value } = option
onChange({ target: { name, value } }) onChange({ target: { name, value } })
}; }
handleKeyDown = event => { handleKeyDown = event => {
if (!this.select.current) { if (!this.select.current) {
@@ -111,7 +120,7 @@ export default class Select extends Component {
if (!this.select.current.state.menuIsOpen && event.key === "Enter") { if (!this.select.current.state.menuIsOpen && event.key === "Enter") {
this.select.current.setState({ menuIsOpen: true }) this.select.current.setState({ menuIsOpen: true })
} }
}; }
render() { render() {
const { value, ...passThroughProps } = this.props const { value, ...passThroughProps } = this.props
@@ -121,11 +130,12 @@ export default class Select extends Component {
{...passThroughProps} {...passThroughProps}
ref={this.select} ref={this.select}
value={Select.findOptionByValue(this.props.options, value)} value={Select.findOptionByValue(this.props.options, value)}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
filterOption={filterOption} filterOption={filterOption}
theme={customTheme} theme={customTheme}
styles={customStyles(this.props)} styles={customStyles(this.props)}
components={{ SingleValue }}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/> />
) )
} }

View File

@@ -1,14 +1,14 @@
import React from 'react' import React from "react"
import PropTypes from 'prop-types' import PropTypes from "prop-types"
const Spinner = ({ style }) => ( const Spinner = ({ style }) => (
<div className='moco-bx-spinner__container' style={style}> <div className="moco-bx-spinner__container" style={style}>
<div className='moco-bx-spinner' role='status' /> <div className="moco-bx-spinner" role="status" />
</div> </div>
) )
Spinner.propTypes = { Spinner.propTypes = {
style: PropTypes.object style: PropTypes.object,
} }
export default Spinner export default Spinner

View File

@@ -1,20 +1,21 @@
import React from 'react' import React from "react"
import PropTypes from 'prop-types' import PropTypes from "prop-types"
import logoUrl from "images/logo.png" import logoUrl from "images/moco-159x159.png"
const Header = ({ subdomain }) => ( 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"> <a
<img href={`https://${subdomain}.mocoapp.com/activities`}
className="moco-bx-logo" target="_blank"
src={chrome.extension.getURL(logoUrl)} rel="noopener noreferrer"
/> >
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
</a> </a>
</div> </div>
) )
Header.propTypes = { Header.propTypes = {
subdomain: PropTypes.string subdomain: PropTypes.string,
} }
export default Header export default Header

View File

@@ -0,0 +1,11 @@
import React from "react"
import stopWatch from "images/icons/stopwatch-light.svg"
export default function StopWatch() {
return (
<i
dangerouslySetInnerHTML={{ __html: stopWatch }}
style={{ width: "22px", color: "white", display: "inline-block" }}
/>
)
}

View File

@@ -0,0 +1,29 @@
import React, { useState } from "react"
import PropTypes from "prop-types"
import { useInterval } from "./hooks"
import { differenceInSeconds } from "date-fns"
import { formatDuration } from "utils"
Timer.propTypes = {
startedAt: PropTypes.instanceOf(Date).isRequired,
offset: PropTypes.number,
onTick: PropTypes.func,
}
function Timer({ startedAt, offset = 0, onTick, ...domProps }) {
const [timerLabel, setTimerLabel] = useState(formattedTimerLabel(startedAt, offset))
useInterval(() => {
setTimerLabel(formattedTimerLabel(startedAt, offset))
onTick && onTick()
}, 1000)
return <span {...domProps}>{timerLabel}</span>
}
function formattedTimerLabel(startedAt, offset) {
const seconds = differenceInSeconds(new Date(), startedAt) + offset
return formatDuration(seconds)
}
export default Timer

View File

@@ -0,0 +1,18 @@
import { useEffect, useRef } from "react"
export function useInterval(callback, delay) {
const savedCallback = useRef()
useEffect(() => {
savedCallback.current = callback
})
useEffect(() => {
function tick() {
savedCallback.current()
}
let id = setInterval(tick, delay)
return () => clearInterval(id)
}, [delay])
}

View File

@@ -5,29 +5,40 @@ import Bubble from "./components/Bubble"
import Popup from "components/Popup" import Popup from "components/Popup"
import { createServiceFinder } from "utils/urlMatcher" import { createServiceFinder } from "utils/urlMatcher"
import remoteServices from "./remoteServices" import remoteServices from "./remoteServices"
import { ErrorBoundary } from "utils/notifier"
import { ContentMessenger } from "utils/messaging" import { ContentMessenger } from "utils/messaging"
import "../css/content.scss" import "../css/content.scss"
import { getSettings } from "./utils/browser"
const popupRef = createRef() const popupRef = createRef()
const findService = createServiceFinder(remoteServices)(document)
chrome.runtime.onConnect.addListener(function(port) { let findService
getSettings().then((settings) => {
findService = createServiceFinder(remoteServices, settings.hostOverrides)(document)
})
chrome.runtime.onConnect.addListener(function (port) {
const messenger = new ContentMessenger(port) const messenger = new ContentMessenger(port)
function clickHandler(event) {
if (event.target.closest(".moco-bx-bubble")) {
event.stopPropagation()
messenger.postMessage({ type: "togglePopup" })
}
}
port.onDisconnect.addListener(() => { port.onDisconnect.addListener(() => {
messenger.stop() messenger.stop()
window.removeEventListener("click", clickHandler, true)
}) })
function updateBubble({ service, bookedHours } = {}) { function updateBubble({ service, bookedSeconds, settingTimeTrackingHHMM, timedActivity } = {}) {
if (!document.getElementById("moco-bx-root")) { if (!document.getElementById("moco-bx-root")) {
const domRoot = document.createElement("div") const domRoot = document.createElement("div")
domRoot.setAttribute("id", "moco-bx-root") domRoot.setAttribute("id", "moco-bx-root")
document.body.appendChild(domRoot) document.body.appendChild(domRoot)
window.addEventListener("click", clickHandler, true)
} }
ReactDOM.render( ReactDOM.render(
<ErrorBoundary>
<Transition <Transition
native native
items={service} items={service}
@@ -36,25 +47,22 @@ chrome.runtime.onConnect.addListener(function(port) {
leave={{ opacity: "0" }} leave={{ opacity: "0" }}
config={config.stiff} config={config.stiff}
> >
{service => {(service) =>
service && service &&
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
(props => ( ((props) => (
<animated.div <animated.div className="moco-bx-bubble" style={{ ...props, ...service.position }}>
className="moco-bx-bubble" <Bubble
style={{ ...props, ...service.position }} key={service.url}
onClick={event => { bookedSeconds={bookedSeconds}
event.stopPropagation() settingTimeTrackingHHMM={settingTimeTrackingHHMM}
messenger.postMessage({ type: "togglePopup" }) timedActivity={timedActivity}
}} />
>
<Bubble key={service.url} bookedHours={bookedHours} />
</animated.div> </animated.div>
)) ))
} }
</Transition> </Transition>,
</ErrorBoundary>, document.getElementById("moco-bx-root"),
document.getElementById("moco-bx-root")
) )
} }
@@ -66,10 +74,8 @@ chrome.runtime.onConnect.addListener(function(port) {
} }
ReactDOM.render( ReactDOM.render(
<ErrorBoundary> <Popup ref={popupRef} {...payload} onRequestClose={closePopup} />,
<Popup ref={popupRef} {...payload} onRequestClose={closePopup} /> document.getElementById("moco-bx-popup-root"),
</ErrorBoundary>,
document.getElementById("moco-bx-popup-root")
) )
} }
@@ -86,12 +92,12 @@ chrome.runtime.onConnect.addListener(function(port) {
const service = findService(window.location.href) const service = findService(window.location.href)
messenger.postMessage({ messenger.postMessage({
type: "newService", type: "newService",
payload: { isOpen: !!popupRef.current, service } payload: { isOpen: !!popupRef.current, service },
}) })
}) })
messenger.on("showBubble", ({ payload: { service, bookedHours } }) => { messenger.on("showBubble", ({ payload }) => {
updateBubble({ service, bookedHours }) updateBubble(payload)
}) })
messenger.on("hideBubble", () => { messenger.on("hideBubble", () => {
@@ -105,8 +111,4 @@ chrome.runtime.onConnect.addListener(function(port) {
messenger.on("closePopup", () => { messenger.on("closePopup", () => {
closePopup() closePopup()
}) })
messenger.on("activityCreated", () => {
closePopup()
})
}) })

View File

@@ -1,14 +1,8 @@
import React from 'react' import React from "react"
import ReactDOM from 'react-dom' import ReactDOM from "react-dom"
import Options from './components/Options' import Options from "./components/Options"
import { ErrorBoundary } from 'utils/notifier' import "../css/options.scss"
import '../css/options.scss'
const domContainer = document.querySelector('#moco-bx-root') const domContainer = document.querySelector("#moco-bx-root")
ReactDOM.render( ReactDOM.render(<Options />, domContainer)
<ErrorBoundary>
<Options />
</ErrorBoundary>,
domContainer
)

View File

@@ -3,7 +3,6 @@ import ReactDOM from "react-dom"
import App from "./components/App" import App from "./components/App"
import queryString from "query-string" import queryString from "query-string"
import { parseProps } from "utils" import { parseProps } from "utils"
import { ErrorBoundary } from "utils/notifier"
import "../css/popup.scss" import "../css/popup.scss"
const parsedProps = parseProps([ const parsedProps = parseProps([
@@ -13,20 +12,13 @@ const parsedProps = parseProps([
"projects", "projects",
"activities", "activities",
"schedules", "schedules",
"lastProjectId", "timedActivity",
"lastTaskId",
"roundTimeEntries",
"lastProjectId", "lastProjectId",
"lastTaskId", "lastTaskId",
"fromDate", "fromDate",
"toDate", "toDate",
"errorType", "errorType",
"errorMessage" "errorMessage",
])(queryString.parse(location.search)) ])(queryString.parse(location.search))
ReactDOM.render( ReactDOM.render(<App {...parsedProps} />, document.querySelector("#moco-bx-root"))
<ErrorBoundary>
<App {...parsedProps} />
</ErrorBoundary>,
document.querySelector("#moco-bx-root")
)

View File

@@ -1,68 +1,58 @@
const projectRegex = /\[([\w-]+)\]/ const projectRegex = /\[([\w-]+)\]/
const projectIdentifierBySelector = (selector) => (document) =>
document.querySelector(selector)?.textContent?.trim()?.match(projectRegex)?.[1]
export default { export default {
asana: { asana: {
name: "asana", name: "asana",
host: "https://app.asana.com",
urlPatterns: [ urlPatterns: [
[/^https:\/\/app.asana.com\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]], [/^:host:\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
[ [/^:host:\/0\/search\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
/^https:\/\/app.asana.com\/0\/search\/([^/]+)\/(\d+)/,
["domainUserId", "id"]
]
], ],
description: document => description: (document) =>
document document.querySelector(".ItemRow--highlighted textarea")?.textContent?.trim() ||
.querySelector(".ItemRow--highlighted textarea") document.querySelector(".ItemRow--focused textarea")?.textContent?.trim() ||
?.textContent?.trim() || document.querySelector(".SingleTaskPane textarea")?.textContent?.trim() ||
document document.querySelector(".SingleTaskTitleInput-taskName textarea")?.textContent?.trim(),
.querySelector(".ItemRow--focused textarea") projectId: projectIdentifierBySelector(".TopbarPageHeaderStructure-titleRow h1"),
?.textContent?.trim() || allowHostOverride: false,
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": {
name: "github", name: "github",
urlPatterns: ["https\\://github.com/:org/:repo/pull/:id(/:tab)"], host: "https://github.com",
id: (document, service, { org, repo, id }) => urlPatterns: [":host:/:org/:repo/pull/:id(/:tab)"],
[service.key, org, repo, id].join("."), id: (document, service, { org, repo, id }) => [service.key, org, repo, id].join("."),
description: (document, service, { org, repo, id }) => description: (document) => document.querySelector(".js-issue-title")?.textContent?.trim(),
document.querySelector(".js-issue-title")?.textContent?.trim(), projectId: projectIdentifierBySelector(".js-issue-title"),
projectId: document => { allowHostOverride: false,
const match = document
.querySelector(".js-issue-title")
?.textContent.trim()
?.match(projectRegex)
return match && match[1]
}
}, },
"github-issue": { "github-issue": {
name: "github", name: "github",
urlPatterns: ["https\\://github.com/:org/:repo/issues/:id"], host: "https://github.com",
id: (document, service, { org, repo, id }) => urlPatterns: [":host:/:org/:repo/issues/:id"],
[service.key, org, repo, id].join("."), id: (document, service, { org, repo, id }) => [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(),
projectId: projectIdentifierBySelector(".js-issue-title"),
allowHostOverride: false,
}, },
jira: { jira: {
name: "jira", name: "jira",
host: "https://:org.atlassian.net",
urlPatterns: [ urlPatterns: [
"https\\://:org.atlassian.net/secure/RapidBoard.jspa", ":host:/secure/RapidBoard.jspa",
"https\\://:org.atlassian.net/browse/:id", ":host:/browse/:id",
"https\\://:org.atlassian.net/jira/software/projects/:projectId/boards/:board", ":host:/jira/software/projects/:projectId/boards/:board",
"https\\://:org.atlassian.net/jira/software/projects/:projectId/boards/:board/backlog" ":host:/jira/software/projects/:projectId/boards/:board/backlog",
], ],
queryParams: { queryParams: {
id: "selectedIssue", id: "selectedIssue",
projectId: "projectKey" projectId: "projectKey",
}, },
description: (document, service, { id }) => { description: (document, service, { id }) => {
const title = const title =
@@ -70,51 +60,115 @@ export default {
.querySelector('[aria-label="Edit Summary"]') .querySelector('[aria-label="Edit Summary"]')
?.parentNode?.querySelector("h1") ?.parentNode?.querySelector("h1")
?.textContent?.trim() || ?.textContent?.trim() ||
document document.querySelector(".ghx-selected .ghx-summary")?.textContent?.trim()
.querySelector(".ghx-selected .ghx-summary")
?.textContent?.trim()
return `#${id} ${title || ""}` return `#${id} ${title || ""}`
} },
allowHostOverride: true,
}, },
meistertask: { meistertask: {
name: "meistertask", name: "meistertask",
urlPatterns: ["https\\://www.meistertask.com/app/task/:id/:slug"], host: "https://www.meistertask.com",
description: document => { urlPatterns: [":host:/app/task/:id/:slug"],
const json = description: (document) => {
document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}" const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
const data = JSON.parse(json) const data = JSON.parse(json)
return data.taskName return data.taskName
}, },
projectId: document => { projectId: (document) => {
const json = const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
const data = JSON.parse(json) const data = JSON.parse(json)
const match = data.projectName?.match(projectRegex) const match = data.taskName?.match(projectRegex) || data.projectName?.match(projectRegex)
return match && match[1] return match && match[1]
} },
allowHostOverride: false,
}, },
trello: { trello: {
name: "trello", name: "trello",
urlPatterns: ["https\\://trello.com/c/:id/:title"], host: "https://trello.com",
urlPatterns: [":host:/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,
projectId: (document) =>
projectIdentifierBySelector(".js-title-helper")(document) ||
projectIdentifierBySelector(".js-board-editing-target")(document),
allowHostOverride: false,
}, },
youtrack: { youtrack: {
name: "youtrack", name: "youtrack",
urlPatterns: ["https\\://:org.myjetbrains.com/youtrack/issue/:id"], host: "https://:org.myjetbrains.com",
description: document => urlPatterns: [":host:/youtrack/issue/:id"],
document.querySelector("yt-issue-body h1")?.textContent?.trim() description: (document) => document.querySelector("yt-issue-body h1")?.textContent?.trim(),
projectId: projectIdentifierBySelector("yt-issue-body h1"),
allowHostOverride: true,
},
wrike: {
name: "wrike",
host: "https://www.wrike.com",
urlPatterns: [
":host:/workspace.htm#path=mywork",
":host:/workspace.htm#path=folder",
"https\\://app-eu.wrike.com/workspace.htm#path=mywork",
"https\\://app-eu.wrike.com/workspace.htm#path=folder",
],
queryParams: {
id: ["t", "ot"],
},
description: (document) => document.querySelector(".title-field-ghost")?.textContent?.trim(),
projectId: projectIdentifierBySelector(".header-title__main"),
allowHostOverride: false,
}, },
wunderlist: { wunderlist: {
name: "wunderlist", name: "wunderlist",
urlPatterns: ["https\\://www.wunderlist.com/(webapp)#/tasks/:id(/*)"], host: "https://www.wunderlist.com",
description: document => urlPatterns: [":host:/(webapp)#/tasks/:id(/*)"],
description: (document) =>
document document
.querySelector(".taskItem.selected .taskItem-titleWrapper-title") .querySelector(".taskItem.selected .taskItem-titleWrapper-title")
?.textContent?.trim() ?.textContent?.trim(),
} projectId: projectIdentifierBySelector(".taskItem.selected .taskItem-titleWrapper-title"),
allowHostOverride: false,
},
"gitlab-mr": {
name: "gitlab",
host: "https://gitlab.com",
urlPatterns: [
":host:/:org/:group/:projectId/-/merge_requests/:id",
":host:/:org/:projectId/-/merge_requests/:id",
],
description: (document, service, { id }) => {
const title = document.querySelector("h2.title")?.textContent?.trim()
return `#${id} ${title || ""}`.trim()
},
allowHostOverride: true,
},
"gitlab-issues": {
name: "gitlab",
host: "https://gitlab.com",
urlPatterns: [
":host:/:org/:group/:projectId/-/issues/:id",
":host:/:org/:projectId/-/issues/:id",
],
description: (document, service, { id }) => {
const title = document.querySelector("h2.title")?.textContent?.trim()
return `#${id} ${title || ""}`.trim()
},
allowHostOverride: true,
},
monday: {
name: "monday",
host: "https://:org.monday.com",
urlPatterns: [":host:/boards/:board/pulses/:id"],
description: (document, service, { id }) => {
return document.querySelector(".pulse_title")?.textContent?.trim()
},
allowHostOverride: false,
},
} }

View File

@@ -1,12 +1,14 @@
export default class TimeInputParser { export default class TimeInputParser {
#input; #input
constructor(input) { constructor(input) {
this.#input = input.toLowerCase().replace(/[\s()]/g, "") this.#input = (input ?? "").toLowerCase().replace(/[\s()]/g, "")
} }
parseSeconds() { parseSeconds() {
if (this.#isDecimal()) { if (this.#input === "") {
return 0
} else if (this.#isDecimal()) {
return Math.round(parseFloat(this.#parseDecimal()) * 3600) return Math.round(parseFloat(this.#parseDecimal()) * 3600)
} else if (this.#isTime()) { } else if (this.#isTime()) {
return this.#parseTimeAsSeconds() return this.#parseTimeAsSeconds()
@@ -25,11 +27,11 @@ export default class TimeInputParser {
const calculated = hours * 3600 + minutes * 60 const calculated = hours * 3600 + minutes * 60
return isNegative ? -calculated : calculated return isNegative ? -calculated : calculated
}; }
#parseDecimal = () => { #parseDecimal = () => {
return this.#input.replace(/[.,]/g, ".") return this.#input.replace(/[.,]/g, ".")
}; }
#parseTimeAsSeconds = () => { #parseTimeAsSeconds = () => {
const match = this.#isTime() const match = this.#isTime()
@@ -39,12 +41,12 @@ export default class TimeInputParser {
const minutes = parseInt(match[3]) const minutes = parseInt(match[3])
return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative) return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative)
}; }
#parseMinutesAsSeconds = () => { #parseMinutesAsSeconds = () => {
const minutes = parseInt(this.#isMinutes()[1]) const minutes = parseInt(this.#isMinutes()[1])
return minutes * 60 return minutes * 60
}; }
#parseRange = () => { #parseRange = () => {
const match = this.#isRange() const match = this.#isRange()
@@ -54,7 +56,7 @@ export default class TimeInputParser {
const to_hours = parseInt(match[3]) const to_hours = parseInt(match[3])
const to_minutes = parseInt(match[4]) const to_minutes = parseInt(match[4])
return (to_hours - from_hours) * 3600 + (to_minutes - from_minutes) * 60 return (to_hours - from_hours) * 3600 + (to_minutes - from_minutes) * 60
}; }
#parseHoursAndMinutes = () => { #parseHoursAndMinutes = () => {
const match = this.#isHoursAndMinutes() const match = this.#isHoursAndMinutes()
@@ -64,28 +66,26 @@ export default class TimeInputParser {
const minutes = parseInt(match[3]) const minutes = parseInt(match[3])
return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative) return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative)
}; }
#isDecimal = () => { #isDecimal = () => {
return this.#input.match(/^([-]?[0-9]{0,2})[.,]{1}([0-9]{1,2})$/) return this.#input.match(/^([-]?[0-9]{0,2})[.,]{1}([0-9]{1,2})$/)
}; }
#isTime = () => { #isTime = () => {
return this.#input.match(/^([-]?)([0-9]{1,2}):([0-9]{2})$/) return this.#input.match(/^([-]?)([0-9]{1,2}):([0-9]{2})$/)
}; }
#isMinutes = () => { #isMinutes = () => {
return this.#input.match(/^([-]?[0-9]{1,3})(m|mins?)$/) return this.#input.match(/^([-]?[0-9]{1,3})(m|mins?)$/)
}; }
#isRange = () => { #isRange = () => {
return this.#input.match( return this.#input.match(/^([0-9]{1,2})[:.]{0,1}([0-9]{2})-([0-9]{1,2})[:.]{0,1}([0-9]{2})$/)
/^([0-9]{1,2})[:.]{0,1}([0-9]{2})-([0-9]{1,2})[:.]{0,1}([0-9]{2})$/ }
)
};
#isHoursAndMinutes = () => { #isHoursAndMinutes = () => {
// 1h 14m(in) // 1h 14m(in)
return this.#input.match(/^([-]?)([0-9]{1,2})h([0-9]{1,2})(m|mins?)$/) return this.#input.match(/^([-]?)([0-9]{1,2})h([0-9]{1,2})(m|mins?)$/)
}; }
} }

View File

@@ -1,34 +1,53 @@
import { head } from "lodash/fp" import { head, pick, reduce, filter, prop, pipe } from "lodash/fp"
export const isChrome = () => typeof browser === "undefined" && chrome import remoteServices from "../remoteServices"
export const isFirefox = () => typeof browser !== "undefined" && chrome
const DEFAULT_SUBDOMAIN = "unset" const DEFAULT_SUBDOMAIN = "unset"
export const isChrome = () => typeof browser === "undefined" && chrome
export const isFirefox = () => typeof browser !== "undefined" && chrome
export const defaultHostOverrides = pipe(
filter(prop("allowHostOverride")),
reduce((acc, remoteService) => {
acc[remoteService.name] = remoteService.host
return acc
}, {}),
)(remoteServices)
// We pick only the keys defined in `defaultHostOverrides`, so that
// deleted host overrides get cleared from the settings
const getHostOverrides = (settings) => ({
...defaultHostOverrides,
...pick(Object.keys(defaultHostOverrides), settings.hostOverrides || {}),
})
export const getSettings = (withDefaultSubdomain = true) => { export const getSettings = (withDefaultSubdomain = true) => {
const keys = ["subdomain", "apiKey"] const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM", "hostOverrides"]
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, (settings) => {
if (withDefaultSubdomain) { if (withDefaultSubdomain) {
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN settings.subdomain = settings.subdomain || DEFAULT_SUBDOMAIN
} }
resolve({ ...data, version }) settings.hostOverrides = getHostOverrides(settings)
resolve({ ...settings, version })
}) })
}) })
} else { } else {
return browser.storage.sync.get(keys).then(data => { return browser.storage.sync.get(keys).then((settings) => {
if (withDefaultSubdomain) { if (withDefaultSubdomain) {
data.subdomain = data.subdomain || DEFAULT_SUBDOMAIN settings.subdomain = settings.subdomain || DEFAULT_SUBDOMAIN
} }
return { ...data, version } settings.hostOverrides = getHostOverrides(settings)
return { ...settings, version }
}) })
} }
} }
export const setStorage = items => { export const setStorage = (items) => {
if (isChrome()) { if (isChrome()) {
return new Promise(resolve => { return new Promise((resolve) => {
chrome.storage.sync.set(items, resolve) chrome.storage.sync.set(items, resolve)
}) })
} else { } else {
@@ -36,9 +55,9 @@ export const setStorage = items => {
} }
} }
export const queryTabs = queryInfo => { export const queryTabs = (queryInfo) => {
if (isChrome()) { if (isChrome()) {
return new Promise(resolve => chrome.tabs.query(queryInfo, resolve)) return new Promise((resolve) => chrome.tabs.query(queryInfo, resolve))
} else { } else {
return browser.tabs.query(queryInfo) return browser.tabs.query(queryInfo)
} }
@@ -48,4 +67,4 @@ export const getCurrentTab = () => {
return queryTabs({ currentWindow: true, active: true }).then(head) return queryTabs({ currentWindow: true, active: true }).then(head)
} }
export const isBrowserTab = tab => /^(?:chrome|about):/.test(tab.url) export const isBrowserTab = (tab) => /^(?:chrome|about):/.test(tab.url)

View File

@@ -9,71 +9,71 @@ import {
get, get,
find, find,
curry, curry,
pick pick,
head,
defaultTo,
padCharsStart,
} from "lodash/fp" } from "lodash/fp"
import { startOfWeek, endOfWeek } from "date-fns"
import { format } from "date-fns" import { format } from "date-fns"
const nilToArray = input => input || [] const nilToArray = (input) => input || []
export const ERROR_UNAUTHORIZED = "unauthorized" export const ERROR_UNAUTHORIZED = "unauthorized"
export const ERROR_UPGRADE_REQUIRED = "upgrade-required" export const ERROR_UPGRADE_REQUIRED = "upgrade-required"
export const ERROR_UNKNOWN = "unknown" export const ERROR_UNKNOWN = "unknown"
export const noop = () => null export const noop = () => null
export const asArray = (input) => (Array.isArray(input) ? input : [input])
export const findProjectBy = (prop) => (val) => (projects) => {
if (!val) {
return undefined
}
return compose(find(pathEq(prop, val)), flatMap(get("options")))(projects)
}
export const findProjectBy = prop => val =>
compose(
find(pathEq(prop, val)),
flatMap(get("options"))
)
export const findProjectByIdentifier = findProjectBy("identifier") export const findProjectByIdentifier = findProjectBy("identifier")
export const findProjectByValue = findProjectBy("value") export const findProjectByValue = findProjectBy("value")
export const findTask = id => export const findTask = (id) => compose(find(pathEq("value", Number(id))), get("tasks"))
compose(
find(pathEq("value", Number(id))), export const defaultTask = (tasks) =>
get("tasks") compose(defaultTo(head(tasks)), find(pathEq("isDefault", true)), nilToArray)(tasks)
)
function taskOptions(tasks) { function taskOptions(tasks) {
return tasks.map(({ id, name, billable }) => ({ return tasks.map(({ id, name, billable, default: isDefault }) => ({
label: billable ? name : `(${name})`, label: billable ? name : `(${name})`,
value: id, value: id,
billable billable,
isDefault,
})) }))
} }
export function projectOptions(projects) { export function projectOptions(projects) {
return projects.map(project => ({ return projects.map((project) => ({
value: project.id, value: project.id,
label: project.intern ? `(${project.name})` : project.name, label: project.intern ? `(${project.name})` : project.name,
identifier: project.identifier, identifier: project.identifier,
customerName: project.customer_name, customerName: project.customer_name,
tasks: taskOptions(project.tasks) tasks: taskOptions(project.tasks),
})) }))
} }
export const groupedProjectOptions = compose( export const groupedProjectOptions = compose(
map(([customerName, projects]) => ({ map(([customerName, projects]) => ({
label: customerName, label: customerName,
options: projectOptions(projects) options: projectOptions(projects),
})), })),
toPairs, toPairs,
groupBy("customer_name"), groupBy("customer_name"),
nilToArray nilToArray,
) )
export const serializeProps = attrs => export const serializeProps = (attrs) => compose(mapValues(JSON.stringify), pick(attrs))
compose(
mapValues(JSON.stringify),
pick(attrs)
)
export const parseProps = attrs => export const parseProps = (attrs) => compose(mapValues(JSON.parse), pick(attrs))
compose(
mapValues(JSON.parse),
pick(attrs)
)
export const trace = curry((tag, value) => { export const trace = curry((tag, value) => {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@@ -82,12 +82,13 @@ export const trace = curry((tag, value) => {
}) })
export const weekStartsOn = 1 export const weekStartsOn = 1
export const formatDate = date => format(date, "YYYY-MM-DD") export const formatDate = (date) => format(date, "yyyy-MM-dd")
export const getStartOfWeek = () => startOfWeek(new Date(), { weekStartsOn })
export const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
export const extensionSettingsUrl = () => export const extensionSettingsUrl = () => `chrome://extensions/?id=${chrome.runtime.id}`
`chrome://extensions/?id=${chrome.runtime.id}`
export const extractAndSetTag = changeset => { export const extractAndSetTag = (changeset) => {
let { description } = changeset let { description } = changeset
const match = description.match(/^#(\S+)/) const match = description.match(/^#(\S+)/)
if (!match) { if (!match) {
@@ -96,6 +97,25 @@ export const extractAndSetTag = changeset => {
return { return {
...changeset, ...changeset,
description: description.replace(/^#\S+\s/, ""), description: description.replace(/^#\S+\s/, ""),
tag: match[1] tag: match[1],
}
}
export const formatDuration = (
durationInSeconds,
{ settingTimeTrackingHHMM = true, showSeconds = true } = {},
) => {
if (settingTimeTrackingHHMM) {
const hours = Math.floor(durationInSeconds / 3600)
const minutes = Math.floor((durationInSeconds % 3600) / 60)
const result = `${hours}:${padCharsStart("0", 2, minutes)}`
if (!showSeconds) {
return result
} else {
const seconds = durationInSeconds % 60
return result + `:${padCharsStart("0", 2, seconds)}`
}
} else {
return (durationInSeconds / 3600).toFixed(2)
} }
} }

View File

@@ -4,45 +4,55 @@ import {
ERROR_UPGRADE_REQUIRED, ERROR_UPGRADE_REQUIRED,
ERROR_UNKNOWN, ERROR_UNKNOWN,
groupedProjectOptions, groupedProjectOptions,
weekStartsOn getStartOfWeek,
getEndOfWeek,
} from "utils" } from "utils"
import { get, forEach, reject, isNil } from "lodash/fp" import { get, forEach, reject, isNil } from "lodash/fp"
import { startOfWeek, endOfWeek } from "date-fns"
import { createMatcher } from "utils/urlMatcher" import { createMatcher } from "utils/urlMatcher"
import remoteServices from "remoteServices" import remoteServices from "remoteServices"
import { queryTabs, isBrowserTab, getSettings } from "utils/browser" import { queryTabs, isBrowserTab, getSettings, setStorage } from "utils/browser"
const getStartOfWeek = () => startOfWeek(new Date(), { weekStartsOn }) let matcher
const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
const matcher = createMatcher(remoteServices) const initMatcher = (settings) => {
matcher = createMatcher(remoteServices, settings.hostOverrides)
}
getSettings().then((settings) => {
initMatcher(settings)
})
export function tabUpdated(tab, { messenger, settings }) { export function tabUpdated(tab, { messenger, settings }) {
messenger.connectTab(tab) messenger.connectTab(tab)
const service = matcher(tab.url) const service = matcher(tab.url)
const apiClient = new ApiClient(settings)
if (service?.match?.id) { if (service?.match?.id) {
messenger.postMessage(tab, { type: "requestService" }) messenger.postMessage(tab, { type: "requestService" })
messenger.once("newService", ({ payload: { service } }) => { messenger.once("newService", ({ payload: { service } }) => {
const apiClient = new ApiClient(settings)
apiClient apiClient
.bookedHours(service) .activitiesStatus(service)
.then(({ data }) => { .then(({ data }) => {
messenger.postMessage(tab, { messenger.postMessage(tab, {
type: "showBubble", type: "showBubble",
payload: { payload: {
bookedHours: parseFloat(data[0]?.hours) || 0, bookedSeconds: data.seconds,
service settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
} timedActivity: data.timed_activity,
service,
},
}) })
}) })
.catch(() => { .catch(() => {
messenger.postMessage(tab, { messenger.postMessage(tab, {
type: "showBubble", type: "showBubble",
payload: { payload: {
bookedHours: 0, bookedSeconds: 0,
service settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
} service,
},
}) })
}) })
}) })
@@ -52,13 +62,15 @@ export function tabUpdated(tab, { messenger, settings }) {
} }
export function settingsChanged(settings, { messenger }) { export function settingsChanged(settings, { messenger }) {
initMatcher(settings)
queryTabs({ currentWindow: true }) queryTabs({ currentWindow: true })
.then(reject(isBrowserTab)) .then(reject(isBrowserTab))
.then( .then(
forEach(tab => { forEach(tab => {
messenger.postMessage(tab, { type: "closePopup" }) messenger.postMessage(tab, { type: "closePopup" })
tabUpdated(tab, { settings, messenger }) tabUpdated(tab, { settings, messenger })
}) }),
) )
} }
@@ -76,25 +88,36 @@ export function togglePopup(tab, { messenger }) {
} }
} }
async function openPopup(tab, { service, messenger }) { export 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()
const settings = await getSettings() const settings = await getSettings()
const apiClient = new ApiClient(settings) const apiClient = new ApiClient(settings)
const responses = []
try { try {
const responses = await Promise.all([ responses.push(await apiClient.login(service))
apiClient.login(service), // we can forgo the following calls if a timed activity exists
if (!responses[0].data.timed_activity) {
responses.push(
...(await Promise.all([
apiClient.projects(), apiClient.projects(),
apiClient.activities(fromDate, toDate), apiClient.activities(fromDate, toDate),
apiClient.schedules(fromDate, toDate) apiClient.schedules(fromDate, toDate),
]) ])),
)
}
const settingTimeTrackingHHMM = get("[0].data.setting_time_tracking_hh_mm", responses)
!isNil(settingTimeTrackingHHMM) && setStorage({ settingTimeTrackingHHMM })
const action = { const action = {
type: "openPopup", type: "openPopup",
payload: { payload: {
service, service,
subdomain: settings.subdomain, subdomain: settings.subdomain,
timedActivity: get("[0].data.timed_activity", responses),
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),
@@ -103,8 +126,8 @@ async function openPopup(tab, { service, messenger }) {
schedules: get("[3].data", responses), schedules: get("[3].data", responses),
fromDate, fromDate,
toDate, toDate,
loading: false loading: false,
} },
} }
messenger.postMessage(tab, action) messenger.postMessage(tab, action)
} catch (error) { } catch (error) {
@@ -119,7 +142,7 @@ async function openPopup(tab, { service, messenger }) {
} }
messenger.postMessage(tab, { messenger.postMessage(tab, {
type: "openPopup", type: "openPopup",
payload: { errorType, errorMessage } payload: { errorType, errorMessage },
}) })
} }
} }

View File

@@ -1,14 +1,14 @@
export class BackgroundMessenger { export class BackgroundMessenger {
#ports = new Map(); #ports = new Map()
#handlers = new Map(); #handlers = new Map()
#onceHandlers = new Map(); #onceHandlers = new Map()
#handler = action => { #handler = action => {
const handler = this.#handlers.get(action.type) const handler = this.#handlers.get(action.type)
if (handler) { if (handler) {
handler(action) handler(action)
} }
}; }
#onceHandler = action => { #onceHandler = action => {
const handler = this.#onceHandlers.get(action.type) const handler = this.#onceHandlers.get(action.type)
@@ -16,7 +16,7 @@ export class BackgroundMessenger {
if (handler) { if (handler) {
handler(action) handler(action)
} }
}; }
#registerPort = (tabId, port) => { #registerPort = (tabId, port) => {
this.#ports.set(tabId, port) this.#ports.set(tabId, port)
@@ -25,14 +25,14 @@ export class BackgroundMessenger {
port.onDisconnect.addListener(() => { port.onDisconnect.addListener(() => {
this.#unregisterPort(tabId, port) this.#unregisterPort(tabId, port)
}) })
}; }
#unregisterPort = (tabId, port) => { #unregisterPort = (tabId, port) => {
port.onMessage.removeListener(this.#handler) port.onMessage.removeListener(this.#handler)
port.onMessage.removeListener(this.#onceHandler) port.onMessage.removeListener(this.#onceHandler)
port.disconnect() port.disconnect()
this.#ports.delete(tabId) this.#ports.delete(tabId)
}; }
connectTab = tab => { connectTab = tab => {
const currentPort = this.#ports.get(tab.id) const currentPort = this.#ports.get(tab.id)
@@ -40,41 +40,41 @@ export class BackgroundMessenger {
const port = chrome.tabs.connect(tab.id) const port = chrome.tabs.connect(tab.id)
this.#registerPort(tab.id, port) this.#registerPort(tab.id, port)
} }
}; }
disconnectTab = tabId => { disconnectTab = tabId => {
const port = this.#ports.get(tabId) const port = this.#ports.get(tabId)
if (port) { if (port) {
this.#unregisterPort(tabId, port) this.#unregisterPort(tabId, port)
} }
}; }
postMessage = (tab, action) => { postMessage = (tab, action) => {
const port = this.#ports.get(tab.id) const port = this.#ports.get(tab.id)
if (port) { if (port) {
port.postMessage(action) port.postMessage(action)
} }
}; }
once = (type, handler) => { once = (type, handler) => {
this.#onceHandlers.set(type, handler) this.#onceHandlers.set(type, handler)
}; }
on = (type, handler) => { on = (type, handler) => {
this.#handlers.set(type, handler) this.#handlers.set(type, handler)
}; }
} }
export class ContentMessenger { export class ContentMessenger {
#port; #port
#handlers = new Map(); #handlers = new Map()
#handler = action => { #handler = action => {
const handler = this.#handlers.get(action.type) const handler = this.#handlers.get(action.type)
if (handler) { if (handler) {
handler(action) handler(action)
} }
}; }
constructor(port) { constructor(port) {
this.#port = port this.#port = port
@@ -85,15 +85,15 @@ export class ContentMessenger {
if (this.#port) { if (this.#port) {
this.#port.postMessage(action) this.#port.postMessage(action)
} }
}; }
on = (type, handler) => { on = (type, handler) => {
this.#handlers.set(type, handler) this.#handlers.set(type, handler)
}; }
stop = () => { stop = () => {
this.#port.onMessage.removeListener(this.#handler) this.#port.onMessage.removeListener(this.#handler)
this.#port = null this.#port = null
this.#handlers.clear() this.#handlers.clear()
}; }
} }

View File

@@ -1,39 +0,0 @@
import React from "react"
import bugsnag from "@bugsnag/js"
import bugsnagReact from "@bugsnag/plugin-react"
import { includes } from "lodash/fp"
function getAppVersion() {
try {
return chrome.runtime.getManifest().version
} catch (error) {
return
}
}
const filterReport = report => {
const appVersion = getAppVersion()
if (!appVersion) {
return false
}
const scripts = ["background", "content", "options", "popup"].map(
file => `${chrome.extension.getURL(file)}.${appVersion}.js`
)
return scripts.some(script => report.stacktrace.some(includes(script)))
}
const bugsnagClient = bugsnag({
apiKey: process.env.BUGSNAG_API_KEY,
appVersion: getAppVersion(),
collectUserIp: false,
beforeSend: filterReport,
releaseStage: process.env.NODE_ENV,
notifyReleaseStages: ["production"]
})
bugsnagClient.use(bugsnagReact, React)
export default bugsnagClient
export const ErrorBoundary = bugsnagClient.getPlugin("react")

View File

@@ -1,22 +1,33 @@
import UrlPattern from "url-pattern" import UrlPattern from "url-pattern"
import { import { isFunction, isUndefined, compose, toPairs, map, pipe, isNil, reduce } from "lodash/fp"
isFunction, import { asArray } from "./index"
isUndefined,
compose,
toPairs,
map,
pipe
} from "lodash/fp"
import queryString from "query-string" import queryString from "query-string"
const extractQueryParams = (queryParams, query) => { function parseUrl(url) {
return toPairs(queryParams).reduce((acc, [key, param]) => { const urlObject = new URL(url)
const { origin, pathname, search } = urlObject
let { hash } = urlObject
const query = {
...queryString.parse(search),
...queryString.parse(hash),
}
if (hash) {
hash = hash.match(/#[^&]+/)[0]
}
return { origin, pathname, hash, query }
}
function extractQueryParams(queryParams, query) {
return toPairs(queryParams).reduce((acc, [key, params]) => {
const param = asArray(params).find((param) => !isNil(query[param]))
if (param) {
acc[key] = query[param] acc[key] = query[param]
}
return acc return acc
}, {}) }, {})
} }
const createEvaluator = args => fnOrValue => { const createEvaluator = (args) => (fnOrValue) => {
if (isUndefined(fnOrValue)) { if (isUndefined(fnOrValue)) {
return return
} }
@@ -28,21 +39,35 @@ const createEvaluator = args => fnOrValue => {
return fnOrValue return fnOrValue
} }
const prepareHostForRegExp = (host) => {
return host.replace(":", "\\:")
}
const replaceHostInPattern = (host, pattern) => {
if (typeof pattern === "string") {
return pattern.replace(":host:", prepareHostForRegExp(host))
} else if (pattern instanceof RegExp) {
return new RegExp(pattern.source.replace(":host:", prepareHostForRegExp(host)))
} else {
return pattern
}
}
const parseServices = compose( const parseServices = compose(
map(([key, config]) => ({ map(([key, config]) => ({
...config, ...config,
key, key,
patterns: config.urlPatterns.map(pattern => { patterns: config.urlPatterns.map((pattern) => {
if (Array.isArray(pattern)) { if (Array.isArray(pattern)) {
return new UrlPattern(...pattern) return new UrlPattern(...pattern.map((p) => replaceHostInPattern(config.host, p)))
} }
return new UrlPattern(pattern) return new UrlPattern(replaceHostInPattern(config.host, pattern))
}) }),
})), })),
toPairs toPairs,
) )
export const createEnhancer = document => service => { export const createEnhancer = (document) => (service) => {
if (!service) { if (!service) {
return return
} }
@@ -57,31 +82,41 @@ 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 || { right: "calc(2rem + 5px)" } position: service.position || { right: "calc(2rem + 5px)" },
} }
} }
export const createMatcher = remoteServices => { const applyHostOverrides = (remoteServices, hostOverrides) =>
const services = parseServices(remoteServices) pipe(
return tabUrl => { toPairs,
const { origin, pathname, hash, search } = new URL(tabUrl) reduce((acc, [key, remoteService]) => {
acc[key] = {
...remoteService,
key,
host: hostOverrides[remoteService.name] || remoteService.host,
}
return acc
}, {}),
)(remoteServices)
export const createMatcher = (remoteServices, hostOverrides) => {
const services = parseServices(applyHostOverrides(remoteServices, hostOverrides))
return (tabUrl) => {
const { origin, pathname, hash, query } = parseUrl(tabUrl)
const url = `${origin}${pathname}${hash}` const url = `${origin}${pathname}${hash}`
const query = queryString.parse(search) const service = services.find((service) => {
const service = services.find(service => return service.patterns.some((pattern) => pattern.match(url))
service.patterns.some(pattern => pattern.match(url)) })
)
if (!service) { if (!service) {
return return
} }
const pattern = service.patterns.find(pattern => pattern.match(url)) const pattern = service.patterns.find((pattern) => pattern.match(url))
let match = pattern.match(url) let match = pattern.match(url)
if (service.queryParams) { if (service.queryParams) {
const extractedQueryParams = extractQueryParams( const extractedQueryParams = extractQueryParams(service.queryParams, query)
service.queryParams,
query
)
match = { ...extractedQueryParams, ...match } match = { ...extractedQueryParams, ...match }
} }
@@ -89,16 +124,13 @@ export const createMatcher = remoteServices => {
...match, ...match,
...service, ...service,
url: tabUrl, url: tabUrl,
match match,
} }
} }
} }
export const createServiceFinder = remoteServices => document => { export const createServiceFinder = (remoteServices, hostOverrides) => (document) => {
const matcher = createMatcher(remoteServices) const matcher = createMatcher(remoteServices, hostOverrides)
const enhancer = createEnhancer(document) const enhancer = createEnhancer(document)
return pipe( return pipe(matcher, enhancer)
matcher,
enhancer
)
} }

View File

@@ -4,26 +4,21 @@
"description": "MOCO Zeiterfassung Plugin", "description": "MOCO Zeiterfassung Plugin",
"version": "0.9.20", "version": "0.9.20",
"manifest_version": 2, "manifest_version": 2,
"description": "MOCO Time Tracking Plugin",
"icons": { "icons": {
"16": "src/images/logo.png", "16": "src/images/moco-32x32.png",
"32": "src/images/logo.png", "32": "src/images/moco-32x32.png",
"48": "src/images/logo.png", "48": "src/images/moco-159x159.png",
"128": "src/images/logo.png" "128": "src/images/moco-159x159.png"
}, },
"options_ui": { "options_ui": {
"page": "options.html" "page": "options.html"
}, },
"permissions": [ "permissions": ["https://*.mocoapp.com/*", "storage", "tabs"],
"https://*.mocoapp.com/*",
"storage",
"tabs"
],
"background": { "background": {
"page": "background.html" "page": "background.html"
}, },
"browser_action": { "browser_action": {
"default_icon": "src/images/logo.png" "default_icon": "src/images/moco-32x32.png"
}, },
"content_scripts": [ "content_scripts": [
{ {

View File

@@ -9,14 +9,16 @@ export const projects = [
{ {
id: 2733682, id: 2733682,
name: "Bugfixing", name: "Bugfixing",
billable: true billable: true,
default: false,
}, },
{ {
id: 2733681, id: 2733681,
name: "Development", name: "Development",
billable: true billable: true,
} default: true,
] },
],
}, },
{ {
id: 944724773, id: 944724773,
@@ -28,24 +30,28 @@ export const projects = [
{ {
id: 1621304, id: 1621304,
name: "Roadmap Features", name: "Roadmap Features",
billable: true billable: true,
default: false,
}, },
{ {
id: 1621310, id: 1621310,
name: "Bugfixing", name: "Bugfixing",
billable: true billable: true,
default: false,
}, },
{ {
id: 1621305, id: 1621305,
name: "Quickwins", name: "Quickwins",
billable: true billable: true,
default: true,
}, },
{ {
id: 1621323, id: 1621323,
name: "Refactorings", name: "Refactorings",
billable: true billable: true,
} default: false,
] },
],
}, },
{ {
id: 944837106, id: 944837106,
@@ -57,24 +63,28 @@ export const projects = [
{ {
id: 2500080, id: 2500080,
name: "Intercom & Mails", name: "Intercom & Mails",
billable: false billable: false,
default: false,
}, },
{ {
id: 2500081, id: 2500081,
name: "Demos", name: "Demos",
billable: false billable: false,
default: true,
}, },
{ {
id: 2506050, id: 2506050,
name: "Calls", name: "Calls",
billable: false billable: false,
default: false,
}, },
{ {
id: 2500084, id: 2500084,
name: "Importe", name: "Importe",
billable: false billable: false,
} default: false,
] },
],
}, },
{ {
id: 944621413, id: 944621413,
@@ -86,23 +96,27 @@ export const projects = [
{ {
id: 874014, id: 874014,
name: "Entwicklung", name: "Entwicklung",
billable: true billable: true,
default: false,
}, },
{ {
id: 874015, id: 874015,
name: "Grafik", name: "Grafik",
billable: true billable: true,
default: false,
}, },
{ {
id: 874016, id: 874016,
name: "Konzept", name: "Konzept",
billable: true billable: true,
default: false,
}, },
{ {
id: 874017, id: 874017,
name: "Projektleitung", name: "Projektleitung",
billable: true billable: true,
} default: false,
] },
} ],
},
] ]

View File

@@ -3,16 +3,26 @@ import {
findProjectByValue, findProjectByValue,
findProjectByIdentifier, findProjectByIdentifier,
findTask, findTask,
defaultTask,
groupedProjectOptions, groupedProjectOptions,
extractAndSetTag extractAndSetTag,
formatDuration,
} from "../../src/js/utils" } from "../../src/js/utils"
import { map } from "lodash/fp" import { map, compose } from "lodash/fp"
const getProjectBy = finder => key =>
compose(
finder(key),
groupedProjectOptions,
)(projects)
const getProjectByValue = getProjectBy(findProjectByValue)
const getProjectByIdentifier = getProjectBy(findProjectByIdentifier)
describe("utils", () => { describe("utils", () => {
describe("findProjectByValue", () => { describe("findProjectByValue", () => {
it("finds an existing project", () => { it("finds an existing project", () => {
const options = groupedProjectOptions(projects) const project = getProjectByValue(944837106)
const project = findProjectByValue(944837106)(options)
expect(project.value).toEqual(944837106) expect(project.value).toEqual(944837106)
expect(project.label).toEqual("Support") expect(project.label).toEqual("Support")
expect(project.customerName).toEqual("MOCO APP") expect(project.customerName).toEqual("MOCO APP")
@@ -20,14 +30,12 @@ describe("utils", () => {
}) })
it("returns undefined if project is not found", () => { it("returns undefined if project is not found", () => {
const options = groupedProjectOptions(projects) const project = getProjectByValue(123)
const project = findProjectByValue(123)(options)
expect(project).toBe(undefined) expect(project).toBe(undefined)
}) })
it("returns undefined for undefined id", () => { it("returns undefined for undefined id", () => {
const options = groupedProjectOptions(projects) const project = getProjectByValue(undefined)
const project = findProjectByValue(undefined)(options)
expect(project).toBe(undefined) expect(project).toBe(undefined)
}) })
}) })
@@ -57,16 +65,14 @@ describe("utils", () => {
describe("findTask", () => { describe("findTask", () => {
it("find an existing task", () => { it("find an existing task", () => {
const options = groupedProjectOptions(projects) const project = getProjectByValue(944837106)
const project = findProjectByValue(944837106)(options)
const task = findTask(2506050)(project) const task = findTask(2506050)(project)
expect(task.value).toEqual(2506050) expect(task.value).toEqual(2506050)
expect(task.label).toEqual("(Calls)") expect(task.label).toEqual("(Calls)")
}) })
it("returns undefined if task is not found", () => { it("returns undefined if task is not found", () => {
const options = groupedProjectOptions(projects) const project = getProjectByValue(944837106)
const project = findProjectByValue(944837106)(options)
const task = findTask(123)(project) const task = findTask(123)(project)
expect(task).toBe(undefined) expect(task).toBe(undefined)
}) })
@@ -77,14 +83,32 @@ describe("utils", () => {
}) })
}) })
describe("defaultTask", () => {
it("find a default task", () => {
const project = getProjectByValue(944837106)
const task = defaultTask(project.tasks)
expect(task.label).toBe("(Demos)")
})
it("returns first task if no default is defined", () => {
const project = getProjectByValue(944621413)
const task = defaultTask(project.tasks)
expect(task.label).toBe("Entwicklung")
})
it("return undefined if no tasks given", () => {
let task = defaultTask(null)
expect(task).toBeUndefined()
task = defaultTask([])
expect(task).toBeUndefined()
})
})
describe("groupedProjectOptions", () => { describe("groupedProjectOptions", () => {
it("transforms projects into grouped options by company", () => { it("transforms projects into grouped options by company", () => {
const result = groupedProjectOptions(projects) const result = groupedProjectOptions(projects)
expect(map("label", result)).toEqual([ expect(map("label", result)).toEqual(["Simplificator", "MOCO APP", "sharoo"])
"Simplificator",
"MOCO APP",
"sharoo"
])
}) })
}) })
@@ -92,19 +116,19 @@ describe("utils", () => {
it("sets the correct tag and updates description", () => { it("sets the correct tag and updates description", () => {
const changeset = { const changeset = {
description: "#meeting Lorem ipsum", description: "#meeting Lorem ipsum",
tag: "" tag: "",
} }
expect(extractAndSetTag(changeset)).toEqual({ expect(extractAndSetTag(changeset)).toEqual({
description: "Lorem ipsum", description: "Lorem ipsum",
tag: "meeting" tag: "meeting",
}) })
}) })
it("only matches tag at the beginning", () => { it("only matches tag at the beginning", () => {
const changeset = { const changeset = {
description: "Lorem #meeting ipsum", description: "Lorem #meeting ipsum",
tag: "" tag: "",
} }
expect(extractAndSetTag(changeset)).toEqual(changeset) expect(extractAndSetTag(changeset)).toEqual(changeset)
@@ -113,10 +137,27 @@ describe("utils", () => {
it("returns the changeset if not tag is set", () => { it("returns the changeset if not tag is set", () => {
const changeset = { const changeset = {
description: "Without tag", description: "Without tag",
tag: "" tag: "",
} }
expect(extractAndSetTag(changeset)).toEqual(changeset) expect(extractAndSetTag(changeset)).toEqual(changeset)
}) })
}) })
describe("formatDuration", () => {
it("format with defaults", () => {
expect(formatDuration(3600)).toBe("1:00:00")
expect(formatDuration(3661)).toBe("1:01:01")
})
it("format without seconds", () => {
expect(formatDuration(3600, { showSeconds: false })).toBe("1:00")
expect(formatDuration(3661, { showSeconds: false })).toBe("1:01")
})
it("format in decimals", () => {
expect(formatDuration(3600, { settingTimeTrackingHHMM: false })).toBe("1.00")
expect(formatDuration(3661, { settingTimeTrackingHHMM: false })).toBe("1.02")
})
})
}) })

View File

@@ -6,21 +6,19 @@ describe("utils", () => {
let matcher let matcher
beforeEach(() => { beforeEach(() => {
matcher = createMatcher(remoteServices) matcher = createMatcher(remoteServices, {})
}) })
describe("createMatcher", () => { describe("createMatcher", () => {
it("matches host and path", () => { it("matches host and path", () => {
const service = matcher( const service = matcher("https://github.com/hundertzehn/mocoapp/pull/123")
"https://github.com/hundertzehn/mocoapp/pull/123"
)
expect(service.key).toEqual("github-pr") expect(service.key).toEqual("github-pr")
expect(service.name).toEqual("github") expect(service.name).toEqual("github")
}) })
it("matches query string", () => { it("matches query string", () => {
let service = matcher( let service = matcher(
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail&selectedIssue=TEST1-1" "https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail&selectedIssue=TEST1-1",
) )
expect(service.key).toEqual("jira") expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira") expect(service.name).toEqual("jira")
@@ -32,7 +30,7 @@ describe("utils", () => {
expect(service.match.id).toEqual("TEST1-1") expect(service.match.id).toEqual("TEST1-1")
service = matcher( service = matcher(
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail" "https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail",
) )
expect(service.key).toEqual("jira") expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira") expect(service.name).toEqual("jira")
@@ -44,7 +42,7 @@ describe("utils", () => {
expect(service.match.id).toBeUndefined() expect(service.match.id).toBeUndefined()
service = matcher( service = matcher(
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail&selectedIssue=" "https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&projectKey=TEST1&modal=detail&selectedIssue=",
) )
expect(service.key).toEqual("jira") expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira") expect(service.name).toEqual("jira")
@@ -55,9 +53,7 @@ describe("utils", () => {
expect(service.match.projectId).toEqual("TEST1") expect(service.match.projectId).toEqual("TEST1")
expect(service.match.id).toEqual("") expect(service.match.id).toEqual("")
service = matcher( service = matcher("https://moco-bx.atlassian.net/secure/RapidBoard.jspa")
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa"
)
expect(service.key).toEqual("jira") expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira") expect(service.name).toEqual("jira")
expect(service.match.org).toEqual("moco-bx") expect(service.match.org).toEqual("moco-bx")
@@ -65,7 +61,7 @@ describe("utils", () => {
expect(service.match.id).toBeUndefined() expect(service.match.id).toBeUndefined()
service = matcher( service = matcher(
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&modal=detail&selectedIssue=TEST2-1" "https://moco-bx.atlassian.net/secure/RapidBoard.jspa?rapidView=2&modal=detail&selectedIssue=TEST2-1",
) )
expect(service.key).toEqual("jira") expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira") expect(service.name).toEqual("jira")
@@ -78,29 +74,84 @@ describe("utils", () => {
}) })
it("matches url with hash", () => { it("matches url with hash", () => {
let service = matcher( let service = matcher("https://www.wunderlist.com/webapp#/tasks/4771178545")
"https://www.wunderlist.com/webapp#/tasks/4771178545"
)
expect(service.key).toEqual("wunderlist") expect(service.key).toEqual("wunderlist")
expect(service.name).toEqual("wunderlist") expect(service.name).toEqual("wunderlist")
expect(service.match.id).toEqual("4771178545") expect(service.match.id).toEqual("4771178545")
}) })
it("does not match different host", () => { it("does not match different host", () => {
const service = matcher( const service = matcher("https://trello.com/hundertzehn/mocoapp/pull/123")
"https://trello.com/hundertzehn/mocoapp/pull/123"
)
expect(service).toBeFalsy() expect(service).toBeFalsy()
}) })
it("matches query string in the hash", () => {
const service = matcher(
"https://www.wrike.com/workspace.htm?acc=2771711#path=folder&id=342769537&p=342762920&a=2771711&c=board&ot=342769562&so=10&bso=10&sd=0&st=space-342762920",
)
expect(service.key).toEqual("wrike")
expect(service.name).toEqual("wrike")
expect(service.id).toEqual("342769562")
expect(service.match.id).toEqual("342769562")
})
it("matches query parameter with different names", () => {
expect(
matcher(
"https://www.wrike.com/workspace.htm?acc=2771711#path=mywork&id=342769537&p=342762920&a=2771711&c=board&ot=1234&so=10&bso=10&sd=0&st=space-342762920",
).id,
).toEqual("1234")
expect(
matcher(
"https://www.wrike.com/workspace.htm?acc=2771711#path=folder&id=342769537&p=342762920&a=2771711&c=board&t=1234&so=10&bso=10&sd=0&st=space-342762920",
).id,
).toEqual("1234")
})
it("should match gitlab-mergerequest url", () => {
const service = matcher(
"https://gitlab.com/testorganisatzion/testproject/-/merge_requests/1",
)
expect(service.id).toEqual("1")
expect(service.match.id).toEqual("1")
expect(service.name).toEqual("gitlab")
expect(service.projectId).toEqual("testproject")
})
it("should match gitlab-mergerequest url with group", () => {
const service = matcher(
"https://gitlab.com/testorganisatzion/test-group/testproject/-/merge_requests/1",
)
expect(service.id).toEqual("1")
expect(service.match.id).toEqual("1")
expect(service.name).toEqual("gitlab")
expect(service.projectId).toEqual("testproject")
})
it("should match gitlab-issue url", () => {
const service = matcher("https://gitlab.com/testorganisatzion/testproject/-/issues/1")
expect(service.id).toEqual("1")
expect(service.match.id).toEqual("1")
expect(service.name).toEqual("gitlab")
expect(service.projectId).toEqual("testproject")
})
it("should match gitlab-issue url with group", () => {
const service = matcher(
"https://gitlab.com/testorganisatzion/test-group/testproject/-/issues/1",
)
expect(service.id).toEqual("1")
expect(service.match.id).toEqual("1")
expect(service.name).toEqual("gitlab")
expect(service.projectId).toEqual("testproject")
})
}) })
describe("createEnhancer", () => { describe("createEnhancer", () => {
it("enhances a services", () => { it("enhances a services", () => {
const url = "https://github.com/hundertzehn/mocoapp/pull/123" const url = "https://github.com/hundertzehn/mocoapp/pull/123"
const document = { const document = {
querySelector: jest querySelector: jest.fn().mockReturnValue({ textContent: "[4321] Foo" }),
.fn()
.mockReturnValue({ textContent: "[4321] Foo" })
} }
const service = matcher(url) const service = matcher(url)
const enhancedService = createEnhancer(document)(service) const enhancedService = createEnhancer(document)(service)
@@ -111,4 +162,27 @@ describe("utils", () => {
}) })
}) })
}) })
describe("urlMatcher with overrideHosts", () => {
let matcher
beforeEach(() => {
matcher = createMatcher(remoteServices, {
github: "https://my-custom-github-url.com",
})
})
describe("createMatcher", () => {
it("matches overridden host and path", () => {
const service = matcher("https://my-custom-github-url.com/hundertzehn/mocoapp/pull/123")
expect(service.key).toEqual("github-pr")
expect(service.name).toEqual("github")
})
it("doesn't match default host and path", () => {
const service = matcher("https://github.com/hundertzehn/mocoapp/pull/123")
expect(service).toBe(undefined)
})
})
})
}) })

View File

@@ -2,27 +2,22 @@ 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")
const MiniCssExtractPlugin = require("mini-css-extract-plugin") const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin") const HtmlWebpackPlugin = require("html-webpack-plugin")
const RemoveSourceMapPlugin = require("./webpack/RemoveSourceMapPlugin")
const ZipPlugin = require("zip-webpack-plugin") const ZipPlugin = require("zip-webpack-plugin")
const {
BugsnagBuildReporterPlugin,
BugsnagSourceMapUploaderPlugin
} = require("webpack-bugsnag-plugins")
module.exports = env => { module.exports = (env) => {
const config = { const config = {
entry: { entry: {
background: "./src/js/background.js", background: "./src/js/background.js",
content: "./src/js/content.js", content: "./src/js/content.js",
popup: "./src/js/popup.js", popup: "./src/js/popup.js",
options: "./src/js/options.js" options: "./src/js/options.js",
}, },
output: { output: {
path: path.join(__dirname, `build/${env.browser}`), path: path.join(__dirname, `build/${env.browser}`),
filename: `[name].${process.env.npm_package_version}.js` filename: `[name].${process.env.npm_package_version}.js`,
}, },
module: { module: {
rules: [ rules: [
@@ -30,104 +25,85 @@ module.exports = env => {
test: /\.scss$/, test: /\.scss$/,
use: [ use: [
{ {
loader: MiniCssExtractPlugin.loader loader: MiniCssExtractPlugin.loader,
}, },
"css-loader", "css-loader",
{ {
loader: "sass-loader", loader: "sass-loader",
options: { options: {
includePaths: [path.join(__dirname, "src/css")] sassOptions: {
} includePaths: [path.join(__dirname, "src/css")],
} },
},
},
], ],
exclude: /node_modules/ exclude: /node_modules/,
}, },
{ {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: "babel-loader" loader: "babel-loader",
} },
}, },
{ {
test: /\.(jpg|png)$/, test: /\.(jpg|png)$/,
loader: "file-loader", loader: "file-loader",
options: { options: {
name: "[path][name].[ext]" name: "[path][name].[ext]",
}, },
exclude: /node_modules/ exclude: /node_modules/,
} },
] {
test: /\.svg$/,
loader: "svg-inline-loader",
},
],
}, },
plugins: [ plugins: [
new CleanWebpackPlugin([`build/${env.browser}`]), new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ["!manifest.json", "!*.html"],
}),
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",
chunkFilename: "[id].css" chunkFilename: "[id].css",
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "background.html"), template: path.join(__dirname, "src", "background.html"),
filename: "background.html", filename: path.resolve(`build/${env.browser}`, "background.html"),
chunks: ["background"] chunks: ["background"],
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "popup.html"), template: path.join(__dirname, "src", "popup.html"),
filename: "popup.html", filename: path.resolve(`build/${env.browser}`, "popup.html"),
chunks: ["popup"] chunks: ["popup"],
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "options.html"), template: path.join(__dirname, "src", "options.html"),
filename: "options.html", filename: path.resolve(`build/${env.browser}`, "options.html"),
chunks: ["options"] chunks: ["options"],
}) }),
], ],
resolve: { resolve: {
modules: [path.join(__dirname, "src/js"), "node_modules"], modules: [path.join(__dirname, "src/js"), "node_modules"],
alias: { alias: {
images: path.join(__dirname, "src/images") images: path.join(__dirname, "src/images"),
} },
}, },
mode: env.NODE_ENV || "development", mode: env.NODE_ENV || "development",
devtool: "cheap-module-source-map" devtool: "cheap-module-source-map",
} }
if (env.NODE_ENV === "production") { if (env.NODE_ENV === "production") {
config.devtool = "source-maps" config.devtool = "none"
if (process.env.BUGSNAG_API_KEY) {
config.plugins.push( config.plugins.push(
new BugsnagBuildReporterPlugin({
apiKey: process.env.BUGSNAG_API_KEY,
appVersion: process.env.npm_package_version,
releaseStage: "production"
}),
// important: upload sourcemaps before removing source mapping url
new BugsnagSourceMapUploaderPlugin({
apiKey: process.env.BUGSNAG_API_KEY,
appVersion: process.env.npm_package_version,
publicPath:
env.browser === "firefox"
? "moz-extension*://*/"
: "chrome-extension*://*/", // extra asterisk after protocol needed
overwrite: true
})
)
}
config.plugins.push(
new RemoveSourceMapPlugin(),
new ZipPlugin({ new ZipPlugin({
filename: `moco-bx-${env.browser}-v${ filename: `moco-bx-${env.browser}-v${process.env.npm_package_version}.zip`,
process.env.npm_package_version exclude: [/\.map$/],
}.zip`, }),
exclude: [/\.map$/]
})
) )
} }

View File

@@ -3,39 +3,37 @@ const { compact } = require("lodash/fp")
const baseConfig = require("./webpack.base.config") const baseConfig = require("./webpack.base.config")
module.exports = env => { module.exports = (env) => {
const config = baseConfig(env) const config = baseConfig(env)
config.plugins.unshift( config.plugins.unshift(
new CopyWebpackPlugin([ new CopyWebpackPlugin({
patterns: [
{ {
from: "src/manifest.json", from: "src/manifest.json",
transform: function(content, _path) { transform: function (content, _path) {
const manifest = JSON.parse( const manifest = JSON.parse(
content content.toString().replace(/\[version\]/g, process.env.npm_package_version),
.toString()
.replace(/\[version\]/g, process.env.npm_package_version)
) )
return Buffer.from( return Buffer.from(
JSON.stringify({ JSON.stringify({
...manifest, ...manifest,
permissions: compact([ permissions: compact([
...manifest.permissions, ...manifest.permissions,
env.NODE_ENV === "development" env.NODE_ENV === "development" ? "http://*.mocoapp.localhost/*" : null,
? "http://*.mocoapp.localhost/*"
: null
]), ]),
options_ui: { options_ui: {
...manifest.options_ui, ...manifest.options_ui,
chrome_style: true chrome_style: true,
}, },
description: process.env.npm_package_description, description: process.env.npm_package_description,
version: process.env.npm_package_version version: process.env.npm_package_version,
}) }),
) )
} },
} },
]) ],
}),
) )
return config return config

View File

@@ -1,47 +1,45 @@
const uuidv4 = require("uuid/v4") const { v4: uuidv4 } = require("uuid")
const CopyWebpackPlugin = require("copy-webpack-plugin") const CopyWebpackPlugin = require("copy-webpack-plugin")
const { compact } = require("lodash/fp") const { compact } = require("lodash/fp")
const baseConfig = require("./webpack.base.config") const baseConfig = require("./webpack.base.config")
module.exports = env => { module.exports = (env) => {
const config = baseConfig(env) const config = baseConfig(env)
config.plugins.unshift( config.plugins.unshift(
new CopyWebpackPlugin([ new CopyWebpackPlugin({
patterns: [
{ {
from: "src/manifest.json", from: "src/manifest.json",
transform: function(content, _path) { transform: function (content, _path) {
const manifest = JSON.parse( const manifest = JSON.parse(
content content.toString().replace(/\[version\]/g, process.env.npm_package_version),
.toString()
.replace(/\[version\]/g, process.env.npm_package_version)
) )
return Buffer.from( return Buffer.from(
JSON.stringify({ JSON.stringify({
...manifest, ...manifest,
permissions: compact([ permissions: compact([
...manifest.permissions, ...manifest.permissions,
env.NODE_ENV === "development" env.NODE_ENV === "development" ? "http://*.mocoapp.localhost/*" : null,
? "http://*.mocoapp.localhost/*"
: null
]), ]),
options_ui: { options_ui: {
...manifest.options_ui, ...manifest.options_ui,
browser_style: true browser_style: true,
}, },
browser_specific_settings: { browser_specific_settings: {
gecko: { gecko: {
id: process.env.APPLICATION_ID || `{${uuidv4()}}` 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,
}) }),
) )
} },
} },
]) ],
}),
) )
return config return config

View File

@@ -1,23 +0,0 @@
module.exports = class RemoveSourceMapPlugin {
constructor(options = {}) {
this.test = options.test || /\.(js|css)$/
}
apply(compiler) {
compiler.hooks.afterEmit.tap("RemoveSourceMapPlugin", compilation => {
Object.keys(compilation.assets)
.filter(key => this.test.test(key))
.forEach(key => {
const asset = compilation.assets[key]
const source = asset
.source()
.replace(/# sourceMappingURL=(.*\.map)/g, "# $1")
compilation.assets[key] = Object.assign(asset, {
source: function() {
return source
}
})
})
})
}
}

8504
yarn.lock

File diff suppressed because it is too large Load Diff