From 767fdd5f73e6fa9aa94ae74469138c93b7ae6885 Mon Sep 17 00:00:00 2001 From: Alexander Ivantsov Date: Tue, 3 Oct 2017 12:49:45 +0200 Subject: [PATCH] New codestyle (#62) * update eslint config * update eslint * use latest yarn version * add comment for travis config --- .eslintrc.js | 130 +++++----- .travis.yml | 6 +- jest.config.js | 28 +-- package.json | 2 +- scripts/deploy.js | 104 ++++---- scripts/locales.js | 4 +- scripts/release.js | 28 +-- src/locales/en.js | 204 +++++++-------- src/locales/ru.js | 204 +++++++-------- src/manifest/chrome.js | 14 +- src/manifest/firefox.js | 16 +- src/manifest/index.js | 6 +- src/pages/background/index.js | 26 +- src/pages/background/modules/cookie.js | 78 +++--- src/pages/background/modules/notification.js | 74 +++--- src/pages/background/modules/popup-button.js | 27 +- src/pages/background/modules/storage.js | 51 ++-- .../background/modules/websocket/client.js | 62 ++--- .../background/modules/websocket/index.js | 213 ++++++++-------- .../background/redux/actions/messages.js | 87 ++++--- .../background/redux/actions/notification.js | 146 +++++------ src/pages/background/redux/actions/popup.js | 24 +- .../background/redux/actions/settings.js | 30 +-- src/pages/background/redux/actions/user.js | 26 +- src/pages/background/redux/ext-store.js | 52 ++-- .../background/redux/middlewares/index.js | 11 +- .../background/redux/middlewares/raven.js | 28 +-- src/pages/background/redux/reducers/index.js | 16 +- .../background/redux/reducers/messages.js | 94 +++---- .../background/redux/reducers/settings.js | 38 +-- src/pages/background/redux/reducers/user.js | 38 +-- src/pages/background/redux/store.js | 4 +- .../utils/__tests__/parser/fixtures.js | 66 ++--- .../utils/__tests__/parser/index.js | 183 +++++++------- .../utils/__tests__/session-generator.test.js | 8 +- .../utils/__tests__/url-resolver.test.js | 28 +-- src/pages/background/utils/api.js | 234 +++++++++--------- src/pages/background/utils/cookie.js | 48 ++-- src/pages/background/utils/parser.js | 90 +++---- src/pages/background/utils/runtime.js | 2 +- .../background/utils/session-generator.js | 18 +- src/pages/background/utils/tab.js | 16 +- src/pages/background/utils/url-resolver.js | 4 +- src/pages/popup/actions.js | 44 ++-- src/pages/popup/components/App.js | 156 ++++++------ src/pages/popup/components/Button/Button.js | 54 ++-- .../popup/components/Donation/Donation.js | 28 +-- src/pages/popup/components/Header/Header.js | 80 +++--- .../components/Header/Icons/ComposeIcon.js | 6 +- .../components/Header/Icons/ReloadIcon.js | 6 +- .../components/Header/Icons/SettingsIcon.js | 6 +- .../Header/__tests__/Header.test.js | 92 +++---- .../components/List/HoverMenu/HoverMenu.js | 26 +- .../HoverMenuButton/HoverMenuButton.js | 70 +++--- .../HoverMenuButton/Icons/ReadIcon.js | 6 +- .../HoverMenuButton/Icons/RemoveIcon.js | 6 +- .../HoverMenuButton/Icons/SpamIcon.js | 6 +- .../__tests__/HoverMenuButton.js | 70 +++--- .../List/HoverMenu/HoverMenuButton/types.js | 30 +-- src/pages/popup/components/List/Item/Item.js | 98 ++++---- .../List/ItemPlaceholder/ItemPlaceholder.js | 16 +- src/pages/popup/components/List/List.js | 142 +++++------ .../components/List/__tests__/List.test.js | 104 ++++---- src/pages/popup/components/Translation.js | 24 +- .../UnavailableMessage/UnavailableMessage.js | 64 ++--- .../popup/components/__tests__/App.test.js | 88 +++---- src/pages/popup/index.js | 18 +- src/pages/raven.js | 22 +- src/pages/settings/actions.js | 8 +- src/pages/settings/components/App.js | 30 +-- src/pages/settings/components/Field/Field.js | 64 ++--- .../components/Field/Items/Checkbox.js | 24 +- .../settings/components/Field/Items/Link.js | 20 +- .../settings/components/Field/Items/Select.js | 86 +++---- .../Field/Items/__tests__/Select.test.js | 152 ++++++------ .../components/Field/Items/proptypes.js | 4 +- .../components/Field/__tests__/Field.test.js | 56 ++--- src/pages/settings/components/Field/types.js | 6 +- .../settings/components/__tests__/App.test.js | 28 +-- src/pages/settings/components/config.js | 68 ++--- src/pages/settings/index.js | 18 +- src/pages/shared/config.js | 24 +- src/pages/shared/utils/__mocks__/i18n.js | 12 +- src/pages/shared/utils/__tests__/i18n.js | 142 +++++------ src/pages/shared/utils/i18n.js | 39 ++- webpack/base.js | 110 ++++---- webpack/dev.js | 112 +++++---- webpack/prod.js | 63 ++--- webpack/utils.js | 40 +-- yarn.lock | 192 ++++++++++++-- 90 files changed, 2641 insertions(+), 2487 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4bce742..c0d8498 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,86 +1,76 @@ const {resolve} = require('./webpack/base'); -const INDENT = 4; - const base = { - // disabled - 'max-len': 'off', - 'arrow-parens': 'off', - 'arrow-body-style': 'off', - 'no-prototype-builtins': 'off', - 'no-plusplus': 'off', + // disabled + 'max-len': 'off', + 'arrow-parens': 'off', + 'arrow-body-style': 'off', + 'no-prototype-builtins': 'off', + 'no-plusplus': 'off', - // modified - indent: ['error', INDENT, { - SwitchCase: 1 - }], - 'brace-style': ['error', 'stroustrup', { - 'allowSingleLine': false - }], - 'object-curly-spacing': ['error', 'never'], - 'one-var': ['error', { - initialized: 'never' - }], - 'one-var-declaration-per-line': ['error', 'initializations'], + // modified + 'object-curly-spacing': ['error', 'never'], + 'one-var': ['error', { + initialized: 'never' + }], + 'one-var-declaration-per-line': ['error', 'initializations'], }; const react = { - // disabled - 'react/forbid-prop-types': 'off', - 'react/no-array-index-key': 'off', + // disabled + 'react/forbid-prop-types': 'off', + 'react/no-array-index-key': 'off', - // modified - 'react/jsx-filename-extension': ['error', {extensions: ['.js']}], - 'react/jsx-indent': ['error', INDENT], - 'react/jsx-indent-props': ['error', INDENT], - 'react/jsx-tag-spacing': ['error', { - closingSlash: 'never', - beforeSelfClosing: 'never', - afterOpening: 'never' - }], - 'react/sort-comp': ['error', { - order: [ - 'static-methods', - 'lifecycle', - 'rendering', - 'everything-else' - ], - groups: { - rendering: [ - 'render', - '/^render.+$/' - ] - } - }], + // modified + 'react/jsx-filename-extension': ['error', {extensions: ['.js']}], + 'react/jsx-tag-spacing': ['error', { + closingSlash: 'never', + beforeSelfClosing: 'never', + afterOpening: 'never' + }], + 'react/sort-comp': ['error', { + order: [ + 'static-methods', + 'lifecycle', + 'rendering', + 'everything-else' + ], + groups: { + rendering: [ + 'render', + '/^render.+$/' + ] + } + }], }; const other = { - 'jsx-a11y/no-static-element-interactions': 'off' + 'jsx-a11y/no-static-element-interactions': 'off' }; module.exports = { - parser: 'babel-eslint', - extends: 'airbnb', - env: { - webextensions: true, - browser: true, - jest: true - }, - globals: { - __DEV__: false - }, - settings: { - 'import/resolver': { - webpack: { - config: { - resolve - } - } + parser: 'babel-eslint', + extends: 'airbnb', + env: { + webextensions: true, + browser: true, + jest: true + }, + globals: { + __DEV__: false + }, + settings: { + 'import/resolver': { + webpack: { + config: { + resolve } - }, - rules: Object.assign({}, - base, - react, - other - ) + } + } + }, + rules: { + ...base, + ...react, + ...other, + } }; diff --git a/.travis.yml b/.travis.yml index 6cf8a5f..26d4277 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ language: node_js node_js: - 8 +before_install: + - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.1.0 # otherwise Travis uses 0.27.* + - export PATH=$HOME/.yarn/bin:$PATH script: + - set -e # fail the build if one of the scripts fails - https://github.com/travis-ci/travis-ci/issues/1066 - npm run lint - npm run test:coverage cache: @@ -13,4 +17,4 @@ deploy: script: npm run publish skip_cleanup: true on: - tags: true \ No newline at end of file + tags: true diff --git a/jest.config.js b/jest.config.js index cabd2dd..bd8bcf9 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,16 +1,16 @@ module.exports = { - rootDir: './src', - clearMocks: true, - resetModules: true, - moduleDirectories: [ - 'node_modules', - 'src/pages', - ], - moduleNameMapper: { - '\\.(less)$': 'identity-obj-proxy', - }, - testPathIgnorePatterns: [ - '/node_modules/', - 'fixtures.js', - ], + rootDir: './src', + clearMocks: true, + resetModules: true, + moduleDirectories: [ + 'node_modules', + 'src/pages', + ], + moduleNameMapper: { + '\\.(less)$': 'identity-obj-proxy', + }, + testPathIgnorePatterns: [ + '/node_modules/', + 'fixtures.js', + ], }; diff --git a/package.json b/package.json index c530bab..1e41ba9 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "copy-webpack-plugin": "^4.0.0", "css-loader": "^0.28.7", "date-fns": "^1.28.5", - "eslint": "^3.19.0", + "eslint": "^4.8.0", "eslint-config-airbnb": "^15.1.0", "eslint-import-resolver-webpack": "^0.8.3", "eslint-plugin-import": "^2.7.0", diff --git a/scripts/deploy.js b/scripts/deploy.js index bbcdffb..86c0742 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -6,73 +6,73 @@ const clientId = process.env.CLIENT_ID; const refreshToken = process.env.REFRESH_TOKEN; function getError(text, err) { - throw new Error(`Failed to ${text}: ${(err.response && err.response.text) || err}`); + throw new Error(`Failed to ${text}: ${(err.response && err.response.text) || err}`); } function getToken() { - return request - .post('https://accounts.google.com/o/oauth2/token') - .type('form') - .send({ - client_id: clientId, - refresh_token: refreshToken, - grant_type: 'refresh_token', - redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', - }) - .then(res => { - const accessToken = res.body.access_token; + return request + .post('https://accounts.google.com/o/oauth2/token') + .type('form') + .send({ + client_id: clientId, + refresh_token: refreshToken, + grant_type: 'refresh_token', + redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + }) + .then(res => { + const accessToken = res.body.access_token; - if (!accessToken) { - throw new Error(res.body); - } + if (!accessToken) { + throw new Error(res.body); + } - console.log('(1/3) Token received!'); + console.log('(1/3) Token received!'); - return accessToken; - }) - .catch(err => getError('receive access token', err)); + return accessToken; + }) + .catch(err => getError('receive access token', err)); } function upload(accessToken) { - return request - .put(`https://www.googleapis.com/upload/chromewebstore/v1.1/items/${appId}`) - .set({ - Authorization: `Bearer ${accessToken}`, - 'x-goog-api-version': 2, - }) - .attach('file', filename) - .then(res => { - if (res.body.uploadState !== 'SUCCESS') { - throw new Error(res.body.itemError[0].error_detail); - } + return request + .put(`https://www.googleapis.com/upload/chromewebstore/v1.1/items/${appId}`) + .set({ + Authorization: `Bearer ${accessToken}`, + 'x-goog-api-version': 2, + }) + .attach('file', filename) + .then(res => { + if (res.body.uploadState !== 'SUCCESS') { + throw new Error(res.body.itemError[0].error_detail); + } - console.log('(2/3) Uploaded!'); + console.log('(2/3) Uploaded!'); - return accessToken; - }) - .catch(err => getError('upload package', err)); + return accessToken; + }) + .catch(err => getError('upload package', err)); } function publish(accessToken) { - return request - .post(`https://www.googleapis.com/chromewebstore/v1.1/items/${appId}/publish`) - .set({ - Authorization: `Bearer ${accessToken}`, - 'x-goog-api-version': 2, - 'Content-Length': 0, - // publishTarget: 'trustedTesters' - }) - .then(res => { - if (res.body.status[0] !== 'OK') { - throw new Error(res.body); - } + return request + .post(`https://www.googleapis.com/chromewebstore/v1.1/items/${appId}/publish`) + .set({ + Authorization: `Bearer ${accessToken}`, + 'x-goog-api-version': 2, + 'Content-Length': 0, + // publishTarget: 'trustedTesters' + }) + .then(res => { + if (res.body.status[0] !== 'OK') { + throw new Error(res.body); + } - console.log('(3/3) Published!'); - }) - .catch(err => getError('publish package', err)); + console.log('(3/3) Published!'); + }) + .catch(err => getError('publish package', err)); } getToken() - .then(upload) - .then(publish) - .catch(console.log); + .then(upload) + .then(publish) + .catch(console.log); diff --git a/scripts/locales.js b/scripts/locales.js index 2761a0c..b33154e 100644 --- a/scripts/locales.js +++ b/scripts/locales.js @@ -1,6 +1,6 @@ const i18n = require('webext-i18n'); i18n({ - inputDir: './src/locales', - outputDir: './dist/_locales', + inputDir: './src/locales', + outputDir: './dist/_locales', }).then(() => console.log('Locales are generated.')); diff --git a/scripts/release.js b/scripts/release.js index 708fcc5..b4515af 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -2,30 +2,30 @@ const {execSync} = require('child_process'); const fs = require('fs-extra'); const paths = [ - './package.json', - './src/manifest/base.json', + './package.json', + './src/manifest/base.json', ]; function validate(newVersion) { - const semverRegex = /^\d+\.\d+\.\d+$/; + const semverRegex = /^\d+\.\d+\.\d+$/; - if (!newVersion || !semverRegex.test(newVersion)) { - throw new Error('The release version is not provided or is incorrect'); - } + if (!newVersion || !semverRegex.test(newVersion)) { + throw new Error('The release version is not provided or is incorrect'); + } } function updateVersion(newVersion) { - paths.forEach(path => { - const config = fs.readJsonSync(path); - config.version = newVersion; - fs.writeJsonSync(path, config, {spaces: 2}); - }); + paths.forEach(path => { + const config = fs.readJsonSync(path); + config.version = newVersion; + fs.writeJsonSync(path, config, {spaces: 2}); + }); } function makeCommit(newVersion) { - execSync(`git add ${paths.join(' ')}`); - execSync(`git commit -m "bump v${newVersion}" --no-verify`); - execSync(`git tag v${newVersion}`); + execSync(`git add ${paths.join(' ')}`); + execSync(`git commit -m "bump v${newVersion}" --no-verify`); + execSync(`git tag v${newVersion}`); } const newVersion = process.argv[2]; diff --git a/src/locales/en.js b/src/locales/en.js index c8d5c74..394b149 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1,108 +1,108 @@ module.exports = { - ext: { - name: 'Yandex.Mail notifier', - description: 'Tunable notifier for Yandex.Mail', + ext: { + name: 'Yandex.Mail notifier', + description: 'Tunable notifier for Yandex.Mail', + }, + notification: { + unread: { + title: 'You have unread emails', + message: 'Unread emails: $1', }, - notification: { - unread: { - title: 'You have unread emails', - message: 'Unread emails: $1', - }, - notAuth: { - title: 'You\'re not authorized', - message: 'Please, log in into your Yandex account', - }, + notAuth: { + title: 'You\'re not authorized', + message: 'Please, log in into your Yandex account', }, - popup: { - title: 'Yandex.Mail notifier', - compose: 'Compose', - actions: { - open: 'Open', - read: 'Mark as read', - spam: 'Spam', - remove: 'Remove', - }, - emptyList: 'You have no unread emails', - loadingError: 'Cannot load emails... Please try again later', - unavailable: { - title: 'Something went wrong...', - subTitle: 'There are a few possible reasons why the extension may not work properly:', - notAuth: { - label: 'You are not authorized.', - checkAuth: 'Check that you\'re authorized on Yandex (exactly .$1 domain). Also you can choose another domain in the extension settings.', - relogin: 'Try to logout and login again.', - }, - noConnection: { - label: 'There is no connection to the Internet.', - }, - nothingHelped: 'If nothing above helped, you can try to reload the extension.', - reloadBtn: 'Reload extension', - }, - donation: { - text: 'Want more settings, new features and just stable work of the extension?', - donateBtn: 'Support the project!', - }, - months: [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - ], + }, + popup: { + title: 'Yandex.Mail notifier', + compose: 'Compose', + actions: { + open: 'Open', + read: 'Mark as read', + spam: 'Spam', + remove: 'Remove', }, - settings: { - newMessageNotification: { - label: 'Notify when new email is received', - options: [ - 'off', - 'desktop notification', - 'desktop & sound notification', - ], - }, - unreadMessagesNotification: { - label: 'Remind about unread emails via desktop notification', - options: [ - 'off', - 'every 5 min', - 'every 15 min', - 'every 30 min', - ], - }, - unreadMessagesSound: { - label: 'Remind about unread emails via sound notification', - description: 'Plays according to the reminder interval', - }, - notAuthNotification: { - label: 'Notify if you\'re not logged in', - description: 'Every 5 min', - options: [ - 'off', - 'desktop notification', - 'desktop & sound notification', - ], - }, - setShortcuts: { - label: 'Shortcuts', - linkText: 'edit', - }, - preferredDomain: { - label: 'Preferred domain', - description: 'Will be used for authorization, links etc', - options: [ - 'ru', - 'ua', - 'by', - 'kz', - 'com', - 'com.tr', - ], - }, + emptyList: 'You have no unread emails', + loadingError: 'Cannot load emails... Please try again later', + unavailable: { + title: 'Something went wrong...', + subTitle: 'There are a few possible reasons why the extension may not work properly:', + notAuth: { + label: 'You are not authorized.', + checkAuth: 'Check that you\'re authorized on Yandex (exactly .$1 domain). Also you can choose another domain in the extension settings.', + relogin: 'Try to logout and login again.', + }, + noConnection: { + label: 'There is no connection to the Internet.', + }, + nothingHelped: 'If nothing above helped, you can try to reload the extension.', + reloadBtn: 'Reload extension', }, + donation: { + text: 'Want more settings, new features and just stable work of the extension?', + donateBtn: 'Support the project!', + }, + months: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ], + }, + settings: { + newMessageNotification: { + label: 'Notify when new email is received', + options: [ + 'off', + 'desktop notification', + 'desktop & sound notification', + ], + }, + unreadMessagesNotification: { + label: 'Remind about unread emails via desktop notification', + options: [ + 'off', + 'every 5 min', + 'every 15 min', + 'every 30 min', + ], + }, + unreadMessagesSound: { + label: 'Remind about unread emails via sound notification', + description: 'Plays according to the reminder interval', + }, + notAuthNotification: { + label: 'Notify if you\'re not logged in', + description: 'Every 5 min', + options: [ + 'off', + 'desktop notification', + 'desktop & sound notification', + ], + }, + setShortcuts: { + label: 'Shortcuts', + linkText: 'edit', + }, + preferredDomain: { + label: 'Preferred domain', + description: 'Will be used for authorization, links etc', + options: [ + 'ru', + 'ua', + 'by', + 'kz', + 'com', + 'com.tr', + ], + }, + }, }; diff --git a/src/locales/ru.js b/src/locales/ru.js index e05ca96..d8ae59d 100644 --- a/src/locales/ru.js +++ b/src/locales/ru.js @@ -1,108 +1,108 @@ module.exports = { - ext: { - name: 'Клиент для Яндекс.Почта', - description: 'Удобный клиент для Яндекс.Почта', + ext: { + name: 'Клиент для Яндекс.Почта', + description: 'Удобный клиент для Яндекс.Почта', + }, + notification: { + unread: { + title: 'У вас есть непрочитанные письма', + message: 'Непрочитанных писем: $1', }, - notification: { - unread: { - title: 'У вас есть непрочитанные письма', - message: 'Непрочитанных писем: $1', - }, - notAuth: { - title: 'Вы не авторизованы', - message: 'Пожалуйста, войдите в свой Яндекс аккаунт', - }, + notAuth: { + title: 'Вы не авторизованы', + message: 'Пожалуйста, войдите в свой Яндекс аккаунт', }, - popup: { - title: 'Яндекс.Почта', - compose: 'Написать', - actions: { - open: 'Открыть', - read: 'Прочитано', - spam: 'Спам', - remove: 'Удалить', - }, - emptyList: 'У вас нет непрочитанных писем', - loadingError: 'Не удалось загрузить письма... Попробуйте позже', - unavailable: { - title: 'Что-то пошло не так...', - subTitle: 'Возможны следующие причины почему расширение может работать не корректно:', - notAuth: { - label: 'Вы не авторизованы.', - checkAuth: 'Проверьте, что вы авторизованы в Яндекс (именно .$1 домен). Также вы можете выбрать другой домен в настройках расширения.', - relogin: 'Попробуйте разлогиниться и залогиниться снова.', - }, - noConnection: { - label: 'Отсутствует соединение с интернетом.', - }, - nothingHelped: 'Если ничего из вышеперечисленного не помогло, попробуйте перезагрузить расширение.', - reloadBtn: 'Перезагрузить расширение', - }, - donation: { - text: 'Хотите новых настроек, функций и просто стабильной работы расширения?', - donateBtn: 'Поддержите проект!', - }, - months: [ - 'Января', - 'Февраля', - 'Марта', - 'Апреля', - 'Мая', - 'Июня', - 'Июля', - 'Августа', - 'Сентября', - 'Октября', - 'Ноября', - 'Декабря', - ], + }, + popup: { + title: 'Яндекс.Почта', + compose: 'Написать', + actions: { + open: 'Открыть', + read: 'Прочитано', + spam: 'Спам', + remove: 'Удалить', }, - settings: { - newMessageNotification: { - label: 'Показывать уведомление при получении нового письма', - options: [ - 'отключено', - 'только текстовое', - 'текстовое и звуковое', - ], - }, - unreadMessagesNotification: { - label: 'Показывать текстовое уведомление при наличие непрочитанных писем', - options: [ - 'отключено', - 'каждые 5 мин', - 'каждые 15 мин', - 'каждые 30 мин', - ], - }, - unreadMessagesSound: { - label: 'Проигрывать звуковое уведомление при наличие непрочитанных писем', - description: 'В соответствие с интервалом выбраным выше', - }, - notAuthNotification: { - label: 'Показывать уведомления если вы не авторизованы', - description: 'Каждые 5 мин', - options: [ - 'отключено', - 'только текстовое', - 'текстовое и звуковое', - ], - }, - setShortcuts: { - label: 'Быстрые клавиши', - linkText: 'редактировать', - }, - preferredDomain: { - label: 'Предпочитаемый домен', - description: 'Будет использован для авторизиции, ссылок и т.д.', - options: [ - 'ru', - 'ua', - 'by', - 'kz', - 'com', - 'com.tr', - ], - }, + emptyList: 'У вас нет непрочитанных писем', + loadingError: 'Не удалось загрузить письма... Попробуйте позже', + unavailable: { + title: 'Что-то пошло не так...', + subTitle: 'Возможны следующие причины почему расширение может работать не корректно:', + notAuth: { + label: 'Вы не авторизованы.', + checkAuth: 'Проверьте, что вы авторизованы в Яндекс (именно .$1 домен). Также вы можете выбрать другой домен в настройках расширения.', + relogin: 'Попробуйте разлогиниться и залогиниться снова.', + }, + noConnection: { + label: 'Отсутствует соединение с интернетом.', + }, + nothingHelped: 'Если ничего из вышеперечисленного не помогло, попробуйте перезагрузить расширение.', + reloadBtn: 'Перезагрузить расширение', }, + donation: { + text: 'Хотите новых настроек, функций и просто стабильной работы расширения?', + donateBtn: 'Поддержите проект!', + }, + months: [ + 'Января', + 'Февраля', + 'Марта', + 'Апреля', + 'Мая', + 'Июня', + 'Июля', + 'Августа', + 'Сентября', + 'Октября', + 'Ноября', + 'Декабря', + ], + }, + settings: { + newMessageNotification: { + label: 'Показывать уведомление при получении нового письма', + options: [ + 'отключено', + 'только текстовое', + 'текстовое и звуковое', + ], + }, + unreadMessagesNotification: { + label: 'Показывать текстовое уведомление при наличие непрочитанных писем', + options: [ + 'отключено', + 'каждые 5 мин', + 'каждые 15 мин', + 'каждые 30 мин', + ], + }, + unreadMessagesSound: { + label: 'Проигрывать звуковое уведомление при наличие непрочитанных писем', + description: 'В соответствие с интервалом выбраным выше', + }, + notAuthNotification: { + label: 'Показывать уведомления если вы не авторизованы', + description: 'Каждые 5 мин', + options: [ + 'отключено', + 'только текстовое', + 'текстовое и звуковое', + ], + }, + setShortcuts: { + label: 'Быстрые клавиши', + linkText: 'редактировать', + }, + preferredDomain: { + label: 'Предпочитаемый домен', + description: 'Будет использован для авторизиции, ссылок и т.д.', + options: [ + 'ru', + 'ua', + 'by', + 'kz', + 'com', + 'com.tr', + ], + }, + }, }; diff --git a/src/manifest/chrome.js b/src/manifest/chrome.js index af03244..afc6a77 100644 --- a/src/manifest/chrome.js +++ b/src/manifest/chrome.js @@ -1,9 +1,9 @@ module.exports = { - minimum_chrome_version: '55', - options_ui: { - page: 'pages/settings.html', - chrome_style: true, - }, - incognito: 'spanning', - content_security_policy: "script-src 'self' 'unsafe-eval' http://localhost:8080; object-src 'self'", + minimum_chrome_version: '55', + options_ui: { + page: 'pages/settings.html', + chrome_style: true, + }, + incognito: 'spanning', + content_security_policy: "script-src 'self' 'unsafe-eval' http://localhost:8080; object-src 'self'", }; diff --git a/src/manifest/firefox.js b/src/manifest/firefox.js index 158a1ce..ec34b19 100644 --- a/src/manifest/firefox.js +++ b/src/manifest/firefox.js @@ -1,11 +1,11 @@ module.exports = { - applications: { - gecko: { - id: 'jid0-vsitoD18LSTi4VQGGLCVmIoJSsU@jetpack', - strict_min_version: '53.0', - }, - }, - options_ui: { - page: 'pages/settings.html', + applications: { + gecko: { + id: 'jid0-vsitoD18LSTi4VQGGLCVmIoJSsU@jetpack', + strict_min_version: '53.0', }, + }, + options_ui: { + page: 'pages/settings.html', + }, }; diff --git a/src/manifest/index.js b/src/manifest/index.js index 1a503fe..9a3aa28 100644 --- a/src/manifest/index.js +++ b/src/manifest/index.js @@ -1,4 +1,6 @@ const baseManifest = require('./base.json'); -// eslint-disable-next-line global-require, import/no-dynamic-require -module.exports = (target) => Object.assign({}, baseManifest, require(`./${target}`)); +module.exports = (target) => ({ + ...baseManifest, + ...require(`./${target}`), // eslint-disable-line global-require, import/no-dynamic-require +}); diff --git a/src/pages/background/index.js b/src/pages/background/index.js index c926a30..df53cea 100644 --- a/src/pages/background/index.js +++ b/src/pages/background/index.js @@ -9,26 +9,26 @@ import initNotification from './modules/notification'; import initButtonState from './modules/popup-button'; async function loadStorageData() { - const data = await storage.getAll(); + const data = await storage.getAll(); - store.dispatch({ - type: LOAD_STORAGE_DATA, - data, - }); + store.dispatch({ + type: LOAD_STORAGE_DATA, + data, + }); } function initModules() { - initWS(); - initCookieListener(); - initNotification(); - initButtonState(); + initWS(); + initCookieListener(); + initNotification(); + initButtonState(); } (async () => { - // we should prepare the store before starting any service - await loadStorageData(); + // we should prepare the store before starting any service + await loadStorageData(); - initModules(); + initModules(); - store.dispatch(login()); + store.dispatch(login()); })(); diff --git a/src/pages/background/modules/cookie.js b/src/pages/background/modules/cookie.js index d9cf111..09fc98c 100644 --- a/src/pages/background/modules/cookie.js +++ b/src/pages/background/modules/cookie.js @@ -5,53 +5,53 @@ import {config} from '../utils/cookie'; import resolveUrl from '../utils/url-resolver'; const handleCookieChange = debounce(({ - prevLogin, - nextLogin, - removed, + prevLogin, + nextLogin, + removed, }) => { - // user logged out from all accounts - if (removed) { - console.log('LOGOUT', prevLogin, nextLogin, removed); - store.dispatch(logout()); - return; - } + // user logged out from all accounts + if (removed) { + console.log('LOGOUT', prevLogin, nextLogin, removed); + store.dispatch(logout()); + return; + } - // user just logged in for the first time - if (!prevLogin && nextLogin) { - console.log('LOGIN', prevLogin, nextLogin, removed); - store.dispatch(login()); - return; - } + // user just logged in for the first time + if (!prevLogin && nextLogin) { + console.log('LOGIN', prevLogin, nextLogin, removed); + store.dispatch(login()); + return; + } - // user changed the account - if (prevLogin !== nextLogin) { - console.log('ACCOUNT CHANGED', prevLogin, nextLogin, removed); - store.dispatch(logout()); - store.dispatch(login()); - return; - } + // user changed the account + if (prevLogin !== nextLogin) { + console.log('ACCOUNT CHANGED', prevLogin, nextLogin, removed); + store.dispatch(logout()); + store.dispatch(login()); + return; + } - console.log('SOMETHING ELSE', prevLogin, nextLogin, removed); + console.log('SOMETHING ELSE', prevLogin, nextLogin, removed); }, 300); export default function initCookieListener() { - chrome.cookies.onChanged.addListener(({cookie, removed}) => { - const { - domain, - name, - value, - path, - } = cookie; + chrome.cookies.onChanged.addListener(({cookie, removed}) => { + const { + domain, + name, + value, + path, + } = cookie; - if (domain.includes(resolveUrl(config.domain)) && + if (domain.includes(resolveUrl(config.domain)) && name === config.items.login && path === config.path - ) { - handleCookieChange({ - prevLogin: store.getState().user.email, - nextLogin: value, - removed, - }); - } - }); + ) { + handleCookieChange({ + prevLogin: store.getState().user.email, + nextLogin: value, + removed, + }); + } + }); } diff --git a/src/pages/background/modules/notification.js b/src/pages/background/modules/notification.js index 1a83a95..53cfdeb 100644 --- a/src/pages/background/modules/notification.js +++ b/src/pages/background/modules/notification.js @@ -3,60 +3,58 @@ import store from '../redux/store'; import {showUnreadNotification, showNotAuthNotification} from '../redux/actions/notification'; const ALARMS = { - UNREAD: 'UNREAD', - NOT_AUTH: 'NOT_AUTH', + UNREAD: 'UNREAD', + NOT_AUTH: 'NOT_AUTH', }; const {alarms} = chrome; alarms.onAlarm.addListener(({name}) => { - const {dispatch} = store; + const {dispatch} = store; - if (name === ALARMS.UNREAD) { - dispatch(showUnreadNotification()); - } - else if (name === ALARMS.NOT_AUTH) { - dispatch(showNotAuthNotification()); - } + if (name === ALARMS.UNREAD) { + dispatch(showUnreadNotification()); + } else if (name === ALARMS.NOT_AUTH) { + dispatch(showNotAuthNotification()); + } }); function initUnreadNotification({user, settings: {unreadMessagesNotification}}) { - // on "unreadMessagesNotification" change + user is logged out - if (!user.authorized) { - return; - } + // on "unreadMessagesNotification" change + user is logged out + if (!user.authorized) { + return; + } - alarms.clear(ALARMS.UNREAD); // on "unreadMessagesNotification" change + user is logged in - alarms.clear(ALARMS.NOT_AUTH); // on login + alarms.clear(ALARMS.UNREAD); // on "unreadMessagesNotification" change + user is logged in + alarms.clear(ALARMS.NOT_AUTH); // on login - if (unreadMessagesNotification) { - alarms.create(ALARMS.UNREAD, {periodInMinutes: unreadMessagesNotification}); - } + if (unreadMessagesNotification) { + alarms.create(ALARMS.UNREAD, {periodInMinutes: unreadMessagesNotification}); + } } function initNotAuthNotification({user, settings}) { - // on "notAuthNotification" change + user is logged in - if (user.authorized) { - return; - } + // on "notAuthNotification" change + user is logged in + if (user.authorized) { + return; + } - alarms.clear(ALARMS.NOT_AUTH); // on "notAuthNotification" change + user is logged out - alarms.clear(ALARMS.UNREAD); // on logout + alarms.clear(ALARMS.NOT_AUTH); // on "notAuthNotification" change + user is logged out + alarms.clear(ALARMS.UNREAD); // on logout - if (settings.notAuthNotification) { - alarms.create(ALARMS.NOT_AUTH, {periodInMinutes: 5}); - } + if (settings.notAuthNotification) { + alarms.create(ALARMS.NOT_AUTH, {periodInMinutes: 5}); + } } export default function () { - subscribe('user.authorized', (state) => { - if (state.user.authorized) { - initUnreadNotification(state); - } - else { - initNotAuthNotification(state); - } - }); - - subscribe('settings.unreadMessagesNotification', initUnreadNotification); - subscribe('settings.notAuthNotification', initNotAuthNotification); + subscribe('user.authorized', (state) => { + if (state.user.authorized) { + initUnreadNotification(state); + } else { + initNotAuthNotification(state); + } + }); + + subscribe('settings.unreadMessagesNotification', initUnreadNotification); + subscribe('settings.notAuthNotification', initNotAuthNotification); } diff --git a/src/pages/background/modules/popup-button.js b/src/pages/background/modules/popup-button.js index 8ecfcb2..9bb1520 100644 --- a/src/pages/background/modules/popup-button.js +++ b/src/pages/background/modules/popup-button.js @@ -3,23 +3,22 @@ import {subscribe} from 'redux-subscriber'; const popup = chrome.browserAction; function setState({user, messages}) { - const enabled = user.authorized; - const badge = (messages.unreadCount || '').toString(); + const enabled = user.authorized; + const badge = (messages.unreadCount || '').toString(); - // enable or disable popup button (onClick works only if no popup set) - if (enabled) { - popup.setIcon({path: '/assets/icon.png'}); - } - else { - popup.setIcon({path: '/assets/icon-disabled.png'}); - } + // enable or disable popup button (onClick works only if no popup set) + if (enabled) { + popup.setIcon({path: '/assets/icon.png'}); + } else { + popup.setIcon({path: '/assets/icon-disabled.png'}); + } - // set badge - popup.setBadgeText({text: badge}); - popup.setBadgeBackgroundColor({color: '#3d5afe'}); + // set badge + popup.setBadgeText({text: badge}); + popup.setBadgeBackgroundColor({color: '#3d5afe'}); } export default function () { - subscribe('user.authorized', setState); - subscribe('messages.unreadCount', setState); + subscribe('user.authorized', setState); + subscribe('messages.unreadCount', setState); } diff --git a/src/pages/background/modules/storage.js b/src/pages/background/modules/storage.js index 5d7a1a2..01c5509 100644 --- a/src/pages/background/modules/storage.js +++ b/src/pages/background/modules/storage.js @@ -1,32 +1,31 @@ const chromeStore = chrome.storage.sync; export default { - getAll() { - return new Promise(resolve => { - chromeStore.get(null, data => { - const parsedData = Object.keys(data).reduce((obj, key) => { - try { - // eslint-disable-next-line no-param-reassign - obj[key] = JSON.parse(data[key]); - } - catch (err) { - // swallow - } + getAll() { + return new Promise(resolve => { + chromeStore.get(null, data => { + const parsedData = Object.keys(data).reduce((obj, key) => { + try { + // eslint-disable-next-line no-param-reassign + obj[key] = JSON.parse(data[key]); + } catch (err) { + // swallow + } - return obj; - }, {}); + return obj; + }, {}); - resolve(parsedData); - }); - }); - }, - set(name, value) { - chromeStore.set({[name]: JSON.stringify(value)}); - }, - remove(name) { - chromeStore.remove(name); - }, - clear() { - chromeStore.clear(); - }, + resolve(parsedData); + }); + }); + }, + set(name, value) { + chromeStore.set({[name]: JSON.stringify(value)}); + }, + remove(name) { + chromeStore.remove(name); + }, + clear() { + chromeStore.clear(); + }, }; diff --git a/src/pages/background/modules/websocket/client.js b/src/pages/background/modules/websocket/client.js index c4cf127..53a95f6 100644 --- a/src/pages/background/modules/websocket/client.js +++ b/src/pages/background/modules/websocket/client.js @@ -2,63 +2,63 @@ import qs from 'query-string'; import sessionId from '../../utils/session-generator'; import resolveUrl from '../../utils/url-resolver'; import { - RECONNECT, - MESSAGE, + RECONNECT, + MESSAGE, } from './constants'; let ws, emitEvent; function onClose(err) { - console.error('[SOCKET]: Socket is closed', err); // eslint-disable-line no-console - emitEvent(RECONNECT, err); + console.error('[SOCKET]: Socket is closed', err); // eslint-disable-line no-console + emitEvent(RECONNECT, err); } function onError(...args) { - console.error('[SOCKET]: An error has occurred in socket', args); // eslint-disable-line no-console - emitEvent(RECONNECT); + console.error('[SOCKET]: An error has occurred in socket', args); // eslint-disable-line no-console + emitEvent(RECONNECT); } function onMessage({data}) { - emitEvent(MESSAGE, JSON.parse(data)); + emitEvent(MESSAGE, JSON.parse(data)); } function connect({ - uid, - token, + uid, + token, }) { - const queryParams = qs.stringify({ - client: 'bar', - service: 'mail', - session: sessionId, - oauth_token: token, - uid, - }); + const queryParams = qs.stringify({ + client: 'bar', + service: 'mail', + session: sessionId, + oauth_token: token, + uid, + }); // eslint-disable-next-line no-use-before-define // disconnect(); - ws = new WebSocket(resolveUrl(`wss://push.yandex.{domain}/v1/subscribe?${queryParams}`)); + ws = new WebSocket(resolveUrl(`wss://push.yandex.{domain}/v1/subscribe?${queryParams}`)); - ws.addEventListener('close', onClose, false); - ws.addEventListener('message', onMessage, false); - ws.addEventListener('error', onError, false); + ws.addEventListener('close', onClose, false); + ws.addEventListener('message', onMessage, false); + ws.addEventListener('error', onError, false); } function disconnect() { - if (ws) { - ws.close(); + if (ws) { + ws.close(); - ws.removeEventListener('close', onClose, false); - ws.removeEventListener('message', onMessage, false); - ws.removeEventListener('error', onError, false); - } + ws.removeEventListener('close', onClose, false); + ws.removeEventListener('message', onMessage, false); + ws.removeEventListener('error', onError, false); + } } export default function (ev) { - emitEvent = ev; + emitEvent = ev; - return { - connect, - disconnect, - }; + return { + connect, + disconnect, + }; } diff --git a/src/pages/background/modules/websocket/index.js b/src/pages/background/modules/websocket/index.js index 953b4a8..f8b4b30 100644 --- a/src/pages/background/modules/websocket/index.js +++ b/src/pages/background/modules/websocket/index.js @@ -5,147 +5,144 @@ import {login, logout} from '../../redux/actions/user'; import {loadMessagesCount} from '../../redux/actions/messages'; import {showNewNotification} from '../../redux/actions/notification'; import { - RECONNECT, - MESSAGE, + RECONNECT, + MESSAGE, } from './constants'; import initWSClient from './client'; const {alarms} = chrome; const config = { - connect: { - name: 'connect', - time: 1, // try to connect if failed before - }, - reconnect: { - name: 'reconnect', - time: 1.9, // reconnect to websocket; we use double value to prevent receiving the 3rd ping before reconnect - }, + connect: { + name: 'connect', + time: 1, // try to connect if failed before + }, + reconnect: { + name: 'reconnect', + time: 1.9, // reconnect to websocket; we use double value to prevent receiving the 3rd ping before reconnect + }, }; let wsClient; async function connect() { - const {dispatch, getState} = store; - const { - authorized, - token, - uid, - } = getState().user; - - alarms.clear(config.reconnect.name); - - try { - alarms.create(config.reconnect.name, {delayInMinutes: config.reconnect.time}); - - wsClient.connect({ - uid, - token, - }); - - // we don't need to dispatch login action if user is already authorized - // e.g. after calling "connect" first time the subscriber below will call "reconnect" - if (!authorized) { - dispatch(login()); - } - } - catch (err) { - dispatch(logout()); + const {dispatch, getState} = store; + const { + authorized, + token, + uid, + } = getState().user; + + alarms.clear(config.reconnect.name); - alarms.create(config.connect.name, {delayInMinutes: config.connect.time}); + try { + alarms.create(config.reconnect.name, {delayInMinutes: config.reconnect.time}); - // throw unhandled exception for raven - throw err; + wsClient.connect({ + uid, + token, + }); + + // we don't need to dispatch login action if user is already authorized + // e.g. after calling "connect" first time the subscriber below will call "reconnect" + if (!authorized) { + dispatch(login()); } + } catch (err) { + dispatch(logout()); + + alarms.create(config.connect.name, {delayInMinutes: config.connect.time}); + + // throw unhandled exception for raven + throw err; + } } const reconnect = debounce((err) => { - wsClient.disconnect(); - - if (err && err.code) { - window.Raven.captureException(err, { - extra: { - code: err.code, - reason: err.reason, - }, - }); - store.dispatch(logout()); - store.dispatch(login()); - // TODO: if the error wouldn't disappear enable reloading - // reloadApp(); - - return; - } + wsClient.disconnect(); + + if (err && err.code) { + window.Raven.captureException(err, { + extra: { + code: err.code, + reason: err.reason, + }, + }); + store.dispatch(logout()); + store.dispatch(login()); + // TODO: if the error wouldn't disappear enable reloading + // reloadApp(); - connect(); + return; + } + + connect(); }, 500); function disconnect() { - alarms.clear(config.reconnect.name); - wsClient.disconnect(); + alarms.clear(config.reconnect.name); + wsClient.disconnect(); } function onMessage(data) { - const {dispatch} = store; - const { - operation, - message, - } = data; - - if (operation !== 'insert' || message.hdr_status !== 'New') { - dispatch(loadMessagesCount()); - return; - } - - const { - new_messages: unreadCount, - mid: id, - hdr_from: from, - hdr_subject: subject, - firstline, - } = message; - - const nameMatch = from.match(/^"(.+)"/); - const emailMatch = from.match(/<(.+)>$/); - - dispatch(loadMessagesCount(parseInt(unreadCount, 10))); - dispatch(showNewNotification({ - id, - from: nameMatch[1] || emailMatch[1], - subject: subject !== 'No subject' ? subject : '', - message: firstline, - })); + const {dispatch} = store; + const { + operation, + message, + } = data; + + if (operation !== 'insert' || message.hdr_status !== 'New') { + dispatch(loadMessagesCount()); + return; + } + + const { + new_messages: unreadCount, + mid: id, + hdr_from: from, + hdr_subject: subject, + firstline, + } = message; + + const nameMatch = from.match(/^"(.+)"/); + const emailMatch = from.match(/<(.+)>$/); + + dispatch(loadMessagesCount(parseInt(unreadCount, 10))); + dispatch(showNewNotification({ + id, + from: nameMatch[1] || emailMatch[1], + subject: subject !== 'No subject' ? subject : '', + message: firstline, + })); } function emitEvent(eventType, data) { - if (eventType === RECONNECT) { - reconnect(data); - } - if (eventType === MESSAGE) { - onMessage(data); - } + if (eventType === RECONNECT) { + reconnect(data); + } + if (eventType === MESSAGE) { + onMessage(data); + } } function handleAlarm({name}) { - if (name === config.connect.name) { - connect(); - } - else if (name === config.reconnect.name) { - reconnect(); - } + if (name === config.connect.name) { + connect(); + } else if (name === config.reconnect.name) { + reconnect(); + } } export default function () { - wsClient = initWSClient(emitEvent); + wsClient = initWSClient(emitEvent); - alarms.onAlarm.addListener(handleAlarm); + alarms.onAlarm.addListener(handleAlarm); - subscribe('user.authorized', ({user: {authorized}}) => { - if (authorized) { - reconnect(); - } - else { - disconnect(); - } - }); + subscribe('user.authorized', ({user: {authorized}}) => { + if (authorized) { + reconnect(); + } else { + disconnect(); + } + }); } diff --git a/src/pages/background/redux/actions/messages.js b/src/pages/background/redux/actions/messages.js index 77d6a95..286b4ab 100644 --- a/src/pages/background/redux/actions/messages.js +++ b/src/pages/background/redux/actions/messages.js @@ -1,69 +1,68 @@ import { - LOAD_MESSAGES_COUNT, - LOAD_MESSAGES, - LOAD_MESSAGES_SUCCESS, - LOAD_MESSAGES_ERROR, - UPDATE_MESSAGE, - INVALIDATE_MESSAGES, + LOAD_MESSAGES_COUNT, + LOAD_MESSAGES, + LOAD_MESSAGES_SUCCESS, + LOAD_MESSAGES_ERROR, + UPDATE_MESSAGE, + INVALIDATE_MESSAGES, } from 'shared/redux-consts/messages'; import { - getMessages, - getMessagesCount, - updateMessageStatus, + getMessages, + getMessagesCount, + updateMessageStatus, } from '../../utils/api'; let popupIsOpen = false; export function loadMessagesCount(data) { - return async (dispatch) => { - const unreadMessagesCount = data || await getMessagesCount(); + return async (dispatch) => { + const unreadMessagesCount = data || await getMessagesCount(); - dispatch({ - type: LOAD_MESSAGES_COUNT, - data: unreadMessagesCount, - }); - }; + dispatch({ + type: LOAD_MESSAGES_COUNT, + data: unreadMessagesCount, + }); + }; } export function loadMessages() { - return async (dispatch) => { - popupIsOpen = true; + return async (dispatch) => { + popupIsOpen = true; - dispatch({type: LOAD_MESSAGES}); + dispatch({type: LOAD_MESSAGES}); - try { - const data = await getMessages(); + try { + const data = await getMessages(); - // to prevent rewriting "loading" value, we dispatch the action only if popup is open - // "popupIsOpen" could be rewritten by "invalidateMessages" action - if (popupIsOpen) { - dispatch({ - type: LOAD_MESSAGES_SUCCESS, - data, - }); - } - } - catch (err) { - dispatch({type: LOAD_MESSAGES_ERROR}); + // to prevent rewriting "loading" value, we dispatch the action only if popup is open + // "popupIsOpen" could be rewritten by "invalidateMessages" action + if (popupIsOpen) { + dispatch({ + type: LOAD_MESSAGES_SUCCESS, + data, + }); + } + } catch (err) { + dispatch({type: LOAD_MESSAGES_ERROR}); - // throw unhandled exception for raven - throw err; - } - }; + // throw unhandled exception for raven + throw err; + } + }; } export function updateMessage({data}) { - updateMessageStatus(data); + updateMessageStatus(data); - // kind of optimistic update :) - return { - type: UPDATE_MESSAGE, - id: data.id, - }; + // kind of optimistic update :) + return { + type: UPDATE_MESSAGE, + id: data.id, + }; } export function invalidateMessages() { - popupIsOpen = false; + popupIsOpen = false; - return {type: INVALIDATE_MESSAGES}; + return {type: INVALIDATE_MESSAGES}; } diff --git a/src/pages/background/redux/actions/notification.js b/src/pages/background/redux/actions/notification.js index 6bb0e12..aa8bc13 100644 --- a/src/pages/background/redux/actions/notification.js +++ b/src/pages/background/redux/actions/notification.js @@ -2,9 +2,9 @@ import i18n from 'shared/utils/i18n'; import {openUrl} from '../../utils/tab'; const CONSTANTS = { - SHOW_NEW_NOTIFICATION: 'SHOW_NEW_NOTIFICATION', - SHOW_UNREAD_NOTIFICATION: 'SHOW_UNREAD_NOTIFICATION', - SHOW_NOT_AUTH_NOTIFICATION: 'SHOW_NOT_AUTH_NOTIFICATION', + SHOW_NEW_NOTIFICATION: 'SHOW_NEW_NOTIFICATION', + SHOW_UNREAD_NOTIFICATION: 'SHOW_UNREAD_NOTIFICATION', + SHOW_NOT_AUTH_NOTIFICATION: 'SHOW_NOT_AUTH_NOTIFICATION', }; const sevenSec = 7 * 1000; @@ -13,93 +13,93 @@ const audio = new Audio('../assets/notification.wav'); const notifications = {}; notification.onClicked.addListener(id => { - notifications[id](); - delete notifications[id]; + notifications[id](); + delete notifications[id]; }); function showNotification(data) { - notification.create({ - type: 'basic', - iconUrl: '../assets/icon.png', - title: data.title, - message: data.subTitle, - contextMessage: data.message, - }, (id) => { - notifications[id] = data.onClick; - - setTimeout(() => { - if (id) { - notification.clear(id); - } - }, sevenSec); - }); + notification.create({ + type: 'basic', + iconUrl: '../assets/icon.png', + title: data.title, + message: data.subTitle, + contextMessage: data.message, + }, (id) => { + notifications[id] = data.onClick; + + setTimeout(() => { + if (id) { + notification.clear(id); + } + }, sevenSec); + }); } function playSound() { - audio.play(); + audio.play(); } export function showNewNotification(data) { - return (dispatch, getState) => { - const {newMessageNotification} = getState().settings; - - if (newMessageNotification) { - showNotification({ - title: data.from, - subTitle: data.subject, - message: data.message, - onClick() { - openUrl(`#message/${data.id}`); - }, - }); - - if (newMessageNotification === 2) { - playSound(); - } - - dispatch({type: CONSTANTS.SHOW_NEW_NOTIFICATION}); - } - }; + return (dispatch, getState) => { + const {newMessageNotification} = getState().settings; + + if (newMessageNotification) { + showNotification({ + title: data.from, + subTitle: data.subject, + message: data.message, + onClick() { + openUrl(`#message/${data.id}`); + }, + }); + + if (newMessageNotification === 2) { + playSound(); + } + + dispatch({type: CONSTANTS.SHOW_NEW_NOTIFICATION}); + } + }; } export function showUnreadNotification() { - return (dispatch, getState) => { - const {messages: {unreadCount}, settings} = getState(); + return (dispatch, getState) => { + const {messages: {unreadCount}, settings} = getState(); - if (!unreadCount) { - return; - } + if (!unreadCount) { + return; + } - showNotification({ - title: i18n.text('notification.unread.title'), - subTitle: i18n.text('notification.unread.message', unreadCount), - onClick: openUrl, - }); + showNotification({ + title: i18n.text('notification.unread.title'), + subTitle: i18n.text('notification.unread.message', unreadCount), + onClick: openUrl, + }); - if (settings.unreadMessagesSound) { - playSound(); - } + if (settings.unreadMessagesSound) { + playSound(); + } - dispatch({type: CONSTANTS.SHOW_UNREAD_NOTIFICATION}); - }; + dispatch({type: CONSTANTS.SHOW_UNREAD_NOTIFICATION}); + }; } export function showNotAuthNotification() { - return (dispatch, getState) => { - const {notAuthNotification} = getState().settings; - - if (notAuthNotification) { - showNotification({ - title: i18n.text('notification.notAuth.title'), - subTitle: i18n.text('notification.notAuth.message'), - onClick: openUrl, - }); - - if (notAuthNotification === 2) { - playSound(); - } - - dispatch({type: CONSTANTS.SHOW_NOT_AUTH_NOTIFICATION}); - } - }; + return (dispatch, getState) => { + const {notAuthNotification} = getState().settings; + + if (notAuthNotification) { + showNotification({ + title: i18n.text('notification.notAuth.title'), + subTitle: i18n.text('notification.notAuth.message'), + onClick: openUrl, + }); + + if (notAuthNotification === 2) { + playSound(); + } + + dispatch({type: CONSTANTS.SHOW_NOT_AUTH_NOTIFICATION}); + } + }; } diff --git a/src/pages/background/redux/actions/popup.js b/src/pages/background/redux/actions/popup.js index 15a91a4..560140a 100644 --- a/src/pages/background/redux/actions/popup.js +++ b/src/pages/background/redux/actions/popup.js @@ -1,33 +1,33 @@ import { - OPEN_LINK, - OPEN_SETTINGS, - RELOAD_APP, + OPEN_LINK, + OPEN_SETTINGS, + RELOAD_APP, } from 'shared/redux-consts/popup'; import * as tabUtils from '../../utils/tab'; import * as runtimeUtils from '../../utils/runtime'; export function openLink({url} = {}) { - tabUtils.openUrl(url); + tabUtils.openUrl(url); - return {type: OPEN_LINK}; + return {type: OPEN_LINK}; } export function openSettings() { - tabUtils.openSettings(); + tabUtils.openSettings(); - return {type: OPEN_SETTINGS}; + return {type: OPEN_SETTINGS}; } export function reloadApp() { - window.Raven.captureMessage('[Event] reload-extension-click', {level: 'info'}); + window.Raven.captureMessage('[Event] reload-extension-click', {level: 'info'}); - runtimeUtils.reloadApp(); + runtimeUtils.reloadApp(); - return {type: RELOAD_APP}; + return {type: RELOAD_APP}; } export function openDonationLink() { - window.Raven.captureMessage('[Event] donate-click', {level: 'info'}); + window.Raven.captureMessage('[Event] donate-click', {level: 'info'}); - return openLink({url: 'https://www.paypal.me/yandexmailnotifier/5'}); + return openLink({url: 'https://www.paypal.me/yandexmailnotifier/5'}); } diff --git a/src/pages/background/redux/actions/settings.js b/src/pages/background/redux/actions/settings.js index 5675bdc..9bdb416 100644 --- a/src/pages/background/redux/actions/settings.js +++ b/src/pages/background/redux/actions/settings.js @@ -4,22 +4,22 @@ import {reloadApp} from '../../utils/runtime'; // eslint-disable-next-line import/prefer-default-export export function updateSettings(data) { - return (dispatch, getState) => { - const {settings} = getState(); - const nextSettings = { - ...settings, - ...data, - }; + return (dispatch, getState) => { + const {settings} = getState(); + const nextSettings = { + ...settings, + ...data, + }; - storage.set('settings', nextSettings); + storage.set('settings', nextSettings); - dispatch({ - type: UPDATE_SETTINGS, - data: nextSettings, - }); + dispatch({ + type: UPDATE_SETTINGS, + data: nextSettings, + }); - if (settings.preferredDomain !== nextSettings.preferredDomain) { - reloadApp(); - } - }; + if (settings.preferredDomain !== nextSettings.preferredDomain) { + reloadApp(); + } + }; } diff --git a/src/pages/background/redux/actions/user.js b/src/pages/background/redux/actions/user.js index ef30468..46ec856 100644 --- a/src/pages/background/redux/actions/user.js +++ b/src/pages/background/redux/actions/user.js @@ -3,23 +3,23 @@ import storage from '../../modules/storage'; import {loadUser} from '../../utils/api'; export function login() { - return async (dispatch, getState) => { - const {token} = getState().user; - const data = await loadUser(token); + return async (dispatch, getState) => { + const {token} = getState().user; + const data = await loadUser(token); - if (token !== data.token) { - storage.set('user', {token: data.token}); - } + if (token !== data.token) { + storage.set('user', {token: data.token}); + } - dispatch({ - type: LOGIN, - data, - }); - }; + dispatch({ + type: LOGIN, + data, + }); + }; } export function logout() { - storage.remove('user'); + storage.remove('user'); - return {type: LOGOUT}; + return {type: LOGOUT}; } diff --git a/src/pages/background/redux/ext-store.js b/src/pages/background/redux/ext-store.js index 51174dc..b3e14ad 100644 --- a/src/pages/background/redux/ext-store.js +++ b/src/pages/background/redux/ext-store.js @@ -1,41 +1,41 @@ import {createBackgroundStore} from 'redux-webext'; import { - LOAD_MESSAGES, - UPDATE_MESSAGE, + LOAD_MESSAGES, + UPDATE_MESSAGE, } from 'shared/redux-consts/messages'; import { - OPEN_LINK, - OPEN_SETTINGS, - RELOAD_APP, - OPEN_DONATION_LINK, + OPEN_LINK, + OPEN_SETTINGS, + RELOAD_APP, + OPEN_DONATION_LINK, } from 'shared/redux-consts/popup'; import {UPDATE_SETTINGS} from 'shared/redux-consts/settings'; import store from './store'; import { - loadMessages, - updateMessage, - invalidateMessages, + loadMessages, + updateMessage, + invalidateMessages, } from './actions/messages'; import { - openLink, - openSettings, - reloadApp, - openDonationLink, + openLink, + openSettings, + reloadApp, + openDonationLink, } from './actions/popup'; import {updateSettings} from './actions/settings'; export default createBackgroundStore({ - store, - actions: { - [LOAD_MESSAGES]: loadMessages, - [UPDATE_MESSAGE]: updateMessage, - [OPEN_LINK]: openLink, - [OPEN_SETTINGS]: openSettings, - [RELOAD_APP]: reloadApp, - [OPEN_DONATION_LINK]: openDonationLink, - [UPDATE_SETTINGS]: updateSettings, - }, - onDisconnect() { - store.dispatch(invalidateMessages()); - }, + store, + actions: { + [LOAD_MESSAGES]: loadMessages, + [UPDATE_MESSAGE]: updateMessage, + [OPEN_LINK]: openLink, + [OPEN_SETTINGS]: openSettings, + [RELOAD_APP]: reloadApp, + [OPEN_DONATION_LINK]: openDonationLink, + [UPDATE_SETTINGS]: updateSettings, + }, + onDisconnect() { + store.dispatch(invalidateMessages()); + }, }); diff --git a/src/pages/background/redux/middlewares/index.js b/src/pages/background/redux/middlewares/index.js index 8d224fd..deb5eb2 100644 --- a/src/pages/background/redux/middlewares/index.js +++ b/src/pages/background/redux/middlewares/index.js @@ -4,12 +4,11 @@ const middlewares = [thunkMiddleware]; /* eslint-disable global-require */ if (__DEV__) { - const {createLogger} = require('redux-logger'); - middlewares.push(createLogger({collapsed: true})); -} -else { - const ravenMiddleware = require('./raven').default; - middlewares.push(ravenMiddleware); + const {createLogger} = require('redux-logger'); + middlewares.push(createLogger({collapsed: true})); +} else { + const ravenMiddleware = require('./raven').default; + middlewares.push(ravenMiddleware); } /* eslint-enable global-require */ diff --git a/src/pages/background/redux/middlewares/raven.js b/src/pages/background/redux/middlewares/raven.js index 6e42d4e..9514108 100644 --- a/src/pages/background/redux/middlewares/raven.js +++ b/src/pages/background/redux/middlewares/raven.js @@ -4,24 +4,24 @@ const {Raven} = window; let state, lastAction; window.onunhandledrejection = (err) => { - Raven.captureException(err.reason, { - extra: { - lastAction, - state, - }, - }); + Raven.captureException(err.reason, { + extra: { + lastAction, + state, + }, + }); }; // eslint-disable-next-line consistent-return export default store => next => action => { - // save this data for unhandled exceptions - state = store.getState(); - lastAction = action; + // save this data for unhandled exceptions + state = store.getState(); + lastAction = action; - Raven.captureBreadcrumb({ - category: 'redux', - message: JSON.stringify(action), - }); + Raven.captureBreadcrumb({ + category: 'redux', + message: JSON.stringify(action), + }); - return next(action); + return next(action); }; diff --git a/src/pages/background/redux/reducers/index.js b/src/pages/background/redux/reducers/index.js index 4542eb5..b83d652 100644 --- a/src/pages/background/redux/reducers/index.js +++ b/src/pages/background/redux/reducers/index.js @@ -5,16 +5,16 @@ import messages from './messages'; import settings from './settings'; const appReducer = combineReducers({ - user, - messages, - settings, + user, + messages, + settings, }); export default function (state, action) { - // clear the whole state except settings on logout action - const nextState = action.type === LOGOUT ? - {settings: state.settings} : - state; + // clear the whole state except settings on logout action + const nextState = action.type === LOGOUT ? + {settings: state.settings} : + state; - return appReducer(nextState, action); + return appReducer(nextState, action); } diff --git a/src/pages/background/redux/reducers/messages.js b/src/pages/background/redux/reducers/messages.js index b50e331..e6f4aca 100644 --- a/src/pages/background/redux/reducers/messages.js +++ b/src/pages/background/redux/reducers/messages.js @@ -1,58 +1,58 @@ import { - LOAD_MESSAGES_COUNT, - LOAD_MESSAGES, - LOAD_MESSAGES_SUCCESS, - LOAD_MESSAGES_ERROR, - UPDATE_MESSAGE, - INVALIDATE_MESSAGES, + LOAD_MESSAGES_COUNT, + LOAD_MESSAGES, + LOAD_MESSAGES_SUCCESS, + LOAD_MESSAGES_ERROR, + UPDATE_MESSAGE, + INVALIDATE_MESSAGES, } from 'shared/redux-consts/messages'; const initialState = { - unreadCount: 0, - items: [], - loading: true, - error: false, + unreadCount: 0, + items: [], + loading: true, + error: false, }; // Another one solution for loading is showing cached messages with // "New messages are loading..." and then "Show new msgs" button (smth like Inbox by google) export default function (state = initialState, action) { - switch (action.type) { - case LOAD_MESSAGES_COUNT: - return { - ...state, - unreadCount: action.data, - }; - case LOAD_MESSAGES: - case INVALIDATE_MESSAGES: - return { - ...state, - loading: true, - error: false, - }; - case LOAD_MESSAGES_SUCCESS: - return { - ...state, - items: action.data, - loading: false, - error: false, - }; - case LOAD_MESSAGES_ERROR: - return { - ...state, - loading: false, - error: true, - }; - case UPDATE_MESSAGE: - // don't update unreadCount manually because it'll lead to race condition - // e.g. we removed 5 messages at once but server handled only 3, we'll show user 10 - 5 - // but when we receive server response it'll be 10 - 3 - return { - ...state, - items: state.items.filter(({id}) => id !== action.id), - }; - default: - return state; - } + switch (action.type) { + case LOAD_MESSAGES_COUNT: + return { + ...state, + unreadCount: action.data, + }; + case LOAD_MESSAGES: + case INVALIDATE_MESSAGES: + return { + ...state, + loading: true, + error: false, + }; + case LOAD_MESSAGES_SUCCESS: + return { + ...state, + items: action.data, + loading: false, + error: false, + }; + case LOAD_MESSAGES_ERROR: + return { + ...state, + loading: false, + error: true, + }; + case UPDATE_MESSAGE: + // don't update unreadCount manually because it'll lead to race condition + // e.g. we removed 5 messages at once but server handled only 3, we'll show user 10 - 5 + // but when we receive server response it'll be 10 - 3 + return { + ...state, + items: state.items.filter(({id}) => id !== action.id), + }; + default: + return state; + } } diff --git a/src/pages/background/redux/reducers/settings.js b/src/pages/background/redux/reducers/settings.js index 7e8ef46..39bc25f 100644 --- a/src/pages/background/redux/reducers/settings.js +++ b/src/pages/background/redux/reducers/settings.js @@ -3,26 +3,26 @@ import {LOAD_STORAGE_DATA} from 'shared/redux-consts/meta'; import {UPDATE_SETTINGS} from 'shared/redux-consts/settings'; const initialState = { - newMessageNotification: 1, // only desktop notification - unreadMessagesNotification: 5, // 5 min - unreadMessagesSound: false, - notAuthNotification: 1, // only desktop notification - preferredDomain: appConfig.supportedDomains[0], + newMessageNotification: 1, // only desktop notification + unreadMessagesNotification: 5, // 5 min + unreadMessagesSound: false, + notAuthNotification: 1, // only desktop notification + preferredDomain: appConfig.supportedDomains[0], }; export default function (state = initialState, action) { - switch (action.type) { - case LOAD_STORAGE_DATA: - return { - ...state, - ...action.data.settings, - }; - case UPDATE_SETTINGS: - return { - ...state, - ...action.data, - }; - default: - return state; - } + switch (action.type) { + case LOAD_STORAGE_DATA: + return { + ...state, + ...action.data.settings, + }; + case UPDATE_SETTINGS: + return { + ...state, + ...action.data, + }; + default: + return state; + } } diff --git a/src/pages/background/redux/reducers/user.js b/src/pages/background/redux/reducers/user.js index 88e1190..3c3457d 100644 --- a/src/pages/background/redux/reducers/user.js +++ b/src/pages/background/redux/reducers/user.js @@ -2,26 +2,26 @@ import {LOAD_STORAGE_DATA} from 'shared/redux-consts/meta'; import {LOGIN} from 'shared/redux-consts/user'; const initialState = { - authorized: false, - email: '', - uid: null, - token: null, + authorized: false, + email: '', + uid: null, + token: null, }; export default function (state = initialState, action) { - switch (action.type) { - case LOAD_STORAGE_DATA: - return { - ...state, - ...action.data.user, - }; - case LOGIN: - return { - ...state, - ...action.data, - authorized: true, - }; - default: - return state; - } + switch (action.type) { + case LOAD_STORAGE_DATA: + return { + ...state, + ...action.data.user, + }; + case LOGIN: + return { + ...state, + ...action.data, + authorized: true, + }; + default: + return state; + } } diff --git a/src/pages/background/redux/store.js b/src/pages/background/redux/store.js index d3d7b01..17c7d75 100644 --- a/src/pages/background/redux/store.js +++ b/src/pages/background/redux/store.js @@ -4,8 +4,8 @@ import middlewares from './middlewares'; import reducer from './reducers'; const store = createStore( - reducer, - applyMiddleware(...middlewares), + reducer, + applyMiddleware(...middlewares), ); initSubscriber(store); diff --git a/src/pages/background/utils/__tests__/parser/fixtures.js b/src/pages/background/utils/__tests__/parser/fixtures.js index ce3dd41..f31e4d9 100644 --- a/src/pages/background/utils/__tests__/parser/fixtures.js +++ b/src/pages/background/utils/__tests__/parser/fixtures.js @@ -1,42 +1,42 @@ export const folders = [{ - id: '0123', - symbol: 'spam', + id: '0123', + symbol: 'spam', }, { - id: '0234', - symbol: 'inbox', + id: '0234', + symbol: 'inbox', }, { - id: '0345', - symbol: 'draft', + id: '0345', + symbol: 'draft', }]; export const messages = [{ - id: '123', - subject: '', - from: { - name: '', - email: 'sender@test.com 123', - }, - firstline: 'test message 123', - date: new Date(), - fid: folders[0].id, + id: '123', + subject: '', + from: { + name: '', + email: 'sender@test.com 123', + }, + firstline: 'test message 123', + date: new Date(), + fid: folders[0].id, }, { - id: '234', - subject: 'No subject', - from: { - name: 'Sender 345', - email: '', - }, - firstline: 'test message 234', - date: new Date(), - fid: folders[1].id, + id: '234', + subject: 'No subject', + from: { + name: 'Sender 345', + email: '', + }, + firstline: 'test message 234', + date: new Date(), + fid: folders[1].id, }, { - id: '345', - subject: 'test subject 345', - from: { - name: 'Sender 345', - email: 'sender@test.com 345', - }, - firstline: 'test message 345', - date: new Date(), - fid: folders[2].id, + id: '345', + subject: 'test subject 345', + from: { + name: 'Sender 345', + email: 'sender@test.com 345', + }, + firstline: 'test message 345', + date: new Date(), + fid: folders[2].id, }]; diff --git a/src/pages/background/utils/__tests__/parser/index.js b/src/pages/background/utils/__tests__/parser/index.js index 23b1685..17e6865 100644 --- a/src/pages/background/utils/__tests__/parser/index.js +++ b/src/pages/background/utils/__tests__/parser/index.js @@ -2,112 +2,111 @@ import {folders, messages} from './fixtures'; import parseXML from '../../parser'; function createElement( - name, - children = [], - attrs = {}, + name, + children = [], + attrs = {}, ) { - const element = document.createElement(name); - children.forEach(child => element.appendChild(child)); + const element = document.createElement(name); + children.forEach(child => element.appendChild(child)); - if (typeof attrs === 'object') { - Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key])); - } - else { - element.textContent = attrs; - } + if (typeof attrs === 'object') { + Object.keys(attrs).forEach(key => element.setAttribute(key, attrs[key])); + } else { + element.textContent = attrs; + } - return element; + return element; } const folderElements = folders.map(({id, symbol}) => - createElement('folder', [ - createElement('fid', [], id), - createElement('symbol', [], symbol), - ]), + createElement('folder', [ + createElement('fid', [], id), + createElement('symbol', [], symbol), + ]), ); const messageElements = messages.map(({ - id, - subject, - from, - firstline, - date, - fid, + id, + subject, + from, + firstline, + date, + fid, }) => - createElement('message', [ - createElement('from', [ - createElement('name', [], from.name), - createElement('email', [], from.email), - ]), - createElement('subject', [ - createElement('text', [], subject), - ]), - createElement('firstline', [], firstline), - ], { - mid: id, - recv_date: date, - fid, - }), + createElement('message', [ + createElement('from', [ + createElement('name', [], from.name), + createElement('email', [], from.email), + ]), + createElement('subject', [ + createElement('text', [], subject), + ]), + createElement('firstline', [], firstline), + ], { + mid: id, + recv_date: date, + fid, + }), ); describe('background/utils/parser', () => { - describe('bad xml', () => { - it('messages', () => { - const xml = createElement('doc', [createElement('folder_list')]); - - expect(() => parseXML(xml)).toThrowError('Bad response format in messages xml'); - }); - - it('folders', () => { - const xml = createElement('doc', [createElement('mailbox_list')]); - - expect(() => parseXML(xml)).toThrowError('Bad response format in folders xml'); - }); - - it('error', () => { - const errorCode = 500; - const xml = createElement('doc', [ - createElement('mailbox_list', [ - createElement('error', [], {code: errorCode}), - ]), - createElement('folder_list'), - ]); - - expect(() => parseXML(xml)).toThrowError(`Messages xml has error field with code: ${errorCode}`); - }); + describe('bad xml', () => { + it('messages', () => { + const xml = createElement('doc', [createElement('folder_list')]); + + expect(() => parseXML(xml)).toThrowError('Bad response format in messages xml'); }); - describe('valid xml', () => { - it('no messages', () => { - const expected = []; - const xml = createElement('doc', [ - createElement('mailbox_list', expected), - createElement('folder_list'), - ]); - - expect(parseXML(xml)).toEqual(expected); - }); - - it('with folders filtering', () => { - const xml = createElement('doc', [ - createElement('mailbox_list', [ - createElement('details'), - ...messageElements, - ]), - createElement('folder_list', folderElements), - ]); - - const items = parseXML(xml); - const expected = messages[1]; - const result = items[0]; - - expect(items.length).toBe(1); - expect(result).toEqual({ - ...expected, - date: expected.date.toString(), - from: expected.from.name, - subject: '', - }); - }); + it('folders', () => { + const xml = createElement('doc', [createElement('mailbox_list')]); + + expect(() => parseXML(xml)).toThrowError('Bad response format in folders xml'); + }); + + it('error', () => { + const errorCode = 500; + const xml = createElement('doc', [ + createElement('mailbox_list', [ + createElement('error', [], {code: errorCode}), + ]), + createElement('folder_list'), + ]); + + expect(() => parseXML(xml)).toThrowError(`Messages xml has error field with code: ${errorCode}`); + }); + }); + + describe('valid xml', () => { + it('no messages', () => { + const expected = []; + const xml = createElement('doc', [ + createElement('mailbox_list', expected), + createElement('folder_list'), + ]); + + expect(parseXML(xml)).toEqual(expected); + }); + + it('with folders filtering', () => { + const xml = createElement('doc', [ + createElement('mailbox_list', [ + createElement('details'), + ...messageElements, + ]), + createElement('folder_list', folderElements), + ]); + + const items = parseXML(xml); + const expected = messages[1]; + const result = items[0]; + + expect(items.length).toBe(1); + expect(result).toEqual({ + ...expected, + date: expected.date.toString(), + from: expected.from.name, + subject: '', + }); }); + }); }); diff --git a/src/pages/background/utils/__tests__/session-generator.test.js b/src/pages/background/utils/__tests__/session-generator.test.js index 486f430..ae13e34 100644 --- a/src/pages/background/utils/__tests__/session-generator.test.js +++ b/src/pages/background/utils/__tests__/session-generator.test.js @@ -3,8 +3,8 @@ import sessionId from '../session-generator'; const chars = '[0-9A-F]'; describe('utils/session-generator', () => { - it('output format is valid', () => { - const regexp = new RegExp(`^${chars}{8}-${chars}{4}-${chars}{4}-${chars}{4}-${chars}{12}$`); - expect(sessionId).toEqual(expect.stringMatching(regexp)); - }); + it('output format is valid', () => { + const regexp = new RegExp(`^${chars}{8}-${chars}{4}-${chars}{4}-${chars}{4}-${chars}{12}$`); + expect(sessionId).toEqual(expect.stringMatching(regexp)); + }); }); diff --git a/src/pages/background/utils/__tests__/url-resolver.test.js b/src/pages/background/utils/__tests__/url-resolver.test.js index b89a658..21ace59 100644 --- a/src/pages/background/utils/__tests__/url-resolver.test.js +++ b/src/pages/background/utils/__tests__/url-resolver.test.js @@ -2,24 +2,24 @@ import resolveUrl from '../url-resolver'; import store from '../../redux/store'; jest.mock('../../redux/store', () => ({ - getState: jest.fn(() => ({ - settings: { - preferredDomain: 'testdomain', - }, - })), + getState: jest.fn(() => ({ + settings: { + preferredDomain: 'testdomain', + }, + })), })); describe('utils/url-resolve', () => { - it('doesnt change string without pattern', () => { - const input = 'https://link-without-pattern.com'; + it('doesnt change string without pattern', () => { + const input = 'https://link-without-pattern.com'; - expect(resolveUrl(input)).toBe(input); - }); + expect(resolveUrl(input)).toBe(input); + }); - it('change string with pattern', () => { - const input = 'https://link-without-pattern.{domain}'; - const domain = store.getState().settings.preferredDomain; + it('change string with pattern', () => { + const input = 'https://link-without-pattern.{domain}'; + const domain = store.getState().settings.preferredDomain; - expect(resolveUrl(input)).toBe('https://link-without-pattern.{domain}'.replace('{domain}', domain)); - }); + expect(resolveUrl(input)).toBe('https://link-without-pattern.{domain}'.replace('{domain}', domain)); + }); }); diff --git a/src/pages/background/utils/api.js b/src/pages/background/utils/api.js index f88de9c..50d4209 100644 --- a/src/pages/background/utils/api.js +++ b/src/pages/background/utils/api.js @@ -1,165 +1,165 @@ import request from 'superagent'; import appConfig from 'shared/config'; import { - getUid as getCookieUid, - getSessionId, + getUid as getCookieUid, + getSessionId, } from './cookie'; import resolveUrl from './url-resolver'; import parseXML from './parser'; const API_URL = 'https://mail.yandex.{domain}/api'; const AUTH_CONFIG = { - tokenUrl: 'https://oauth.yandex.{domain}/token', - passportUrl: 'https://pass.yandex.{domain}/accounts', - clientId: '49c545918c574ac28dd7d27e8297065a', - clientSecret: '813caaea334a4fb5be54a8b9af3f4c97', + tokenUrl: 'https://oauth.yandex.{domain}/token', + passportUrl: 'https://pass.yandex.{domain}/accounts', + clientId: '49c545918c574ac28dd7d27e8297065a', + clientSecret: '813caaea334a4fb5be54a8b9af3f4c97', }; // prevent sending 'Origin' header, otherwise yandex doesn't execute an operation chrome.webRequest.onBeforeSendHeaders.addListener(({requestHeaders}) => ({ - requestHeaders: requestHeaders.filter(({name}) => name !== 'Origin'), + requestHeaders: requestHeaders.filter(({name}) => name !== 'Origin'), }), { - urls: appConfig.supportedDomains.map(domain => `${API_URL}/*`.replace('{domain}', domain)), + urls: appConfig.supportedDomains.map(domain => `${API_URL}/*`.replace('{domain}', domain)), }, ['blocking', 'requestHeaders']); async function sendRequest(data) { - const { - method = 'get', - url, - type = 'json', - form, - query, - } = data; - - const res = await request(method, url) - .type(form ? 'form' : 'json') - .send(form) - .query(query); + const { + method = 'get', + url, + type = 'json', + form, + query, + } = data; + + const res = await request(method, url) + .type(form ? 'form' : 'json') + .send(form) + .query(query); // json - if (type === 'json') { - const resData = res.body || JSON.parse(res.text); + if (type === 'json') { + const resData = res.body || JSON.parse(res.text); - if (resData.code === appConfig.errors.notAuthorized) { - throw new Error(appConfig.errors.notAuthorized); - } - - return resData; + if (resData.code === appConfig.errors.notAuthorized) { + throw new Error(appConfig.errors.notAuthorized); } - // xml - const {responseXML} = res.xhr; - - if (!responseXML) { - throw new Error(`No XML in the response: ${res.text}`); - } - - // sometimes Yandex sends "redirect" instead of result, so we need just follow that url in response - // check on "redirect_to" should be before "error", because "redirect_to" response contains "error" as well - const redirect = responseXML.querySelector('redirect_to'); - if (redirect) { - return sendRequest({ - ...data, - url: redirect.textContent, - }); - } + return resData; + } - const err = responseXML.querySelector('error'); - if (err) { - const errText = err.textContent || err.getAttribute('code'); - throw new Error(`Error in the response: ${errText}`); - } + // xml + const {responseXML} = res.xhr; - return res.xhr.responseXML; -} + if (!responseXML) { + throw new Error(`No XML in the response: ${res.text}`); + } -async function loadUserInfo() { - const cookieUid = await getCookieUid(); - const { - default_uid: uid, - accounts, - } = await sendRequest({ - url: resolveUrl(AUTH_CONFIG.passportUrl), - query: { - yu: cookieUid, - }, + // sometimes Yandex sends "redirect" instead of result, so we need just follow that url in response + // check on "redirect_to" should be before "error", because "redirect_to" response contains "error" as well + const redirect = responseXML.querySelector('redirect_to'); + if (redirect) { + return sendRequest({ + ...data, + url: redirect.textContent, }); + } - if (!accounts) { - throw new Error(appConfig.errors.notAuthorized); - } + const err = responseXML.querySelector('error'); + if (err) { + const errText = err.textContent || err.getAttribute('code'); + throw new Error(`Error in the response: ${errText}`); + } - const user = accounts.find(item => item.uid === uid); + return res.xhr.responseXML; +} - return { - uid, - email: user.defaultEmail, - }; +async function loadUserInfo() { + const cookieUid = await getCookieUid(); + const { + default_uid: uid, + accounts, + } = await sendRequest({ + url: resolveUrl(AUTH_CONFIG.passportUrl), + query: { + yu: cookieUid, + }, + }); + + if (!accounts) { + throw new Error(appConfig.errors.notAuthorized); + } + + const user = accounts.find(item => item.uid === uid); + + return { + uid, + email: user.defaultEmail, + }; } async function loadToken(uid) { - const sessionId = await getSessionId(); - const res = await sendRequest({ - method: 'post', - url: resolveUrl(AUTH_CONFIG.tokenUrl), - form: { - grant_type: 'sessionid', - host: resolveUrl('yandex.{domain}'), - client_id: AUTH_CONFIG.clientId, - client_secret: AUTH_CONFIG.clientSecret, - sessionid: sessionId, - uid, - }, - }); - - return res.access_token; + const sessionId = await getSessionId(); + const res = await sendRequest({ + method: 'post', + url: resolveUrl(AUTH_CONFIG.tokenUrl), + form: { + grant_type: 'sessionid', + host: resolveUrl('yandex.{domain}'), + client_id: AUTH_CONFIG.clientId, + client_secret: AUTH_CONFIG.clientSecret, + sessionid: sessionId, + uid, + }, + }); + + return res.access_token; } export async function loadUser(token) { - const userInfo = await loadUserInfo(); + const userInfo = await loadUserInfo(); - return { - ...userInfo, - token: token || await loadToken(userInfo.uid), - }; + return { + ...userInfo, + token: token || await loadToken(userInfo.uid), + }; } export async function getMessagesCount() { - const res = await sendRequest({ - url: resolveUrl(`${API_URL}/v2/bar/counters`), - query: {silent: true}, - }); + const res = await sendRequest({ + url: resolveUrl(`${API_URL}/v2/bar/counters`), + query: {silent: true}, + }); - if (!res.counters) { - throw new Error(res); - } + if (!res.counters) { + throw new Error(res); + } - return res.counters.unread; + return res.counters.unread; } export async function getMessages() { - const res = await sendRequest({ - url: resolveUrl(`${API_URL}/mailbox_list`), - type: 'xml', - query: { - first: 0, - last: 100, - extra_cond: 'only_new', - goto: 'all', - }, - }); - - return parseXML(res); + const res = await sendRequest({ + url: resolveUrl(`${API_URL}/mailbox_list`), + type: 'xml', + query: { + first: 0, + last: 100, + extra_cond: 'only_new', + goto: 'all', + }, + }); + + return parseXML(res); } export function updateMessageStatus({oper, id}) { - return sendRequest({ - method: 'post', - url: resolveUrl(`${API_URL}/mailbox_oper`), - type: 'xml', - form: { - ids: [id], - oper, - }, - }); + return sendRequest({ + method: 'post', + url: resolveUrl(`${API_URL}/mailbox_oper`), + type: 'xml', + form: { + ids: [id], + oper, + }, + }); } diff --git a/src/pages/background/utils/cookie.js b/src/pages/background/utils/cookie.js index f29f175..1ca8db2 100644 --- a/src/pages/background/utils/cookie.js +++ b/src/pages/background/utils/cookie.js @@ -1,44 +1,44 @@ import resolveUrl from './url-resolver'; const { - runtime, - cookies, + runtime, + cookies, } = chrome; export const config = { - domain: '.yandex.{domain}', - path: '/', - items: { - login: 'yandex_login', - sessionId: 'Session_id', - uid: 'yandexuid', - }, + domain: '.yandex.{domain}', + path: '/', + items: { + login: 'yandex_login', + sessionId: 'Session_id', + uid: 'yandexuid', + }, }; function getCookieByName(name) { - return new Promise(resolve => { - cookies.getAll({ - domain: resolveUrl(config.domain), - path: config.path, - name, - }, res => { - if (runtime.lastError) { - throw new Error(`Last error in cookie ${runtime.lastError.message}`); - } + return new Promise(resolve => { + cookies.getAll({ + domain: resolveUrl(config.domain), + path: config.path, + name, + }, res => { + if (runtime.lastError) { + throw new Error(`Last error in cookie ${runtime.lastError.message}`); + } - resolve( - Array.isArray(res) && + resolve( + Array.isArray(res) && res[0] && res[0].value, - ); - }); + ); }); + }); } export function getUid() { - return getCookieByName(config.items.uid); + return getCookieByName(config.items.uid); } export function getSessionId() { - return getCookieByName(config.items.sessionId); + return getCookieByName(config.items.sessionId); } diff --git a/src/pages/background/utils/parser.js b/src/pages/background/utils/parser.js index 6bee30d..1ba4fb2 100644 --- a/src/pages/background/utils/parser.js +++ b/src/pages/background/utils/parser.js @@ -1,69 +1,69 @@ const IGNORED_FOLDERS = [ - 'spam', - 'archive', - 'trash', - 'sent', - 'outbox', - 'draft', + 'spam', + 'archive', + 'trash', + 'sent', + 'outbox', + 'draft', ]; function getText(element, selector) { - return element.querySelector(selector).textContent; + return element.querySelector(selector).textContent; } function getFilteredMessage(messages, folders) { - return messages.filter(({fid}) => { - const folder = folders.find(({id}) => id === fid); + return messages.filter(({fid}) => { + const folder = folders.find(({id}) => id === fid); - return folder && !IGNORED_FOLDERS.includes(folder.symbol); - }); + return folder && !IGNORED_FOLDERS.includes(folder.symbol); + }); } function parseFolders(folders) { - return [...folders].map(folder => ({ - id: getText(folder, 'fid'), - symbol: getText(folder, 'symbol'), - })); + return [...folders].map(folder => ({ + id: getText(folder, 'fid'), + symbol: getText(folder, 'symbol'), + })); } function parseMessages(messages) { - return [...messages].map(message => { - const from = message.querySelector('from'); - let subject = getText(message.querySelector('subject'), 'text'); + return [...messages].map(message => { + const from = message.querySelector('from'); + let subject = getText(message.querySelector('subject'), 'text'); - if (subject === 'No subject') { - subject = ''; - } + if (subject === 'No subject') { + subject = ''; + } - return { - subject, - id: message.getAttribute('mid'), - from: getText(from, 'name') || getText(from, 'email'), - firstline: getText(message, 'firstline'), - date: message.getAttribute('recv_date'), - fid: message.getAttribute('fid'), - }; - }); + return { + subject, + id: message.getAttribute('mid'), + from: getText(from, 'name') || getText(from, 'email'), + firstline: getText(message, 'firstline'), + date: message.getAttribute('recv_date'), + fid: message.getAttribute('fid'), + }; + }); } export default function (xml) { - let messages = xml.querySelector('mailbox_list'); - if (!messages) { - throw new Error('Bad response format in messages xml'); - } + let messages = xml.querySelector('mailbox_list'); + if (!messages) { + throw new Error('Bad response format in messages xml'); + } - let folders = xml.querySelector('folder_list'); - if (!folders) { - throw new Error('Bad response format in folders xml'); - } + let folders = xml.querySelector('folder_list'); + if (!folders) { + throw new Error('Bad response format in folders xml'); + } - const error = messages.querySelector('error'); - if (error) { - throw new Error(`Messages xml has error field with code: ${error.getAttribute('code')}`); - } + const error = messages.querySelector('error'); + if (error) { + throw new Error(`Messages xml has error field with code: ${error.getAttribute('code')}`); + } - messages = parseMessages(messages.querySelectorAll('message')); - folders = parseFolders(folders.querySelectorAll('folder')); + messages = parseMessages(messages.querySelectorAll('message')); + folders = parseFolders(folders.querySelectorAll('folder')); - return getFilteredMessage(messages, folders); + return getFilteredMessage(messages, folders); } diff --git a/src/pages/background/utils/runtime.js b/src/pages/background/utils/runtime.js index f65f085..cf9a0a8 100644 --- a/src/pages/background/utils/runtime.js +++ b/src/pages/background/utils/runtime.js @@ -1,4 +1,4 @@ // eslint-disable-next-line import/prefer-default-export export function reloadApp() { - chrome.runtime.reload(); + chrome.runtime.reload(); } diff --git a/src/pages/background/utils/session-generator.js b/src/pages/background/utils/session-generator.js index 926cf4d..b2fff32 100644 --- a/src/pages/background/utils/session-generator.js +++ b/src/pages/background/utils/session-generator.js @@ -2,16 +2,16 @@ const mask = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; const availableChars = '0123456789ABCDEF'; function generateSessionId() { - return mask - .split('') - .map(char => { - if (char === 'x') { - return availableChars[Math.floor(16 * Math.random())]; - } + return mask + .split('') + .map(char => { + if (char === 'x') { + return availableChars[Math.floor(16 * Math.random())]; + } - return char; - }) - .join(''); + return char; + }) + .join(''); } export default generateSessionId(); diff --git a/src/pages/background/utils/tab.js b/src/pages/background/utils/tab.js index 6d1d424..047487e 100644 --- a/src/pages/background/utils/tab.js +++ b/src/pages/background/utils/tab.js @@ -1,17 +1,17 @@ import resolveUrl from './url-resolver'; export function openUrl(url) { - // when we use the function as a callback for click event, "url" will be an event object - let finalUrl = typeof url !== 'string' ? '' : url; + // when we use the function as a callback for click event, "url" will be an event object + let finalUrl = typeof url !== 'string' ? '' : url; - // e.g. donation link is absolute however others are relative - if (!finalUrl.includes('http')) { - finalUrl = resolveUrl(`https://mail.yandex.{domain}/${finalUrl}`); - } + // e.g. donation link is absolute however others are relative + if (!finalUrl.includes('http')) { + finalUrl = resolveUrl(`https://mail.yandex.{domain}/${finalUrl}`); + } - chrome.tabs.create({url: finalUrl}); + chrome.tabs.create({url: finalUrl}); } export function openSettings() { - chrome.runtime.openOptionsPage(); + chrome.runtime.openOptionsPage(); } diff --git a/src/pages/background/utils/url-resolver.js b/src/pages/background/utils/url-resolver.js index ba5db9d..e79e0ff 100644 --- a/src/pages/background/utils/url-resolver.js +++ b/src/pages/background/utils/url-resolver.js @@ -1,7 +1,7 @@ import store from '../redux/store'; export default function (url) { - const currentDomain = store.getState().settings.preferredDomain; + const currentDomain = store.getState().settings.preferredDomain; - return url.replace('{domain}', currentDomain); + return url.replace('{domain}', currentDomain); } diff --git a/src/pages/popup/actions.js b/src/pages/popup/actions.js index 63c7dde..a59bd83 100644 --- a/src/pages/popup/actions.js +++ b/src/pages/popup/actions.js @@ -1,51 +1,51 @@ import { - LOAD_MESSAGES, - UPDATE_MESSAGE, + LOAD_MESSAGES, + UPDATE_MESSAGE, } from 'shared/redux-consts/messages'; import { - OPEN_LINK, - OPEN_SETTINGS, - RELOAD_APP, - OPEN_DONATION_LINK, + OPEN_LINK, + OPEN_SETTINGS, + RELOAD_APP, + OPEN_DONATION_LINK, } from 'shared/redux-consts/popup'; // only FF needs it function closePopup() { - window.close(); + window.close(); } export function loadMessages() { - return {type: LOAD_MESSAGES}; + return {type: LOAD_MESSAGES}; } export function updateMessage(data) { - return { - type: UPDATE_MESSAGE, - data, - }; + return { + type: UPDATE_MESSAGE, + data, + }; } export function openLink(url) { - closePopup(); + closePopup(); - return { - type: OPEN_LINK, - url, - }; + return { + type: OPEN_LINK, + url, + }; } export function openSettings() { - closePopup(); + closePopup(); - return {type: OPEN_SETTINGS}; + return {type: OPEN_SETTINGS}; } export function reloadApp() { - return {type: RELOAD_APP}; + return {type: RELOAD_APP}; } export function openDonationLink() { - closePopup(); + closePopup(); - return {type: OPEN_DONATION_LINK}; + return {type: OPEN_DONATION_LINK}; } diff --git a/src/pages/popup/components/App.js b/src/pages/popup/components/App.js index 517fee1..21545f1 100644 --- a/src/pages/popup/components/App.js +++ b/src/pages/popup/components/App.js @@ -8,90 +8,90 @@ import Header from './Header/Header'; import List from './List/List'; class App extends Component { - static propTypes = { - user: PropTypes.shape({ - authorized: PropTypes.bool.isRequired, - email: PropTypes.string.isRequired, - }).isRequired, - messages: PropTypes.shape({ - unreadCount: PropTypes.number.isRequired, - items: PropTypes.arrayOf(PropTypes.object).isRequired, - loading: PropTypes.bool.isRequired, - error: PropTypes.bool.isRequired, - }).isRequired, - settings: PropTypes.shape({ - preferredDomain: PropTypes.string.isRequired, - }).isRequired, - loadMessages: PropTypes.func.isRequired, - updateMessage: PropTypes.func.isRequired, - openLink: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, - reloadApp: PropTypes.func.isRequired, - openDonationLink: PropTypes.func.isRequired, - }; + static propTypes = { + user: PropTypes.shape({ + authorized: PropTypes.bool.isRequired, + email: PropTypes.string.isRequired, + }).isRequired, + messages: PropTypes.shape({ + unreadCount: PropTypes.number.isRequired, + items: PropTypes.arrayOf(PropTypes.object).isRequired, + loading: PropTypes.bool.isRequired, + error: PropTypes.bool.isRequired, + }).isRequired, + settings: PropTypes.shape({ + preferredDomain: PropTypes.string.isRequired, + }).isRequired, + loadMessages: PropTypes.func.isRequired, + updateMessage: PropTypes.func.isRequired, + openLink: PropTypes.func.isRequired, + openSettings: PropTypes.func.isRequired, + reloadApp: PropTypes.func.isRequired, + openDonationLink: PropTypes.func.isRequired, + }; - componentDidMount() { - const { - user, - loadMessages, - } = this.props; + componentDidMount() { + const { + user, + loadMessages, + } = this.props; - if (user.authorized) { - loadMessages(); - } + if (user.authorized) { + loadMessages(); } + } - render() { - const { - user, - messages: { - unreadCount, - items, - loading, - error, - }, - settings: { - preferredDomain, - }, - loadMessages, - updateMessage, - openLink, - openSettings, - reloadApp, - openDonationLink, - } = this.props; + render() { + const { + user, + messages: { + unreadCount, + items, + loading, + error, + }, + settings: { + preferredDomain, + }, + loadMessages, + updateMessage, + openLink, + openSettings, + reloadApp, + openDonationLink, + } = this.props; - if (!user.authorized) { - return ( - - ); - } - - return ( -
-
- - -
- ); + if (!user.authorized) { + return ( + + ); } + + return ( +
+
+ + +
+ ); + } } export default connect(state => state, actions)(App); diff --git a/src/pages/popup/components/Button/Button.js b/src/pages/popup/components/Button/Button.js index 1b31eba..7e76de4 100644 --- a/src/pages/popup/components/Button/Button.js +++ b/src/pages/popup/components/Button/Button.js @@ -4,42 +4,42 @@ import React from 'react'; import styles from './Button.less'; const TYPES = { - primary: { - name: 'primary', - className: styles.primary, - }, - secondary: { - name: 'secondary', - className: styles.secondary, - }, - icon: { - name: 'icon', - className: styles.icon, - }, + primary: { + name: 'primary', + className: styles.primary, + }, + secondary: { + name: 'secondary', + className: styles.secondary, + }, + icon: { + name: 'icon', + className: styles.icon, + }, }; const Button = ({ - children, - onClick, - disabled, - type, + children, + onClick, + disabled, + type, }) => ( - + ); Button.propTypes = { - children: PropTypes.any.isRequired, - onClick: PropTypes.func.isRequired, - disabled: PropTypes.bool, - type: PropTypes.oneOf(Object.keys(TYPES)), + children: PropTypes.any.isRequired, + onClick: PropTypes.func.isRequired, + disabled: PropTypes.bool, + type: PropTypes.oneOf(Object.keys(TYPES)), }; Button.defaultProps = { - disabled: false, - type: TYPES.primary.name, + disabled: false, + type: TYPES.primary.name, }; export default Button; diff --git a/src/pages/popup/components/Donation/Donation.js b/src/pages/popup/components/Donation/Donation.js index 2ada9e0..046d313 100644 --- a/src/pages/popup/components/Donation/Donation.js +++ b/src/pages/popup/components/Donation/Donation.js @@ -6,20 +6,20 @@ import Translation from '../Translation'; import styles from './Donation.less'; export default function Donation({onClick}) { - return ( -
- -
- -
-
- ); + return ( +
+ +
+ +
+
+ ); } Donation.propTypes = { - onClick: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, }; diff --git a/src/pages/popup/components/Header/Header.js b/src/pages/popup/components/Header/Header.js index 07259ec..9ea51f2 100644 --- a/src/pages/popup/components/Header/Header.js +++ b/src/pages/popup/components/Header/Header.js @@ -9,51 +9,51 @@ import ComposeIcon from './Icons/ComposeIcon'; import styles from './Header.less'; const Header = ({ - user, - unreadMessagesCount, - disabled, - reloadMessages, - openLink, - openSettings, + user, + unreadMessagesCount, + disabled, + reloadMessages, + openLink, + openSettings, }) => ( -
- -
- openLink()}>{/* we need callback here, otherwise event will be passed as a first param */} - {user} ({unreadMessagesCount}) - - -
- +
+ + + +
); Header.propTypes = { - user: PropTypes.string.isRequired, - unreadMessagesCount: PropTypes.number.isRequired, - disabled: PropTypes.bool.isRequired, - reloadMessages: PropTypes.func.isRequired, - openLink: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, + user: PropTypes.string.isRequired, + unreadMessagesCount: PropTypes.number.isRequired, + disabled: PropTypes.bool.isRequired, + reloadMessages: PropTypes.func.isRequired, + openLink: PropTypes.func.isRequired, + openSettings: PropTypes.func.isRequired, }; export default Header; diff --git a/src/pages/popup/components/Header/Icons/ComposeIcon.js b/src/pages/popup/components/Header/Icons/ComposeIcon.js index 4716893..02e99c4 100644 --- a/src/pages/popup/components/Header/Icons/ComposeIcon.js +++ b/src/pages/popup/components/Header/Icons/ComposeIcon.js @@ -1,9 +1,9 @@ import React from 'react'; const ComposeIcon = (props) => ( - - - + + + ); export default ComposeIcon; diff --git a/src/pages/popup/components/Header/Icons/ReloadIcon.js b/src/pages/popup/components/Header/Icons/ReloadIcon.js index 54e194b..a2e6f61 100644 --- a/src/pages/popup/components/Header/Icons/ReloadIcon.js +++ b/src/pages/popup/components/Header/Icons/ReloadIcon.js @@ -1,9 +1,9 @@ import React from 'react'; const ReloadIcon = (props) => ( - - - + + + ); export default ReloadIcon; diff --git a/src/pages/popup/components/Header/Icons/SettingsIcon.js b/src/pages/popup/components/Header/Icons/SettingsIcon.js index 3a78e81..20f5ee0 100644 --- a/src/pages/popup/components/Header/Icons/SettingsIcon.js +++ b/src/pages/popup/components/Header/Icons/SettingsIcon.js @@ -1,9 +1,9 @@ import React from 'react'; const SettingsIcon = (props) => ( - - - + + + ); export default SettingsIcon; diff --git a/src/pages/popup/components/Header/__tests__/Header.test.js b/src/pages/popup/components/Header/__tests__/Header.test.js index c41f59d..d935e97 100644 --- a/src/pages/popup/components/Header/__tests__/Header.test.js +++ b/src/pages/popup/components/Header/__tests__/Header.test.js @@ -7,66 +7,66 @@ const openLink = jest.fn(); const openSettings = jest.fn(); const commonProps = { - user: 'username@ya.ru', - unreadMessagesCount: 5, - disabled: false, - reloadMessages, - openLink, - openSettings, + user: 'username@ya.ru', + unreadMessagesCount: 5, + disabled: false, + reloadMessages, + openLink, + openSettings, }; function render(props) { - const component = renderer.create( -
, - ); + const component = renderer.create( +
, + ); - return component.toJSON(); + return component.toJSON(); } describe('popup/Header', () => { - beforeEach(() => { - window.chrome = { - i18n: { - getMessage: jest.fn(() => 'text'), - }, - }; - }); + beforeEach(() => { + window.chrome = { + i18n: { + getMessage: jest.fn(() => 'text'), + }, + }; + }); - describe('render', () => { - it('disabled', () => { - const tree = render({disabled: true}); - expect(tree).toMatchSnapshot(); - }); + describe('render', () => { + it('disabled', () => { + const tree = render({disabled: true}); + expect(tree).toMatchSnapshot(); + }); - it('active', () => { - const tree = render(); - expect(tree).toMatchSnapshot(); - }); + it('active', () => { + const tree = render(); + expect(tree).toMatchSnapshot(); }); + }); - it('callbacks', () => { - const tree = render(); - const [ - composeLink, - centerBlock, - settingsLink, - ] = tree.children; + it('callbacks', () => { + const tree = render(); + const [ + composeLink, + centerBlock, + settingsLink, + ] = tree.children; - composeLink.props.onClick(); - expect(openLink).lastCalledWith('#compose'); + composeLink.props.onClick(); + expect(openLink).lastCalledWith('#compose'); - const [mailLink, reloadBtn] = centerBlock.children; + const [mailLink, reloadBtn] = centerBlock.children; - mailLink.props.onClick(); - expect(openLink).lastCalledWith(); + mailLink.props.onClick(); + expect(openLink).lastCalledWith(); - reloadBtn.props.onClick(); - expect(reloadMessages).lastCalledWith(); + reloadBtn.props.onClick(); + expect(reloadMessages).lastCalledWith(); - settingsLink.props.onClick(); - expect(openSettings).lastCalledWith(); - }); + settingsLink.props.onClick(); + expect(openSettings).lastCalledWith(); + }); }); diff --git a/src/pages/popup/components/List/HoverMenu/HoverMenu.js b/src/pages/popup/components/List/HoverMenu/HoverMenu.js index f800eda..ebb20ef 100644 --- a/src/pages/popup/components/List/HoverMenu/HoverMenu.js +++ b/src/pages/popup/components/List/HoverMenu/HoverMenu.js @@ -5,24 +5,24 @@ import Button, {types} from './HoverMenuButton/HoverMenuButton'; import styles from './HoverMenu.less'; const HoverMenu = ({ - id, - onActionClick, + id, + onActionClick, }) => { - const buttons = Object.keys(types).map((key, index) => ( - -
+

+
+
+ ); UnavailableMessage.propTypes = { - domain: PropTypes.string.isRequired, - reloadApp: PropTypes.func.isRequired, + domain: PropTypes.string.isRequired, + reloadApp: PropTypes.func.isRequired, }; export default UnavailableMessage; diff --git a/src/pages/popup/components/__tests__/App.test.js b/src/pages/popup/components/__tests__/App.test.js index 4e80e74..3497a9e 100644 --- a/src/pages/popup/components/__tests__/App.test.js +++ b/src/pages/popup/components/__tests__/App.test.js @@ -8,58 +8,58 @@ jest.mock('../List/List', () => () =>
List
); jest.mock('../Donation/Donation', () => () =>
Donation
); const baseProps = { - user: { - authorized: true, - email: 'test@ya.ru', - }, - messages: { - unreadCount: 5, - items: [ - {id: 1}, - {id: 2}, - ], - loading: false, - error: false, - }, - settings: { - preferredDomain: 'ru', - }, - loadMessages: jest.fn(), - updateMessage: jest.fn(), - openLink: jest.fn(), - openSettings: jest.fn(), - reloadApp: jest.fn(), - openDonationLink: jest.fn(), + user: { + authorized: true, + email: 'test@ya.ru', + }, + messages: { + unreadCount: 5, + items: [ + {id: 1}, + {id: 2}, + ], + loading: false, + error: false, + }, + settings: { + preferredDomain: 'ru', + }, + loadMessages: jest.fn(), + updateMessage: jest.fn(), + openLink: jest.fn(), + openSettings: jest.fn(), + reloadApp: jest.fn(), + openDonationLink: jest.fn(), }; function render(props) { - const component = renderer.create( - , - ); + const component = renderer.create( + , + ); - return component.toJSON(); + return component.toJSON(); } describe('popup/App', () => { - it('authorized', () => { - const tree = render(); + it('authorized', () => { + const tree = render(); - expect(tree).toMatchSnapshot(); - expect(baseProps.loadMessages).toHaveBeenCalledTimes(1); - }); - - it('not authorized', () => { - const tree = render({ - user: { - ...baseProps.user, - authorized: false, - }, - }); + expect(tree).toMatchSnapshot(); + expect(baseProps.loadMessages).toHaveBeenCalledTimes(1); + }); - expect(tree).toMatchSnapshot(); - expect(baseProps.loadMessages).not.toBeCalled(); + it('not authorized', () => { + const tree = render({ + user: { + ...baseProps.user, + authorized: false, + }, }); + + expect(tree).toMatchSnapshot(); + expect(baseProps.loadMessages).not.toBeCalled(); + }); }); diff --git a/src/pages/popup/index.js b/src/pages/popup/index.js index 7d87c37..b7b1e0f 100644 --- a/src/pages/popup/index.js +++ b/src/pages/popup/index.js @@ -7,17 +7,17 @@ import App from './components/App'; import './styles/layout.less'; async function initApp() { - const store = await createUIStore(); + const store = await createUIStore(); - const mountNode = document.createElement('div'); - document.body.appendChild(mountNode); + const mountNode = document.createElement('div'); + document.body.appendChild(mountNode); - ReactDOM.render( - - - , - mountNode, - ); + ReactDOM.render( + + + , + mountNode, + ); } initApp(); diff --git a/src/pages/raven.js b/src/pages/raven.js index 8c45d5b..e15decb 100644 --- a/src/pages/raven.js +++ b/src/pages/raven.js @@ -3,20 +3,20 @@ import appConfig from 'shared/config'; import config from '../manifest/base.json'; Raven.config('https://de838c75714548c1b68ab1f14fa01e98@sentry.io/120928', { - environment: __DEV__ ? 'development' : 'production', - release: config.version, - maxBreadcrumbs: 50, - ignoreErrors: [ - appConfig.errors.offline, - appConfig.errors.notAuthorized, - ], - shouldSendCallback() { - return !window.navigator.userAgent.includes('Vivaldi'); - }, + environment: __DEV__ ? 'development' : 'production', + release: config.version, + maxBreadcrumbs: 50, + ignoreErrors: [ + appConfig.errors.offline, + appConfig.errors.notAuthorized, + ], + shouldSendCallback() { + return !window.navigator.userAgent.includes('Vivaldi'); + }, }).install(); window.onunhandledrejection = (err) => { - Raven.captureException(err.reason); + Raven.captureException(err.reason); }; // expose for background.js diff --git a/src/pages/settings/actions.js b/src/pages/settings/actions.js index 9f008e2..a2b4b18 100644 --- a/src/pages/settings/actions.js +++ b/src/pages/settings/actions.js @@ -2,8 +2,8 @@ import {UPDATE_SETTINGS} from 'shared/redux-consts/settings'; // eslint-disable-next-line import/prefer-default-export export function updateSettings(name, value) { - return { - type: UPDATE_SETTINGS, - [name]: value, - }; + return { + type: UPDATE_SETTINGS, + [name]: value, + }; } diff --git a/src/pages/settings/components/App.js b/src/pages/settings/components/App.js index 82b2c6a..9678412 100644 --- a/src/pages/settings/components/App.js +++ b/src/pages/settings/components/App.js @@ -6,25 +6,25 @@ import config from './config'; import Field from './Field/Field'; const App = ({settings, updateSettings}) => { - const fields = config.map((item, index) => ( - - )); + const fields = config.map((item, index) => ( + + )); - return ( - - {fields} -
- ); + return ( + + {fields} +
+ ); }; App.propTypes = { - settings: PropTypes.object.isRequired, - updateSettings: PropTypes.func.isRequired, + settings: PropTypes.object.isRequired, + updateSettings: PropTypes.func.isRequired, }; export default connect(state => state, actions)(App); diff --git a/src/pages/settings/components/Field/Field.js b/src/pages/settings/components/Field/Field.js index 5837713..ee03527 100644 --- a/src/pages/settings/components/Field/Field.js +++ b/src/pages/settings/components/Field/Field.js @@ -8,43 +8,43 @@ import Link from './Items/Link'; import styles from './Field.less'; const Field = (props) => { - const { - type, - name, - label, - description, - } = props; + const { + type, + name, + label, + description, + } = props; - let Component; - switch (type) { - case types.checkbox: - Component = Checkbox; - break; - case types.link: - Component = Link; - break; - default: - Component = Select; - } + let Component; + switch (type) { + case types.checkbox: + Component = Checkbox; + break; + case types.link: + Component = Link; + break; + default: + Component = Select; + } - return ( - - - - {description &&
{description}
} - - - - - - ); + return ( + + + + {description &&
{description}
} + + + + + + ); }; Field.propTypes = { - type: PropTypes.oneOf(Object.keys(types)).isRequired, - name: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, + type: PropTypes.oneOf(Object.keys(types)).isRequired, + name: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, }; export default Field; diff --git a/src/pages/settings/components/Field/Items/Checkbox.js b/src/pages/settings/components/Field/Items/Checkbox.js index 4814b75..df1e28f 100644 --- a/src/pages/settings/components/Field/Items/Checkbox.js +++ b/src/pages/settings/components/Field/Items/Checkbox.js @@ -3,22 +3,22 @@ import React from 'react'; import FieldPropTypes from './proptypes'; const Checkbox = ({ - name, - value, - onChange, + name, + value, + onChange, }) => ( - onChange(name, e.target.checked)} - /> + onChange(name, e.target.checked)} + /> ); Checkbox.propTypes = { - ...FieldPropTypes, - value: PropTypes.bool.isRequired, + ...FieldPropTypes, + value: PropTypes.bool.isRequired, }; export default Checkbox; diff --git a/src/pages/settings/components/Field/Items/Link.js b/src/pages/settings/components/Field/Items/Link.js index 529c451..c3d6f63 100644 --- a/src/pages/settings/components/Field/Items/Link.js +++ b/src/pages/settings/components/Field/Items/Link.js @@ -2,16 +2,16 @@ import PropTypes from 'prop-types'; import React, {Component} from 'react'; export default class Link extends Component { - static propTypes = { - url: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - } + static propTypes = { + url: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + } - render() { - return {this.props.text}; - } + render() { + return {this.props.text}; + } - onClick = () => { - chrome.tabs.create({url: this.props.url}); - } + onClick = () => { + chrome.tabs.create({url: this.props.url}); + } } diff --git a/src/pages/settings/components/Field/Items/Select.js b/src/pages/settings/components/Field/Items/Select.js index 29a3848..6b6f66e 100644 --- a/src/pages/settings/components/Field/Items/Select.js +++ b/src/pages/settings/components/Field/Items/Select.js @@ -3,52 +3,52 @@ import React, {Component} from 'react'; import FieldPropTypes from './proptypes'; export default class Select extends Component { - static propTypes = { - ...FieldPropTypes, - value: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - ]).isRequired, - options: PropTypes.arrayOf(PropTypes.shape({ - value: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - ]).isRequired, - label: PropTypes.string.isRequired, - })).isRequired, - } + static propTypes = { + ...FieldPropTypes, + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + ]).isRequired, + options: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + ]).isRequired, + label: PropTypes.string.isRequired, + })).isRequired, + } - render() { - const { - name, - value, - options, - } = this.props; + render() { + const { + name, + value, + options, + } = this.props; - const optionElements = options.map((item, index) => ( - - )); + const optionElements = options.map((item, index) => ( + + )); - return ( - - ); - } + return ( + + ); + } - onChange = (e) => { - const { - name, - onChange, - } = this.props; - const {value} = e.target; - const parsedValue = isNaN(value) ? value : parseInt(value, 10); + onChange = (e) => { + const { + name, + onChange, + } = this.props; + const {value} = e.target; + const parsedValue = isNaN(value) ? value : parseInt(value, 10); - onChange(name, parsedValue); - } + onChange(name, parsedValue); + } } diff --git a/src/pages/settings/components/Field/Items/__tests__/Select.test.js b/src/pages/settings/components/Field/Items/__tests__/Select.test.js index 025b14c..42aed4b 100644 --- a/src/pages/settings/components/Field/Items/__tests__/Select.test.js +++ b/src/pages/settings/components/Field/Items/__tests__/Select.test.js @@ -3,94 +3,94 @@ import renderer from 'react-test-renderer'; import Select from '../Select'; function testCase(props) { - const component = renderer.create( - , + ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); - return tree; + return tree; } describe('settings/Select', () => { - it('no options', () => { - testCase({ - value: 123, - options: [], - }); + it('no options', () => { + testCase({ + value: 123, + options: [], }); + }); - describe('with options', () => { - function testCaseWithOptions({ - value, - newValue, - actualNewValue, - options, - }) { - const name = 'field'; - const onChange = jest.fn(); - - const tree = testCase({ - name, - value, - options, - onChange, - }); + describe('with options', () => { + function testCaseWithOptions({ + value, + newValue, + actualNewValue, + options, + }) { + const name = 'field'; + const onChange = jest.fn(); - tree.props.onChange({target: {value: newValue}}); - expect(onChange).lastCalledWith(name, actualNewValue); - } + const tree = testCase({ + name, + value, + options, + onChange, + }); - describe('number options', () => { - it('without zero value', () => { - testCaseWithOptions({ - value: 1, - newValue: '2', // select onChange always called with string - actualNewValue: 2, - options: [{ - label: 'option1', - value: 1, - }, { - label: 'option2', - value: 2, - }], - }); - }); + tree.props.onChange({target: {value: newValue}}); + expect(onChange).lastCalledWith(name, actualNewValue); + } - it('with zero value', () => { - testCaseWithOptions({ - value: 1, - newValue: '0', // select onChange always called with string - actualNewValue: 0, - options: [{ - label: 'option0', - value: 0, - }, { - label: 'option1', - value: 1, - }], - }); - }); + describe('number options', () => { + it('without zero value', () => { + testCaseWithOptions({ + value: 1, + newValue: '2', // select onChange always called with string + actualNewValue: 2, + options: [{ + label: 'option1', + value: 1, + }, { + label: 'option2', + value: 2, + }], }); + }); - it('string options', () => { - testCaseWithOptions({ - value: 'opt1', - newValue: 'opt2', - actualNewValue: 'opt2', - options: [{ - label: 'option1', - value: 'opt1', - }, { - label: 'option2', - value: 'opt2', - }], - }); + it('with zero value', () => { + testCaseWithOptions({ + value: 1, + newValue: '0', // select onChange always called with string + actualNewValue: 0, + options: [{ + label: 'option0', + value: 0, + }, { + label: 'option1', + value: 1, + }], }); + }); + }); + + it('string options', () => { + testCaseWithOptions({ + value: 'opt1', + newValue: 'opt2', + actualNewValue: 'opt2', + options: [{ + label: 'option1', + value: 'opt1', + }, { + label: 'option2', + value: 'opt2', + }], + }); }); + }); }); diff --git a/src/pages/settings/components/Field/Items/proptypes.js b/src/pages/settings/components/Field/Items/proptypes.js index c85ab08..b04125e 100644 --- a/src/pages/settings/components/Field/Items/proptypes.js +++ b/src/pages/settings/components/Field/Items/proptypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; export default { - name: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, }; diff --git a/src/pages/settings/components/Field/__tests__/Field.test.js b/src/pages/settings/components/Field/__tests__/Field.test.js index 9789f71..6e77c44 100644 --- a/src/pages/settings/components/Field/__tests__/Field.test.js +++ b/src/pages/settings/components/Field/__tests__/Field.test.js @@ -7,41 +7,41 @@ jest.mock('../Items/Select', () => (props) =>
Select
); jest.mock('../Items/Link', () => (props) =>
Link
); function testCase(props) { - const component = renderer.create( - , - ); + const component = renderer.create( + , + ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); } describe('settings/Field', () => { - it('checkbox', () => { - testCase({ - type: 'checkbox', - value: false, - onChange: jest.fn(), - }); + it('checkbox', () => { + testCase({ + type: 'checkbox', + value: false, + onChange: jest.fn(), }); + }); - it('select', () => { - testCase({ - type: 'select', - value: 1, - onChange: jest.fn(), - }); + it('select', () => { + testCase({ + type: 'select', + value: 1, + onChange: jest.fn(), }); + }); - it('link', () => { - testCase({ - type: 'link', - url: 'https://smth.com', - text: 'site url', - }); + it('link', () => { + testCase({ + type: 'link', + url: 'https://smth.com', + text: 'site url', }); + }); }); diff --git a/src/pages/settings/components/Field/types.js b/src/pages/settings/components/Field/types.js index 212ae02..5015063 100644 --- a/src/pages/settings/components/Field/types.js +++ b/src/pages/settings/components/Field/types.js @@ -1,5 +1,5 @@ export default { - checkbox: 'checkbox', - select: 'select', - link: 'link', + checkbox: 'checkbox', + select: 'select', + link: 'link', }; diff --git a/src/pages/settings/components/__tests__/App.test.js b/src/pages/settings/components/__tests__/App.test.js index fbc34d3..d80fd14 100644 --- a/src/pages/settings/components/__tests__/App.test.js +++ b/src/pages/settings/components/__tests__/App.test.js @@ -6,19 +6,19 @@ jest.mock('shared/utils/i18n'); jest.mock('../Field/Field', () => (props) =>
Field
); it('settings/App', () => { - const component = renderer.create( - , - ); + const component = renderer.create( + , + ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); }); diff --git a/src/pages/settings/components/config.js b/src/pages/settings/components/config.js index 7401e38..c201052 100644 --- a/src/pages/settings/components/config.js +++ b/src/pages/settings/components/config.js @@ -3,51 +3,51 @@ import i18n from 'shared/utils/i18n'; import types from './Field/types'; const fields = [{ - type: types.select, - name: 'newMessageNotification', - optionValues: [0, 1, 2], + type: types.select, + name: 'newMessageNotification', + optionValues: [0, 1, 2], }, { - type: types.select, - name: 'unreadMessagesNotification', - optionValues: [0, 5, 15, 30], // interval in min + type: types.select, + name: 'unreadMessagesNotification', + optionValues: [0, 5, 15, 30], // interval in min }, { - type: types.checkbox, - name: 'unreadMessagesSound', + type: types.checkbox, + name: 'unreadMessagesSound', }, { - type: types.select, - name: 'notAuthNotification', - optionValues: [0, 1, 2], + type: types.select, + name: 'notAuthNotification', + optionValues: [0, 1, 2], }, { - type: types.select, - name: 'preferredDomain', - optionValues: appConfig.supportedDomains, + type: types.select, + name: 'preferredDomain', + optionValues: appConfig.supportedDomains, }]; // TODO: enable it for FF as well https://bugzilla.mozilla.org/show_bug.cgi?id=1303384 if (typeof browser === 'undefined') { - fields.push({ - type: types.link, - name: 'setShortcuts', - url: 'chrome://extensions/configureCommands', - text: i18n.text('settings.setShortcuts.linkText'), - }); + fields.push({ + type: types.link, + name: 'setShortcuts', + url: 'chrome://extensions/configureCommands', + text: i18n.text('settings.setShortcuts.linkText'), + }); } export default fields.map(field => { - const baseKey = `settings.${field.name}`; - let options; + const baseKey = `settings.${field.name}`; + let options; - if (field.hasOwnProperty('optionValues')) { - options = field.optionValues.map((value, index) => ({ - value, - label: i18n.text(`${baseKey}.options.${index}`), - })); - } + if (field.hasOwnProperty('optionValues')) { + options = field.optionValues.map((value, index) => ({ + value, + label: i18n.text(`${baseKey}.options.${index}`), + })); + } - return { - ...field, - label: i18n.text(`${baseKey}.label`), - description: i18n.text(`${baseKey}.description`), - options, - }; + return { + ...field, + label: i18n.text(`${baseKey}.label`), + description: i18n.text(`${baseKey}.description`), + options, + }; }); diff --git a/src/pages/settings/index.js b/src/pages/settings/index.js index 16e706a..dfc5d96 100644 --- a/src/pages/settings/index.js +++ b/src/pages/settings/index.js @@ -7,17 +7,17 @@ import App from './components/App'; import './layout.less'; async function initApp() { - const store = await createUIStore(); + const store = await createUIStore(); - const mountNode = document.createElement('div'); - document.body.appendChild(mountNode); + const mountNode = document.createElement('div'); + document.body.appendChild(mountNode); - ReactDOM.render( - - - , - mountNode, - ); + ReactDOM.render( + + + , + mountNode, + ); } initApp(); diff --git a/src/pages/shared/config.js b/src/pages/shared/config.js index 0953c8d..fd4d65a 100644 --- a/src/pages/shared/config.js +++ b/src/pages/shared/config.js @@ -1,14 +1,14 @@ export default { - errors: { - offline: 'network is offline', - notAuthorized: 'AUTH_NO_AUTH', - }, - supportedDomains: [ - 'ru', - 'ua', - 'by', - 'kz', - 'com', - 'com.tr', - ], + errors: { + offline: 'network is offline', + notAuthorized: 'AUTH_NO_AUTH', + }, + supportedDomains: [ + 'ru', + 'ua', + 'by', + 'kz', + 'com', + 'com.tr', + ], }; diff --git a/src/pages/shared/utils/__mocks__/i18n.js b/src/pages/shared/utils/__mocks__/i18n.js index ae02e00..e3e67c2 100644 --- a/src/pages/shared/utils/__mocks__/i18n.js +++ b/src/pages/shared/utils/__mocks__/i18n.js @@ -1,8 +1,8 @@ export default { - text(key) { - return key; - }, - date(str) { - return str; - }, + text(key) { + return key; + }, + date(str) { + return str; + }, }; diff --git a/src/pages/shared/utils/__tests__/i18n.js b/src/pages/shared/utils/__tests__/i18n.js index f42180a..ef18e4d 100644 --- a/src/pages/shared/utils/__tests__/i18n.js +++ b/src/pages/shared/utils/__tests__/i18n.js @@ -2,101 +2,101 @@ import dateFns from 'date-fns'; import i18n from '../i18n'; const months = Array - .from(Array(12).keys()) - .reduce((obj, item) => { - obj[`popup_months_${item}`] = `month#${item}`; // eslint-disable-line no-param-reassign - return obj; - }, {}); + .from(Array(12).keys()) + .reduce((obj, item) => { + obj[`popup_months_${item}`] = `month#${item}`; // eslint-disable-line no-param-reassign + return obj; + }, {}); const keys = { - key_number_one: 'key number one', - ...months, + key_number_one: 'key number one', + ...months, }; let getMessage; describe('shared/utils/i18n', () => { - beforeEach(() => { - getMessage = jest.fn(key => keys[key]); - - window.chrome = { - i18n: { - getMessage, - }, - }; + beforeEach(() => { + getMessage = jest.fn(key => keys[key]); + + window.chrome = { + i18n: { + getMessage, + }, + }; + }); + + describe('text', () => { + it('key with dots', () => { + expect(i18n.text('key.number.one')).toBe(keys.key_number_one); + expect(getMessage).lastCalledWith('key_number_one', []); }); - describe('text', () => { - it('key with dots', () => { - expect(i18n.text('key.number.one')).toBe(keys.key_number_one); - expect(getMessage).lastCalledWith('key_number_one', []); - }); + it('key with underscores', () => { + const input = 'key_number_one'; - it('key with underscores', () => { - const input = 'key_number_one'; - - expect(i18n.text(input)).toBe(keys.key_number_one); - expect(getMessage).lastCalledWith(input, []); - }); + expect(i18n.text(input)).toBe(keys.key_number_one); + expect(getMessage).lastCalledWith(input, []); + }); - it('with arguments', () => { - const input = 'key_number_one'; - const arg1 = 'arg1'; - const arg2 = 23; + it('with arguments', () => { + const input = 'key_number_one'; + const arg1 = 'arg1'; + const arg2 = 23; - expect(i18n.text(input, arg1, arg2)).toBe(keys.key_number_one); - expect(getMessage).lastCalledWith(input, [arg1, arg2]); - }); + expect(i18n.text(input, arg1, arg2)).toBe(keys.key_number_one); + expect(getMessage).lastCalledWith(input, [arg1, arg2]); }); + }); - // Yandex uses ISO format ("2016-11-17T22:33:22") for date - describe('date', () => { - describe('this day', () => { - const timezoneHoursOffset = (new Date()).getTimezoneOffset() / 60; + // Yandex uses ISO format ("2016-11-17T22:33:22") for date + describe('date', () => { + describe('this day', () => { + const timezoneHoursOffset = (new Date()).getTimezoneOffset() / 60; - it('no need to add nils', () => { - const date = new Date(); - const hours = 12; - const min = 10; - const expected = `${hours + timezoneHoursOffset}:${min}`; + it('no need to add nils', () => { + const date = new Date(); + const hours = 12; + const min = 10; + const expected = `${hours + timezoneHoursOffset}:${min}`; - date.setHours(hours, min); + date.setHours(hours, min); - expect(i18n.date(date.toISOString())).toBe(expected); - }); + expect(i18n.date(date.toISOString())).toBe(expected); + }); - it('need to add nils', () => { - const date = new Date(); - const hours = 9; - const min = 5; - const expected = `0${hours + timezoneHoursOffset}:0${min}`; + it('need to add nils', () => { + const date = new Date(); + const hours = 9; + const min = 5; + const expected = `0${hours + timezoneHoursOffset}:0${min}`; - date.setHours(hours, min); + date.setHours(hours, min); - expect(i18n.date(date.toISOString())).toBe(expected); - }); - }); + expect(i18n.date(date.toISOString())).toBe(expected); + }); + }); - it('prev month', () => { - const today = new Date(); - const date = dateFns.subMonths(today, 1); - const month = date.getMonth(); + it('prev month', () => { + const today = new Date(); + const date = dateFns.subMonths(today, 1); + const month = date.getMonth(); - const expected = dateFns.isSameYear(today, date) ? - `${date.getDate()} ${months[`popup_months_${month}`]}` : - `${date.getDate()} ${months[`popup_months_${month}`]} ${date.getFullYear()}`; + const expected = dateFns.isSameYear(today, date) ? + `${date.getDate()} ${months[`popup_months_${month}`]}` : + `${date.getDate()} ${months[`popup_months_${month}`]} ${date.getFullYear()}`; - expect(i18n.date(date.toISOString())).toBe(expected); - expect(getMessage).lastCalledWith(`popup_months_${month}`, []); - }); + expect(i18n.date(date.toISOString())).toBe(expected); + expect(getMessage).lastCalledWith(`popup_months_${month}`, []); + }); - it('another year', () => { - const date = dateFns.subYears(new Date(), 1); - const month = date.getMonth(); + it('another year', () => { + const date = dateFns.subYears(new Date(), 1); + const month = date.getMonth(); - const expected = `${date.getDate()} ${months[`popup_months_${month}`]} ${date.getFullYear()}`; + const expected = `${date.getDate()} ${months[`popup_months_${month}`]} ${date.getFullYear()}`; - expect(i18n.date(date.toISOString())).toBe(expected); - expect(getMessage).lastCalledWith(`popup_months_${month}`, []); - }); + expect(i18n.date(date.toISOString())).toBe(expected); + expect(getMessage).lastCalledWith(`popup_months_${month}`, []); }); + }); }); diff --git a/src/pages/shared/utils/i18n.js b/src/pages/shared/utils/i18n.js index ce7fd2b..c9052f9 100644 --- a/src/pages/shared/utils/i18n.js +++ b/src/pages/shared/utils/i18n.js @@ -1,39 +1,38 @@ function addNil(value) { - return value < 10 ? `0${value}` : value; + return value < 10 ? `0${value}` : value; } function text(key, ...args) { - return chrome.i18n.getMessage(key.replace(/\./g, '_'), args); + return chrome.i18n.getMessage(key.replace(/\./g, '_'), args); } function parseDate(value) { - const localDate = new Date(value); - const timezone = localDate.getTimezoneOffset() * 60 * 1000; // in milliseconds + const localDate = new Date(value); + const timezone = localDate.getTimezoneOffset() * 60 * 1000; // in milliseconds - return new Date(localDate.valueOf() + timezone); + return new Date(localDate.valueOf() + timezone); } function date(value) { - const date = parseDate(value); // eslint-disable-line no-shadow - const currentDate = new Date(); - let yearPostfix = ''; + const date = parseDate(value); // eslint-disable-line no-shadow + const currentDate = new Date(); + let yearPostfix = ''; - if (currentDate.toDateString() === date.toDateString()) { - const hour = addNil(date.getHours()); - const min = addNil(date.getMinutes()); + if (currentDate.toDateString() === date.toDateString()) { + const hour = addNil(date.getHours()); + const min = addNil(date.getMinutes()); - return `${hour}:${min}`; - } - else if (currentDate.getYear() !== date.getYear()) { - yearPostfix = ` ${date.getFullYear()}`; - } + return `${hour}:${min}`; + } else if (currentDate.getYear() !== date.getYear()) { + yearPostfix = ` ${date.getFullYear()}`; + } - const month = text(`popup.months.${date.getMonth()}`); + const month = text(`popup.months.${date.getMonth()}`); - return `${date.getDate()} ${month}${yearPostfix}`; + return `${date.getDate()} ${month}${yearPostfix}`; } export default { - text, - date, + text, + date, }; diff --git a/webpack/base.js b/webpack/base.js index 263f349..8ffe07b 100644 --- a/webpack/base.js +++ b/webpack/base.js @@ -5,64 +5,64 @@ const CircularDependencyPlugin = require('circular-dependency-plugin'); const {pagesPath} = require('./utils'); module.exports = { - entry: { - raven: `${pagesPath}/raven`, - background: `${pagesPath}/background`, - popup: `${pagesPath}/popup`, - settings: `${pagesPath}/settings`, + entry: { + raven: `${pagesPath}/raven`, + background: `${pagesPath}/background`, + popup: `${pagesPath}/popup`, + settings: `${pagesPath}/settings`, + }, + output: { + path: path.resolve('dist/pages'), + filename: '[name].js', + }, + resolve: { + alias: { + shared: path.resolve('./src/pages/shared'), }, - output: { - path: path.resolve('dist/pages'), - filename: '[name].js', + }, + moduleRules: { + js: { + test: /\.js$/, + use: ['babel-loader'], + exclude: /node_modules/, }, - resolve: { - alias: { - shared: path.resolve('./src/pages/shared'), + css: { + test: /\.less$/, + use: [ + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: true, + localIdentName: '[name]__[local]___[hash:base64:5]', + }, }, + 'less-loader', + ], }, - moduleRules: { - js: { - test: /\.js$/, - use: ['babel-loader'], - exclude: /node_modules/, - }, - css: { - test: /\.less$/, - use: [ - { - loader: 'css-loader', - options: { - importLoaders: 1, - modules: true, - localIdentName: '[name]__[local]___[hash:base64:5]', - }, - }, - 'less-loader', - ], - }, - }, - plugins: { - copy: new CopyPlugin([{ - from: 'src', - to: path.resolve('dist'), - ignore: [ - 'manifest/**/*', - 'pages/**/*', - 'locales/**/*', - ], - }]), - shell(target) { - return new ShellPlugin({ - onBuildEnd: [ - `node ./scripts/generate-manifest ${target}`, - 'node ./scripts/locales', - ], - dev: false, - }); - }, - circularDependency: new CircularDependencyPlugin({ - exclude: /node_modules/, - failOnError: true, - }), + }, + plugins: { + copy: new CopyPlugin([{ + from: 'src', + to: path.resolve('dist'), + ignore: [ + 'manifest/**/*', + 'pages/**/*', + 'locales/**/*', + ], + }]), + shell(target) { + return new ShellPlugin({ + onBuildEnd: [ + `node ./scripts/generate-manifest ${target}`, + 'node ./scripts/locales', + ], + dev: false, + }); }, + circularDependency: new CircularDependencyPlugin({ + exclude: /node_modules/, + failOnError: true, + }), + }, }; diff --git a/webpack/dev.js b/webpack/dev.js index c4c5a38..06159db 100644 --- a/webpack/dev.js +++ b/webpack/dev.js @@ -2,67 +2,71 @@ const webpack = require('webpack'); const WriteFilePlugin = require('write-file-webpack-plugin'); const {generateHtmlPlugins} = require('./utils'); const { - entry, - output, - resolve, - moduleRules, - plugins, + entry, + output, + resolve, + moduleRules, + plugins, } = require('./base'); const baseConfig = { - entry, - output, - resolve, - module: { - rules: [ - moduleRules.js, - Object.assign({}, moduleRules.css, { - use: [ - 'style-loader', - ...moduleRules.css.use, - ], - }), + entry, + output, + resolve, + module: { + rules: [ + moduleRules.js, + { + ...moduleRules.css, + use: [ + 'style-loader', + ...moduleRules.css.use, ], - }, - plugins: [ - plugins.circularDependency, - plugins.copy, - ...generateHtmlPlugins(entry), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('development'), - __DEV__: true, - }), + }, ], + }, + plugins: [ + plugins.circularDependency, + plugins.copy, + ...generateHtmlPlugins(entry), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development'), + __DEV__: true, + }), + ], }; module.exports = (target) => { - if (target === 'chrome') { - return Object.assign({}, baseConfig, { - output: Object.assign({}, baseConfig.output, { - publicPath: 'http://localhost:8080', - }), - plugins: [ - ...baseConfig.plugins, - plugins.shell('chrome'), - new WriteFilePlugin({ - log: false, - test: /^((?!hot-update).)*$/, - }), - ], - devtool: 'eval', - devServer: { - stats: 'minimal', - }, - }); - } + if (target === 'chrome') { + return { + ...baseConfig, + output: { + ...baseConfig.output, + publicPath: 'http://localhost:8080', + }, + plugins: [ + ...baseConfig.plugins, + plugins.shell('chrome'), + new WriteFilePlugin({ + log: false, + test: /^((?!hot-update).)*$/, + }), + ], + devtool: 'eval', + devServer: { + stats: 'minimal', + }, + }; + } - // firefox - return Object.assign({}, baseConfig, { - plugins: [ - ...baseConfig.plugins, - plugins.shell('firefox'), - ], - devtool: 'cheap-module-source-map', - watch: true, - }); + // firefox + return { + ...baseConfig, + plugins: [ + ...baseConfig.plugins, + plugins.shell('firefox'), + ], + devtool: 'cheap-module-source-map', + watch: true, + }; }; diff --git a/webpack/prod.js b/webpack/prod.js index 21c2877..4ca5d80 100644 --- a/webpack/prod.js +++ b/webpack/prod.js @@ -3,41 +3,42 @@ const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const {generateHtmlPlugins} = require('./utils'); const { - entry, - output, - resolve, - moduleRules, - plugins, + entry, + output, + resolve, + moduleRules, + plugins, } = require('./base'); const extractTextPlugin = new ExtractTextPlugin('[name].css'); module.exports = (target) => ({ - entry, - output, - resolve, - module: { - rules: [ - moduleRules.js, - Object.assign({}, moduleRules.css, { - use: extractTextPlugin.extract(moduleRules.css.use), - }), - ], - }, - plugins: [ - new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false, - }), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production'), - __DEV__: false, - }), - new UglifyJSPlugin(), - plugins.circularDependency, - plugins.copy, - plugins.shell(target), - ...generateHtmlPlugins(entry), - extractTextPlugin, + entry, + output, + resolve, + module: { + rules: [ + moduleRules.js, + { + ...moduleRules.css, + use: extractTextPlugin.extract(moduleRules.css.use), + }, ], + }, + plugins: [ + new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false, + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production'), + __DEV__: false, + }), + new UglifyJSPlugin(), + plugins.circularDependency, + plugins.copy, + plugins.shell(target), + ...generateHtmlPlugins(entry), + extractTextPlugin, + ], }); diff --git a/webpack/utils.js b/webpack/utils.js index 5172fcd..891e6db 100644 --- a/webpack/utils.js +++ b/webpack/utils.js @@ -3,28 +3,28 @@ const HtmlPlugin = require('html-webpack-plugin'); const pagesPath = './src/pages'; function generateHtmlPlugins(entriesObj) { - // not supported by nodejs - // const { - // raven, - // ...entries, - // } = entriesObj; - const extraScript = 'raven'; - const entries = Object.keys(entriesObj).filter(name => name !== extraScript); + // TODO: not supported by nodejs + // const { + // raven, + // ...entries, + // } = entriesObj; + const extraScript = 'raven'; + const entries = Object.keys(entriesObj).filter(name => name !== extraScript); - return entries.map(name => new HtmlPlugin({ - filename: `./${name}.html`, - chunksSortMode(a, b) { - // raven has a bigger id - return b.id - a.id; - }, - chunks: [ - extraScript, - name, - ], - })); + return entries.map(name => new HtmlPlugin({ + filename: `./${name}.html`, + chunksSortMode(a, b) { + // raven has a bigger id + return b.id - a.id; + }, + chunks: [ + extraScript, + name, + ], + })); } module.exports = { - pagesPath, - generateHtmlPlugins, + pagesPath, + generateHtmlPlugins, }; diff --git a/yarn.lock b/yarn.lock index c3f2c01..4ecebb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -86,7 +86,7 @@ ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" -ajv-keywords@^2.0.0: +ajv-keywords@^2.0.0, ajv-keywords@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" @@ -106,7 +106,7 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5: +ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.0, ajv@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.3.tgz#c06f598778c44c6b161abafe3466b81ad1814ed2" dependencies: @@ -406,7 +406,7 @@ babel-code-frame@7.0.0-beta.0: esutils "^2.0.2" js-tokens "^3.0.0" -babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.26.0: +babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -1219,6 +1219,12 @@ cli-cursor@^1.0.1: dependencies: restore-cursor "^1.0.1" +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -1371,7 +1377,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@^1.4.7, concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -2274,7 +2280,14 @@ eslint-restricted-globals@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" -eslint@3.19.0, eslint@^3.19.0, eslint@^3.7.1: +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@3.19.0, eslint@^3.7.1: version "3.19.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" dependencies: @@ -2314,7 +2327,49 @@ eslint@3.19.0, eslint@^3.19.0, eslint@^3.7.1: text-table "~0.2.0" user-home "^2.0.0" -espree@^3.4.0: +eslint@^4.8.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.8.0.tgz#229ef0e354e0e61d837c7a80fdfba825e199815e" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.0.1" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.5.1" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.4.0, espree@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.1.tgz#0c988b8ab46db53100a1954ae4ba995ddd27d87e" dependencies: @@ -2486,6 +2541,14 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" +external-editor@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.5.tgz#52c249a3981b9ba187c7cacf5beb50bf1d91a6bc" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.33" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -2572,6 +2635,12 @@ figures@^1.3.5: escape-string-regexp "^1.0.5" object-assign "^4.1.0" +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + file-entry-cache@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" @@ -2825,6 +2894,10 @@ function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + fx-runner@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/fx-runner/-/fx-runner-1.0.7.tgz#c102cfdf21234c2dbd16723dccb1186f99c0b641" @@ -2924,7 +2997,7 @@ globals@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/globals/-/globals-10.1.0.tgz#4425a1881be0d336b4a823a82a7be725d5dd987c" -globals@^9.14.0, globals@^9.18.0: +globals@^9.14.0, globals@^9.17.0, globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -3255,7 +3328,7 @@ iconv-lite@0.4.13: version "0.4.13" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" -iconv-lite@0.4.19, iconv-lite@~0.4.13: +iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -3283,7 +3356,7 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" -ignore@^3.2.0: +ignore@^3.2.0, ignore@^3.3.3: version "3.3.5" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" @@ -3350,6 +3423,25 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + internal-ip@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" @@ -3536,6 +3628,10 @@ is-primitive@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + is-property@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" @@ -3963,7 +4059,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@^3.5.1, js-yaml@^3.7.0: +js-yaml@^3.5.1, js-yaml@^3.7.0, js-yaml@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" dependencies: @@ -3981,6 +4077,10 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +jschardet@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" + jsdom@^9.12.0: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" @@ -4596,6 +4696,10 @@ mute-stream@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + mv@~2: version "2.1.1" resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" @@ -4899,6 +5003,12 @@ onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + open@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc" @@ -4959,7 +5069,7 @@ os-shim@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -5074,7 +5184,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" @@ -5178,6 +5288,10 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -5482,6 +5596,10 @@ progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -5996,7 +6114,7 @@ require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" -require-uncached@1.0.3, require-uncached@^1.0.2: +require-uncached@1.0.3, require-uncached@^1.0.2, require-uncached@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" dependencies: @@ -6028,6 +6146,13 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" @@ -6059,12 +6184,28 @@ run-async@^0.1.0: dependencies: once "^1.3.0" +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" dependencies: aproba "^1.1.1" +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + rx-lite@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" @@ -6257,6 +6398,12 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -6476,7 +6623,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: +string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: @@ -6627,6 +6774,17 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" +table@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + tapable@^0.1.8: version "0.1.10" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" @@ -6742,6 +6900,12 @@ tmp@0.0.30: dependencies: os-tmpdir "~1.0.1" +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"