Compare commits

...

158 Commits

Author SHA1 Message Date
dependabot-preview[bot]
2d29dc56cf Bump mobx from 5.15.6 to 5.15.7
Bumps [mobx](https://github.com/mobxjs/mobx) from 5.15.6 to 5.15.7.
- [Release notes](https://github.com/mobxjs/mobx/releases)
- [Changelog](https://github.com/mobxjs/mobx/blob/mobx6/CHANGELOG.md)
- [Commits](https://github.com/mobxjs/mobx/compare/5.15.6...5.15.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-02 01:13:03 +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
Manuel Bouza
1533c2261f Update projecte regex to match on alphanumeric values with hyphens 2019-03-30 07:51:59 +01:00
Manuel Bouza
d8398fca5f Pump version to 1.1.0 2019-03-30 07:26:25 +01:00
Manuel Bouza
02a0bec738 Browser extension fixes (#8)
* Set full url on service

* Link logo to `/activities` in modal

* Update changelog

* Honor the selected task and set the correct billability
2019-03-30 06:59:18 +01:00
Manuel Bouza
0f5172a820 Read project identifier in asana service (#7) 2019-03-30 06:54:28 +01:00
Tobias Miesel
a3f94738b6 Merge pull request #6 from hundertzehn/feature/meistertask
Add support for Meistertask
2019-03-30 06:52:54 +01:00
Manuel Bouza
c153eb6c91 Update regex for project identifier to match anywhere 2019-03-29 22:26:35 +01:00
Manuel Bouza
87aaa99276 Parse description and projectId 2019-03-29 22:24:32 +01:00
Manuel Bouza
e6b6f67814 Add support for Meistertask service 2019-03-29 22:24:32 +01:00
Manuel Bouza
1f8bc33830 Change default subdomain to unset 2019-03-28 09:12:34 +01:00
Manuel Bouza
8e55c13d72 Add license information 2019-03-26 16:57:23 +01:00
60 changed files with 6385 additions and 4987 deletions

View File

@@ -3,6 +3,7 @@
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": 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=

View File

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

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/
build/
.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,30 +1,179 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- Add support for starting/stopping a timer
- Show hours as HH:MM or decimal in the Bubble, depending on setting in MOCO
## [1.5.2] - 2020-09-10
### Fixed
- Remember last tracked project and task on card
## [1.5.1] - 2020-08-04
## [1.0.18] - 2019-03-23
### Added
- First release of version 1
- Add support for Monday
## [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
### Changed
- 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
### Added
- Read project identifier from Asana project title
- Add support for meistertask.com
### Fixed
- Link logo in modal to MOCO activities page
- Set full url on service, including query params
## [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.20] - 2019-03-26
## [1.0.18] - 2019-03-23
### Added
- Add support for tags in description
## [1.0.21] - 2019-03-26
### Changed
- Update README with example configuration and instructions for local installation
- First release of version 1

21
LICENSE.txt Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2019, hundertzehn GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -2,33 +2,34 @@
## Development
* run `yarn`
* run `yarn start:chrome` or `yarn start:firefox` (`yarn start` is an alias for `yarn start:chrome`)
* load extension into browser:
* Chrome: visit `chrome://extensions` and load unpacked extension from `build/chrome`
* Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox`
* reload browser extension after change
- run `yarn`
- run `yarn start:chrome` or `yarn start:firefox` (`yarn start` is an alias for `yarn start:chrome`)
- load extension into browser:
- Chrome: visit `chrome://extensions` and load unpacked extension from `build/chrome`
- Firefox: visit `about:debugging` and load temporary Add-on from `build/firefox/manifest.json`
- the browser should automatically pick up your changes but from time to time it may be useful to reload the extension
## Production Build
* bump version in `package.json`
* run `yarn build`
* The Chrome and Firefox extensions are available as ZIP-files in `build/chrome` and `build/firefox` respectively
- bump version in `package.json`
- Update `CHANGELOG.md`
- run `yarn build`
- The Chrome and Firefox extensions are available as ZIP-files in `build/chrome` and `build/firefox` respectively
## Install Local Builds
### Chrome
1. `yarn build:chrome`
1. Visit `chrome://extensions`
2. Enable `Developer mode`
3. `Load unpacked` and select the `build/chrome` folder.
- `yarn build:chrome`
- Visit `chrome://extensions`
- Enable `Developer mode`
- `Load unpacked` and select the `build/chrome` folder.
### Firefox
1. `yarn build:firefox`
1. Visit `about:debugging`
2. Click on `Load temporary Add-on` and select the ZIP-file in `build/firefox`
- `yarn build:firefox`
- Visit `about:debugging`
- 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).
@@ -36,9 +37,9 @@ You can keep the extension settings between builds by providing a stable `APPLIC
`APPLICATION_ID=my-custom-moco-extension@mycompany.com yarn build:firefox`
## 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:
@@ -46,9 +47,10 @@ A remote service is configured as follows:
{
service_key: {
name: "service_name",
host: "https://:subdomain.example.com",
urlPatterns: [
"https:\\://:subdomain.example.com/card/:id",
[/^https:\/\/(\w+).example.com\/card\/(\d+), ["subdomain", "id"]],
":host:/card/:id",
[/^:host:\/card\/(\d+), ["subdomain", "id"]],
],
queryParams: {
projectId: "currentList"
@@ -59,24 +61,25 @@ A remote service is configured as follows:
?.textContent
?.trim()
return `#${id} ${service.key} ${title || ""}`
},
},
projectId: (document, service, { subdomain, id, projectId }) => {
return projectId
},
position: { left: "50%", transform: "translate(-50%)" }
position: { left: "50%", transform: "translate(-50%)" },
allowHostOverride: false,
}
}
```
| Parameter | Description |
|--------------|:-------------|
| 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` |
| 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. |
| 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. |
| Parameter | Description |
| ------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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` |
| 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`. |
| 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`. |
| position | `Object` &mdash; CSS properties as object styles for position the bubble. Defaults to `{ right: calc(4rem + 5px)` |
| position | `Object` &mdash; CSS properties as object styles for position the bubble. Defaults to `{ right: calc(4rem + 5px)` |
## Adding a Custom Service

View File

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

View File

@@ -7,17 +7,25 @@ button.moco-bx-btn {
white-space: nowrap;
color: white;
background-image: none;
background-color: #7dc332;
border-color: #7dc332;
background-color: $green;
border-color: $green;
border-radius: 0;
border-style: solid;
box-shadow: none;
font-size: 100%;
cursor: pointer;
&:focus {
outline: none;
}
&::-moz-focus-inner {
border: 0;
}
&:hover:not(:disabled) {
background-color: #639a28;
border-color: #639a28;
background-color: $green-dark;
border-color: $green-dark;
}
&:disabled {
@@ -40,3 +48,17 @@ button.moco-bx-btn {
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";
input {
@@ -15,7 +16,8 @@ input {
margin-bottom: 0.25rem;
}
input, textarea {
input,
textarea {
padding: 6px 12px;
background-color: white;
border-color: #cccccc;
@@ -24,6 +26,10 @@ input {
border-style: solid;
border-width: 1px;
min-height: 20px;
&::placeholder {
color: #ccc;
}
}
.text-muted {
@@ -31,13 +37,14 @@ input {
}
&.has-error {
input, textarea {
border-color: #FB3A2F;
input,
textarea {
border-color: $red;
}
}
.form-error {
color: #FB3A2F;
color: $red;
}
.input-group {
@@ -56,8 +63,13 @@ input {
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-left: none;
line-height: 18px;
&--right {
border-left: none;
}
&--left {
border-right: none;
}
}
}
}
@@ -71,8 +83,8 @@ input[name="hours"] {
outline: 0 !important;
&:focus {
border: 1px solid #38b5eb;
box-shadow: 0 0 0 1px #38b5eb;
border: 1px solid $blue;
box-shadow: 0 0 0 1px $blue;
}
}
@@ -84,8 +96,7 @@ textarea[name="description"] {
outline: 0 !important;
&:focus {
border: 1px solid #38b5eb;
box-shadow: 0 0 0 1px #38b5eb;
border: 1px solid $blue;
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;
$popup-width: 420px;
$popup-height: 463px;
$green: #7dc332;
$green-dark: #639a28;
$blue: #38b5eb;
$red: #fb3a2f;
$gray-base: #a3a3a3;

View File

@@ -4,6 +4,11 @@
#moco-bx-root {
font-family: $font-family;
color: $font-color;
pointer-events: all;
.text-red {
color: $red;
}
.moco-bx-bubble {
box-sizing: content-box;
@@ -13,8 +18,7 @@
width: 60px;
background-color: white;
border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05),
2px 2px 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);
padding: 5px;
z-index: 9999;
@@ -48,6 +52,7 @@
#moco-bx-popup-root {
font-family: $font-family;
color: $font-color;
pointer-events: all;
iframe {
border: 0;
@@ -58,16 +63,16 @@
}
.moco-bx-popup {
position: fixed; /* Stay in place */
z-index: 2000; /* Sit on top */
padding-top: 100px; /* Location of the box */
position: fixed;
z-index: 2000;
padding-top: 100px;
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
z-index: 9999;
.moco-bx-popup-content {
background-color: white;

View File

@@ -11,6 +11,11 @@
.moco-bx-options {
padding: 0rem 2rem 2rem;
a {
color: $blue;
text-decoration: none;
}
p {
margin: 0.5rem 0;
}
@@ -20,6 +25,11 @@
margin: 1rem 0 2rem;
}
h3 {
font-size: 1.1rem;
margin: 1rem 0 1rem;
}
label {
font-weight: normal;
margin-bottom: 5px;
@@ -32,11 +42,21 @@
}
.text-success {
color: #7DC332;
color: $green;
}
.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 "spinner";
@import "variables";
html {
overflow: hidden;
@@ -14,33 +14,48 @@ html {
#moco-bx-root {
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 {
width: 324px;
padding: 3rem 6rem;
.moco-bx-logo__container {
display: flex;
justify-content: center;
margin-bottom: 3rem;
text-align: center;
img.moco-bx-logo {
flex: 0 0 48px;
width: 48px;
height: 48px;
}
h1 {
line-height: 48px;
margin: 0;
}
}
.moco-bx-calendar {
display: flex;
justify-content: space-between;
margin-bottom: 3rem;
margin-bottom: 2rem;
.moco-bx-calendar__day {
display: flex;
@@ -66,12 +81,11 @@ html {
flex: 0 0 42px;
color: white;
background-color: #eee;
}
&.moco-bx-calendar__day--filled {
.moco-bx-calendar__hours {
background-color: #7dc332;
background-color: $green;
}
}
@@ -84,43 +98,73 @@ html {
&.moco-bx-calendar__day--active {
.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 {
font-size: 18px;
line-height: 1.5;
width: 420px;
padding: 3rem;
text-align: center;
h1 {
font-size: 35px;
font-weight: normal;
margin-top: 0;
line-height: 1.3;
}
img {
width: auto;
max-width: 100%;
margin-top: 1.5rem;
margin-bottom: 2rem;
&.moco-bx-logo {
width: 48px;
margin-bottom: 2rem;
}
}
ol {
text-align: left;
&.firefox-addons {
margin-top: 0;
width: auto;
}
}
button {
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 => {
if (process.env.NODE_ENV === "production") {
return `https://${encodeURIComponent(
subdomain
)}.mocoapp.com/api/browser_extensions`
return `https://${encodeURIComponent(subdomain)}.mocoapp.com/api/browser_extensions`
} else {
return `http://${encodeURIComponent(
subdomain
)}.mocoapp.localhost:3001/api/browser_extensions`
return `http://${encodeURIComponent(subdomain)}.mocoapp.localhost:3000/api/browser_extensions`
}
}
export default class Client {
#client;
#apiKey;
#client
#apiKey
constructor({ subdomain, apiKey, version }) {
this.#apiKey = apiKey
@@ -25,9 +21,9 @@ export default class Client {
headers: {
common: {
"x-api-key": apiKey,
"x-extension-version": version
}
}
"x-extension-version": version,
},
},
})
}
@@ -35,29 +31,31 @@ export default class Client {
this.#client.post("session", {
api_key: this.#apiKey,
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) =>
this.#client.get("schedules", {
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` }
});
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
})
activities = (fromDate, toDate) =>
this.#client.get("activities", {
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` }
});
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
})
bookedHours = service => {
activitiesStatus = service => {
if (!service) {
return Promise.resolve({ data: { hours: 0 } })
}
return this.#client.get("activities/tags", {
params: { selection: [service.id], remote_service: service.name }
return this.#client.get("activities/status", {
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,19 +1,40 @@
import "@babel/polyfill"
import ApiClient from "api/Client"
import {
isChrome,
getCurrentTab,
getSettings,
isBrowserTab
} from "utils/browser"
import { isChrome, getCurrentTab, getSettings, isBrowserTab } from "utils/browser"
import { BackgroundMessenger } from "utils/messaging"
import {
tabUpdated,
settingsChanged,
togglePopup
} from "utils/messageHandlers"
import { tabUpdated, settingsChanged, togglePopup, openPopup } from "utils/messageHandlers"
import { isNil } from "lodash"
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", () => {
getCurrentTab().then(tab => {
if (tab && !isBrowserTab(tab)) {
@@ -39,23 +60,12 @@ chrome.runtime.onMessage.addListener(action => {
const apiClient = new ApiClient(settings)
apiClient
.createActivity(activity)
.then(() => {
messenger.postMessage(tab, { type: "closePopup" })
apiClient.bookedHours(service).then(({ data }) => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: parseFloat(data[0]?.hours) || 0,
service
}
})
})
})
.then(() => resetBubble({ tab, settings, service }))
.catch(error => {
if (error.response?.status === 422) {
chrome.runtime.sendMessage({
type: "setFormErrors",
payload: error.response.data
payload: error.response.data,
})
}
})
@@ -63,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") {
let url
if (isChrome()) {

View File

@@ -3,6 +3,7 @@ import PropTypes from "prop-types"
import Spinner from "components/Spinner"
import Form from "components/Form"
import Calendar from "components/Calendar"
import TimerView from "components/App/TimerView"
import { observable, computed } from "mobx"
import { Observer, observer } from "mobx-react"
import { Spring, animated, config } from "react-spring/renderprops"
@@ -14,15 +15,17 @@ import {
findProjectByValue,
findProjectByIdentifier,
findTask,
formatDate
defaultTask,
formatDate,
} from "utils"
import { parseISO } from "date-fns"
import InvalidConfigurationError from "components/Errors/InvalidConfigurationError"
import UpgradeRequiredError from "components/Errors/UpgradeRequiredError"
import UnknownError from "components/Errors/UnknownError"
import { parse } from "date-fns"
import Header from "./shared/Header"
import { head } from "lodash"
import TimeInputParser from "utils/TimeInputParser"
import {get} from "lodash/fp";
@observer
class App extends Component {
@@ -34,55 +37,74 @@ class App extends Component {
name: PropTypes.string,
description: PropTypes.string,
projectId: PropTypes.string,
taskId: PropTypes.string
taskId: PropTypes.string,
}),
subdomain: PropTypes.string,
activities: PropTypes.array,
schedules: 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,
lastTaskId: PropTypes.number,
roundTimeEntries: PropTypes.bool,
fromDate: PropTypes.string,
toDate: PropTypes.string,
errorType: PropTypes.string,
errorMessage: PropTypes.string
};
errorMessage: PropTypes.string,
}
static defaultProps = {
activities: [],
schedules: [],
projects: [],
roundTimeEntries: false
};
}
@observable changeset = {};
@observable formErrors = {};
@observable changeset = {}
@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() {
const { service, projects, lastProjectId, lastTaskId } = this.props
const project =
findProjectByIdentifier(service?.projectId)(projects) ||
findProjectByValue(Number(lastProjectId))(projects) ||
head(projects)
const task =
findTask(service?.taskId || lastTaskId)(project) || head(project?.tasks)
const { service } = this.props
const defaults = {
remote_service: service?.name,
remote_id: service?.id,
remote_url: service?.url,
date: formatDate(new Date()),
assignment_id: project?.value,
task_id: task?.value,
billable: task?.billable,
assignment_id: this.project?.value,
task_id: this.task?.value,
billable: this.billable,
hours: "",
seconds:
this.changeset.hours &&
new TimeInputParser(this.changeset.hours).parseSeconds(),
description: service?.description,
tag: ""
seconds: new TimeInputParser(this.changeset.hours).parseSeconds(),
description: service?.description || "",
tag: "",
}
return { ...defaults, ...this.changeset }
@@ -90,6 +112,7 @@ class App extends Component {
componentDidMount() {
window.addEventListener("keydown", this.handleKeyDown)
parent.postMessage({ __mocoBX: { iFrameHeight: window.document.body.scrollHeight } }, "*")
chrome.runtime.onMessage.addListener(this.handleSetFormErrors)
}
@@ -98,25 +121,34 @@ class App extends Component {
chrome.runtime.onMessage.removeListener(this.handleSetFormErrors)
}
handleChange = event => {
handleChange = (event) => {
const { projects } = this.props
const {
target: { name, value }
target: { name, value },
} = event
this.changeset[name] = value
if (name === "assignment_id") {
const project = findProjectByValue(value)(projects)
this.changeset.task_id = head(project?.tasks).value || null
this.changeset.task_id = defaultTask(project?.tasks)?.value
}
};
}
handleSelectDate = date => {
handleSelectDate = (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()
const { service } = this.props
@@ -124,34 +156,36 @@ class App extends Component {
type: "createActivity",
payload: {
activity: extractAndSetTag(this.changesetWithDefaults),
service
}
service,
},
})
};
}
handleKeyDown = event => {
handleKeyDown = (event) => {
if (event.keyCode === 27) {
event.stopPropagation()
chrome.runtime.sendMessage({ type: "closePopup" })
}
};
}
handleSetFormErrors = ({ type, payload }) => {
if (type === "setFormErrors") {
this.formErrors = payload
}
};
}
render() {
const {
loading,
subdomain,
projects,
timedActivity,
activities,
schedules,
fromDate,
toDate,
errorType,
errorMessage
errorMessage,
} = this.props
if (loading) {
@@ -171,35 +205,34 @@ class App extends Component {
}
return (
<Spring
native
from={{ opacity: 0 }}
to={{ opacity: 1 }}
config={config.stiff}
>
{props => (
<Spring native from={{ opacity: 0 }} to={{ opacity: 1 }} config={config.stiff}>
{(props) => (
<animated.div className="moco-bx-app-container" style={props}>
<Header />
<Header subdomain={subdomain} />
<Observer>
{() => (
<>
<Calendar
fromDate={parse(fromDate)}
toDate={parse(toDate)}
activities={activities}
schedules={schedules}
selectedDate={new Date(this.changesetWithDefaults.date)}
onChange={this.handleSelectDate}
/>
<Form
changeset={this.changesetWithDefaults}
projects={projects}
errors={this.formErrors}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
/>
</>
)}
{() =>
timedActivity ? (
<TimerView timedActivity={timedActivity} onStopTimer={this.handleStopTimer} />
) : (
<>
<Calendar
fromDate={parseISO(fromDate)}
toDate={parseISO(toDate)}
activities={activities}
schedules={schedules}
selectedDate={new Date(this.changesetWithDefaults.date)}
onChange={this.handleSelectDate}
/>
<Form
changeset={this.changesetWithDefaults}
projects={projects}
errors={this.formErrors}
onChange={this.handleChange}
onSubmit={this.handleSubmit}
/>
</>
)
}
</Observer>
</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,23 +1,46 @@
import React from "react"
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, onClick }) => (
<div className="moco-bx-bubble-inner" onClick={onClick}>
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
{bookedHours > 0 ? (
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span>
) : null}
</div>
)
const Bubble = ({ bookedSeconds, timedActivity, settingTimeTrackingHHMM }) => {
const logo = timedActivity ? mocoTimerLogo : mocoLogo
return (
<div className="moco-bx-bubble-inner">
<img className="moco-bx-logo" src={chrome.extension.getURL(logo)} />
{!timedActivity && bookedSeconds > 0 && (
<span className="moco-bx-booked-hours">
{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>
)
}
Bubble.propTypes = {
bookedHours: PropTypes.number,
onClick: PropTypes.func.isRequired
bookedSeconds: PropTypes.number,
timedActivity: PropTypes.shape({
timer_started_at: PropTypes.string.isRequired,
seconds: PropTypes.number.isRequired,
}),
settingTimeTrackingHHMM: PropTypes.bool,
}
Bubble.defaultProps = {
bookedHours: 0
bookedSeconds: 0,
settingTimeTrackingHHMM: false,
}
export default Bubble

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,21 +1,24 @@
import React from "react"
import PropTypes from "prop-types"
import logo from "images/logo.png"
import logo from "images/moco-159x159.png"
const UnknownError = ({ message = "Unbekannter Fehler" }) => (
<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>
<p>Bitte überprüfe deine Internetverbindung.</p>
<p>Wir wurden per Email benachrichtigt und untersuchen den Vorfall.</p>
<br />
<p>Fehlermeldung:</p>
<pre>{message}</pre>
</div>
)
UnknownError.propTypes = {
message: PropTypes.string
message: PropTypes.string,
}
export default UnknownError

View File

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

View File

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

View File

@@ -3,68 +3,96 @@ import { observable } from "mobx"
import { observer } from "mobx-react"
import { isChrome, getSettings, setStorage } from "utils/browser"
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
class Options extends Component {
@observable subdomain = "";
@observable apiKey = "";
@observable errorMessage = null;
@observable isSuccess = false;
@observable subdomain = ""
@observable apiKey = ""
@observable hostOverrides = {}
@observable errorMessage = null
@observable isSuccess = false
@observable showHostOverrideOptions = false
componentDidMount() {
getSettings(false).then(({ subdomain, apiKey }) => {
this.subdomain = subdomain || ""
this.apiKey = apiKey || ""
getSettings(false).then((settings) => {
this.subdomain = settings.subdomain || ""
this.apiKey = settings.apiKey || ""
this.hostOverrides = settings.hostOverrides
})
}
onChange = event => {
handleChange = (event) => {
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.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 apiClient = new ApiClient({
subdomain: this.subdomain,
apiKey: this.apiKey,
version
version,
})
apiClient
.login()
.then(({ data }) =>
setStorage({ settingTimeTrackingHHMM: data.setting_time_tracking_hh_mm }),
)
.then(() => {
this.isSuccess = true
this.closeWindow()
})
.catch(error => {
this.errorMessage =
error.response?.data?.message || "Anmeldung fehlgeschlagen"
.catch((error) => {
this.errorMessage = error.response?.data?.message || "Anmeldung fehlgeschlagen"
})
})
};
}
handleInputKeyDown = event => {
handleInputKeyDown = (event) => {
if (event.key === "Enter") {
this.handleSubmit()
}
};
}
closeWindow = () => {
isChrome() && window.close()
};
}
render() {
return (
<div className="moco-bx-options">
<h2 style={{ textAlign: "center" }}>Einstellungen</h2>
{this.errorMessage && (
<div className="text-danger">{this.errorMessage}</div>
)}
{this.isSuccess && (
<div className="text-success">Anmeldung erfolgreich</div>
)}
{this.errorMessage && <div className="text-danger">{this.errorMessage}</div>}
{this.isSuccess && <div className="text-success">Anmeldung erfolgreich</div>}
<div className="form-group">
<label>Internetadresse</label>
<div className="input-group">
@@ -73,9 +101,9 @@ class Options extends Component {
name="subdomain"
value={this.subdomain}
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 className="form-group">
@@ -85,13 +113,59 @@ class Options extends Component {
name="apiKey"
value={this.apiKey}
onKeyDown={this.handleInputKeyDown}
onChange={this.onChange}
onChange={this.handleChange}
/>
<p className="text-muted">
Den API-Schlüssel findest du in deinem Profil unter
&quot;Integrationen&quot;.
Den API-Schlüssel findest du in deinem Profil unter &quot;Integrationen&quot;.
</p>
</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}>
OK
</button>

View File

@@ -1,87 +1,65 @@
import React, { Component } from "react"
import React, { useEffect, useRef, forwardRef } from "react"
import PropTypes from "prop-types"
import queryString from "query-string"
import {
ERROR_UNKNOWN,
ERROR_UNAUTHORIZED,
ERROR_UPGRADE_REQUIRED,
serializeProps
} from "utils"
import { isChrome } from "utils/browser"
import { serializeProps } from "utils"
function getStyles(errorType) {
return {
width: "516px",
height:
errorType === ERROR_UNAUTHORIZED
? "834px"
: errorType === ERROR_UPGRADE_REQUIRED
? isChrome()
? "369px"
: "461px"
: errorType === ERROR_UNKNOWN
? "550px"
: "558px"
}
}
const Popup = forwardRef((props, ref) => {
const iFrameRef = useRef()
class Popup extends Component {
static propTypes = {
service: PropTypes.object,
errorType: PropTypes.string,
onRequestClose: PropTypes.func.isRequired
};
handleRequestClose = event => {
const handleRequestClose = event => {
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 serializedProps = serializeProps([
"loading",
"service",
"lastProjectId",
"lastTaskId",
"roundTimeEntries",
"projects",
"activities",
"schedules",
"lastProjectId",
"lastTaskId",
"fromDate",
"toDate",
"errorType",
"errorMessage"
])(this.props)
const handleMessage = event => {
if (iFrameRef.current && event.data?.__mocoBX?.iFrameHeight > 300) {
iFrameRef.current.style.height = `${event.data.__mocoBX.iFrameHeight}px`
}
}
const styles = getStyles(this.props.errorType)
useEffect(() => {
window.addEventListener("message", handleMessage)
return () => {
window.removeEventListener("message", handleMessage)
}
}, [])
return (
<div className="moco-bx-popup" onClick={this.handleRequestClose}>
<div className="moco-bx-popup-content" style={styles}>
<iframe
src={chrome.extension.getURL(
`popup.html?${queryString.stringify(serializedProps)}`
)}
width={styles.width}
height={styles.height}
/>
</div>
const serializedProps = serializeProps([
"loading",
"service",
"subdomain",
"projects",
"activities",
"schedules",
"timedActivity",
"lastProjectId",
"lastTaskId",
"fromDate",
"toDate",
"errorType",
"errorMessage",
])(props)
return (
<div ref={ref} className="moco-bx-popup" onClick={handleRequestClose}>
<div className="moco-bx-popup-content" style={{ width: "516px" }}>
<iframe
ref={iFrameRef}
src={chrome.extension.getURL(`popup.html?${queryString.stringify(serializedProps)}`)}
style={{ width: "516px", height: "576px", transition: "height 0.1s ease-in-out" }}
/>
</div>
)
}
</div>
)
})
Popup.displayName = "Popup"
Popup.propTypes = {
service: PropTypes.object,
errorType: PropTypes.string,
onRequestClose: PropTypes.func.isRequired,
}
export default Popup

View File

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

View File

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

View File

@@ -1,13 +1,21 @@
import React from 'react'
import logoUrl from "images/logo.png"
import React from "react"
import PropTypes from "prop-types"
import logoUrl from "images/moco-159x159.png"
const Header = () => (
const Header = ({ subdomain }) => (
<div className="moco-bx-logo__container">
<img
className="moco-bx-logo"
src={chrome.extension.getURL(logoUrl)}
/>
<a
href={`https://${subdomain}.mocoapp.com/activities`}
target="_blank"
rel="noopener noreferrer"
>
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
</a>
</div>
)
Header.propTypes = {
subdomain: PropTypes.string,
}
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,59 +5,64 @@ import Bubble from "./components/Bubble"
import Popup from "components/Popup"
import { createServiceFinder } from "utils/urlMatcher"
import remoteServices from "./remoteServices"
import { ErrorBoundary } from "utils/notifier"
import { ContentMessenger } from "utils/messaging"
import "../css/content.scss"
import { getSettings } from "./utils/browser"
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)
function clickHandler(event) {
if (event.target.closest(".moco-bx-bubble")) {
event.stopPropagation()
messenger.postMessage({ type: "togglePopup" })
}
}
port.onDisconnect.addListener(() => {
messenger.stop()
window.removeEventListener("click", clickHandler, true)
})
function updateBubble({ service, bookedHours } = {}) {
function updateBubble({ service, bookedSeconds, settingTimeTrackingHHMM, timedActivity } = {}) {
if (!document.getElementById("moco-bx-root")) {
const domRoot = document.createElement("div")
domRoot.setAttribute("id", "moco-bx-root")
document.body.appendChild(domRoot)
window.addEventListener("click", clickHandler, true)
}
ReactDOM.render(
<ErrorBoundary>
<Transition
native
items={service}
from={{ opacity: "0" }}
enter={{ opacity: "1" }}
leave={{ opacity: "0" }}
config={config.stiff}
>
{service =>
service &&
// eslint-disable-next-line react/display-name
(props => (
<animated.div
className="moco-bx-bubble"
style={{ ...props, ...service.position }}
>
<Bubble
key={service.url}
bookedHours={bookedHours}
onClick={event => {
event.stopPropagation()
messenger.postMessage({ type: "togglePopup" })
}}
/>
</animated.div>
))
}
</Transition>
</ErrorBoundary>,
document.getElementById("moco-bx-root")
<Transition
native
items={service}
from={{ opacity: "0" }}
enter={{ opacity: "1" }}
leave={{ opacity: "0" }}
config={config.stiff}
>
{(service) =>
service &&
// eslint-disable-next-line react/display-name
((props) => (
<animated.div className="moco-bx-bubble" style={{ ...props, ...service.position }}>
<Bubble
key={service.url}
bookedSeconds={bookedSeconds}
settingTimeTrackingHHMM={settingTimeTrackingHHMM}
timedActivity={timedActivity}
/>
</animated.div>
))
}
</Transition>,
document.getElementById("moco-bx-root"),
)
}
@@ -69,10 +74,8 @@ chrome.runtime.onConnect.addListener(function(port) {
}
ReactDOM.render(
<ErrorBoundary>
<Popup ref={popupRef} {...payload} onRequestClose={closePopup} />
</ErrorBoundary>,
document.getElementById("moco-bx-popup-root")
<Popup ref={popupRef} {...payload} onRequestClose={closePopup} />,
document.getElementById("moco-bx-popup-root"),
)
}
@@ -89,12 +92,12 @@ chrome.runtime.onConnect.addListener(function(port) {
const service = findService(window.location.href)
messenger.postMessage({
type: "newService",
payload: { isOpen: !!popupRef.current, service }
payload: { isOpen: !!popupRef.current, service },
})
})
messenger.on("showBubble", ({ payload: { service, bookedHours } }) => {
updateBubble({ service, bookedHours })
messenger.on("showBubble", ({ payload }) => {
updateBubble(payload)
})
messenger.on("hideBubble", () => {
@@ -108,8 +111,4 @@ chrome.runtime.onConnect.addListener(function(port) {
messenger.on("closePopup", () => {
closePopup()
})
messenger.on("activityCreated", () => {
closePopup()
})
})

View File

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

View File

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

View File

@@ -1,59 +1,58 @@
const projectRegex = /\[([\w-]+)\]/
const projectIdentifierBySelector = (selector) => (document) =>
document.querySelector(selector)?.textContent?.trim()?.match(projectRegex)?.[1]
export default {
asana: {
name: "asana",
host: "https://app.asana.com",
urlPatterns: [
[/^https:\/\/app.asana.com\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
[
/^https:\/\/app.asana.com\/0\/search\/([^/]+)\/(\d+)/,
["domainUserId", "id"]
]
[/^:host:\/0\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
[/^:host:\/0\/search\/([^/]+)\/(\d+)/, ["domainUserId", "id"]],
],
description: document =>
document
.querySelector(".ItemRow--highlighted textarea")
?.textContent?.trim() ||
document
.querySelector(".ItemRow--focused textarea")
?.textContent?.trim() ||
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim()
description: (document) =>
document.querySelector(".ItemRow--highlighted textarea")?.textContent?.trim() ||
document.querySelector(".ItemRow--focused textarea")?.textContent?.trim() ||
document.querySelector(".SingleTaskPane textarea")?.textContent?.trim() ||
document.querySelector(".SingleTaskTitleInput-taskName textarea")?.textContent?.trim(),
projectId: projectIdentifierBySelector(".TopbarPageHeaderStructure-titleRow h1"),
allowHostOverride: false,
},
"github-pr": {
name: "github",
urlPatterns: ["https\\://github.com/:org/:repo/pull/:id(/:tab)"],
id: (document, service, { org, repo, id }) =>
[service.key, org, repo, id].join("."),
description: (document, service, { org, repo, id }) =>
document.querySelector(".js-issue-title")?.textContent?.trim(),
projectId: document => {
const match = document
.querySelector(".js-issue-title")
?.textContent.trim()
?.match(/^\[(\d+)\]/)
return match && match[1]
}
host: "https://github.com",
urlPatterns: [":host:/:org/:repo/pull/:id(/:tab)"],
id: (document, service, { org, repo, id }) => [service.key, org, repo, id].join("."),
description: (document) => document.querySelector(".js-issue-title")?.textContent?.trim(),
projectId: projectIdentifierBySelector(".js-issue-title"),
allowHostOverride: false,
},
"github-issue": {
name: "github",
urlPatterns: ["https\\://github.com/:org/:repo/issues/:id"],
id: (document, service, { org, repo, id }) =>
[service.key, org, repo, id].join("."),
host: "https://github.com",
urlPatterns: [":host:/:org/:repo/issues/:id"],
id: (document, service, { org, repo, id }) => [service.key, org, repo, id].join("."),
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: {
name: "jira",
host: "https://:org.atlassian.net",
urlPatterns: [
"https\\://:org.atlassian.net/secure/RapidBoard.jspa",
"https\\://:org.atlassian.net/browse/:id",
"https\\://:org.atlassian.net/jira/software/projects/:projectId/boards/:board",
"https\\://:org.atlassian.net/jira/software/projects/:projectId/boards/:board/backlog"
":host:/secure/RapidBoard.jspa",
":host:/browse/:id",
":host:/jira/software/projects/:projectId/boards/:board",
":host:/jira/software/projects/:projectId/boards/:board/backlog",
],
queryParams: {
id: "selectedIssue",
projectId: "projectKey"
projectId: "projectKey",
},
description: (document, service, { id }) => {
const title =
@@ -61,33 +60,115 @@ export default {
.querySelector('[aria-label="Edit Summary"]')
?.parentNode?.querySelector("h1")
?.textContent?.trim() ||
document
.querySelector(".ghx-selected .ghx-summary")
?.textContent?.trim()
document.querySelector(".ghx-selected .ghx-summary")?.textContent?.trim()
return `#${id} ${title || ""}`
}
},
allowHostOverride: true,
},
meistertask: {
name: "meistertask",
host: "https://www.meistertask.com",
urlPatterns: [":host:/app/task/:id/:slug"],
description: (document) => {
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
const data = JSON.parse(json)
return data.taskName
},
projectId: (document) => {
const json = document.getElementById("mt-toggl-data")?.dataset?.togglJson || "{}"
const data = JSON.parse(json)
const match = data.taskName?.match(projectRegex) || data.projectName?.match(projectRegex)
return match && match[1]
},
allowHostOverride: false,
},
trello: {
name: "trello",
urlPatterns: ["https\\://trello.com/c/:id/:title"],
host: "https://trello.com",
urlPatterns: [":host:/c/:id/: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: {
name: "youtrack",
urlPatterns: ["https\\://:org.myjetbrains.com/youtrack/issue/:id"],
description: document =>
document.querySelector("yt-issue-body h1")?.textContent?.trim()
host: "https://:org.myjetbrains.com",
urlPatterns: [":host:/youtrack/issue/:id"],
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: {
name: "wunderlist",
urlPatterns: ["https\\://www.wunderlist.com/(webapp)#/tasks/:id(/*)"],
description: document =>
host: "https://www.wunderlist.com",
urlPatterns: [":host:/(webapp)#/tasks/:id(/*)"],
description: (document) =>
document
.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 {
#input;
#input
constructor(input) {
this.#input = input.toLowerCase().replace(/[\s()]/g, "")
this.#input = (input ?? "").toLowerCase().replace(/[\s()]/g, "")
}
parseSeconds() {
if (this.#isDecimal()) {
if (this.#input === "") {
return 0
} else if (this.#isDecimal()) {
return Math.round(parseFloat(this.#parseDecimal()) * 3600)
} else if (this.#isTime()) {
return this.#parseTimeAsSeconds()
@@ -25,11 +27,11 @@ export default class TimeInputParser {
const calculated = hours * 3600 + minutes * 60
return isNegative ? -calculated : calculated
};
}
#parseDecimal = () => {
return this.#input.replace(/[.,]/g, ".")
};
}
#parseTimeAsSeconds = () => {
const match = this.#isTime()
@@ -39,12 +41,12 @@ export default class TimeInputParser {
const minutes = parseInt(match[3])
return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative)
};
}
#parseMinutesAsSeconds = () => {
const minutes = parseInt(this.#isMinutes()[1])
return minutes * 60
};
}
#parseRange = () => {
const match = this.#isRange()
@@ -54,7 +56,7 @@ export default class TimeInputParser {
const to_hours = parseInt(match[3])
const to_minutes = parseInt(match[4])
return (to_hours - from_hours) * 3600 + (to_minutes - from_minutes) * 60
};
}
#parseHoursAndMinutes = () => {
const match = this.#isHoursAndMinutes()
@@ -64,28 +66,26 @@ export default class TimeInputParser {
const minutes = parseInt(match[3])
return this.#calculateFromHoursAndMinutes(hours, minutes, isNegative)
};
}
#isDecimal = () => {
return this.#input.match(/^([-]?[0-9]{0,2})[.,]{1}([0-9]{1,2})$/)
};
}
#isTime = () => {
return this.#input.match(/^([-]?)([0-9]{1,2}):([0-9]{2})$/)
};
}
#isMinutes = () => {
return this.#input.match(/^([-]?[0-9]{1,3})(m|mins?)$/)
};
}
#isRange = () => {
return this.#input.match(
/^([0-9]{1,2})[:.]{0,1}([0-9]{2})-([0-9]{1,2})[:.]{0,1}([0-9]{2})$/
)
};
return this.#input.match(/^([0-9]{1,2})[:.]{0,1}([0-9]{2})-([0-9]{1,2})[:.]{0,1}([0-9]{2})$/)
}
#isHoursAndMinutes = () => {
// 1h 14m(in)
return this.#input.match(/^([-]?)([0-9]{1,2})h([0-9]{1,2})(m|mins?)$/)
};
}
}

View File

@@ -1,32 +1,53 @@
import { head } from "lodash/fp"
import { head, pick, reduce, filter, prop, pipe } from "lodash/fp"
import remoteServices from "../remoteServices"
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) => {
const keys = ["subdomain", "apiKey"]
const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM", "hostOverrides"]
const { version } = chrome.runtime.getManifest()
if (isChrome()) {
return new Promise(resolve => {
chrome.storage.sync.get(keys, data => {
return new Promise((resolve) => {
chrome.storage.sync.get(keys, (settings) => {
if (withDefaultSubdomain) {
data.subdomain = data.subdomain || "__unset__"
settings.subdomain = settings.subdomain || DEFAULT_SUBDOMAIN
}
resolve({ ...data, version })
settings.hostOverrides = getHostOverrides(settings)
resolve({ ...settings, version })
})
})
} else {
return browser.storage.sync.get(keys).then(data => {
return browser.storage.sync.get(keys).then((settings) => {
if (withDefaultSubdomain) {
data.subdomain = data.subdomain || "__unset__"
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()) {
return new Promise(resolve => {
return new Promise((resolve) => {
chrome.storage.sync.set(items, resolve)
})
} else {
@@ -34,9 +55,9 @@ export const setStorage = items => {
}
}
export const queryTabs = queryInfo => {
export const queryTabs = (queryInfo) => {
if (isChrome()) {
return new Promise(resolve => chrome.tabs.query(queryInfo, resolve))
return new Promise((resolve) => chrome.tabs.query(queryInfo, resolve))
} else {
return browser.tabs.query(queryInfo)
}
@@ -46,4 +67,4 @@ export const getCurrentTab = () => {
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,
find,
curry,
pick
pick,
head,
defaultTo,
padCharsStart,
} from "lodash/fp"
import { startOfWeek, endOfWeek } from "date-fns"
import { format } from "date-fns"
const nilToArray = input => input || []
const nilToArray = (input) => input || []
export const ERROR_UNAUTHORIZED = "unauthorized"
export const ERROR_UPGRADE_REQUIRED = "upgrade-required"
export const ERROR_UNKNOWN = "unknown"
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 findProjectByValue = findProjectBy("value")
export const findTask = id =>
compose(
find(pathEq("value", Number(id))),
get("tasks")
)
export const findTask = (id) => compose(find(pathEq("value", Number(id))), get("tasks"))
export const defaultTask = (tasks) =>
compose(defaultTo(head(tasks)), find(pathEq("isDefault", true)), nilToArray)(tasks)
function taskOptions(tasks) {
return tasks.map(({ id, name, billable }) => ({
return tasks.map(({ id, name, billable, default: isDefault }) => ({
label: billable ? name : `(${name})`,
value: id,
billable
billable,
isDefault,
}))
}
export function projectOptions(projects) {
return projects.map(project => ({
return projects.map((project) => ({
value: project.id,
label: project.intern ? `(${project.name})` : project.name,
identifier: project.identifier,
customerName: project.customer_name,
tasks: taskOptions(project.tasks)
tasks: taskOptions(project.tasks),
}))
}
export const groupedProjectOptions = compose(
map(([customerName, projects]) => ({
label: customerName,
options: projectOptions(projects)
options: projectOptions(projects),
})),
toPairs,
groupBy("customer_name"),
nilToArray
nilToArray,
)
export const serializeProps = attrs =>
compose(
mapValues(JSON.stringify),
pick(attrs)
)
export const serializeProps = (attrs) => compose(mapValues(JSON.stringify), pick(attrs))
export const parseProps = attrs =>
compose(
mapValues(JSON.parse),
pick(attrs)
)
export const parseProps = (attrs) => compose(mapValues(JSON.parse), pick(attrs))
export const trace = curry((tag, value) => {
// eslint-disable-next-line no-console
@@ -82,12 +82,13 @@ export const trace = curry((tag, value) => {
})
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 = () =>
`chrome://extensions/?id=${chrome.runtime.id}`
export const extensionSettingsUrl = () => `chrome://extensions/?id=${chrome.runtime.id}`
export const extractAndSetTag = changeset => {
export const extractAndSetTag = (changeset) => {
let { description } = changeset
const match = description.match(/^#(\S+)/)
if (!match) {
@@ -96,6 +97,25 @@ export const extractAndSetTag = changeset => {
return {
...changeset,
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_UNKNOWN,
groupedProjectOptions,
weekStartsOn
getStartOfWeek,
getEndOfWeek,
} from "utils"
import { get, forEach, reject, isNil } from "lodash/fp"
import { startOfWeek, endOfWeek } from "date-fns"
import { createMatcher } from "utils/urlMatcher"
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 })
const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
const matcher = createMatcher(remoteServices)
let matcher
const initMatcher = (settings) => {
matcher = createMatcher(remoteServices, settings.hostOverrides)
}
getSettings().then((settings) => {
initMatcher(settings)
})
export function tabUpdated(tab, { messenger, settings }) {
messenger.connectTab(tab)
const service = matcher(tab.url)
const apiClient = new ApiClient(settings)
if (service?.match?.id) {
messenger.postMessage(tab, { type: "requestService" })
messenger.once("newService", ({ payload: { service } }) => {
const apiClient = new ApiClient(settings)
apiClient
.bookedHours(service)
.activitiesStatus(service)
.then(({ data }) => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: parseFloat(data[0]?.hours) || 0,
service
}
bookedSeconds: data.seconds,
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
timedActivity: data.timed_activity,
service,
},
})
})
.catch(() => {
messenger.postMessage(tab, {
type: "showBubble",
payload: {
bookedHours: 0,
service
}
bookedSeconds: 0,
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
service,
},
})
})
})
@@ -52,13 +62,15 @@ export function tabUpdated(tab, { messenger, settings }) {
}
export function settingsChanged(settings, { messenger }) {
initMatcher(settings)
queryTabs({ currentWindow: true })
.then(reject(isBrowserTab))
.then(
forEach(tab => {
messenger.postMessage(tab, { type: "closePopup" })
tabUpdated(tab, { settings, messenger })
})
}),
)
}
@@ -76,52 +88,61 @@ export function togglePopup(tab, { messenger }) {
}
}
function openPopup(tab, { service, messenger }) {
export async function openPopup(tab, { service, messenger }) {
messenger.postMessage(tab, { type: "openPopup", payload: { loading: true } })
const fromDate = getStartOfWeek()
const toDate = getEndOfWeek()
getSettings()
.then(settings => new ApiClient(settings))
.then(apiClient =>
Promise.all([
apiClient.login(service),
apiClient.projects(),
apiClient.activities(fromDate, toDate),
apiClient.schedules(fromDate, toDate)
])
)
.then(responses => {
const action = {
type: "openPopup",
payload: {
service,
lastProjectId: get("[0].data.last_project_id", responses),
lastTaskId: get("[0].data.last_task_id", responses),
roundTimeEntries: get("[0].data.round_time_entries", responses),
projects: groupedProjectOptions(get("[1].data.projects", responses)),
activities: get("[2].data", responses),
schedules: get("[3].data", responses),
fromDate,
toDate,
loading: false
}
}
messenger.postMessage(tab, action)
})
.catch(error => {
let errorType, errorMessage
if (error.response?.status === 401) {
errorType = ERROR_UNAUTHORIZED
} else if (error.response?.status === 426) {
errorType = ERROR_UPGRADE_REQUIRED
} else {
errorType = ERROR_UNKNOWN
errorMessage = error.message
}
messenger.postMessage(tab, {
type: "openPopup",
payload: { errorType, errorMessage }
})
const settings = await getSettings()
const apiClient = new ApiClient(settings)
const responses = []
try {
responses.push(await 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.activities(fromDate, toDate),
apiClient.schedules(fromDate, toDate),
])),
)
}
const settingTimeTrackingHHMM = get("[0].data.setting_time_tracking_hh_mm", responses)
!isNil(settingTimeTrackingHHMM) && setStorage({ settingTimeTrackingHHMM })
const action = {
type: "openPopup",
payload: {
service,
subdomain: settings.subdomain,
timedActivity: get("[0].data.timed_activity", responses),
lastProjectId: get("[0].data.last_project_id", responses),
lastTaskId: get("[0].data.last_task_id", responses),
roundTimeEntries: get("[0].data.round_time_entries", responses),
projects: groupedProjectOptions(get("[1].data.projects", responses)),
activities: get("[2].data", responses),
schedules: get("[3].data", responses),
fromDate,
toDate,
loading: false,
},
}
messenger.postMessage(tab, action)
} catch (error) {
let errorType, errorMessage
if (error.response?.status === 401) {
errorType = ERROR_UNAUTHORIZED
} else if (error.response?.status === 426) {
errorType = ERROR_UPGRADE_REQUIRED
} else {
errorType = ERROR_UNKNOWN
errorMessage = error.message
}
messenger.postMessage(tab, {
type: "openPopup",
payload: { errorType, errorMessage },
})
}
}

View File

@@ -1,14 +1,14 @@
export class BackgroundMessenger {
#ports = new Map();
#handlers = new Map();
#onceHandlers = new Map();
#ports = new Map()
#handlers = new Map()
#onceHandlers = new Map()
#handler = action => {
const handler = this.#handlers.get(action.type)
if (handler) {
handler(action)
}
};
}
#onceHandler = action => {
const handler = this.#onceHandlers.get(action.type)
@@ -16,7 +16,7 @@ export class BackgroundMessenger {
if (handler) {
handler(action)
}
};
}
#registerPort = (tabId, port) => {
this.#ports.set(tabId, port)
@@ -25,14 +25,14 @@ export class BackgroundMessenger {
port.onDisconnect.addListener(() => {
this.#unregisterPort(tabId, port)
})
};
}
#unregisterPort = (tabId, port) => {
port.onMessage.removeListener(this.#handler)
port.onMessage.removeListener(this.#onceHandler)
port.disconnect()
this.#ports.delete(tabId)
};
}
connectTab = tab => {
const currentPort = this.#ports.get(tab.id)
@@ -40,41 +40,41 @@ export class BackgroundMessenger {
const port = chrome.tabs.connect(tab.id)
this.#registerPort(tab.id, port)
}
};
}
disconnectTab = tabId => {
const port = this.#ports.get(tabId)
if (port) {
this.#unregisterPort(tabId, port)
}
};
}
postMessage = (tab, action) => {
const port = this.#ports.get(tab.id)
if (port) {
port.postMessage(action)
}
};
}
once = (type, handler) => {
this.#onceHandlers.set(type, handler)
};
}
on = (type, handler) => {
this.#handlers.set(type, handler)
};
}
}
export class ContentMessenger {
#port;
#handlers = new Map();
#port
#handlers = new Map()
#handler = action => {
const handler = this.#handlers.get(action.type)
if (handler) {
handler(action)
}
};
}
constructor(port) {
this.#port = port
@@ -85,15 +85,15 @@ export class ContentMessenger {
if (this.#port) {
this.#port.postMessage(action)
}
};
}
on = (type, handler) => {
this.#handlers.set(type, handler)
};
}
stop = () => {
this.#port.onMessage.removeListener(this.#handler)
this.#port = null
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 {
isFunction,
isUndefined,
compose,
toPairs,
map,
pipe
} from "lodash/fp"
import { isFunction, isUndefined, compose, toPairs, map, pipe, isNil, reduce } from "lodash/fp"
import { asArray } from "./index"
import queryString from "query-string"
const extractQueryParams = (queryParams, query) => {
return toPairs(queryParams).reduce((acc, [key, param]) => {
acc[key] = query[param]
function parseUrl(url) {
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]
}
return acc
}, {})
}
const createEvaluator = args => fnOrValue => {
const createEvaluator = (args) => (fnOrValue) => {
if (isUndefined(fnOrValue)) {
return
}
@@ -28,21 +39,35 @@ const createEvaluator = args => 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(
map(([key, config]) => ({
...config,
key,
patterns: config.urlPatterns.map(pattern => {
patterns: config.urlPatterns.map((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) {
return
}
@@ -57,48 +82,55 @@ export const createEnhancer = document => service => {
description: evaluate(service.description),
projectId: evaluate(service.projectId),
taskId: evaluate(service.taskId),
position: service.position || { right: "calc(2rem + 5px)" }
position: service.position || { right: "calc(2rem + 5px)" },
}
}
export const createMatcher = remoteServices => {
const services = parseServices(remoteServices)
return tabUrl => {
const { origin, pathname, hash, search } = new URL(tabUrl)
const applyHostOverrides = (remoteServices, hostOverrides) =>
pipe(
toPairs,
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 query = queryString.parse(search)
const service = services.find(service =>
service.patterns.some(pattern => pattern.match(url))
)
const service = services.find((service) => {
return service.patterns.some((pattern) => pattern.match(url))
})
if (!service) {
return
}
const pattern = service.patterns.find(pattern => pattern.match(url))
const pattern = service.patterns.find((pattern) => pattern.match(url))
let match = pattern.match(url)
if (service.queryParams) {
const extractedQueryParams = extractQueryParams(
service.queryParams,
query
)
const extractedQueryParams = extractQueryParams(service.queryParams, query)
match = { ...extractedQueryParams, ...match }
}
return {
...match,
...service,
url,
match
url: tabUrl,
match,
}
}
}
export const createServiceFinder = remoteServices => document => {
const matcher = createMatcher(remoteServices)
export const createServiceFinder = (remoteServices, hostOverrides) => (document) => {
const matcher = createMatcher(remoteServices, hostOverrides)
const enhancer = createEnhancer(document)
return pipe(
matcher,
enhancer
)
return pipe(matcher, enhancer)
}

View File

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

View File

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

View File

@@ -3,16 +3,26 @@ import {
findProjectByValue,
findProjectByIdentifier,
findTask,
defaultTask,
groupedProjectOptions,
extractAndSetTag
extractAndSetTag,
formatDuration,
} 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("findProjectByValue", () => {
it("finds an existing project", () => {
const options = groupedProjectOptions(projects)
const project = findProjectByValue(944837106)(options)
const project = getProjectByValue(944837106)
expect(project.value).toEqual(944837106)
expect(project.label).toEqual("Support")
expect(project.customerName).toEqual("MOCO APP")
@@ -20,14 +30,12 @@ describe("utils", () => {
})
it("returns undefined if project is not found", () => {
const options = groupedProjectOptions(projects)
const project = findProjectByValue(123)(options)
const project = getProjectByValue(123)
expect(project).toBe(undefined)
})
it("returns undefined for undefined id", () => {
const options = groupedProjectOptions(projects)
const project = findProjectByValue(undefined)(options)
const project = getProjectByValue(undefined)
expect(project).toBe(undefined)
})
})
@@ -57,16 +65,14 @@ describe("utils", () => {
describe("findTask", () => {
it("find an existing task", () => {
const options = groupedProjectOptions(projects)
const project = findProjectByValue(944837106)(options)
const project = getProjectByValue(944837106)
const task = findTask(2506050)(project)
expect(task.value).toEqual(2506050)
expect(task.label).toEqual("(Calls)")
})
it("returns undefined if task is not found", () => {
const options = groupedProjectOptions(projects)
const project = findProjectByValue(944837106)(options)
const project = getProjectByValue(944837106)
const task = findTask(123)(project)
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", () => {
it("transforms projects into grouped options by company", () => {
const result = groupedProjectOptions(projects)
expect(map("label", result)).toEqual([
"Simplificator",
"MOCO APP",
"sharoo"
])
expect(map("label", result)).toEqual(["Simplificator", "MOCO APP", "sharoo"])
})
})
@@ -92,19 +116,19 @@ describe("utils", () => {
it("sets the correct tag and updates description", () => {
const changeset = {
description: "#meeting Lorem ipsum",
tag: ""
tag: "",
}
expect(extractAndSetTag(changeset)).toEqual({
description: "Lorem ipsum",
tag: "meeting"
tag: "meeting",
})
})
it("only matches tag at the beginning", () => {
const changeset = {
description: "Lorem #meeting ipsum",
tag: ""
tag: "",
}
expect(extractAndSetTag(changeset)).toEqual(changeset)
@@ -113,10 +137,27 @@ describe("utils", () => {
it("returns the changeset if not tag is set", () => {
const changeset = {
description: "Without tag",
tag: ""
tag: "",
}
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
beforeEach(() => {
matcher = createMatcher(remoteServices)
matcher = createMatcher(remoteServices, {})
})
describe("createMatcher", () => {
it("matches host and path", () => {
const service = matcher(
"https://github.com/hundertzehn/mocoapp/pull/123"
)
const service = matcher("https://github.com/hundertzehn/mocoapp/pull/123")
expect(service.key).toEqual("github-pr")
expect(service.name).toEqual("github")
})
it("matches query string", () => {
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.name).toEqual("jira")
@@ -32,7 +30,7 @@ describe("utils", () => {
expect(service.match.id).toEqual("TEST1-1")
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.name).toEqual("jira")
@@ -44,7 +42,7 @@ describe("utils", () => {
expect(service.match.id).toBeUndefined()
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.name).toEqual("jira")
@@ -55,9 +53,7 @@ describe("utils", () => {
expect(service.match.projectId).toEqual("TEST1")
expect(service.match.id).toEqual("")
service = matcher(
"https://moco-bx.atlassian.net/secure/RapidBoard.jspa"
)
service = matcher("https://moco-bx.atlassian.net/secure/RapidBoard.jspa")
expect(service.key).toEqual("jira")
expect(service.name).toEqual("jira")
expect(service.match.org).toEqual("moco-bx")
@@ -65,7 +61,7 @@ describe("utils", () => {
expect(service.match.id).toBeUndefined()
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.name).toEqual("jira")
@@ -78,29 +74,84 @@ describe("utils", () => {
})
it("matches url with hash", () => {
let service = matcher(
"https://www.wunderlist.com/webapp#/tasks/4771178545"
)
let service = matcher("https://www.wunderlist.com/webapp#/tasks/4771178545")
expect(service.key).toEqual("wunderlist")
expect(service.name).toEqual("wunderlist")
expect(service.match.id).toEqual("4771178545")
})
it("does not match different host", () => {
const service = matcher(
"https://trello.com/hundertzehn/mocoapp/pull/123"
)
const service = matcher("https://trello.com/hundertzehn/mocoapp/pull/123")
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", () => {
it("enhances a services", () => {
const url = "https://github.com/hundertzehn/mocoapp/pull/123"
const document = {
querySelector: jest
.fn()
.mockReturnValue({ textContent: "[4321] Foo" })
querySelector: jest.fn().mockReturnValue({ textContent: "[4321] Foo" }),
}
const service = matcher(url)
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 webpack = require("webpack")
const CleanWebpackPlugin = require("clean-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const RemoveSourceMapPlugin = require("./webpack/RemoveSourceMapPlugin")
const ZipPlugin = require("zip-webpack-plugin")
const {
BugsnagBuildReporterPlugin,
BugsnagSourceMapUploaderPlugin
} = require("webpack-bugsnag-plugins")
module.exports = env => {
module.exports = (env) => {
const config = {
entry: {
background: "./src/js/background.js",
content: "./src/js/content.js",
popup: "./src/js/popup.js",
options: "./src/js/options.js"
options: "./src/js/options.js",
},
output: {
path: path.join(__dirname, `build/${env.browser}`),
filename: `[name].${process.env.npm_package_version}.js`
filename: `[name].${process.env.npm_package_version}.js`,
},
module: {
rules: [
@@ -30,104 +25,85 @@ module.exports = env => {
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
loader: MiniCssExtractPlugin.loader,
},
"css-loader",
{
loader: "sass-loader",
options: {
includePaths: [path.join(__dirname, "src/css")]
}
}
sassOptions: {
includePaths: [path.join(__dirname, "src/css")],
},
},
},
],
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
loader: "babel-loader",
},
},
{
test: /\.(jpg|png)$/,
loader: "file-loader",
options: {
name: "[path][name].[ext]"
name: "[path][name].[ext]",
},
exclude: /node_modules/
}
]
exclude: /node_modules/,
},
{
test: /\.svg$/,
loader: "svg-inline-loader",
},
],
},
plugins: [
new CleanWebpackPlugin([`build/${env.browser}`]),
new CleanWebpackPlugin({
cleanAfterEveryBuildPatterns: ["!manifest.json", "!*.html"],
}),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(env.NODE_ENV),
"process.env.BUGSNAG_API_KEY": JSON.stringify(
process.env.BUGSNAG_API_KEY
)
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
chunkFilename: "[id].css",
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "background.html"),
filename: "background.html",
chunks: ["background"]
filename: path.resolve(`build/${env.browser}`, "background.html"),
chunks: ["background"],
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "popup.html"),
filename: "popup.html",
chunks: ["popup"]
filename: path.resolve(`build/${env.browser}`, "popup.html"),
chunks: ["popup"],
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "options.html"),
filename: "options.html",
chunks: ["options"]
})
filename: path.resolve(`build/${env.browser}`, "options.html"),
chunks: ["options"],
}),
],
resolve: {
modules: [path.join(__dirname, "src/js"), "node_modules"],
alias: {
images: path.join(__dirname, "src/images")
}
images: path.join(__dirname, "src/images"),
},
},
mode: env.NODE_ENV || "development",
devtool: "cheap-module-source-map"
devtool: "cheap-module-source-map",
}
if (env.NODE_ENV === "production") {
config.devtool = "source-maps"
if (process.env.BUGSNAG_API_KEY) {
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.devtool = "none"
config.plugins.push(
new RemoveSourceMapPlugin(),
new ZipPlugin({
filename: `moco-bx-${env.browser}-v${
process.env.npm_package_version
}.zip`,
exclude: [/\.map$/]
})
filename: `moco-bx-${env.browser}-v${process.env.npm_package_version}.zip`,
exclude: [/\.map$/],
}),
)
}

View File

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

8496
yarn.lock

File diff suppressed because it is too large Load Diff