diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b135058c0..9f09cf2cd 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -756,9 +756,21 @@ "panel_detail_rewards_title": { "message": "Ghostery Rewards" }, - "panel_detail_rewards_text": { + "panel_detail_rewards_coming_soon": { "message": "Feature coming soon." }, + "panel_detail_rewards_cliqz_text": { + "message": "Feature already active in the Cliqz MyOffrz control panel." + }, + "panel_detail_rewards_loading": { + "message": "Loading Rewards..." + }, + "panel_detail_rewards_none_found": { + "message": "No Rewards found" + }, + "panel_detail_rewards_off": { + "message": "Ghostery Rewards is OFF" + }, "panel_detail_premium_title": { "message": "Ghostery Premium" }, @@ -1599,5 +1611,83 @@ }, "enable_when_not_scanned": { "message": "To use this function, navigate to another page." + }, + "rewards_new_text": { + "message": "new Reward was discovered" + }, + "rewards_copy_code": { + "message": "copy code" + }, + "rewards_code_copied": { + "message": "code copied" + }, + "rewards_expires_in": { + "description": "example: Expires in 8 minutes. TYPE will be rewards_expires_in_TYPE listed below.", + "message": "Expires in $COUNT$ $TYPE$", + "placeholders": { + "count": { + "content": "$1" + }, + "type": { + "content": "$2" + } + } + }, + "rewards_expires_in_days": { + "message": "days" + }, + "rewards_expires_in_hours": { + "message": "hours" + }, + "rewards_expires_in_mins": { + "message": "minutes" + }, + "rewards_expires_in_secs": { + "message": "seconds" + }, + "rewards_terms_conditions": { + "message": "Terms & Conditions" + }, + "rewards_disable": { + "message": "Turn off Ghostery rewards" + }, + "rewards_disable_notification": { + "message": "Ghostery Rewards is now off. You can always turn it on in Ghostery Settings or in the Ghostery Dashboard." + }, + "rewards_disable_confirm": { + "message": "OK, got it." + }, + "rewards_about": { + "message": "About Ghostery Rewards" + }, + "rewards_settings": { + "message": "Ghostery Rewards Settings" + }, + "rewards_delete": { + "message": "Delete this reward" + }, + "rewards_first_prompt": { + "message": "Would you like to continue to Ghostery Rewards?" + }, + "rewards_second_prompt": { + "message": "Are you sure you would like to opt-out of Ghostery Rewards?" + }, + "rewards_on": { + "message": "on" + }, + "rewards_off": { + "message": "off" + }, + "rewards_on_toast_notification": { + "message": "Ghostery Rewards is ON" + }, + "rewards_off_toast_notification": { + "message": "Ghostery Rewards is OFF" + }, + "rewards_code_copied_toast_notification": { + "message": "Rewards code copied!" + }, + "rewards_learn_more": { + "message": "Learn More" } } diff --git a/app/content-scripts/rewards/HotDog.jsx b/app/content-scripts/rewards/HotDog.jsx new file mode 100644 index 000000000..baa42fd64 --- /dev/null +++ b/app/content-scripts/rewards/HotDog.jsx @@ -0,0 +1,109 @@ +/** + * HotDog Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; +import msgModule from '../utils/msg'; +import { log } from '../../../src/utils/common'; + +const msg = msgModule('hotdog'); +const { sendMessage } = msg; + +/** + * @class Create the Rewards "HotDog", aka UI element + * @memberOf RewardsContentScript + */ +class HotDog extends Component { + constructor(props) { + super(props); + this.state = { + closed: false + }; + + this.iframeEl = window.parent.document.getElementById('ghostery-iframe-container'); + if (this.iframeEl) { + this.iframeEl.classList = ''; + this.iframeEl.classList.add('hot-dog'); + } + + this.ghostyStar = `url(${chrome.extension.getURL('app/images/rewards/ghosty-star.svg')})`; + this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/light-x.svg')})`; + + this.close = this.close.bind(this); + this.navigate = this.navigate.bind(this); + } + + componentWillReceiveProps(nextProps) { + if (nextProps.reward && nextProps.reward !== null) { + this.sendSignal('offer_notification_hotdog', nextProps); + } + } + + messageBackground(name, message) { + if (this.props.port) { + this.props.port.postMessage({ + name, + message + }); + } else { + sendMessage(name, message); + } + } + + sendSignal(actionId, props = this.props) { + // Cliqz metrics + const offerId = props.reward.offer_id; + const message = { + offerId, + actionId + }; + this.messageBackground('rewardSignal', message); + } + + navigate() { + this.sendSignal('offer_click_hotdog'); + if (this.iframeEl) { + this.iframeEl.classList.add('offer-card'); + } + this.props.history.push('/offercard'); + } + + close() { + this.sendSignal('offer_closed_hotdog'); + if (this.iframeEl) { + this.iframeEl.classList = ''; + } + this.setState({ + closed: true + }); + } + + render() { + return ( +
+ { this.state.closed !== true && +
+
+
+ 1 {t('rewards_new_text')}! +
+
+
+
+ } +
+ ); + } +} + +export default withRouter(HotDog); diff --git a/app/content-scripts/rewards/Notification.jsx b/app/content-scripts/rewards/Notification.jsx new file mode 100644 index 000000000..4ec830d8a --- /dev/null +++ b/app/content-scripts/rewards/Notification.jsx @@ -0,0 +1,71 @@ +/** + * Notification Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; + +/** + * @class Handles notifications for Rewards + * @memberOf RewardsContentScript + */ +class Notification extends Component { + constructor(props) { + super(props); + this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/white-x.svg')})`; + this.state = { + closed: false + }; + + this.closeNotification = this.closeNotification.bind(this); + } + + closeNotification(confirm = null) { + this.setState({ + closed: true + }); + if (typeof this.props.data.closeCallback === 'function' && confirm !== null) { + this.props.data.closeCallback(confirm); + } + } + + render() { + return ( +
+ {!this.state.closed && +
+
+
{ this.closeNotification(); }} style={{ backgroundImage: this.closeIcon }} /> +
+ {this.props.data.message} +
+ {this.props.data.buttons && +
+
{ this.closeNotification(true); }}> + Yes +
+
{ this.closeNotification(false); }}> + No +
+
+ } + {this.props.data.textLink + && {this.props.data.textLink.text} + } +
+
+ } +
+ ); + } +} + +export default Notification; diff --git a/app/content-scripts/rewards/OfferCard.jsx b/app/content-scripts/rewards/OfferCard.jsx new file mode 100644 index 000000000..d78b89923 --- /dev/null +++ b/app/content-scripts/rewards/OfferCard.jsx @@ -0,0 +1,316 @@ +/** + * Offer Card Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; +import ReactDOM from 'react-dom'; +import { Link } from 'react-router-dom'; +import msgModule from '../utils/msg'; +import { computeTimeDelta } from '../../panel/utils/utils'; +import { log } from '../../../src/utils/common'; +import Notification from './Notification'; +import Settings from './Settings'; +import ClickOutside from '../../panel/components/BuildingBlocks/ClickOutside'; +import Tooltip from '../../panel/components/Tooltip'; + +const msg = msgModule('rewards'); +const { sendMessage } = msg; + +/** + * @class Generate Rewards offer card + * @memberOf RewardsContentScript + */ +class OfferCard extends Component { + constructor(props) { + super(props); + + this.state = { + closed: false, + copyText: t('rewards_copy_code'), + showPrompt: this.props.conf.rewardsPromptAccepted ? false : 1, + showSettings: false, + rewardUI: props.reward && props.reward.offer_data && props.reward.offer_data.ui_info.template_data || {}, + }; + + this.iframeEl = window.parent.document.getElementById('ghostery-iframe-container'); + if (this.iframeEl) { + this.iframeEl.classList = ''; + this.iframeEl.classList.add('offer-card'); + } + + this.betaLogo = `url(${chrome.extension.getURL('app/images/rewards/ghostery-rewards-beta.png')})`; + this.closeIcon = `url(${chrome.extension.getURL('app/images/drawer/x.svg')})`; + this.ghostyGrey = `url(${chrome.extension.getURL('app/images/rewards/ghosty-grey.svg')})`; + this.kebabIcon = `url(${chrome.extension.getURL('app/images/rewards/settings-kebab.svg')})`; + + this.closeOfferCard = this.closeOfferCard.bind(this); + this.copyCode = this.copyCode.bind(this); + this.disableRewards = this.disableRewards.bind(this); + this.disableRewardsNotification = this.disableRewardsNotification.bind(this); + this.toggleSettings = this.toggleSettings.bind(this); + this.handleImageLoaded = this.handleImageLoaded.bind(this); + this.handlePrompt = this.handlePrompt.bind(this); + + this.notifications = [ + { + type: 'first-prompt', + buttons: true, + message: t('rewards_first_prompt'), + textLink: { + href: 'https://www.ghostery.com/faqs', + text: t('rewards_learn_more'), + callback: this.sendSignal('offer_first_learn') + }, + closeCallback: (option) => { this.handlePrompt(1, option); }, + }, + { + type: 'second-prompt', + buttons: true, + message: t('rewards_second_prompt'), + textLink: {}, + closeCallback: (option) => { this.handlePrompt(2, option); }, + }, + { + type: 'disabled-message', + buttons: false, + message: t('rewards_disable_notification'), + textLink: { + text: t('rewards_disable_confirm') + }, + closeCallback: this.closeOfferCard, + } + ]; + + const { reward } = props; + this.messageBackground('rewardSeen', { + offerId: reward.offer_id + }); + this.sendSignal('offer_shown', { reward }); + this.sendSignal('offer_dsp_session', { reward }); + this.sendSignal('offer_shown_card', { reward }); + } + + messageBackground(name, message) { + if (this.props.port) { + this.props.port.postMessage({ + name, + message + }); + } else { + sendMessage(name, message); + } + } + + sendSignal(actionId, props = this.props) { + // Cliqz metrics + const offerId = props.reward.offer_id; + const message = { + offerId, + actionId + }; + this.messageBackground('rewardSignal', message); + } + + copyCode() { + this.sendSignal('code_copied'); + this.offerCardRef.querySelector('.reward-code-input').select(); + document.execCommand('copy'); + + // 'copied' feedback for user + this.setState({ + copyText: `${t('rewards_code_copied')}!` + }); + + // prevent multiple clicks + clearTimeout(this.timeout); + this.timeout = setTimeout(() => { + this.setState({ + copyText: t('rewards_copy_code') + }); + }, 3000); + } + + toggleSettings(e) { + if (!this.state.showSettings) { + this.sendSignal('offer_settings'); + } + this.setState({ + showSettings: !this.state.showSettings + }); + } + + disableRewards() { + this.sendSignal('rewards_off'); + this.messageBackground('rewardsDisabled'); + } + + disableRewardsNotification() { + this.disableRewards(); + this.setState({ + showPrompt: 3 + }); + } + + handlePrompt(promptNumber, option) { + // @TODO update user settings + if (promptNumber === 1) { + if (!option) { + this.setState({ + showPrompt: 2 + }); + return; + } + this.sendSignal('offer_first_optin'); + } else if (promptNumber === 2) { + if (option) { + this.sendSignal('offer_first_optout'); + this.disableRewards(); + this.closeOfferCard(); + return; + } + this.sendSignal('offer_first_optlater'); + } + this.setState({ + showPrompt: false + }); + this.messageBackground('rewardsPromptAccepted'); + } + + closeOfferCard() { + if (this.iframeEl) { + this.iframeEl.classList = ''; + } + this.setState({ + closed: true + }); + } + + redeem() { + this.sendSignal('offer_ca_action'); + } + + handleImageLoaded(e) { + e.target.classList.remove('hide'); + } + + renderNotification(type) { + const notificationProps = this.notifications[type]; + return ( + + ); + } + + renderExpiresText() { + const { expirationMs } = this.props.reward.offer_data; + const expireDays = Math.round((new Date()).setDate(new Date().getDate() + expirationMs / 60 / 60 / 24)); + const delta = computeTimeDelta(new Date(expireDays), new Date()); + return t('rewards_expires_in', [delta.count, t(`rewards_expires_in_${delta.type}`)]); + } + + render() { + return ( + // @TODO condition for hide class +
{ this.offerCardRef = ref; }} className="ghostery-rewards-component"> + { this.state.closed !== true && +
+ { this.state.showPrompt === 1 && + this.renderNotification(0) + } + { this.state.showPrompt === 2 && + this.renderNotification(1) + } + { this.state.showPrompt === 3 && + this.renderNotification(2) + } +
+
+
{ this.sendSignal('offer_closed_card'); this.closeOfferCard(); }} + style={{ backgroundImage: this.closeIcon }} + /> +
+
+
+
+
+ +
+
{ this.kebabRef = node; }} + /> + { this.state.showSettings && +
+ + + +
+ } +
+
+
+ +
+
+
+ {/*
*/} +
+ { this.state.rewardUI.benefit } +
+ + { this.state.rewardUI.headline } + + + { this.state.rewardUI.desc } + +
+
+
+
+ {this.state.rewardUI.code} + +
+ {this.state.copyText} +
+
+ + { this.renderExpiresText() } + + {this.state.rewardUI.conditions && +
+ { t('rewards_terms_conditions') } + +
+ } +
+ + {this.state.rewardUI.call_to_action.text} + +
+
+
+ } +
+ ); + } +} + +export default OfferCard; diff --git a/app/content-scripts/rewards/Settings.jsx b/app/content-scripts/rewards/Settings.jsx new file mode 100644 index 000000000..050220e6a --- /dev/null +++ b/app/content-scripts/rewards/Settings.jsx @@ -0,0 +1,61 @@ +/** + * Settings Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; + +/** + * @class Handles settings for Rewards + * @memberOf RewardsContentScript + */ +class Settings extends Component { + constructor(props) { + super(props); + this.closeIcon = `url(${chrome.extension.getURL('app/images/rewards/white-x.svg')})`; + this.state = { + closed: false + }; + + this.close = this.close.bind(this); + } + + close() { + this.setState({ + closed: true + }); + if (typeof this.props.closeCallback === 'function') { + this.props.closeCallback(); + } + } + + render() { + return ( +
+ {!this.state.closed && +
+
+
+ {t('rewards_about')} +
{ this.close(); this.props.disable(); }}> + {t('rewards_disable')} +
+
+ {/*
{t('rewards_settings')}
+
{t('rewards_delete')}
*/} +
+
} +
+ ); + } +} + +export default Settings; diff --git a/app/content-scripts/rewards/index.jsx b/app/content-scripts/rewards/index.jsx new file mode 100644 index 000000000..aa096c3fe --- /dev/null +++ b/app/content-scripts/rewards/index.jsx @@ -0,0 +1,157 @@ +/** + * Ghostery Rewards + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +/** + * @namespace RewardsContentScript + */ + +/* eslint no-use-before-define: 0 */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router, Route } from 'react-router-dom'; +import ShadowDOM from 'react-shadow'; +import HotDog from './HotDog'; +import OfferCard from './OfferCard'; +import msgModule from '../utils/msg'; +import history from '../../panel/utils/history'; +import globals from '../../../src/classes/Globals'; + +const msg = msgModule('rewards'); +const { sendMessage } = msg; +const { BROWSER_INFO, onMessage } = globals; +const viewport = document.getElementById('viewport'); +const channelsSupported = (typeof chrome.runtime.connect === 'function'); + +/** + * @class Injects Ghostery Rewards components + * @memberOf RewardsContentScript + */ +class RewardsApp { + constructor() { + this.reward = null; + this.conf = null; + this.rewardsContainer = document.createElement('div'); + this.rewardsApp = document.createElement('div'); + this.rewardsIframe = null; + this.iframeStyle = null; + this.port = null; + this.mainView = null; + this.rewardsApp.id = 'ghostery-rewards-app'; + this.rewardsApp.className = 'show'; + this.handleMessages = this.handleMessages.bind(this); + + this.init(); + } + + init() { + if (document.readyState === 'complete' + || document.readyState === 'loaded' + || document.readyState === 'interactive' + ) { + this.start(); + } else { + document.addEventListener('DOMContentLoaded', (event) => { + this.start(); + }); + } + } + + start() { + if (BROWSER_INFO.name === 'chrome') { + this.renderShadow(); + } else { + // use iframe to encapsulate CSS - fallback for everything else besides chrome + this.renderIframe(); + } + } + + renderReact() { + const MainView = this.mainView; + ReactDOM.render(, this.rewardsApp); + } + + renderShadow() { + // use shadowDOM to encapsulate CSS - fully supported in Chrome + this.rewardsContainer.appendChild(this.rewardsApp); + document.body.appendChild(this.rewardsContainer); + this.mainView = props => ( + + +
+ } /> + } /> + } /> +
+
+
+ ); + this.renderReact(); + this.initListener(); + } + + renderIframe() { + this.rewardsIframe = document.createElement('iframe'); + this.rewardsIframe.id = 'ghostery-iframe-container'; + this.rewardsIframe.classList.add('hot-dog'); + this.rewardsIframe.onload = () => { + this.iframeStyle = document.createElement('link'); + this.iframeStyle.rel = 'stylesheet'; + this.iframeStyle.type = 'text/css'; + this.iframeStyle.href = chrome.extension.getURL('dist/css/rewards_styles.css'); + + this.rewardsIframe.contentWindow.document.head.appendChild(this.iframeStyle); + this.rewardsContainer = this.rewardsIframe.contentWindow.document.body; + + this.rewardsApp.classList.add('iframe-child'); + this.rewardsContainer.appendChild(this.rewardsApp); + this.mainView = props => ( + +
+ } /> + } /> + } /> +
+
+ ); + this.renderReact(); + this.initListener(); + }; + document.body.appendChild(this.rewardsIframe); + } + + initListener() { + if (channelsSupported) { + this.port = chrome.runtime.connect({ name: 'rewardsPort' }); + if (this.port) { + this.port.onMessage.addListener(this.handleMessages); + this.port.postMessage({ name: 'rewardsLoaded' }); + } + } else { + // TODO listen for this in background.js + sendMessage('rewardsLoaded'); + onMessage.addListener(this.handleMessages); + } + } + + handleMessages(request, sender, response) { + if (request.name === 'showHotDog') { + this.reward = request.reward; + this.conf = request.conf; + } + if (document.readyState === 'complete' || document.readyState === 'interactive') { + this.renderReact(); + } + } +} + +const rewardsApp = new RewardsApp(); diff --git a/app/images/icon19.png b/app/images/icon19.png index aee8be927..5b8af3175 100644 Binary files a/app/images/icon19.png and b/app/images/icon19.png differ diff --git a/app/images/icon19_off.png b/app/images/icon19_off.png index 035844a28..15b2f16ac 100644 Binary files a/app/images/icon19_off.png and b/app/images/icon19_off.png differ diff --git a/app/images/icon19_star.png b/app/images/icon19_star.png new file mode 100644 index 000000000..74184ca14 Binary files /dev/null and b/app/images/icon19_star.png differ diff --git a/app/images/icon38.png b/app/images/icon38.png index 9e67dc30f..e62dcc553 100644 Binary files a/app/images/icon38.png and b/app/images/icon38.png differ diff --git a/app/images/icon38_off.png b/app/images/icon38_off.png index 9c0afbdb9..45ad51396 100644 Binary files a/app/images/icon38_off.png and b/app/images/icon38_off.png differ diff --git a/app/images/icon38_star.png b/app/images/icon38_star.png new file mode 100644 index 000000000..6257b6b97 Binary files /dev/null and b/app/images/icon38_star.png differ diff --git a/app/images/panel/caret-up.svg b/app/images/panel/caret-up.svg index 376043aae..c221b0230 100644 --- a/app/images/panel/caret-up.svg +++ b/app/images/panel/caret-up.svg @@ -1,12 +1,3 @@ - - - - Shape - Created with Sketch. - - - - - - - \ No newline at end of file + + + diff --git a/app/images/panel/icon-list-view.svg b/app/images/panel/icon-list-view.svg index 59dfa4ee3..2b579f335 100755 --- a/app/images/panel/icon-list-view.svg +++ b/app/images/panel/icon-list-view.svg @@ -1,21 +1,5 @@ - - - - icon- List view - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + diff --git a/app/images/panel/left-left-moon.svg b/app/images/panel/left-left-moon.svg index c23b7f9da..4695be037 100644 --- a/app/images/panel/left-left-moon.svg +++ b/app/images/panel/left-left-moon.svg @@ -1,15 +1,6 @@ - - - - symbol-half circle hover caret left - Created with Sketch. - - - - - - - - - - \ No newline at end of file + + + + + + diff --git a/app/images/panel/left-right-moon.svg b/app/images/panel/left-right-moon.svg index cb6beb6db..28d16e236 100644 --- a/app/images/panel/left-right-moon.svg +++ b/app/images/panel/left-right-moon.svg @@ -1,15 +1,6 @@ - - - - symbol-hover right half ricle caret right - Created with Sketch. - - - - - - - - - - \ No newline at end of file + + + + + + diff --git a/app/images/panel/line-empty-moon.svg b/app/images/panel/line-empty-moon.svg index 0e7716c28..795f5ec84 100644 --- a/app/images/panel/line-empty-moon.svg +++ b/app/images/panel/line-empty-moon.svg @@ -1,10 +1,3 @@ - - - - Line - Created with Sketch. - - - - + + diff --git a/app/images/panel/plus.svg b/app/images/panel/plus.svg index 32c7754e9..064ee3707 100644 --- a/app/images/panel/plus.svg +++ b/app/images/panel/plus.svg @@ -1,19 +1,6 @@ - - - - icon- add site - Created with Sketch. - - - - - - - - - - - - - - \ No newline at end of file + + + + + + diff --git a/app/images/rewards/ghostery-rewards-beta.png b/app/images/rewards/ghostery-rewards-beta.png new file mode 100644 index 000000000..929d71fc8 Binary files /dev/null and b/app/images/rewards/ghostery-rewards-beta.png differ diff --git a/app/images/rewards/ghosty-grey.svg b/app/images/rewards/ghosty-grey.svg new file mode 100644 index 000000000..0fdbf2ac6 --- /dev/null +++ b/app/images/rewards/ghosty-grey.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/rewards/ghosty-star.svg b/app/images/rewards/ghosty-star.svg new file mode 100644 index 000000000..fbba5b5e8 --- /dev/null +++ b/app/images/rewards/ghosty-star.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/images/rewards/light-x.svg b/app/images/rewards/light-x.svg new file mode 100644 index 000000000..c7006483f --- /dev/null +++ b/app/images/rewards/light-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/rewards/settings-kebab.svg b/app/images/rewards/settings-kebab.svg new file mode 100644 index 000000000..bc9b11daa --- /dev/null +++ b/app/images/rewards/settings-kebab.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/images/rewards/white-x.svg b/app/images/rewards/white-x.svg new file mode 100644 index 000000000..9dd5cd1a4 --- /dev/null +++ b/app/images/rewards/white-x.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/panel/actions/PanelActions.js b/app/panel/actions/PanelActions.js index a3dbb2404..a96b6b977 100644 --- a/app/panel/actions/PanelActions.js +++ b/app/panel/actions/PanelActions.js @@ -13,8 +13,8 @@ import { GET_PANEL_DATA, GET_SUMMARY_DATA, GET_BLOCKING_DATA, - SHOW_NOTIFICATION, TOGGLE_CLIQZ_FEATURE, + SHOW_NOTIFICATION, CLOSE_NOTIFICATION, TOGGLE_EXPERT, LOGIN_SUCCESS, LOGIN_FAILED, @@ -44,7 +44,6 @@ export function toggleCliqzFeature(featureName, isEnabled) { }; } - /** * Fetch panel data from background, only on the initial load. Returns combined * Panel, Summary and Blocking data as needed. diff --git a/app/panel/actions/RewardsActions.js b/app/panel/actions/RewardsActions.js new file mode 100644 index 000000000..00a850807 --- /dev/null +++ b/app/panel/actions/RewardsActions.js @@ -0,0 +1,92 @@ +/** + * Rewards Action creators + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { + GET_REWARDS_DATA, + TOGGLE_OFFERS_ENABLED, + REMOVE_OFFER, + SET_OFFER_READ, + SEND_SIGNAL +} from '../constants/constants'; +import { sendMessageInPromise } from '../utils/msg'; + +/** + * Fetch rewards data from background + * @param {Object} tabId null + * @return {Object} dispatch + */ +export function getRewardsData(tabId) { + return function (dispatch) { + return sendMessageInPromise('getPanelData', { + tabId, + view: 'rewards', + }).then((data) => { + dispatch({ + type: GET_REWARDS_DATA, + data, + }); + }); + }; +} + +/** +* Toggles Rewards on/off +* @param {Boolean} enabled Whether offers should be enabled or not. +* @return {Object} +*/ +export function toggleOffersEnabled(enabled) { + return { + type: TOGGLE_OFFERS_ENABLED, + data: { enabled }, + }; +} + +/** + * Removes a reward from the rewards list + * @param {String} id The ID of the reward we want to remove. + * @return {Object} + */ +export function removeOffer(id) { + return { + type: REMOVE_OFFER, + data: { id } + }; +} + +/** + * Sets the unread status of an offer to false + * @param {String} id the ID of the reward we want to update. + * @return {Object} + */ +export function setOfferRead(id) { + return { + type: SET_OFFER_READ, + data: { id } + }; +} + +/** + * Sends a Signal to the Cliqz Offers + * @param {String} actionId the action id of the signal + * @param {String} offerId the offer id to be sent alongside the signal, sometimes + * @return {Object} + */ +export function sendSignal(actionId, offerId) { + return { + type: SEND_SIGNAL, + data: { + actionId, + offerId + } + }; +} diff --git a/app/panel/actions/SummaryActions.js b/app/panel/actions/SummaryActions.js index f2d5f7523..09421df3a 100644 --- a/app/panel/actions/SummaryActions.js +++ b/app/panel/actions/SummaryActions.js @@ -16,8 +16,7 @@ import { UPDATE_TRACKER_COUNTS, UPDATE_GHOSTERY_PAUSED, UPDATE_SITE_POLICY, - FILTER_TRACKERS, - TOGGLE_EXPERT + FILTER_TRACKERS } from '../constants/constants'; import { sendMessageInPromise } from '../utils/msg'; diff --git a/app/panel/actions/__tests__/RewardsActions.js b/app/panel/actions/__tests__/RewardsActions.js new file mode 100644 index 000000000..a0ef98416 --- /dev/null +++ b/app/panel/actions/__tests__/RewardsActions.js @@ -0,0 +1,102 @@ +/** + * Test file for Rewards Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as msg from '../../utils/msg'; +import * as rewardsActions from '../RewardsActions'; +import { + GET_REWARDS_DATA, + TOGGLE_OFFERS_ENABLED, + REMOVE_OFFER, + SET_OFFER_READ, + SEND_SIGNAL +} from '../../constants/constants'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; +msg.sendMessageInPromise = jest.fn(messageType => new Promise((resolve, reject) => { + switch (messageType) { + case 'getPanelData': + resolve(testData); + break; + default: + resolve(); + } +})); + +describe('app/panel/actions/RewardsActions.js', () => { + test('getRewardsData action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { data, type: GET_REWARDS_DATA }; + + return store.dispatch(rewardsActions.getRewardsData()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); + + test('toggleOffersEnabled action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const enabled = true; + const expectedPayload = { data: { enabled }, type: TOGGLE_OFFERS_ENABLED }; + store.dispatch(rewardsActions.toggleOffersEnabled(enabled)); + + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + + test('removeOffer action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const id = 'test_reward_id'; + const expectedPayload = { data: { id }, type: REMOVE_OFFER }; + store.dispatch(rewardsActions.removeOffer(id)); + + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + + test('setOfferRead action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const id = 'test_reward_id'; + const expectedPayload = { data: { id }, type: SET_OFFER_READ }; + store.dispatch(rewardsActions.setOfferRead(id)); + + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + + test('sendSignal action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const actionId = 'action_id'; + const offerId = 'offer_id'; + const expectedPayload = { data: { actionId, offerId }, type: SEND_SIGNAL }; + store.dispatch(rewardsActions.sendSignal(actionId, offerId)); + + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); +}); diff --git a/app/panel/components/Blocking/BlockingHeader.jsx b/app/panel/components/Blocking/BlockingHeader.jsx index 7dce9e79d..0b13789b2 100644 --- a/app/panel/components/Blocking/BlockingHeader.jsx +++ b/app/panel/components/Blocking/BlockingHeader.jsx @@ -16,7 +16,8 @@ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import { updateSummaryBlockingCount } from '../../utils/blocking'; -import ClickOutside from '../helpers/ClickOutside'; +import ClickOutside from '../BuildingBlocks/ClickOutside'; + /** * @class Implement Blocking Header component. This component is shared * by Blocking view and Global Blocking subview in Settings. diff --git a/app/panel/components/helpers/ClickOutside.jsx b/app/panel/components/BuildingBlocks/ClickOutside.jsx similarity index 90% rename from app/panel/components/helpers/ClickOutside.jsx rename to app/panel/components/BuildingBlocks/ClickOutside.jsx index 7656fb294..c40ba9789 100644 --- a/app/panel/components/helpers/ClickOutside.jsx +++ b/app/panel/components/BuildingBlocks/ClickOutside.jsx @@ -10,14 +10,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 */ -/** - * @namespace PanelComponentHelpers - */ + import React, { Component } from 'react'; /** * @class Implement Click Outside component which handles * clicks outside of designated component - * @memberOf PanelComponentHelpers + * @memberOf PanelBuildingBlocks */ class ClickOutside extends React.Component { constructor(props) { @@ -53,7 +51,10 @@ class ClickOutside extends React.Component { */ clickHandler(e) { const el = this.container; - if (!el.contains(e.target) && e.target !== this.props.excludeEl) { + if (!el.contains(e.target) + && !el.contains(e.path[0]) + && e.target !== this.props.excludeEl + && e.path[0] !== this.props.excludeEl) { this.props.onClickOutside(e); } } diff --git a/app/panel/components/BuildingBlocks/CliqzFeatures.jsx b/app/panel/components/BuildingBlocks/CliqzFeatures.jsx index 7513e6d22..212f8c6ee 100644 --- a/app/panel/components/BuildingBlocks/CliqzFeatures.jsx +++ b/app/panel/components/BuildingBlocks/CliqzFeatures.jsx @@ -17,7 +17,7 @@ import Tooltip from '../Tooltip'; /** * @class Implements buttons to render and toggle for Cliqz's features on/off. - * @memberof PanelClasses + * @memberof PanelBuildingBlocks */ class CliqzFeatures extends React.Component { constructor(props) { diff --git a/app/panel/components/BuildingBlocks/DonutGraph.jsx b/app/panel/components/BuildingBlocks/DonutGraph.jsx index 43f80cb16..1eba11821 100644 --- a/app/panel/components/BuildingBlocks/DonutGraph.jsx +++ b/app/panel/components/BuildingBlocks/DonutGraph.jsx @@ -18,7 +18,7 @@ import { scaleLinear } from 'd3-scale'; /** * @class Generate donut graph. Used to display tracker data in the Summary View. - * @memberOf PanelClasses + * @memberOf PanelBuildingBlocks */ class DonutGraph extends React.Component { constructor(props) { diff --git a/app/panel/components/BuildingBlocks/GhosteryFeatures.jsx b/app/panel/components/BuildingBlocks/GhosteryFeatures.jsx index ea4600d9f..808ab470e 100644 --- a/app/panel/components/BuildingBlocks/GhosteryFeatures.jsx +++ b/app/panel/components/BuildingBlocks/GhosteryFeatures.jsx @@ -17,7 +17,7 @@ import Tooltip from '../Tooltip'; /** * @class Implements buttons to render and toggle for Ghostery's features on/off. - * @memberof PanelClasses + * @memberof PanelBuildingBlocks */ class GhosteryFeatures extends React.Component { constructor(props) { diff --git a/app/panel/components/BuildingBlocks/NotScanned.jsx b/app/panel/components/BuildingBlocks/NotScanned.jsx index ef010894e..2e23dc151 100644 --- a/app/panel/components/BuildingBlocks/NotScanned.jsx +++ b/app/panel/components/BuildingBlocks/NotScanned.jsx @@ -17,7 +17,7 @@ import ClassNames from 'classnames'; /** * @class Implements the Not Scanned component displayed in the Summary view * when a site is not scannable or has not yet been scanned. - * @memberof PanelClasses + * @memberof PanelBuildingBlocks */ const NotScanned = (props) => { const notScannedClassNames = ClassNames('sub-component', 'not-scanned', { diff --git a/app/panel/components/BuildingBlocks/PauseButton.jsx b/app/panel/components/BuildingBlocks/PauseButton.jsx index 2aec23a37..ac688e11b 100644 --- a/app/panel/components/BuildingBlocks/PauseButton.jsx +++ b/app/panel/components/BuildingBlocks/PauseButton.jsx @@ -17,7 +17,7 @@ import Tooltip from '../Tooltip'; /** * @class Implements the Pause button on the Summary view. - * @memberof PanelClasses + * @memberof PanelBuildingBlocks */ class PauseButton extends React.Component { constructor(props) { diff --git a/app/panel/components/BuildingBlocks/RewardDetail.jsx b/app/panel/components/BuildingBlocks/RewardDetail.jsx new file mode 100644 index 000000000..8aa69bc18 --- /dev/null +++ b/app/panel/components/BuildingBlocks/RewardDetail.jsx @@ -0,0 +1,154 @@ +/** + * Reward Detail Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; +import ClassNames from 'classnames'; +import { computeTimeDelta } from '../../utils/utils'; +import { sendMessage } from '../../utils/msg'; +import Tooltip from '../Tooltip'; + +/** + * @class Implements the details for a single reward for for the Rewards Panel + * @memberof PanelBuildingBlocks + */ +class RewardDetail extends React.Component { + constructor(props) { + super(props); + this.state = { + copyText: t('rewards_copy_code'), + }; + + // Event Bindings + this.handleCopyClick = this.handleCopyClick.bind(this); + this.handleRedeemClick = this.handleRedeemClick.bind(this); + } + + /** + * Lifecycle event + */ + componentWillMount() { + this.props.actions.setOfferRead(this.props.id); + } + + /** + * Handles clicking the copy button + */ + handleCopyClick() { + // Copy the reward code + this.copyNode.querySelector('input').select(); + document.execCommand('copy'); + + // Show a toast notification + this.props.actions.showNotification({ + text: t('rewards_code_copied_toast_notification'), + classes: 'purple', + }); + + // Update and reset Copy Code text + this.setState({ copyText: t('rewards_code_copied') }); + setTimeout(() => { + this.setState({ copyText: t('rewards_copy_code') }); + }, 3000); + + // Send a signal to the offers black box + this.props.actions.sendSignal('code_copied', this.props.id); + } + + /** + * Handles clicking the redeem button + * @param {Object} event the event object + */ + handleRedeemClick(event) { + event.preventDefault(); + this.props.actions.sendSignal('offer_ca_action', this.props.id); + sendMessage('openNewTab', { + url: this.props.redeemUrl, + become_active: true, + }); + window.close(); + } + + /** + * Helper render function for the expires text. + * @return {JSX} JSX for the Rewards Detail + */ + renderExpiresText() { + const { expires } = this.props; + const delta = computeTimeDelta(new Date(expires), new Date()); + return t('rewards_expires_in', [delta.count, t(`rewards_expires_in_${delta.type}`)]); + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the details for a Reward + */ + render() { + const { + code, + text, + description, + conditions, + logoUrl, + pictureUrl, + redeemText + } = this.props; + + return ( +
+
+ + +
+ {text && ( +
+ { text } +
+ )} + {description && ( +
+ { description } +
+ )} + {code && ( +
+ { this.copyNode = node; }}> + { code } + + + + {this.state.copyText} + +
+ )} +
+
+ { this.renderExpiresText() } +
+ {conditions && ( +
+ + { t('rewards_terms_conditions') } + + +
+ )} +
+
+ { redeemText } +
+
+ ); + } +} + +export default RewardDetail; diff --git a/app/panel/components/BuildingBlocks/RewardListItem.jsx b/app/panel/components/BuildingBlocks/RewardListItem.jsx new file mode 100644 index 000000000..72e5f48b7 --- /dev/null +++ b/app/panel/components/BuildingBlocks/RewardListItem.jsx @@ -0,0 +1,122 @@ +/** + * Reward List Item Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import ClassNames from 'classnames'; +import { computeTimeDelta } from '../../utils/utils'; + +/** + * @class Implements a single reward in a list for the Rewards Panel + * @memberof PanelBuildingBlocks + */ +class RewardListItem extends React.Component { + constructor(props) { + super(props); + this.state = {}; + + // Event Bindings + this.handleClick = this.handleClick.bind(this); + this.clickCloseButton = this.clickCloseButton.bind(this); + } + + /** + * Handle the click event: everything normal unless it is disabled + * @param {Object} event the click event + */ + handleClick(event) { + const { disabled, unread, id } = this.props; + if (disabled) { + event.preventDefault(); + } else { + if (unread) { + this.props.actions.sendSignal('offer_click_specific_new', id); + } + this.props.actions.sendSignal('offer_click_specific', id); + } + } + + /** + * Handle clicking on the close button + * @param {Object} event the click event + */ + clickCloseButton(event) { + // Prevent the event from propagating and linking to the Reward Detail + event.preventDefault(); + + const { id } = this.props; + this.props.actions.removeOffer(id); + this.props.actions.sendSignal('remove_offer_link', id); + } + + /** + * Helper render function for the expires text. + * @return {JSX} JSX for the Rewards Items List + */ + renderExpiresText() { + const { expires } = this.props; + const delta = computeTimeDelta(new Date(expires), new Date()); + return t('rewards_expires_in', [delta.count, t(`rewards_expires_in_${delta.type}`)]); + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering a Reward within the Rewards List + */ + render() { + const { + id, + isLong, + text, + unread, + disabled, + logoUrl, + pictureUrl + } = this.props; + const itemClassName = ClassNames('RewardListItem', 'row', { + 'RewardListItem--greyscale': disabled, + 'RewardListItem--unread': unread, + 'RewardListItem--elongated': isLong, + 'not-clickable': disabled, + clickable: !disabled, + }); + + return ( + +
+
+ +
+
+
{ text }
+
{ this.renderExpiresText() }
+
+
+
+ + + +
+
+ + + +
+
+
+ + ); + } +} + +export default RewardListItem; diff --git a/app/panel/components/BuildingBlocks/ToggleSlider.jsx b/app/panel/components/BuildingBlocks/ToggleSlider.jsx new file mode 100644 index 000000000..cd430b289 --- /dev/null +++ b/app/panel/components/BuildingBlocks/ToggleSlider.jsx @@ -0,0 +1,83 @@ +/** + * Toggle Slider Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React, { Component } from 'react'; +import ClassNames from 'classnames'; + +/** + * @class Implements an on/off button as a slider component. + * Is used throughout the extension: Rewards Panel, (todo: more) + * @memberof PanelBuildingBlocks + */ +class ToggleSlider extends React.Component { + constructor(props) { + super(props); + this.state = { + checked: this.props.isChecked, + }; + + // Event Bindings + this._handleChange = this._handleChange.bind(this); + } + + /** + * Lifecycle event + */ + componentWillReceiveProps(nextProps) { + this.setState({ + checked: nextProps.isChecked, + }); + } + + /** + * Handles a change to the the slider. This will update the slider in one + * of two ways. It can use the `onChange` property to update the isChecked + * property in the parent. Or it can just set the state directly. + */ + _handleChange(event) { + if (typeof this.props.onChange === 'function') { + this.props.onChange(event); + } else { + this.setState({ + checked: !this.state.checked, + }); + } + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the Toggle Slider used throghout the extension + */ + render() { + const compClassNames = ClassNames('ToggleSlider', this.props.className); + const labelClassNames = ClassNames('ToggleSlider__switch', { + disabled: this.props.isDisabled, + }); + + return ( +
+ +
+ ); + } +} + +export default ToggleSlider; diff --git a/app/panel/components/BuildingBlocks/index.js b/app/panel/components/BuildingBlocks/index.js index 6d3ab2d18..061f6b840 100644 --- a/app/panel/components/BuildingBlocks/index.js +++ b/app/panel/components/BuildingBlocks/index.js @@ -11,16 +11,27 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0 */ +/** + * @namespace PanelBuildingBlocks + */ +import ClickOutside from './ClickOutside'; import CliqzFeatures from './CliqzFeatures'; import DonutGraph from './DonutGraph'; import GhosteryFeatures from './GhosteryFeatures'; import NotScanned from './NotScanned'; import PauseButton from './PauseButton'; +import ToggleSlider from './ToggleSlider'; +import RewardDetail from './RewardDetail'; +import RewardListItem from './RewardListItem'; export { + ClickOutside, CliqzFeatures, DonutGraph, GhosteryFeatures, NotScanned, - PauseButton + PauseButton, + ToggleSlider, + RewardDetail, + RewardListItem }; diff --git a/app/panel/components/Detail.jsx b/app/panel/components/Detail.jsx index cb33319e6..e623f2be4 100644 --- a/app/panel/components/Detail.jsx +++ b/app/panel/components/Detail.jsx @@ -19,7 +19,7 @@ import Summary from '../containers/SummaryContainer'; import Blocking from '../containers/BlockingContainer'; import History from './History'; import Performance from './Performance'; -import Rewards from './Rewards'; +import Rewards from '../containers/RewardsContainer'; import Premium from './Premium'; /** * @class Implement wrapper of the detailed (expert) mode view. @@ -63,6 +63,7 @@ class Detail extends React.Component { const condensedToggleClassNames = ClassNames('condensed-toggle', { condensed: this.props.is_expanded, }); + const { enable_offers, unread_offer_ids } = this.props; return (
@@ -73,7 +74,7 @@ class Detail extends React.Component { - + 0} />
); diff --git a/app/panel/components/DetailMenu.jsx b/app/panel/components/DetailMenu.jsx index 73405e037..174f471f8 100644 --- a/app/panel/components/DetailMenu.jsx +++ b/app/panel/components/DetailMenu.jsx @@ -79,66 +79,30 @@ class DetailMenu extends React.Component { render() { return (
-
-
+
+
- + - + { t('panel_detail_menu_list_title') }
-
- - - - - - - - - - - - { t('panel_detail_menu_history_title') } - -
-
- - - - - - - - - - { t('panel_detail_menu_performance_title') } - -
-
- - - - +
+ + + + + {this.props.hasReward && ( + + )} { t('panel_detail_menu_rewards_title') }
-
- - - - - - - - { t('panel_detail_menu_premium_title') } - -
); diff --git a/app/panel/components/HeaderMenu.jsx b/app/panel/components/HeaderMenu.jsx index 197530a6c..64337c509 100644 --- a/app/panel/components/HeaderMenu.jsx +++ b/app/panel/components/HeaderMenu.jsx @@ -16,7 +16,7 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { Link } from 'react-router-dom'; -import ClickOutside from './helpers/ClickOutside'; +import ClickOutside from './BuildingBlocks/ClickOutside'; import { sendMessage, sendMessageInPromise } from '../utils/msg'; import globals from '../../../src/classes/Globals'; import { log } from '../../../src/utils/common'; diff --git a/app/panel/components/Rewards.jsx b/app/panel/components/Rewards.jsx index aed35d791..b5c5a7aff 100644 --- a/app/panel/components/Rewards.jsx +++ b/app/panel/components/Rewards.jsx @@ -12,58 +12,265 @@ */ import React, { Component } from 'react'; +import { Link, Route } from 'react-router-dom'; +import { ToggleSlider, RewardListItem, RewardDetail } from './BuildingBlocks'; import { sendMessage } from '../utils/msg'; +import globals from '../../../src/classes/Globals'; + +const IS_EDGE = (globals.BROWSER_INFO.name === 'edge'); +const IS_CLIQZ = (globals.BROWSER_INFO.name === 'cliqz'); + /** - * @class Implement Rewards view which opens from a button in - * the footer of the detailed view. See DetailMenu.jsx. + * @class The Rewards Panel shows offers generated by Ghostery Rewards. + * The panel is opened from a button in the Detailed View's footer. + * See DetailMenu.jsx. * @memberof PanelClasses */ class Rewards extends React.Component { constructor(props) { super(props); + this.state = { + rewardsArray: null, + }; // event bindings - this.openNewTab = this.openNewTab.bind(this); + this.toggleOffers = this.toggleOffers.bind(this); + + // helper render functions + this.renderRewardListComponent = this.renderRewardListComponent.bind(this); + this.renderRewardDetailComponent = this.renderRewardDetailComponent.bind(this); + this.handleBackClick = this.handleBackClick.bind(this); + this.handleFaqClick = this.handleFaqClick.bind(this); + } + + /** + * Lifecycle event + */ + componentDidMount() { + this.props.actions.getRewardsData(); + this.props.actions.sendSignal('hub_open'); + } + + /** + * Lifecycle event + */ + componentWillReceiveProps(nextProps) { + const dateNow = new Date(); + let rewardsArray = null; + if (nextProps.rewards) { + rewardsArray = Object.keys(nextProps.rewards).map((key) => { + const reward = nextProps.rewards[key].offer_data; + return { + id: reward.offer_id, + unread: nextProps.unread_offer_ids.indexOf(reward.offer_id) !== -1, + code: reward.ui_info.template_data.code, + text: reward.ui_info.template_data.title, + description: reward.ui_info.template_data.desc, + conditions: reward.ui_info.template_data.conditions, + logo_url: reward.ui_info.template_data.logo_url, + picture_url: reward.ui_info.template_data.picture_url, + redeem_url: reward.ui_info.template_data.call_to_action.url, + redeem_text: reward.ui_info.template_data.call_to_action.text, + expires: Math.round((new Date()).setDate(dateNow.getDate() + reward.expirationMs / 60 / 60 / 24)), + }; + }); + } + this.setState({ rewardsArray }); + } + + /** + * Lifecycle event + */ + componentWillUnmount() { + this.props.actions.sendSignal('hub_closed'); } + + /** + * Handles clicking the back button + */ + handleBackClick() { + const offerId = this.props.location.pathname.split('/detail/rewards/detail/')[1]; + this.props.actions.sendSignal('offer_return_hub', offerId); + } + /** - * Open informational page from 'Learn More' button on the view. + * Handles clicking the learn more button */ - openNewTab() { + handleFaqClick() { sendMessage('openNewTab', { url: 'https:\/\/www.ghostery.com/faqs/what-new-ghostery-features-can-we-expect-in-the-future/', become_active: true, }); sendMessage('ping', 'rewards_learn'); - window.close(); } + /** - * Render Rewards view. - * @return {ReactComponent} ReactComponent instance + * Handles toggling rewards on/off */ - render() { + toggleOffers() { + const { enable_offers } = this.props; + this.props.actions.showNotification({ + text: !enable_offers ? t('rewards_on_toast_notification') : t('rewards_off_toast_notification'), + classes: 'purple', + }); + this.props.actions.toggleOffersEnabled(!enable_offers); + this.props.actions.sendSignal(enable_offers ? 'rewards_off' : 'rewards_on'); + } + + /** + * Helper render function for the Rewards Header + * @return {JSX} JSX for the Rewards Header + */ + renderRewardsHeader() { + const { enable_offers, location } = this.props; + const showBack = location.pathname.indexOf('/detail/rewards/detail') !== -1; + const showToggle = location.pathname === '/detail/rewards/list'; + + return ( +
+ {showBack && ( + + + + + + )} + { t('panel_detail_rewards_title') } + {showToggle && !IS_EDGE && !IS_CLIQZ && ( + + + {enable_offers ? t('rewards_on') : t('rewards_off')} + + + + )} +
+ ); + } + + /** + * Helper render function for Reward Icon SVG + * @return {JSX} JSX for the Rewards Icon SVG + */ + renderRewardSvg() { return ( -
-
{ t('panel_detail_rewards_title') }
-
-
-
- - - - - -
-
-
-
{ t('panel_detail_rewards_text') }
-
-
-
-
- { t('panel_detail_learn_more') } -
+ + + + + + ); + } + + /** + * Helper render function for the list of Rewards Items + * @return {JSX} JSX for the Rewards Items List + */ + renderRewardListComponent() { + const { actions, enable_offers, is_expanded } = this.props; + const { rewardsArray } = this.state; + + if (IS_CLIQZ) { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_cliqz_text') }
+
+
+ { t('panel_detail_learn_more') }
+ ); + } else if (IS_EDGE) { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_coming_soon') }
+
+
+ { t('panel_detail_learn_more') } +
+
+ ); + } else if (enable_offers && !rewardsArray) { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_loading') }
+
+ ); + } else if (enable_offers && rewardsArray.length === 0) { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_none_found') }
+
+ ); + } else if (!enable_offers && (!rewardsArray || rewardsArray.length === 0)) { + return ( +
+ { this.renderRewardSvg() } +
{ t('panel_detail_rewards_off') }
+
+ ); + } + + const rewardsList = rewardsArray.map((reward, index) => ( + + )); + return
{ rewardsList }
; + } + + /** + * Helper render function for an individual Reward Item + * @return {JSX} JSX for the Rewards Detail Item + */ + renderRewardDetailComponent(routeProps) { + const { id } = routeProps.match.params; + const reward = this.state.rewardsArray.find(el => el.id === id); + + return ( + + ); + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the Rewards portion of the Detailed View + */ + render() { + return ( +
+ {this.renderRewardsHeader()} + +
); } diff --git a/app/panel/components/Settings.jsx b/app/panel/components/Settings.jsx index 3f4895e02..a1bfbc173 100644 --- a/app/panel/components/Settings.jsx +++ b/app/panel/components/Settings.jsx @@ -65,6 +65,9 @@ class Settings extends React.Component { * @param {Object} event checking a checkbox event */ toggleCheckbox(event) { + if (event.currentTarget.name === 'enable_offers') { + this.props.actions.sendSignal(event.currentTarget.checked ? 'rewards_off' : 'rewards_on'); + } this.props.actions.toggleCheckbox({ event: event.currentTarget.name, checked: event.currentTarget.checked, diff --git a/app/panel/components/Settings/OptIn.jsx b/app/panel/components/Settings/OptIn.jsx index 3701b114f..b6e883a3d 100644 --- a/app/panel/components/Settings/OptIn.jsx +++ b/app/panel/components/Settings/OptIn.jsx @@ -43,27 +43,27 @@ const OptIn = (props) => {
{!IS_EDGE && !IS_CLIQZ && -
-
- - -
- +
+
+
+ + +
+ +
-
- } - {settingsData.enable_offers_abtest && -
-
- - -
- +
+
+ + +
+ +
diff --git a/app/panel/components/Tooltip.jsx b/app/panel/components/Tooltip.jsx index 1cf8a204a..bb28f6128 100644 --- a/app/panel/components/Tooltip.jsx +++ b/app/panel/components/Tooltip.jsx @@ -13,6 +13,7 @@ import React, { Component } from 'react'; import ReactDOM from 'react-dom'; +import ClassNames from 'classnames'; /** * @class Implements a Tooltip component that is used in many panel views. @@ -52,12 +53,14 @@ class Tooltip extends React.Component { } /** - * Implements mouseenter. Sets a 1 second delay for showing the tooltip. + * Implements mouseenter. Sets a delay for showing the tooltip with a default of 1 second. */ delayHover() { + const delayType = typeof this.props.delay; + const delay = (delayType === 'number' || delayType === 'string') ? +this.props.delay : 1000; this.delay = setTimeout(() => { this.enter(); - }, 1000); + }, delay); } /** @@ -83,8 +86,12 @@ class Tooltip extends React.Component { * @return {JSX} JSX for rendering the Tooltip component */ render() { + const compClassNames = ClassNames({ + 'dark-theme': this.props.theme === 'dark', + }); + return ( - + {this.state.show && {this.props.header && diff --git a/app/panel/components/__tests__/RewardDetail.jsx b/app/panel/components/__tests__/RewardDetail.jsx new file mode 100644 index 000000000..ccbd45a48 --- /dev/null +++ b/app/panel/components/__tests__/RewardDetail.jsx @@ -0,0 +1,179 @@ +/** + * Reward Detail Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import RewardDetail from '../BuildingBlocks/RewardDetail'; + +// Fake the translation function to only return the translation key +global.t = function (str) { + return str; +}; + +// Fake the Tooltip implementation +jest.mock('../Tooltip'); + +describe('app/panel/components/BuildingBlocks/RewardDetail.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('reward detail is rendered with all values present', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: 'test_code', + conditions: 'test reward conditions', + description: 'test reward description', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: 'test reward title' + }; + const component = renderer.create().toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward detail is rendered with missing text', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: 'test_code', + conditions: 'test reward conditions', + description: 'test reward description', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: '' + }; + const component = renderer.create().toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward detail is rendered with missing description', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: 'test_code', + conditions: 'test reward conditions', + description: '', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: 'test reward title' + }; + const component = renderer.create().toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward detail is rendered with missing code', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: '', + conditions: 'test reward conditions', + description: 'test reward description', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: 'test reward title' + }; + const component = renderer.create().toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward detail is rendered with missing conditions', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: 'test_code', + conditions: '', + description: 'test reward description', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: 'test reward title' + }; + const component = renderer.create().toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('reward detail is rendered correctly the code is copied', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + code: 'test_code', + conditions: 'test reward conditions', + description: 'test reward description', + expires: 1527880170315, + id: 'test_reward_id', + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + pictureUrl: 'https://www.ghostery.com/wp-content/uploads/2017/12/simple-detailed-1024x833.png', + redeemUrl: 'https://www.offer-redeem-test.com', + redeemText: 'redeem now', + text: 'test reward title' + }; + const component = shallow(); + expect(component.text()).toEqual(expect.stringContaining('rewards_copy_code')); + expect(component.text()).not.toEqual(expect.stringContaining('rewards_code_copied')); + component.setState({ copyText: t('rewards_code_copied') }); + expect(component.text()).not.toEqual(expect.stringContaining('rewards_copy_code')); + expect(component.text()).toEqual(expect.stringContaining('rewards_code_copied')); + }); + }); +}); diff --git a/app/panel/components/__tests__/RewardListItem.jsx b/app/panel/components/__tests__/RewardListItem.jsx new file mode 100644 index 000000000..ec8e6440e --- /dev/null +++ b/app/panel/components/__tests__/RewardListItem.jsx @@ -0,0 +1,156 @@ +/** + * Reward List Item Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router'; +import RewardListItem from '../BuildingBlocks/RewardListItem'; + +// Fake the translation function to only return the translation key +global.t = function (str) { + return str; +}; + +describe('app/panel/components/BuildingBlocks/RewardListItem.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('reward list item is rendered correctly', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + disabled: false, + expires: 1527885633645, + id: 'test_reward_id', + index: 0, + isLong: false, + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + text: 'test reward title', + unread: false + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward list item is rendered correctly when disabled', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + disabled: true, + expires: 1527885633645, + id: 'test_reward_id', + index: 0, + isLong: false, + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + text: 'test reward title', + unread: false + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward list item is rendered correctly when unread', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + disabled: false, + expires: 1527885633645, + id: 'test_reward_id', + index: 0, + isLong: false, + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + text: 'test reward title', + unread: true + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward list item is rendered correctly when elongated', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + disabled: false, + expires: 1527885633645, + id: 'test_reward_id', + index: 0, + isLong: true, + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + text: 'test reward title', + unread: false + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('reward list item is rendered correctly when disabled, unread, and elongated', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + toggleOffersEnabled: () => {}, + removeOffer: () => {}, + setOfferRead: () => {}, + sendSignal: () => {} + }, + disabled: true, + expires: 1527885633645, + id: 'test_reward_id', + index: 0, + isLong: true, + logoUrl: 'https://www.ghostery.com/wp-content/themes/ghostery/images/ghostery_logo.svg', + text: 'test reward title', + unread: true + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel/components/__tests__/Rewards.jsx b/app/panel/components/__tests__/Rewards.jsx new file mode 100644 index 000000000..8ceb2cf7a --- /dev/null +++ b/app/panel/components/__tests__/Rewards.jsx @@ -0,0 +1,66 @@ +/** + * Rewards Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import React from 'react'; +import renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router'; +import Rewards from '../Rewards'; + +// Fake the translation function to only return the translation key +global.t = function (str) { + return str; +}; + +describe('app/panel/components/Rewards.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('rewards is rendered correctly when rewards is on and rewards is null', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + sendSignal: () => {}, + }, + location: { + pathname: '/detail/rewards/list', + }, + enable_offers: true, + is_expanded: false + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + + test('rewards is rendered correctly when rewards is off and rewards is null', () => { + const initialState = { + actions: { + getRewardsData: () => {}, + sendSignal: () => {}, + }, + location: { + pathname: '/detail/rewards/list', + }, + enable_offers: false, + is_expanded: false + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/panel/components/__tests__/__snapshots__/RewardDetail.jsx.snap b/app/panel/components/__tests__/__snapshots__/RewardDetail.jsx.snap new file mode 100644 index 000000000..a6eb32169 --- /dev/null +++ b/app/panel/components/__tests__/__snapshots__/RewardDetail.jsx.snap @@ -0,0 +1,339 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/BuildingBlocks/RewardDetail.jsx Snapshot tests with react-test-renderer reward detail is rendered with all values present 1`] = ` +
+
+ + +
+
+ test reward title +
+
+ test reward description +
+
+ + + test_code + + + + + rewards_copy_code + +
+
+
+ rewards_expires_in +
+
+ + rewards_terms_conditions + +
+
+
+ redeem now +
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardDetail.jsx Snapshot tests with react-test-renderer reward detail is rendered with missing code 1`] = ` +
+
+ + +
+
+ test reward title +
+
+ test reward description +
+ +
+
+ rewards_expires_in +
+
+ + rewards_terms_conditions + +
+
+
+ redeem now +
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardDetail.jsx Snapshot tests with react-test-renderer reward detail is rendered with missing conditions 1`] = ` +
+
+ + +
+
+ test reward title +
+
+ test reward description +
+
+ + + test_code + + + + + rewards_copy_code + +
+
+
+ rewards_expires_in +
+ +
+
+ redeem now +
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardDetail.jsx Snapshot tests with react-test-renderer reward detail is rendered with missing description 1`] = ` +
+
+ + +
+
+ test reward title +
+ +
+ + + test_code + + + + + rewards_copy_code + +
+
+
+ rewards_expires_in +
+
+ + rewards_terms_conditions + +
+
+
+ redeem now +
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardDetail.jsx Snapshot tests with react-test-renderer reward detail is rendered with missing text 1`] = ` +
+
+ + +
+ +
+ test reward description +
+
+ + + test_code + + + + + rewards_copy_code + +
+
+
+ rewards_expires_in +
+
+ + rewards_terms_conditions + +
+
+
+ redeem now +
+
+`; diff --git a/app/panel/components/__tests__/__snapshots__/RewardListItem.jsx.snap b/app/panel/components/__tests__/__snapshots__/RewardListItem.jsx.snap new file mode 100644 index 000000000..cb4237378 --- /dev/null +++ b/app/panel/components/__tests__/__snapshots__/RewardListItem.jsx.snap @@ -0,0 +1,331 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/BuildingBlocks/RewardListItem.jsx Snapshot tests with react-test-renderer reward list item is rendered correctly 1`] = ` + +
+
+ +
+
+
+ test reward title +
+
+ rewards_expires_in +
+
+
+
+ + + +
+
+ + + +
+
+
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardListItem.jsx Snapshot tests with react-test-renderer reward list item is rendered correctly when disabled 1`] = ` + +
+
+ +
+
+
+ test reward title +
+
+ rewards_expires_in +
+
+
+
+ + + +
+
+ + + +
+
+
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardListItem.jsx Snapshot tests with react-test-renderer reward list item is rendered correctly when disabled, unread, and elongated 1`] = ` + +
+
+ +
+
+
+ test reward title +
+
+ rewards_expires_in +
+
+
+
+ + + +
+
+ + + +
+
+
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardListItem.jsx Snapshot tests with react-test-renderer reward list item is rendered correctly when elongated 1`] = ` + +
+
+ +
+
+
+ test reward title +
+
+ rewards_expires_in +
+
+
+
+ + + +
+
+ + + +
+
+
+
+`; + +exports[`app/panel/components/BuildingBlocks/RewardListItem.jsx Snapshot tests with react-test-renderer reward list item is rendered correctly when unread 1`] = ` + +
+
+ +
+
+
+ test reward title +
+
+ rewards_expires_in +
+
+
+
+ + + +
+
+ + + +
+
+
+
+`; diff --git a/app/panel/components/__tests__/__snapshots__/Rewards.jsx.snap b/app/panel/components/__tests__/__snapshots__/Rewards.jsx.snap new file mode 100644 index 000000000..88bdf5132 --- /dev/null +++ b/app/panel/components/__tests__/__snapshots__/Rewards.jsx.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/panel/components/Rewards.jsx Snapshot tests with react-test-renderer rewards is rendered correctly when rewards is off and rewards is null 1`] = ` +
+
+ + panel_detail_rewards_title + + + + rewards_off + +
+ +
+
+
+
+ + + + + +
+ panel_detail_rewards_off +
+
+
+`; + +exports[`app/panel/components/Rewards.jsx Snapshot tests with react-test-renderer rewards is rendered correctly when rewards is on and rewards is null 1`] = ` +
+
+ + panel_detail_rewards_title + + + + rewards_on + +
+ +
+
+
+
+ + + + + +
+ panel_detail_rewards_loading +
+
+
+`; diff --git a/app/panel/constants/constants.js b/app/panel/constants/constants.js index 530eebfc7..3641211e0 100644 --- a/app/panel/constants/constants.js +++ b/app/panel/constants/constants.js @@ -16,6 +16,7 @@ export const GET_PANEL_DATA = 'GET_PANEL_DATA'; export const SHOW_NOTIFICATION = 'SHOW_NOTIFICATION'; export const TOGGLE_CLIQZ_FEATURE = 'TOGGLE_CLIQZ_FEATURE'; export const CLOSE_NOTIFICATION = 'CLOSE_NOTIFICATION'; +export const TOGGLE_EXPERT = 'TOGGLE_EXPERT'; export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; export const LOGIN_FAILED = 'LOGIN_FAILED'; export const LOGOUT = 'LOGOUT'; @@ -23,8 +24,6 @@ export const CREATE_ACCOUNT_SUCCESS = 'CREATE_ACCOUNT_SUCCESS'; export const CREATE_ACCOUNT_FAILED = 'CREATE_ACCOUNT_FAILED'; export const FORGOT_PASSWORD_SUCCESS = 'FORGOT_PASSWORD_SUCCESS'; export const FORGOT_PASSWORD_FAILED = 'FORGOT_PASSWORD_FAILED'; -export const TOGGLE_DRAWER_SETTING = 'TOGGLE_DRAWER_SETTING'; -export const UPDATE_NOTIFICATION_STATUS = 'UPDATE_NOTIFICATION_STATUS'; // summary export const GET_SUMMARY_DATA = 'GET_SUMMARY_DATA'; @@ -32,11 +31,17 @@ export const UPDATE_TRACKER_COUNTS = 'UPDATE_TRACKER_COUNTS'; export const UPDATE_GHOSTERY_PAUSED = 'UPDATE_GHOSTERY_PAUSED'; export const UPDATE_SITE_POLICY = 'UPDATE_SITE_POLICY'; export const FILTER_TRACKERS = 'FILTER_TRACKERS'; -export const TOGGLE_EXPERT = 'TOGGLE_EXPERT'; // detail export const TOGGLE_EXPANDED = 'TOGGLE_EXPANDED'; +// rewards +export const GET_REWARDS_DATA = 'GET_REWARDS_DATA'; +export const TOGGLE_OFFERS_ENABLED = 'TOGGLE_OFFERS_ENABLED'; +export const REMOVE_OFFER = 'REMOVE_OFFER'; +export const SET_OFFER_READ = 'SET_OFFER_READ'; +export const SEND_SIGNAL = 'SEND_SIGNAL'; + // blocking export const GET_BLOCKING_DATA = 'GET_BLOCKING_DATA'; export const UPDATE_CATEGORIES = 'UPDATE_CATEGORIES'; @@ -56,6 +61,7 @@ export const EXPORT_SETTINGS = 'EXPORT_SETTINGS'; export const SELECT_ITEM = 'SELECT_ITEM'; export const TOGGLE_CHECKBOX = 'TOGGLE_CHECKBOX'; export const UPDATE_DATABASE = 'UPDATE_DATABASE'; +export const UPDATE_NOTIFICATION_STATUS = 'UPDATE_NOTIFICATION_STATUS'; export const UPDATE_SETTINGS_BLOCK_ALL_TRACKERS = 'UPDATE_SETTINGS_BLOCK_ALL_TRACKERS'; export const UPDATE_SETTINGS_CATEGORY_BLOCKED = 'UPDATE_SETTINGS_CATEGORY_BLOCKED'; export const UPDATE_SETTINGS_TRACKER_BLOCKED = 'UPDATE_SETTINGS_TRACKER_BLOCKED'; diff --git a/app/panel/containers/DetailContainer.js b/app/panel/containers/DetailContainer.js index 63c586b2a..1ae6ccece 100644 --- a/app/panel/containers/DetailContainer.js +++ b/app/panel/containers/DetailContainer.js @@ -26,6 +26,8 @@ import * as actions from '../actions/DetailActions'; */ const mapStateToProps = (state, ownProps) => Object.assign({}, state.detail, { is_expanded: state.panel.is_expanded, + enable_offers: state.panel.enable_offers, + unread_offer_ids: state.panel.unread_offer_ids, }); /** * Bind Detailed view action creators using Redux's bindActionCreators diff --git a/app/panel/containers/RewardsContainer.js b/app/panel/containers/RewardsContainer.js new file mode 100644 index 000000000..45f48fb3a --- /dev/null +++ b/app/panel/containers/RewardsContainer.js @@ -0,0 +1,53 @@ +/** + * Rewards Container + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { connect } from 'react-redux'; +import { withRouter } from 'react-router-dom'; +import { bindActionCreators } from 'redux'; +import Rewards from '../components/Rewards'; +import * as actions from '../actions/RewardsActions'; +import { showNotification } from '../actions/PanelActions'; + +/** + * Map redux store state properties to Rewards view own properties. + * @memberof PanelContainers + * @param {Object} state entire Redux store's state + * @param {Object} ownProps props passed to the connected component + * @return {function} this function returns plain object, which will be merged into Detailed view props + * @todo We are not using ownProps, so we better not specify it explicitly, + * in this case it won't be passed by React (see https://github.com/reactjs/react-redux/blob/master/docs/api.md). + */ +const mapStateToProps = (state, ownProps) => Object.assign({}, state.rewards, { + is_expanded: state.panel.is_expanded, +}); + +/** + * Bind Rewards view action creators using Redux's bindActionCreators + * @memberof PanelContainers + * @param {function} dispatch redux store method which dispatches actions + * @param {Object} ownProps Detailed view component own props + * @return {function} to be used as an argument in redux connect call + */ +const mapDispatchToProps = (dispatch, ownProps) => ({ + actions: bindActionCreators(Object.assign(actions, { showNotification }), dispatch) +}); + +/** + * Connects Rewards view component to the Redux store. Pass updated match, location, and history props to the wrapped component. + * @memberof PanelContainers + * @param {function} mapStateToProps maps redux store state properties to Detailed view own properties + * @param {function} mapDispatchToProps binds Detailed view component action creators + * @return {Object} A higher-order React component class that passes state and action + * creators into Detailed view component. Used by React framework. + */ +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Rewards)); diff --git a/app/panel/containers/SettingsContainer.js b/app/panel/containers/SettingsContainer.js index 0cb873d39..894555a3d 100644 --- a/app/panel/containers/SettingsContainer.js +++ b/app/panel/containers/SettingsContainer.js @@ -16,6 +16,7 @@ import Settings from '../components/Settings'; import * as settingsActions from '../actions/SettingsActions'; import { toggleExpanded } from '../actions/DetailActions'; import { updateSitePolicy } from '../actions/SummaryActions'; +import { sendSignal } from '../actions/RewardsActions'; /** * Map redux store state properties to Settings view component own properties. * @memberOf PanelContainers @@ -50,6 +51,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ actions: bindActionCreators(Object.assign(settingsActions, { toggleExpanded, updateSitePolicy, + sendSignal, }), dispatch), }); /** diff --git a/app/panel/reducers/__tests__/rewards.js b/app/panel/reducers/__tests__/rewards.js new file mode 100644 index 000000000..ee109941f --- /dev/null +++ b/app/panel/reducers/__tests__/rewards.js @@ -0,0 +1,132 @@ +/** + * Test file for the Rewards Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import Immutable from 'seamless-immutable'; +import rewardsReducer from '../rewards'; +import { + GET_REWARDS_DATA, + TOGGLE_OFFERS_ENABLED, + REMOVE_OFFER, + SET_OFFER_READ, + SEND_SIGNAL +} from '../../constants/constants'; + +// Copied from app/panel/reducers/rewards.js +const initialState = Immutable({ + rewards: null, + enable_offers: false, + unread_offer_ids: [], +}); + +describe('app/panel/reducers/rewards.js', () => { + test('initial state is correct', () => { + expect(rewardsReducer(undefined, {})).toEqual(initialState); + }); + + test('reducer correctly handles GET_REWARDS_DATA', () => { + const data = { test: true }; + const action = { data, type: GET_REWARDS_DATA }; + const initState = Immutable({}); + + expect(rewardsReducer(initState, action)).toEqual(data); + }); + + test('reducer correctly handles TOGGLE_OFFERS_ENABLED', () => { + const data = { enabled: true }; + const action = { data, type: TOGGLE_OFFERS_ENABLED }; + + const updatedState = Immutable.merge(initialState, { + enable_offers: action.data.enabled, + }); + + expect(rewardsReducer(initialState, action)).toEqual(updatedState); + }); + + test('reducer correctly handles REMOVE_OFFER', () => { + const data = { id: 'test_reward_id' }; + const action = { data, type: REMOVE_OFFER }; + + const initState = Immutable({ + rewards: { + test_reward_id: { reward: 'test' }, + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id', 'test_reward_id_alt'], + }); + + expect(rewardsReducer(initState, action)).toEqual({ + rewards: { + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id_alt'], + }); + }); + + test('reducer correctly handles SET_OFFER_READ', () => { + const data = { id: 'test_reward_id' }; + const action = { data, type: SET_OFFER_READ }; + + const initState = Immutable({ + rewards: { + test_reward_id: { reward: 'test' }, + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id', 'test_reward_id_alt'], + }); + + expect(rewardsReducer(initState, action)).toEqual({ + rewards: { + test_reward_id: { reward: 'test' }, + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id_alt'], + }); + }); + + test('reducer correctly handles SET_OFFER_READ when offer is not new', () => { + const data = { id: 'test_reward_id' }; + const action = { data, type: SET_OFFER_READ }; + + const initState = Immutable({ + rewards: { + test_reward_id: { reward: 'test' }, + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id_alt'], + }); + + expect(rewardsReducer(initState, action)).toEqual({ + rewards: { + test_reward_id: { reward: 'test' }, + test_reward_id_alt: { reward: 'test_alt' }, + }, + enable_offers: true, + unread_offer_ids: ['test_reward_id_alt'], + }); + }); + + test('reducer correctly handles SEND_SIGNAL', () => { + const data = { + actionId: 'action_id', + offerId: 'offer_id', + }; + const action = { data, type: SEND_SIGNAL }; + + expect(rewardsReducer(initialState, action)).toEqual(initialState); + }); +}); diff --git a/app/panel/reducers/index.js b/app/panel/reducers/index.js index 1313ce387..fa627f7f1 100644 --- a/app/panel/reducers/index.js +++ b/app/panel/reducers/index.js @@ -17,6 +17,7 @@ import { combineReducers } from 'redux'; import panel from './panel'; import summary from './summary'; import detail from './detail'; +import rewards from './rewards'; import blocking from './blocking'; import login from './login'; import createAccount from './createAccount'; @@ -34,6 +35,7 @@ const Reducers = combineReducers({ panel, summary, detail, + rewards, blocking, login, createAccount, diff --git a/app/panel/reducers/panel.js b/app/panel/reducers/panel.js index e393e09ef..c6c537023 100644 --- a/app/panel/reducers/panel.js +++ b/app/panel/reducers/panel.js @@ -23,7 +23,11 @@ import { TOGGLE_EXPANDED, TOGGLE_EXPERT, TOGGLE_CLIQZ_FEATURE, - UPDATE_NOTIFICATION_STATUS + UPDATE_NOTIFICATION_STATUS, + TOGGLE_CHECKBOX, + TOGGLE_OFFERS_ENABLED, + REMOVE_OFFER, + SET_OFFER_READ } from '../constants/constants'; import { sendMessage, sendMessageInPromise } from '../utils/msg'; @@ -141,6 +145,27 @@ export default (state = initialState, action) => { const updated = _updateNotificationStatus(state, action); return Object.assign({}, state, updated); } + case TOGGLE_CHECKBOX: { + if (action.data.event === 'enable_offers') { + const enable_offers = action.data.checked; + return Object.assign({}, state, { enable_offers }); + } + return state; + } + case TOGGLE_OFFERS_ENABLED: { + const enable_offers = action.data.enabled; + return Object.assign({}, state, { enable_offers }); + } + case REMOVE_OFFER: + case SET_OFFER_READ: { + const unread_offer_ids = state.unread_offer_ids.slice(); + const idx = unread_offer_ids.indexOf(action.data.id); + if (idx !== -1) { + unread_offer_ids.splice(idx, 1); + return Object.assign({}, state, { unread_offer_ids }); + } + return state; + } default: return state; } }; @@ -202,7 +227,7 @@ const _showNotification = (state, action) => { updated_notificationShown = false; } - updated_notificationClasses = msg.classes; + updated_notificationClasses = msg.classes || ''; if (msg.filter === 'tooltip') { updated_needsReload.changes = {}; } diff --git a/app/panel/reducers/rewards.js b/app/panel/reducers/rewards.js new file mode 100644 index 000000000..626fd517e --- /dev/null +++ b/app/panel/reducers/rewards.js @@ -0,0 +1,82 @@ +/** + * Rewards Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +import { + GET_REWARDS_DATA, + TOGGLE_OFFERS_ENABLED, + REMOVE_OFFER, + SET_OFFER_READ, + SEND_SIGNAL +} from '../constants/constants'; +import { sendMessage, sendRewardMessage } from '../utils/msg'; + +const initialState = { + rewards: null, + enable_offers: false, + unread_offer_ids: [], +}; + +/** + * Default export for rewards view reducer. + * @memberof PanelReactReducers + * + * @param {Object} state current state + * @param {Object} action action which provides data + * @return {Object} unaltered state + */ +export default (state = initialState, action) => { + switch (action.type) { + case GET_REWARDS_DATA: { + return Object.assign({}, state, action.data); + } + case TOGGLE_OFFERS_ENABLED: { + const enable_offers = action.data.enabled; + sendMessage('setPanelData', { enable_offers }); + return Object.assign({}, state, { enable_offers }); + } + + case REMOVE_OFFER: { + // Remove offer from unread array + const unread_offer_ids = [...state.unread_offer_ids]; + const idx = unread_offer_ids.indexOf(action.data.id); + if (idx !== -1) { + unread_offer_ids.splice(idx, 1); + } + + // Remove offer from offers list + const rewards = Object.assign({}, state.rewards); + delete rewards[action.data.id]; + + sendRewardMessage('deleteReward', { offerId: action.data.id }); + return Object.assign({}, state, { unread_offer_ids, rewards }); + } + + case SET_OFFER_READ: { + const unread_offer_ids = [...state.unread_offer_ids]; + const idx = unread_offer_ids.indexOf(action.data.id); + if (idx !== -1) { + unread_offer_ids.splice(idx, 1); + sendRewardMessage('rewardSeen', { offerId: action.data.id }); + return Object.assign({}, state, { unread_offer_ids }); + } + return state; + } + + case SEND_SIGNAL: { + sendRewardMessage('rewardSignal', action.data); + return state; + } + + default: return state; + } +}; diff --git a/app/panel/utils/msg.js b/app/panel/utils/msg.js index 9c0d77150..e1faf68f5 100644 --- a/app/panel/utils/msg.js +++ b/app/panel/utils/msg.js @@ -85,3 +85,27 @@ export function sendMessage(name, message, callback = function () {}) { message, }, callback); } + +/** + * Send a message to the handlers in src/background relating to rewards. + * This should be used for messages that don't require a callback. + * @memberOf PanelUtils + * + * @param {string} name message name + * @param {Object} message message data + * @param {function} callback callback message + * @return {Object} response + * @todo runtime.sendMessage does not return any value. + */ +export function sendRewardMessage(name, message, callback = function () {}) { + log('Panel sendMessage: sending to background', name); + // @EDGE chrome.runtime.sendMessage(message) works, but + // const callback; chrome.runtime.sendMessage(message, callback) fails to execute and chrome.runtime.lastError is undefined. + // const fallback = function () {}; // Workaround for Edge. callback cannot be undefined. + // callback = callback || fallback; + return chrome.runtime.sendMessage({ + name, + message, + origin: 'rewardsPanel', + }, callback); +} diff --git a/app/panel/utils/utils.js b/app/panel/utils/utils.js index d5b6e884c..1beb7c6bf 100644 --- a/app/panel/utils/utils.js +++ b/app/panel/utils/utils.js @@ -63,6 +63,53 @@ export function removeFromArray(array, position) { return array.filter((item, index) => index !== position); } +/** + * Calculates the time difference between two dates and returns the + * value in a computer interpretable way. + * @param {datetime} start the beginning date-time + * @param {datetime} end the ending date-time + * @return {Object} An object with: + * type: days, hours, minutes, seconds + * count: the number of days, hours, minutes, seconds + */ +export function computeTimeDelta(start, end) { + const time_delta = Math.abs(end.getTime() - start.getTime()); + + const day_ms = 1000 * 60 * 60 * 24; + const num_days = Math.round(time_delta / day_ms); + if (num_days >= 2) { + return { + count: num_days, + type: 'days', + }; + } + + const hour_ms = 1000 * 60 * 60; + const num_hours = Math.round(time_delta / hour_ms); + if (num_hours >= 2) { + return { + count: num_hours, + type: 'hours' + }; + } + + const min_ms = 1000 * 60; + const num_mins = Math.round(time_delta / min_ms); + if (num_mins >= 2) { + return { + count: num_mins, + type: 'mins' + }; + } + + const sec_ms = 1000; + const num_secs = Math.round(time_delta / sec_ms); + return { + count: num_secs, + type: 'secs' + }; +} + /** * Check for valid email * @memberOf PanelUtils diff --git a/app/scss/panel.scss b/app/scss/panel.scss index 24d52d40d..535314ba9 100644 --- a/app/scss/panel.scss +++ b/app/scss/panel.scss @@ -41,6 +41,7 @@ html body { background-color: white; height: 516px; width: 580px; + overflow: hidden; margin: 0px; padding: 0px; } @@ -52,10 +53,12 @@ html body { @import './partials/_summary'; @import './partials/_detail'; @import './partials/_blocking'; +@import './partials/_rewards'; @import './partials/_settings'; @import './partials/_help'; @import './partials/_account'; @import './partials/_drawer'; +@import './partials/_toggle_slider'; @import './partials/_pause_button'; @import './partials/_donut_graph'; @import './partials/_ghostery_features'; diff --git a/app/scss/partials/_callout.scss b/app/scss/partials/_callout.scss index b41e18e9a..7e7e6cf2e 100644 --- a/app/scss/partials/_callout.scss +++ b/app/scss/partials/_callout.scss @@ -5,7 +5,7 @@ * https://www.ghostery.com/ * * Copyright 2018 Ghostery, Inc. All rights reserved. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 @@ -37,6 +37,11 @@ .callout-text {color: #ffffff;} .close-button path {stroke: #ffffff;} } + &.purple { + background-color: #5b0059; + .callout-text {color: #ffffff;} + .close-button path {stroke: #ffffff;} + } &.hideous { background-color: #ff9d00; .callout-text {color: #ffffff;} diff --git a/app/scss/partials/_colors.scss b/app/scss/partials/_colors.scss index e81faafbf..0f21aebc6 100644 --- a/app/scss/partials/_colors.scss +++ b/app/scss/partials/_colors.scss @@ -41,7 +41,8 @@ $button-primary: #3AA2CF; /* MARKETING COLORS */ $red: #E74055; $purple: #720174; -$dark-magenta: #910194; +$dark-purple: #5B005C; +$dark-magenta: #920094; $sinopia: #D3451E; $apple: #67A73A; //button_special $caper: #CEECAF; //success-color diff --git a/app/scss/partials/_detail.scss b/app/scss/partials/_detail.scss index 721799448..37dd7a5c1 100644 --- a/app/scss/partials/_detail.scss +++ b/app/scss/partials/_detail.scss @@ -49,6 +49,9 @@ .no-fill {fill: none;} .stroke {stroke: rgb(74,74,74);} } + + .menu-item { + border-left: 1px solid #cccccc; + } } } diff --git a/app/scss/partials/_rewards.scss b/app/scss/partials/_rewards.scss new file mode 100644 index 000000000..33e67d758 --- /dev/null +++ b/app/scss/partials/_rewards.scss @@ -0,0 +1,267 @@ +/** + * Rewards Panel Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +// Universal Classes +.full-height { + height: 100%; +} +.full-width { + width: 100%; +} +.display-inline-block { + display: inline-block; +} +.send-left-with-padding { + position: absolute; + left: 0; + padding: 5px 13px; +} +.send-right-with-padding { + position: absolute; + right: 0; + padding: 5px 13px; +} +.clickable { + @extend %pointer; +} +.not-clickable { + cursor: not-allowed; +} + +// Rewards Panel Component Classes +.RewardsPanel { + background-color: #ffffff; +} +.RewardsPanel__header { + padding: 10px 0; + background-color: $whisper; +} +.RewardPanel__back svg { + @include transition(fill 0.2s); + fill: #9b9b9b; +} +.RewardPanel__back:hover svg { fill: #4a4a4a; } + +.RewardsPanel__title { + text-transform: uppercase; + font-size: 14px; + color: #9b9b9b; +} +.RewardsPanel__slider_text { + text-transform: uppercase; + padding-right: 7px; +} +.RewardsPanel__scroll_content { + height: 377px; + overflow-y: scroll; +} +.RewardsPanel__info { + margin: 0 40px; + text-align: center; + font-size: 14px; +} +.RewardsPanel__reward_icon { + margin: 70px 0 20px; + fill: #9b9b9b; +} +.RewardsPanel__learn_more { + margin-top: 10px; +} + +// Reward List Item Component Classes +.RewardListItem { + height: 72px; + border: 1px solid #cecece; + border-left-width: 0; + border-radius: 0; + padding: 10px 0 10px 10px; +} +.RewardListItem.RewardListItem--unread + .RewardListItem.RewardListItem--unread, +.RewardListItem:not(.RewardListItem--unread) + .RewardListItem:not(.RewardListItem--unread) { + height: 71px; + border-top: 0; +} +.RewardListItem__image_container { + height: 52px; + width: 100px; + text-align: center; + padding-right: 8px; + border-right: 1px solid #cecece; + margin-right: 18px; +} +.RewardListItem__image { + height: 52px; + width: auto; + background-color: #e0e0e0; +} +.RewardListItem--greyscale .RewardListItem__image { + -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%); +} +.RewardListItem__text { + color: #4a4a4a; + font-size: 13.5px; + font-weight: 600; + max-width: 188px; + margin-bottom: 7px; + line-height: 20px; + max-height: 40px; + text-overflow: ellipsis; + overflow: hidden; + word-wrap: break-word; +} +.RewardListItem--greyscale .RewardListItem__text { + color: #9b9b9b; +} +.RewardListItem__expires { + color: #9b9b9b; + font-size: 11px; + max-width: 188px; +} +.RewardListItem--elongated .RewardListItem__text, +.RewardListItem--elongated .RewardListItem__expires { + max-width: 354px; +} + +.RewardListItem--greyscale .RewardListItem__close_button, +.RewardListItem--greyscale .RewardListItem__details_link { + display: none; +} +.RewardListItem__close_button { + line-height: 0; + padding: 5px 10px; + margin-top: -5px; +} +.RewardListItem__details_link { + line-height: 0; + padding: 5px 10px; + margin-bottom: -5px; +} +.RewardListItem__close_button svg, +.RewardListItem__details_link svg { + @include transition(fill 0.2s); +} +.RewardListItem__close_button svg { fill: #9b9b9b; } +.RewardListItem__close_button:hover svg { fill: #4a4a4a; } + +.RewardListItem__details_link svg { fill: rgba(#4a4a4a, 0); } +.RewardListItem:hover .RewardListItem__details_link svg { fill: rgba(#4a4a4a, 1); } +.RewardListItem__close_button:hover + .RewardListItem__details_link svg { fill: rgba(#4a4a4a, 0); } + +.RewardListItem--unread { + border-color: #5b0059; + border-left-width: 7px; +} +.RewardListItem--unread .RewardListItem__image_container { + border-color: #5b0059; + margin-left: -7px; +} +.RewardListItem--greyscale.RewardListItem--unread, +.RewardListItem--greyscale.RewardListItem--unread .RewardListItem__image_container { + border-color: #cecece; +} + +// Reward Detail Component Classes +.RewardDetail { + height: 377px; +} +.RewardDetail__image_container { + height: 167px; + width: 100%; +} +.RewardDetail__picture { + height: 100%; + width: 100%; + background-color: #efefef; +} +.RewardDetail__logo { + position: absolute; + height: 52px; + width: auto; + margin: 16px 17px; + background-color: #4a4a4a; +} +.RewardDetail__title { + margin: 0 17px; + font-size: 18px; + line-height: 24px; + max-height: 48px; + color: #4a4a4a; + overflow: hidden; + text-overflow: ellipsis; +} +.RewardDetail__description { + margin: 0 17px; + font-size: 13px; + line-height: 18px; + max-height: 36px; + color: #4a4a4a; +} +.RewardDetail__code_container { + margin: 0 17px; + padding: 10px; + background-color: #efefef; + border: 1px solid #cccccc; + border-radius: 3px; +} +.RewardDetail__code { + font-size: 13px; + color: #494949; + font-weight: 600; +} +.RewardDetail__code input { + height: 10px; + width: 10px; + padding: 0; + margin: 0; + display: inline-block; + opacity: 0; + cursor: default; +} +.RewardDetail__copy { + font-size: 10px; + color: #5b005c; + font-weight: 600; + text-transform: uppercase; +} +.RewardDetail__details_container { + margin: 0 17px; +} +.RewardDetail__expires { + font-size: 12px; + font-weight: 600; + color: #4a4a4a; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.RewardDetail__terms { + font-size: 11px; + color: #9b9b9b; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.RewardDetail__redeem_button { + background-color: #5b0059; + color: #ffffff; + font-size: 14px; + font-weight: 600; + text-align: center; + margin: 0 17px 8px; + padding: 10px; + text-transform: uppercase; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} diff --git a/app/scss/partials/_summary_expert.scss b/app/scss/partials/_summary_expert.scss index 09d256683..22c47940a 100644 --- a/app/scss/partials/_summary_expert.scss +++ b/app/scss/partials/_summary_expert.scss @@ -5,7 +5,7 @@ * https://www.ghostery.com/ * * Copyright 2018 Ghostery, Inc. All rights reserved. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 diff --git a/app/scss/partials/_toggle_slider.scss b/app/scss/partials/_toggle_slider.scss new file mode 100644 index 000000000..2701ea0c4 --- /dev/null +++ b/app/scss/partials/_toggle_slider.scss @@ -0,0 +1,55 @@ +/** + * Toggle Slider Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +.ToggleSlider__switch {} +.ToggleSlider__switch { + @extend %pointer; + position: relative; + input { display:none; } +} +.ToggleSlider__slider { + position: relative; + display: block; + height: 14px; + width: 37px; + border-radius: 34px; + background-color: #c1c0c0; + -webkit-transition: .4s; + transition: .4s; +} +.ToggleSlider__slider_circle { + content: ""; + position: absolute; + left: 0px; + top: -3px; + height: 20px; + width: 20px; + border-radius: 50%; + background-color: #f1f1f1; + border: 0.5px solid #979797; + -webkit-transition: .1s; + transition: .1s; +} +.ToggleSlider input:checked + .ToggleSlider__slider { + background-color: #ad80ac; +} +.ToggleSlider input:focus + .ToggleSlider__slider { + box-shadow: 0 0 1px #2196f3; +} +.ToggleSlider input:checked + .ToggleSlider__slider + .ToggleSlider__slider_circle { + background-color: #5b0059; + border-color: #5b0059; + -webkit-transform: translateX(17px); + -ms-transform: translateX(17px); + transform: translateX(17px); +} diff --git a/app/scss/partials/_tooltip.scss b/app/scss/partials/_tooltip.scss index be35680ee..2c46db29e 100644 --- a/app/scss/partials/_tooltip.scss +++ b/app/scss/partials/_tooltip.scss @@ -91,6 +91,13 @@ line-height: 1.5; } } + .dark-theme .tooltip-content { + background-color: #333333; + color: #ffffff; + &:after { + border-color: transparent transparent #333333 #333333; + } + } } .expert .sub-component.cliqz-features .g-tooltip .tooltip-content.right { @@ -131,4 +138,3 @@ margin-left: 0 !important; } } - diff --git a/app/scss/purplebox.scss b/app/scss/purplebox.scss index 91078fcad..fcfc54b13 100644 --- a/app/scss/purplebox.scss +++ b/app/scss/purplebox.scss @@ -5,7 +5,7 @@ * https://www.ghostery.com/ * * Copyright 2018 Ghostery, Inc. All rights reserved. - * + * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0 diff --git a/app/scss/rewards.scss b/app/scss/rewards.scss new file mode 100644 index 000000000..b7de3a330 --- /dev/null +++ b/app/scss/rewards.scss @@ -0,0 +1,450 @@ +/** + * Rewards Content Script Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +//Import Global Partials +@import "settings"; //includes _colors.scss +@import './partials/_tooltip'; + +#ghostery-rewards-app, #ghostery-iframe-container { + z-index: 2147483647 !important; + font-family: $body-font-family!important; + position: fixed; + top: 30px; + right: 30px; +} + +#ghostery-shadow-root { + opacity: 0; + &.resolved { + opacity: 1; + } +} + +#ghostery-iframe-container { + border: none; + &.hot-dog { + height: 59px; + width: 266px; + } + + &.offer-card { + width: 268px; + height: 531px; + } +} + +.ghostery-rewards-component { + * { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .hide { + visibility: hidden; + } + + a { + text-decoration: none; + } + + .flex-grow { + flex: 1; + } + + .hot-dog-container { + border-radius: 25px; + background-color: $dark-purple; + position: relative; + display: flex; + height: 50px; + width: 265px; + opacity: 1; + white-space: nowrap; + background-repeat: no-repeat; + background-position: left 22px center; + border-bottom: 0px; + cursor: pointer; + } + + .ghostery-reward-text { + line-height: 1.3; + color: $white; + font-size: 12px; + margin-top: 16px; + margin-left: 58px; + transition: visibility 1s; + } + + .hot-dog-close { + width: 21px; + height: 21px; + border: 1px solid $dark-purple; + background-color: $white; + position: absolute; + border-radius: 50%; + right: 6px; + top: -9px; + background-repeat: no-repeat; + background-position: center center; + background-size: 9px; + cursor: pointer; + } + + &.ghostery-right { + right: 30px; + } + + &:hover, &.show { + .ghostery-reward-close { + transition: visibility 1000ms; + visibility: visible; + } + .ghostery-reward-text { + transition: visibility 0s; + visibility: visible; + } + } + + .ghostery-reward-card { + color: $tundora; + background-color: #f2f2f2; + width: 265px; + height: 520px; + border: 1px solid #9b9b9b; + border-radius: 4px; + + .reward-card-header { + display: flex; + height: 36px; + width: 100%; + + .reward-card-close { + cursor: pointer; + align-self: center; + margin-right: 8px; + margin-left: auto; + height: 10px; + width: 10px; + background-repeat: no-repeat; + } + + .rewards-logo-beta { + flex: 1; + height: 13px; + margin-left: 8px; + align-self: center; + background-repeat: no-repeat; + background-size: auto 13px; + } + } + + .reward-content-img { + height: 120px; + // margin-top: 8px; + width: 247px; + display: flex; + align-items: center; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + } + + .reward-content { + display: flex; + flex-wrap: wrap; + flex-direction: column; + background-color: $white; + align-items: center; + margin-left: auto; + margin-right: auto; + width: 247px; + height: 450px; + border: 1px solid $dark-purple; + border-radius: 4px; + + .reward-content-header { + display: flex; + flex-direction: row; + height: 48px; + width: 100%; + justify-content: flex-end; + align-items: center; + + .reward-company-logo { + height: 38px; + display: flex; + align-items: center; + + img { + height: 100%; + object-fit: contain; + } + } + + .reward-settings-kebab { + cursor: pointer; + width: 20px; + height: 37px; + background-repeat: no-repeat; + background-position: 6px; + } + + } + + .reward-content-detail { + display: flex; + flex-direction: column; + width: 195px; + height: 131px; + color: $tundora; + + .reward-benefit { + margin-top: 10px; + margin-bottom: 12px; + font-size: 34px; + line-height: 26px; + white-space: nowrap; + // text-overflow: ellipsis; + overflow: hidden; + width: 100%; + min-height: 26px; + } + + .reward-headline { + font-size: 16px; + line-height: 16px; + } + + .reward-description { + margin-top: 14px; + font-size: 13px; + line-height: 15px; + } + + } + + .reward-code { + display: flex; + width: 225px; + height: 32px; + border: 1px solid #dadada; + border-radius: 5px; + background-color: #f2f2f2; + + div { + flex: 1; + user-select: text; + margin-left: 8px; + align-self: center; + font-size: 13px; + font-weight: 500; + border: 0px; + background-color: transparent; + .reward-code-input { + opacity: 0; + position: absolute; + z-index: -1; + } + } + + a { + text-align: right; + align-self: center; + margin-right: 8px; + margin-left: auto; + color: $dark-purple; + font-size: 10px; + font-weight: $global-weight-bold; + cursor: pointer; + text-transform: uppercase; + } + + } + + .reward-content-footer { + display: flex; + width: 225px; + height: 46px; + font-size: 11px; + span { + margin-top: 15px; + flex: 1; + } + .reward-terms { + margin-top: 15px; + color: $tundora; + } + } + + .reward-redeem { + color: $white; + background-color: $dark-purple; + width: 100%; + height: 41px; + text-align: center; + line-height: 41px; + font-size: 14px; + border: 0px; + cursor: pointer; + } + } + + .reward-footer { + display: flex; + height: 32px; + + .reward-feedback { + flex: 2; + align-self: center; + margin-left: 8px; + + .rewards-smiley { + + } + + .rewards-dd-arrow { + + } + + a { + cursor: pointer; + font-size: 12px; + color: #6A7E90; + } + } + + .reward-ghosty { + flex: 1; + background-repeat: no-repeat; + background-position: center right; + height: 100%; + align-self: flex-end; + margin-right: 8px; + } + } + } + + .rewards-popup-container { + width: 100%; + height: 100%; + position: absolute; + text-align: center; + color: $white; + } + + .rewards-settings-container { + .rewards-settings { + color: $tundora; + position: absolute; + margin-left: -195px; + margin-top: -10px; + height: 94px; + width: 175px; + background-color: $white; + border: 1px solid #9b9b9b; + border-radius: 4px; + box-shadow: 0px 4px 10px grey; + display: flex; + flex-direction: column; + text-align: left; + + a { + color: $tundora; + } + + .about, .settings, .delete, .disable { + font-size: 12px; + flex: 1; + display: flex; + align-items: center; + margin-left: 10px; + margin-right: 10px; + font-weight: 500; + cursor: pointer; + user-select: none; + } + + .about { + align-items: flex-end; + } + + .delete { + align-items: flex-start; + } + } + } + + .rewards-notification-container { + .rewards-notification { + margin-top: 17px; + margin-left: auto; + margin-right: auto; + height: 161px; + width: 208px; + background-color: $dark-purple; + border: 2px solid transparent; + border-radius: 4px; + display: flex; + flex-direction: column; + font-size: 13px; + line-height: 2; + + &.first-prompt { + background-color: $white; + border: 2px solid $dark-purple; + color: black; + } + + .notification-text { + margin: 0px 22px 0px 22px; + } + + a { + text-decoration: underline; + cursor: pointer; + } + + .close { + cursor: pointer; + background-repeat: no-repeat; + height: 10px; + width: 10px; + align-self: flex-end; + margin: 10px 10px 0px 0px; + } + + .notification-buttons { + display: flex; + justify-content: center; + flex-direction: row; + div { + &:nth-child(1) { + margin-right:10px; + } + &:nth-child(2) { + margin-left:10px; + } + } + } + } + } + +} + +#ghostery-rewards-app.iframe-child { + top: 9px; + right: 1px; +} diff --git a/app/scss/setup.scss b/app/scss/setup.scss index 9b79e32b4..68bdf3155 100644 --- a/app/scss/setup.scss +++ b/app/scss/setup.scss @@ -27,6 +27,7 @@ @import './partials/_mixins'; @import './partials/_blocking'; @import './partials/_settings'; +@import './partials/_toggle_slider'; @import './partials/_callout'; //Foundation Overrides @@ -285,6 +286,10 @@ body { font-size: 1rem; } } + .AdditionalFeatures--add-padding { + padding-left: 10px; + padding-right: 30px; + } } #display-view { .box-link { @@ -445,53 +450,6 @@ body { } } -.switch { - @extend %pointer; - margin-right: 52px; - align-self: flex-end; - position: relative; - width: 37px; - input { - display:none; - } - .slider { - border-radius: 34px; - height: 14px; - width: 37px; - position: relative; - display: block; - background-color: #C1C0C0; - -webkit-transition: .4s; - transition: .4s; - } - .slider-circle { - content: ""; - border-radius: 50%; - position: absolute; - height: 20px; - width: 20px; - left: 0px; - top: -3px; - background-color: #F1F1F1; - border: 0.5px solid #979797; - -webkit-transition: .1s; - transition: .1s; - } - input:checked + .slider { - opacity: .5; - background-color: #930194; - } - input:focus + .slider { - box-shadow: 0 0 1px #2196F3; - } - input:checked + .slider + .slider-circle { - background-color: #930194; - border-color: #930194; - -webkit-transform: translateX(17px); - -ms-transform: translateX(17px); - transform: translateX(17px); - } -} %nowrap { white-space: nowrap; overflow-x: hidden; diff --git a/app/setup/components/Views/AdditionalFeaturesView.jsx b/app/setup/components/Views/AdditionalFeaturesView.jsx index 913f06e40..c6c21ecbe 100644 --- a/app/setup/components/Views/AdditionalFeaturesView.jsx +++ b/app/setup/components/Views/AdditionalFeaturesView.jsx @@ -13,6 +13,7 @@ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; import globals from '../../../../src/classes/Globals'; +import { ToggleSlider } from '../../../panel/components/BuildingBlocks'; const { IS_CLIQZ } = globals; @@ -108,11 +109,12 @@ class AdditionalFeaturesView extends Component {
- +

{ t('setup_additional_view_antitrack_title') }

@@ -127,11 +129,12 @@ class AdditionalFeaturesView extends Component {
- +

{ t('setup_additional_view_adblock_title') }

@@ -146,11 +149,12 @@ class AdditionalFeaturesView extends Component {
- +

{ t('setup_additional_view_smartblock_title') }

diff --git a/manifest.json b/manifest.json index 3fad99c4f..0ebc944fe 100644 --- a/manifest.json +++ b/manifest.json @@ -91,7 +91,8 @@ "minimum_chrome_version": "49", "minimum_opera_version": "36", "web_accessible_resources": [ - "app/images/*" + "app/images/*", + "dist/css/rewards_styles.css" ], "browser_specific_settings": { "edge": { diff --git a/package-lock.json b/package-lock.json index e389822f2..1b050e329 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,7 @@ "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "esutils": "2.0.2", "js-tokens": "3.0.2" }, @@ -84,9 +84,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -227,7 +227,7 @@ "resolved": "https://registry.npmjs.org/@cliqz-oss/systemjs-builder/-/systemjs-builder-0.16.13.tgz", "integrity": "sha512-k3fYWo2O0dQi1+cpSowWy0LxWULlW2sZs/+wM15USnDuVAMFwGP1HNHzPdrcGaWYrmNcJjmquiq6dFn0hzTx6g==", "requires": { - "babel-core": "6.26.3", + "babel-core": "6.26.0", "babel-plugin-syntax-dynamic-import": "6.18.0", "babel-plugin-transform-amd-system-wrapper": "0.3.7", "babel-plugin-transform-cjs-system-wrapper": "0.6.2", @@ -264,16 +264,6 @@ } } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "dev": true, - "requires": { - "call-me-maybe": "1.0.1", - "glob-to-regexp": "0.3.0" - } - }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -290,9 +280,9 @@ } }, "@types/node": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.0.3.tgz", - "integrity": "sha512-J7nx6JzxmtT4zyvfLipYL7jNaxvlCWpyG7JhhCQ4fQyG+AGfovAHoYR55TFx+X8akfkUJYpt5JG6GPeFMjZaCQ==", + "version": "9.6.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.6.tgz", + "integrity": "sha512-SJe0g5cZeGNDP5sD8mIX3scb+eq8LQQZ60FXiKZHipYSeEFZ5EKml+NNMiO76F74TY4PoMWlNxF/YRY40FOvZQ==", "dev": true }, "BigInt": { @@ -1050,7 +1040,7 @@ "dev": true, "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000833", + "caniuse-db": "1.0.30000830", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "5.2.18", @@ -1063,8 +1053,8 @@ "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "dev": true, "requires": { - "caniuse-db": "1.0.30000833", - "electron-to-chromium": "1.3.45" + "caniuse-db": "1.0.30000830", + "electron-to-chromium": "1.3.44" } } } @@ -1099,9 +1089,9 @@ } }, "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", "requires": { "babel-code-frame": "6.26.0", "babel-generator": "6.26.1", @@ -1875,7 +1865,7 @@ "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "requires": { "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.10.5" }, "dependencies": { @@ -1887,9 +1877,9 @@ } }, "babel-preset-env": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", - "integrity": "sha512-W6VIyA6Ch9ePMI7VptNn2wBM6dbG0eSz25HEiL40nQXCsXGTGZSTZu1Iap+cj3Q0S5a7T9+529l/5Bkvd+afNA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", "requires": { "babel-plugin-check-es2015-constants": "6.22.0", "babel-plugin-syntax-trailing-function-commas": "6.22.0", @@ -1918,7 +1908,7 @@ "babel-plugin-transform-es2015-unicode-regex": "6.24.1", "babel-plugin-transform-exponentiation-operator": "6.24.1", "babel-plugin-transform-regenerator": "6.26.0", - "browserslist": "2.11.3", + "browserslist": "3.2.7", "invariant": "2.2.4", "semver": "5.3.0" } @@ -2029,9 +2019,9 @@ "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "requires": { - "babel-core": "6.26.3", + "babel-core": "6.26.0", "babel-runtime": "6.26.0", - "core-js": "2.5.5", + "core-js": "2.5.6", "home-or-tmp": "2.0.0", "lodash": "4.17.10", "mkdirp": "0.5.1", @@ -2043,7 +2033,7 @@ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { - "core-js": "2.5.5", + "core-js": "2.5.6", "regenerator-runtime": "0.11.1" } }, @@ -2301,7 +2291,7 @@ "resolved": "https://registry.npmjs.org/broccoli-babel-transpiler/-/broccoli-babel-transpiler-6.1.4.tgz", "integrity": "sha512-h63g7iOBWdxj0GuZw8kNsyaD1T9weKsY3I+gp3rOefozbHwUesJ43vzLy0jj3t/rbiP2czcJAlyHS48EcRil8Q==", "requires": { - "babel-core": "6.26.3", + "babel-core": "6.26.0", "broccoli-funnel": "1.2.0", "broccoli-merge-trees": "1.2.4", "broccoli-persistent-filter": "1.4.3", @@ -2428,8 +2418,8 @@ "dev": true }, "browser-core": { - "version": "https://s3.amazonaws.com/cdncliqz/update/edge/ghostery/v7.26/1.26.2.tgz?version=1", - "integrity": "sha512-zsFtGw/1Rnh3qyK4Ca2UNWvbOEYZZcQnssO0DDW17Tr0jXV12pBMjOkk71yKavGF1+l1tVPiro7mPmMjooZbeQ==", + "version": "https://s3.amazonaws.com/cdncliqz/update/edge/ghostery/v7.26/1.26.3.tgz?v=1", + "integrity": "sha512-g8RY6Dsgjt4OIOSBXqjLMlzs95msjDzhF6CuMrUN3iut+mIN594qnJQfVvnOA0/YGNzYXuhlviS46IgTeC4Zgw==", "requires": { "@cliqz-oss/dexie": "2.0.3", "@cliqz-oss/pouchdb": "6.3.4-security-error-fix-2", @@ -2443,7 +2433,6 @@ "cron-parser": "2.4.0", "deep-equal": "1.0.1", "ember-inflector": "2.0.1", - "es6-micro-loader": "github:cliqz-oss/es6-micro-loader#145e7bdbd8be2f4582096175482b1411eb56ec79", "fast-url-parser": "1.1.2", "fs-extra": "3.0.1", "glob": "7.1.2", @@ -2487,9 +2476,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "3.0.1", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" } }, "jsonfile": { @@ -2497,7 +2486,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "react": { @@ -2505,10 +2494,10 @@ "resolved": "https://registry.npmjs.org/react/-/react-16.0.0.tgz", "integrity": "sha1-zn348ZQbA28Cssyp29DLHw6FXi0=", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.1" + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" }, "dependencies": { "prop-types": { @@ -2516,9 +2505,9 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" + "fbjs": "^0.8.16", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" } } } @@ -2528,10 +2517,10 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.0.0.tgz", "integrity": "sha1-nMMHnD3NcNTG4BuEqrKn40wwP1g=", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "prop-types": "15.6.1" + "fbjs": "^0.8.16", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.0" }, "dependencies": { "prop-types": { @@ -2539,9 +2528,9 @@ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz", "integrity": "sha512-4ec7bY1Y66LymSUOH/zARVYObB23AT2h8cf6e/O6ZALB/N0sqZFEx7rq6EYPX2MkOdKORuooI/H5k9TlR4q7kQ==", "requires": { - "fbjs": "0.8.16", - "loose-envify": "1.3.1", - "object-assign": "4.1.1" + "fbjs": "^0.8.16", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" } } } @@ -2647,12 +2636,19 @@ } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.7.tgz", + "integrity": "sha512-oYVLxFVqpX9uMhOIQBLtZL+CX4uY8ZpWcjNTaxyWl5rO8yA9SSNikFnAfvk8J3P/7z3BZwNmEqFKaJoYltj3MQ==", "requires": { - "caniuse-lite": "1.0.30000833", + "caniuse-lite": "1.0.30000839", "electron-to-chromium": "1.3.45" + }, + "dependencies": { + "electron-to-chromium": { + "version": "1.3.45", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", + "integrity": "sha1-RYrBscXHYM6IEaFtK/vZfsMLr7g=" + } } }, "bser": { @@ -2736,7 +2732,7 @@ "chownr": "1.0.1", "glob": "7.1.2", "graceful-fs": "4.1.11", - "lru-cache": "4.1.2", + "lru-cache": "4.1.3", "mississippi": "2.0.0", "mkdirp": "0.5.1", "move-concurrently": "1.0.1", @@ -2837,12 +2833,6 @@ "resolved": "https://registry.npmjs.org/cached-constructors-x/-/cached-constructors-x-1.0.2.tgz", "integrity": "sha512-7lKwmwXweW6E/31RHAJemLtZPfb2xvcABXknFF4b/dNYv4DbSGTgQHckXLQkNw6BB4HKFYW6mJgsNjADAy1ehw==" }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -2906,7 +2896,7 @@ "dev": true, "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000833", + "caniuse-db": "1.0.30000830", "lodash.memoize": "4.1.2", "lodash.uniq": "4.5.0" }, @@ -2917,22 +2907,22 @@ "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "dev": true, "requires": { - "caniuse-db": "1.0.30000833", - "electron-to-chromium": "1.3.45" + "caniuse-db": "1.0.30000830", + "electron-to-chromium": "1.3.44" } } } }, "caniuse-db": { - "version": "1.0.30000833", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000833.tgz", - "integrity": "sha1-K9e+cqQBZY0svLj012AN7r6xxnY=", + "version": "1.0.30000830", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000830.tgz", + "integrity": "sha1-bkUlWzRWSf0V/1kHLaHhK7PeLxM=", "dev": true }, "caniuse-lite": { - "version": "1.0.30000833", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000833.tgz", - "integrity": "sha512-tKNuKu4WLImh4NxoTgntxFpDrRiA0Q6Q1NycNhuMST0Kx+Pt8YnRDW6V8xsyH6AtO2CpAoibatEk5eaEhP3O1g==" + "version": "1.0.30000839", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000839.tgz", + "integrity": "sha512-gJZIfmkuy84agOeAZc7WJOexZhisZaBSFk96gkGM6TkH7+1mBfr/MSPnXC8lO0g7guh/ucbswYjruvDbzc6i0g==" }, "caseless": { "version": "0.12.0", @@ -2998,7 +2988,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", - "fsevents": "1.2.3", + "fsevents": "1.2.2", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -3414,7 +3404,6 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -3422,8 +3411,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "0.3.0", @@ -3583,9 +3571,9 @@ "dev": true }, "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", + "integrity": "sha512-lQUVfQi0aLix2xpyjrrJEvfuYCqPc/HwmTKsC/VNf8q0zsjX7SQZtp4+oRONN5Tsur9GDETPjj+Ub2iDiGZfSQ==" }, "core-util-is": { "version": "1.0.2", @@ -3657,9 +3645,9 @@ } }, "cross-env": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.1.4.tgz", - "integrity": "sha512-Mx8mw6JWhfpYoEk7PGvHxJMLQwQHORAs8+2bX+C1lGQ4h3GkDb1zbzC2Nw85YH9ZQMlO0BHZxMacgrfPmMFxbg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.1.5.tgz", + "integrity": "sha512-GSiNTbvTU3pXzewRKGP0Y+rVP2CzifY2pqSEdtHzLLj41pRdkrgY7e4uSnBoR/pmYaqZr/lwwjg/Q4kNX30hWQ==", "dev": true, "requires": { "cross-spawn": "5.1.0", @@ -3672,7 +3660,7 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "4.1.2", + "lru-cache": "4.1.3", "shebang-command": "1.2.0", "which": "1.3.0" } @@ -3975,7 +3963,7 @@ "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", "requires": { "commander": "2.9.0", - "iconv-lite": "0.4.21", + "iconv-lite": "0.4.23", "rw": "1.3.3" } }, @@ -4408,6 +4396,13 @@ "repeating": "2.0.1" } }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "optional": true + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -4441,27 +4436,6 @@ "randombytes": "2.0.6" } }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "dev": true, - "requires": { - "arrify": "1.0.1", - "path-type": "3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "3.0.0" - } - } - } - }, "discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -4522,9 +4496,9 @@ } }, "domhandler": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", - "integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { "domelementtype": "1.3.0" @@ -4600,13 +4574,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } } } @@ -4632,9 +4606,10 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.45", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", - "integrity": "sha1-RYrBscXHYM6IEaFtK/vZfsMLr7g=" + "version": "1.3.44", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.44.tgz", + "integrity": "sha1-72sVCmDVIwgjiMra2ICF7NL9RoQ=", + "dev": true }, "elegant-spinner": { "version": "1.0.1", @@ -4667,7 +4642,7 @@ "babel-plugin-ember-modules-api-polyfill": "2.3.0", "babel-plugin-transform-es2015-modules-amd": "6.24.1", "babel-polyfill": "6.26.0", - "babel-preset-env": "1.6.1", + "babel-preset-env": "1.7.0", "broccoli-babel-transpiler": "6.1.4", "broccoli-debug": "0.6.4", "broccoli-funnel": "1.2.0", @@ -4723,7 +4698,7 @@ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.21" + "iconv-lite": "0.4.23" } }, "end-of-stream": { @@ -4917,17 +4892,6 @@ "es6-symbol": "3.1.1" } }, - "es6-micro-loader": { - "version": "github:cliqz-oss/es6-micro-loader#145e7bdbd8be2f4582096175482b1411eb56ec79", - "requires": { - "es6-promise": "1.0.0" - } - }, - "es6-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-1.0.0.tgz", - "integrity": "sha1-+Q02KfqnwmFmrk33fIm6zeuNyn8=" - }, "es6-symbol": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", @@ -4997,7 +4961,7 @@ "requires": { "ajv": "5.5.2", "babel-code-frame": "6.26.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "concat-stream": "1.6.2", "cross-spawn": "5.1.0", "debug": "3.1.0", @@ -5060,9 +5024,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -5474,13 +5438,13 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "2.2.4" } }, "expand-template": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", - "integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", + "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==" }, "expand-tilde": { "version": "2.0.2", @@ -5549,7 +5513,7 @@ "dev": true, "requires": { "chardet": "0.4.2", - "iconv-lite": "0.4.21", + "iconv-lite": "0.4.23", "tmp": "0.0.33" }, "dependencies": { @@ -5588,1246 +5552,434 @@ "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz", "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo=" }, - "fast-glob": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.1.tgz", - "integrity": "sha512-wSyW1TBK3ia5V+te0rGPXudeMHoUQW6O5Y9oATiaGhpENmEifPDlOdhpsnlj5HoG6ttIvGiY1DdCmI9X2xGMhg==", + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fast-ordered-set": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-ordered-set/-/fast-ordered-set-1.0.3.tgz", + "integrity": "sha1-P7s2Y097555PftvbSjV97iXRhOs=", + "requires": { + "blank-object": "1.0.2" + } + }, + "fast-url-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.2.tgz", + "integrity": "sha1-SzJGDsOmpw6NSR+ogBXeUzjQHEA=", + "requires": { + "punycode": "1.4.1" + } + }, + "fastparse": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", "dev": true, "requires": { - "@mrmlnc/readdir-enhanced": "2.2.1", - "glob-parent": "3.1.0", - "is-glob": "4.0.0", - "merge2": "1.2.2", - "micromatch": "3.1.10" + "bser": "2.0.0" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "fbjs": { + "version": "0.8.16", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.18" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.3.0", + "object-assign": "4.1.1" + } + }, + "file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "requires": { + "loader-utils": "1.1.0", + "schema-utils": "0.4.5" + }, + "dependencies": { + "ajv": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.0.tgz", + "integrity": "sha512-VDUX1oSajablmiyFyED9L1DFndg0P9h7p1F+NO8FkIzei6EPrR6Zu1n18rd5P8PqaSRd/FrWv3G1TVBqpM83gA==", + "dev": true, + "requires": { + "fast-deep-equal": "2.0.1", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1", + "uri-js": "4.2.1" + } + }, + "ajv-keywords": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "schema-utils": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", + "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", "dev": true, "requires": { - "arr-flatten": "1.1.0", - "array-unique": "0.3.2", - "extend-shallow": "2.0.1", - "fill-range": "4.0.0", - "isobject": "3.0.1", - "repeat-element": "1.1.2", - "snapdragon": "0.8.2", - "snapdragon-node": "2.1.1", - "split-string": "3.1.0", - "to-regex": "3.0.2" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } + "ajv": "6.5.0", + "ajv-keywords": "3.2.0" } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + } + } + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fileset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", + "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", + "dev": true, + "requires": { + "glob": "7.1.2", + "minimatch": "3.0.4" + } + }, + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.0.0", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, "requires": { - "debug": "2.6.4", - "define-property": "0.2.5", - "extend-shallow": "2.0.1", - "posix-character-classes": "0.1.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "0.1.6" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "0.3.2", - "define-property": "1.0.0", - "expand-brackets": "2.1.4", - "extend-shallow": "2.0.1", - "fragment-cache": "0.2.1", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "1.0.2" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "2.0.1", - "is-number": "3.0.0", - "repeat-string": "1.6.1", - "to-regex-range": "2.1.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "0.1.1" - } - } - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "3.1.0", - "path-dirname": "1.0.2" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "6.0.2" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "1.0.0", - "is-data-descriptor": "1.0.0", - "kind-of": "6.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "4.0.0", - "array-unique": "0.3.2", - "braces": "2.3.2", - "define-property": "2.0.2", - "extend-shallow": "3.0.2", - "extglob": "2.0.4", - "fragment-cache": "0.2.1", - "kind-of": "6.0.2", - "nanomatch": "1.2.9", - "object.pick": "1.3.0", - "regex-not": "1.0.2", - "snapdragon": "0.8.2", - "to-regex": "3.0.2" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fast-ordered-set": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/fast-ordered-set/-/fast-ordered-set-1.0.3.tgz", - "integrity": "sha1-P7s2Y097555PftvbSjV97iXRhOs=", - "requires": { - "blank-object": "1.0.2" - } - }, - "fast-url-parser": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.2.tgz", - "integrity": "sha1-SzJGDsOmpw6NSR+ogBXeUzjQHEA=", - "requires": { - "punycode": "1.4.1" - } - }, - "fastparse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", - "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, - "requires": { - "bser": "2.0.0" - } - }, - "fbjs": { - "version": "0.8.16", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", - "integrity": "sha1-XmdDL1UNxBtXK/VYR7ispk5TN9s=", - "requires": { - "core-js": "1.2.7", - "isomorphic-fetch": "2.2.1", - "loose-envify": "1.3.1", - "object-assign": "4.1.1", - "promise": "7.3.1", - "setimmediate": "1.0.5", - "ua-parser-js": "0.7.18" - }, - "dependencies": { - "core-js": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" - } - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" - } - }, - "file-loader": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", - "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", - "dev": true, - "requires": { - "loader-utils": "1.1.0", - "schema-utils": "0.4.5" - }, - "dependencies": { - "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", - "dev": true, - "requires": { - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" - } - }, - "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", - "dev": true - }, - "schema-utils": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", - "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", - "dev": true, - "requires": { - "ajv": "6.4.0", - "ajv-keywords": "3.2.0" - } - } - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "7.1.2", - "minimatch": "3.0.4" - } - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "find-cache-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", - "dev": true, - "requires": { - "commondir": "1.0.1", - "make-dir": "1.2.0", - "pkg-dir": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "2.0.0" - } - }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "dev": true, - "requires": { - "readable-stream": "2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - } - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, - "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" - } - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", - "dev": true - }, - "flow-parser": { - "version": "0.71.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.71.0.tgz", - "integrity": "sha512-rXSvqSBLf8aRI6T3P99jMcUYvZoO1KZcKDkzGJmXvYdNAgRKu7sfGNtxEsn3cX4TgungBuJpX+K8aHRC9/B5MA==", - "dev": true - }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "foundation-sites": { - "version": "6.4.4-rc1", - "resolved": "https://registry.npmjs.org/foundation-sites/-/foundation-sites-6.4.4-rc1.tgz", - "integrity": "sha512-26cL66QFNqMVwM7bmIEqq4jiW+6CkIeW719ci1pchdJ4UK0Om+3Jl7MhkX/lzdzRHB75f2m1IK9lxk3JGOwApA==", - "requires": { - "jquery": "3.0.0", - "what-input": "4.3.1" - } - }, - "fraction.js": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.2.tgz", - "integrity": "sha512-OswcigOSil3vYXgrPSx4NCaSyPikXqVNYN/4CyhS0ucVOJ4GVYr6KQQLLcAudvS/4bBOzxqJ3XIsFaaMjl98ZQ==" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" - } - } - } - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "fs-tree-diff": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.5.7.tgz", - "integrity": "sha512-dJwDX6NBH7IfdfFjZAdHCZ6fIKc8LwR7kzqUhYRFJuX4g9ctG/7cuqJuwegGQsyLEykp6Z4krq+yIFMQlt7d9Q==", - "requires": { - "heimdalljs-logger": "0.1.9", - "object-assign": "4.1.1", - "path-posix": "1.0.0", - "symlink-or-copy": "1.2.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "iferr": "0.1.5", - "imurmurhash": "0.1.4", - "readable-stream": "1.0.33" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.3.tgz", - "integrity": "sha512-X+57O5YkDTiEQGiw8i7wYc2nQgweIekqkepI8Q3y4wVlurgBt2SuwxTeYUYMZIGpLZH3r/TsMjczCMXE5ZOt7Q==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.10.0", - "node-pre-gyp": "0.9.1" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": "2.1.2" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "2.2.4" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.21", - "sax": "1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.9.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.0", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.6", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.1" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" + "isarray": "1.0.0" } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, + } + } + }, + "find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "requires": { + "commondir": "1.0.1", + "make-dir": "1.3.0", + "pkg-dir": "2.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.6", - "bundled": true, + } + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=", + "dev": true + }, + "flow-parser": { + "version": "0.70.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.70.0.tgz", + "integrity": "sha512-gGdyVUZWswG5jcINrVDHd3RY4nJptBTAx9mR9thGsrGGmAUR7omgJXQSpR+fXrLtxSTAea3HpAZNU/yzRJc2Cg==", + "dev": true + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } - }, + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", + "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.18" + } + }, + "foundation-sites": { + "version": "6.4.4-rc1", + "resolved": "https://registry.npmjs.org/foundation-sites/-/foundation-sites-6.4.4-rc1.tgz", + "integrity": "sha512-26cL66QFNqMVwM7bmIEqq4jiW+6CkIeW719ci1pchdJ4UK0Om+3Jl7MhkX/lzdzRHB75f2m1IK9lxk3JGOwApA==", + "requires": { + "jquery": "3.0.0", + "what-input": "4.3.1" + } + }, + "fraction.js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.2.tgz", + "integrity": "sha512-OswcigOSil3vYXgrPSx4NCaSyPikXqVNYN/4CyhS0ucVOJ4GVYr6KQQLLcAudvS/4bBOzxqJ3XIsFaaMjl98ZQ==" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.6" + }, + "dependencies": { "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, - "optional": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", "isarray": "1.0.0", "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "string_decoder": "1.1.1", "util-deprecate": "1.0.2" } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.2.4", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.1", - "yallist": "3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, + } + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" + } + }, + "fs-minipass": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "optional": true, + "requires": { + "minipass": "2.3.0" + } + }, + "fs-tree-diff": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/fs-tree-diff/-/fs-tree-diff-0.5.7.tgz", + "integrity": "sha512-dJwDX6NBH7IfdfFjZAdHCZ6fIKc8LwR7kzqUhYRFJuX4g9ctG/7cuqJuwegGQsyLEykp6Z4krq+yIFMQlt7d9Q==", + "requires": { + "heimdalljs-logger": "0.1.9", + "object-assign": "4.1.1", + "path-posix": "1.0.0", + "symlink-or-copy": "1.2.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "iferr": "0.1.5", + "imurmurhash": "0.1.4", + "readable-stream": "1.0.33" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.2.tgz", + "integrity": "sha512-iownA+hC4uHFp+7gwP/y5SzaiUo7m2vpa0dhpzw8YuKtiZsz7cIXsFbXpLEeBM6WuCQyw1MH4RRe6XI8GFUctQ==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.10.0", + "node-pre-gyp": "0.9.1" + }, + "dependencies": { + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", "dev": true, "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true, - "dev": true } } }, @@ -7101,12 +6253,6 @@ "is-glob": "2.0.1" } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "dev": true - }, "global": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", @@ -7179,9 +6325,9 @@ } }, "got": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.1.tgz", - "integrity": "sha512-tiLX+bnYm5A56T5N/n9Xo89vMaO1mrS9qoDqj3u/anVooqGozvY/HbXzEpDfbNeKsHCBpK40gSbz8wGYSp3i1w==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.0.tgz", + "integrity": "sha512-kBNy/S2CGwrYgDSec5KTWGKUvupwkkTVAjIsVFF2shXO13xpZdFP4d4kxa//CLX2tN/rV0aYwK8vY6UKWGn2vQ==", "dev": true, "requires": { "@sindresorhus/is": "0.7.0", @@ -7305,8 +6451,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-own-property-x": { "version": "3.2.0", @@ -7568,7 +6713,7 @@ "he": "1.1.1", "param-case": "2.1.1", "relateurl": "0.2.7", - "uglify-js": "3.3.23" + "uglify-js": "3.3.22" }, "dependencies": { "commander": { @@ -7584,9 +6729,9 @@ "dev": true }, "uglify-js": { - "version": "3.3.23", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.23.tgz", - "integrity": "sha512-Ks+KqLGDsYn4z+pU7JsKCzC0T3mPYl+rU+VcPZiQOazjE4Uqi4UCRY3qPMDbJi7ze37n1lDXj3biz1ik93vqvw==", + "version": "3.3.22", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.22.tgz", + "integrity": "sha512-tqw96rL6/BG+7LM5VItdhDjTQmL5zG/I0b2RqWytlgeHe2eydZHuBHdA9vuGpCDhH/ZskNGcqDhivoR2xt8RIw==", "dev": true, "requires": { "commander": "2.15.1", @@ -7602,7 +6747,7 @@ "dev": true, "requires": { "domelementtype": "1.3.0", - "domhandler": "2.4.1", + "domhandler": "2.4.2", "domutils": "1.5.1", "entities": "1.1.1", "inherits": "2.0.3", @@ -7681,9 +6826,9 @@ "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" }, "iconv-lite": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { "safer-buffer": "2.1.2" } @@ -7707,16 +6852,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "1.9.1" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -7732,6 +6875,19 @@ "chalk": "2.4.1", "source-map": "0.6.1", "supports-color": "5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + } } }, "source-map": { @@ -7744,7 +6900,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -7769,6 +6924,16 @@ "integrity": "sha512-pUh+xUQQhQzevjRHHFqqcTy0/dP/kS9I8HSrUydhihjuD09W6ldVWFtIrwhXdUJHis3i2rZNqEHpZH/cbinFbg==", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -7857,7 +7022,7 @@ "dev": true, "requires": { "ansi-escapes": "3.1.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "cli-cursor": "2.1.0", "cli-width": "2.2.0", "external-editor": "2.2.0", @@ -7888,9 +7053,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -8427,12 +7592,6 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, - "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8665,9 +7824,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -8693,7 +7852,7 @@ "dev": true, "requires": { "ansi-escapes": "3.1.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "exit": "0.1.2", "glob": "7.1.2", "graceful-fs": "4.1.11", @@ -8783,7 +7942,7 @@ "integrity": "sha512-KSg3EOToCgkX+lIvenKY7J8s426h6ahXxaUFJxvGoEk0562Z6inWj1TnKoGycTASwiLD+6kSYFALcjdosq9KIQ==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "glob": "7.1.2", "jest-environment-jsdom": "22.4.3", "jest-environment-node": "22.4.3", @@ -8806,9 +7965,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -8833,7 +7992,7 @@ "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "diff": "3.5.0", "jest-get-type": "22.4.3", "pretty-format": "22.4.3" @@ -8849,9 +8008,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -8887,7 +8046,7 @@ "requires": { "jest-mock": "22.4.3", "jest-util": "22.4.3", - "jsdom": "11.10.0" + "jsdom": "11.9.0" } }, "jest-environment-node": { @@ -8927,7 +8086,7 @@ "integrity": "sha512-yZCPCJUcEY6R5KJB/VReo1AYI2b+5Ky+C+JA1v34jndJsRcLpU4IZX4rFJn7yDTtdNbO/nNqg+3SDIPNH2ecnw==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "co": "4.6.0", "expect": "22.4.3", "graceful-fs": "4.1.11", @@ -8956,9 +8115,9 @@ "dev": true }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9008,7 +8167,7 @@ "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "jest-get-type": "22.4.3", "pretty-format": "22.4.3" }, @@ -9023,9 +8182,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9051,7 +8210,7 @@ "dev": true, "requires": { "@babel/code-frame": "7.0.0-beta.44", - "chalk": "2.4.1", + "chalk": "2.4.0", "micromatch": "2.3.11", "slash": "1.0.0", "stack-utils": "1.0.1" @@ -9067,9 +8226,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9107,7 +8266,7 @@ "dev": true, "requires": { "browser-resolve": "1.11.2", - "chalk": "2.4.1" + "chalk": "2.4.0" }, "dependencies": { "ansi-styles": { @@ -9120,9 +8279,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9175,10 +8334,10 @@ "integrity": "sha512-Eat/esQjevhx9BgJEC8udye+FfoJ2qvxAZfOAWshYGS22HydHn5BgsvPdTtt9cp0fSl5LxYOFA1Pja9Iz2Zt8g==", "dev": true, "requires": { - "babel-core": "6.26.3", + "babel-core": "6.26.0", "babel-jest": "22.4.3", "babel-plugin-istanbul": "4.1.6", - "chalk": "2.4.1", + "chalk": "2.4.0", "convert-source-map": "1.5.1", "exit": "0.1.2", "graceful-fs": "4.1.11", @@ -9213,9 +8372,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9286,7 +8445,7 @@ "integrity": "sha512-JXA0gVs5YL0HtLDCGa9YxcmmV2LZbwJ+0MfyXBBc5qpgkEYITQFJP7XNhcHFbUvRiniRpRbGVfJrOoYhhGE0RQ==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "jest-diff": "22.4.3", "jest-matcher-utils": "22.4.3", "mkdirp": "0.5.1", @@ -9304,9 +8463,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9332,7 +8491,7 @@ "dev": true, "requires": { "callsites": "2.0.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "graceful-fs": "4.1.11", "is-ci": "1.1.0", "jest-message-util": "22.4.3", @@ -9356,9 +8515,9 @@ "dev": true }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9389,7 +8548,7 @@ "integrity": "sha512-CfFM18W3GSP/xgmA4UouIx0ljdtfD2mjeBC6c89Gg17E44D4tQhAcTrZmf9djvipwU30kSTnk6CzcxdCCeSXfA==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "jest-config": "22.4.3", "jest-get-type": "22.4.3", "leven": "2.1.0", @@ -9406,9 +8565,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -9511,7 +8670,7 @@ "babel-register": "6.26.0", "babylon": "7.0.0-beta.46", "colors": "1.1.2", - "flow-parser": "0.71.0", + "flow-parser": "0.70.0", "lodash": "4.17.10", "micromatch": "2.3.11", "neo-async": "2.5.1", @@ -9645,9 +8804,9 @@ } }, "jsdom": { - "version": "11.10.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.10.0.tgz", - "integrity": "sha512-x5No5FpJgBg3j5aBwA8ka6eGuS5IxbC8FOkmyccKvObtFT0bDMict/LOxINZsZGZSfGdNomLZ/qRV9Bpq/GIBA==", + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.9.0.tgz", + "integrity": "sha512-sb3omwJTJ+HwAltLZevM/KQBusY+l2Ar5UfnTCWk9oUVBiDnQPBNiG1BaTAKttCnneonYbNo7vi4EFDY2lBfNA==", "dev": true, "requires": { "abab": "1.0.4", @@ -10506,7 +9665,7 @@ "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "2.4.1" + "chalk": "2.4.0" }, "dependencies": { "ansi-styles": { @@ -10519,9 +9678,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -10584,9 +9743,9 @@ } }, "lolex": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", - "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.4.1.tgz", + "integrity": "sha512-8QdNQMqlAE2kkc2YWR3Ld0evgE452mmyYZR4HTh54PeH8UAjDipHYh/FHq6y9cAvM68nxGxj5jAz97+WQ2AQEQ==", "dev": true }, "longest": { @@ -10625,9 +9784,9 @@ "dev": true }, "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { "pseudomap": "1.0.2", @@ -10654,9 +9813,9 @@ "dev": true }, "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { "pify": "3.0.0" @@ -10731,6 +9890,12 @@ "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", "dev": true }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, "math-sign-x": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/math-sign-x/-/math-sign-x-3.0.0.tgz", @@ -10790,17 +9955,16 @@ } }, "mem-fs-editor": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-4.0.1.tgz", - "integrity": "sha512-54fptqhSZX1sSYsVVInG2qzUWPPrEv/6qYxHAwXJZQfzDcviJcL+7p/wmupg8SdAOi42m/vilMBemx3D6Sz22g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz", + "integrity": "sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=", "dev": true, "requires": { "commondir": "1.0.1", - "deep-extend": "0.5.1", + "deep-extend": "0.4.2", "ejs": "2.5.9", "glob": "7.1.2", - "globby": "8.0.1", - "isbinaryfile": "3.0.2", + "globby": "6.1.0", "mkdirp": "0.5.1", "multimatch": "2.1.0", "rimraf": "2.6.2", @@ -10814,21 +9978,38 @@ "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", "dev": true }, + "deep-extend": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", + "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", + "dev": true + }, "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { "array-union": "1.0.2", - "dir-glob": "2.0.0", - "fast-glob": "2.2.1", "glob": "7.1.2", - "ignore": "3.3.8", - "pify": "3.0.0", - "slash": "1.0.0" + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, "replace-ext": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", @@ -11009,12 +10190,6 @@ } } }, - "merge2": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", - "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==", - "dev": true - }, "micromatch": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", @@ -11112,19 +10287,47 @@ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "minipass": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.0.tgz", + "integrity": "sha512-jWC2Eg+Np4bxah7llu1IrUNSJQxtLz/J+pOjTM0nFpJXGAaV18XBWhUn031Q1tAA/TJtA1jgwnOe9S2PQa4Lbg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "dev": true + } + } + }, + "minizlib": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "dev": true, + "optional": true, "requires": { - "brace-expansion": "1.1.11" + "minipass": "2.3.0" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, "mississippi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", @@ -11346,6 +10549,18 @@ } } }, + "needle": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", + "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.4", + "iconv-lite": "0.4.23", + "sax": "1.2.4" + } + }, "neo-async": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", @@ -11371,7 +10586,7 @@ "requires": { "@sinonjs/formatio": "2.0.0", "just-extend": "1.1.27", - "lolex": "2.3.2", + "lolex": "2.4.1", "path-to-regexp": "1.7.0", "text-encoding": "0.6.4" } @@ -11451,7 +10666,7 @@ "querystring-es3": "0.2.1", "readable-stream": "2.3.6", "stream-browserify": "2.0.1", - "stream-http": "2.8.1", + "stream-http": "2.8.2", "string_decoder": "1.1.1", "timers-browserify": "2.0.10", "tty-browserify": "0.0.0", @@ -11533,10 +10748,117 @@ "q": "1.1.2" } }, + "node-pre-gyp": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.9.1.tgz", + "integrity": "sha1-8RwHUW3ZL4cZnbx+GDjqt81WyeA=", + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.1", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.3.0", + "tar": "4.4.2" + }, + "dependencies": { + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "optional": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "optional": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "tar": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.2.tgz", + "integrity": "sha512-BfkE9CciGGgDsATqkikUHrQrraBCO+ke/1f6SFAEMnxyyfN9lxC+nW1NFWMpqH865DhHIy9vQi682gk1X7friw==", + "dev": true, + "optional": true, + "requires": { + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.0", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.2" + } + }, + "yallist": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", + "dev": true, + "optional": true + } + } + }, "node-sass": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.0.tgz", - "integrity": "sha512-QFHfrZl6lqRU3csypwviz2XLgGNOoWQbo2GOvtsfQqOfL4cy1BtWnhx/XUeAO9LT3ahBzSRXcEO6DdvAH9DzSg==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.8.3.tgz", + "integrity": "sha512-tfFWhUsCk/Y19zarDcPo5xpj+IW3qCfOjVdHtYeG6S1CKbQOh1zqylnQK6cV3z9k80yxAnFX9Y+a9+XysDhhfg==", "dev": true, "requires": { "async-foreach": "0.1.3", @@ -11572,8 +10894,8 @@ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "dev": true, "requires": { - "lru-cache": "4.1.2", - "which": "1.3.0" + "lru-cache": "^4.0.1", + "which": "^1.2.9" } }, "gauge": { @@ -11582,14 +10904,14 @@ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "har-validator": { @@ -11598,10 +10920,10 @@ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", "dev": true, "requires": { - "chalk": "1.1.3", - "commander": "2.9.0", - "is-my-json-valid": "2.17.2", - "pinkie-promise": "2.0.1" + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" } }, "is-fullwidth-code-point": { @@ -11610,7 +10932,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "nan": { @@ -11625,10 +10947,10 @@ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "request": { @@ -11637,26 +10959,26 @@ "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", "dev": true, "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.7.0", - "caseless": "0.11.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.4.3", - "uuid": "3.2.1" + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~2.1.1", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "oauth-sign": "~0.8.1", + "qs": "~6.3.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.3.0", + "tunnel-agent": "~0.4.1", + "uuid": "^3.0.0" } }, "string-width": { @@ -11665,9 +10987,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } } } @@ -11763,6 +11085,24 @@ "sort-keys": "1.1.2" } }, + "npm-bundled": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.3.tgz", + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -12271,7 +11611,7 @@ "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "@types/node": "10.0.3" + "@types/node": "9.6.6" } }, "pascalcase": { @@ -12605,8 +11945,8 @@ "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "dev": true, "requires": { - "caniuse-db": "1.0.30000833", - "electron-to-chromium": "1.3.45" + "caniuse-db": "1.0.30000830", + "electron-to-chromium": "1.3.44" } } } @@ -12675,16 +12015,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "1.9.1" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -12700,6 +12038,19 @@ "chalk": "2.4.1", "source-map": "0.6.1", "supports-color": "5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + } } }, "source-map": { @@ -12712,7 +12063,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -12733,16 +12083,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "1.9.1" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -12758,6 +12106,19 @@ "chalk": "2.4.1", "source-map": "0.6.1", "supports-color": "5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + } } }, "source-map": { @@ -12770,7 +12131,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -12791,16 +12151,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "1.9.1" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -12816,6 +12174,19 @@ "chalk": "2.4.1", "source-map": "0.6.1", "supports-color": "5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + } } }, "source-map": { @@ -12828,7 +12199,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -12849,16 +12219,14 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "1.9.1" } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "requires": { "ansi-styles": "3.2.1", "escape-string-regexp": "1.0.5", @@ -12874,6 +12242,19 @@ "chalk": "2.4.1", "source-map": "0.6.1", "supports-color": "5.4.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" + } + } } }, "source-map": { @@ -12886,7 +12267,6 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -13012,7 +12392,7 @@ "requires": { "async": "1.5.2", "execspawn": "1.0.1", - "expand-template": "1.1.0", + "expand-template": "1.1.1", "ghreleases": "1.0.7", "github-from-package": "0.0.0", "minimist": "1.2.0", @@ -13198,8 +12578,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "1.4.1", - "once": "1.4.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } } } @@ -13290,31 +12670,28 @@ } }, "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "math-random": "1.0.1" }, "dependencies": { "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" }, "dependencies": { "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -13323,9 +12700,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } } } @@ -13528,6 +12904,11 @@ "warning": "3.0.0" } }, + "react-shadow": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/react-shadow/-/react-shadow-16.2.0.tgz", + "integrity": "sha512-75y6w8LW1fg2BaIiR94sdL+B5YtCWGXPXqrzQNmBXvwOIN4bzqZAIbXagHMBO7rGrJWTqpDReeQoGsuicDJ57g==" + }, "react-test-renderer": { "version": "16.3.2", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.3.2.tgz", @@ -14265,7 +13646,7 @@ "anymatch": "2.0.0", "exec-sh": "0.2.1", "fb-watchman": "2.0.0", - "fsevents": "1.2.3", + "fsevents": "1.2.2", "micromatch": "3.1.10", "minimist": "1.2.0", "walker": "1.0.7", @@ -14886,9 +14267,9 @@ "dev": true }, "shelljs": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", - "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.2.tgz", + "integrity": "sha512-pRXeNrCA2Wd9itwhvLp5LZQvPJ0wU6bcjaTMywHHGX5XWhVN2nzSu7WV0q+oUY7mGK3mgSkDDzP3MgjqdyIgbQ==", "dev": true, "requires": { "glob": "7.1.2", @@ -14937,7 +14318,7 @@ "@sinonjs/formatio": "2.0.0", "diff": "3.5.0", "lodash.get": "4.4.2", - "lolex": "2.3.2", + "lolex": "2.4.1", "nise": "1.3.3", "supports-color": "5.4.0", "type-detect": "4.0.8" @@ -15392,9 +14773,9 @@ } }, "stream-http": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.1.tgz", - "integrity": "sha512-cQ0jo17BLca2r0GfRdZKYAGLU6JRoIWxqSOakUMuKOT6MOK7AAlE856L33QuDmAy/eeOrhLee3dZKX0Uadu93A==", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.2.tgz", + "integrity": "sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==", "dev": true, "requires": { "builtin-status-codes": "3.0.0", @@ -15410,13 +14791,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } } } @@ -15642,7 +15023,7 @@ "requires": { "ajv": "5.5.2", "ajv-keywords": "2.1.1", - "chalk": "2.4.1", + "chalk": "2.4.0", "lodash": "4.17.10", "slice-ansi": "1.0.0", "string-width": "2.1.1" @@ -15658,9 +15039,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -15739,13 +15120,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } } } @@ -16634,15 +16015,15 @@ }, "dependencies": { "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.0.tgz", + "integrity": "sha512-VDUX1oSajablmiyFyED9L1DFndg0P9h7p1F+NO8FkIzei6EPrR6Zu1n18rd5P8PqaSRd/FrWv3G1TVBqpM83gA==", "dev": true, "requires": { - "fast-deep-equal": "1.1.0", + "fast-deep-equal": "2.0.1", "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" + "uri-js": "4.2.1" } }, "ajv-keywords": { @@ -16657,13 +16038,19 @@ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", "dev": true }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "schema-utils": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.5.tgz", "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", "dev": true, "requires": { - "ajv": "6.4.0", + "ajv": "6.5.0", "ajv-keywords": "3.2.0" } }, @@ -16858,9 +16245,9 @@ "dev": true }, "uri-js": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", - "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.1.tgz", + "integrity": "sha512-jpKCA3HjsBfSDOEgxRDAxQCNyHfCPSbq57PqCkd3gAyBuPb3IWxw54EHncqESznIdqSetHfw3D7ylThu2Kcc9A==", "dev": true, "requires": { "punycode": "2.1.0" @@ -17213,14 +16600,14 @@ "dev": true }, "webpack": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.7.0.tgz", - "integrity": "sha512-OXOAip9mjy0ahFYCXu6LLNzTiIQzd2UOHkNHANc/dyxf8CYCgcJ5UKsTXfbfeJb4tqkKb6B1FIQ9Xtl6gftb8Q==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.6.0.tgz", + "integrity": "sha512-Fu/k/3fZeGtIhuFkiYpIy1UDHhMiGKjG4FFPVuvG+5Os2lWA1ttWpmi9Qnn6AgfZqj9MvhZW/rmj/ip+nHr06g==", "dev": true, "requires": { "acorn": "5.5.3", "acorn-dynamic-import": "3.0.0", - "ajv": "6.4.0", + "ajv": "6.5.0", "ajv-keywords": "3.2.0", "chrome-trace-event": "0.1.3", "enhanced-resolve": "4.0.0", @@ -17240,15 +16627,15 @@ }, "dependencies": { "ajv": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz", - "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.0.tgz", + "integrity": "sha512-VDUX1oSajablmiyFyED9L1DFndg0P9h7p1F+NO8FkIzei6EPrR6Zu1n18rd5P8PqaSRd/FrWv3G1TVBqpM83gA==", "dev": true, "requires": { - "fast-deep-equal": "1.1.0", + "fast-deep-equal": "2.0.1", "fast-json-stable-stringify": "2.0.0", "json-schema-traverse": "0.3.1", - "uri-js": "3.0.2" + "uri-js": "4.2.1" } }, "ajv-keywords": { @@ -17426,6 +16813,12 @@ } } }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -17531,7 +16924,7 @@ "integrity": "sha512-yYrjb9TX2k/J1Y5UNy3KYdZq10xhYcF8nMpAW6o3hy6Q8WSIEf9lJHG/ePnOBfziPM3fvQwfOwa13U/Fh8qTfA==", "dev": true, "requires": { - "ajv": "6.4.0", + "ajv": "6.5.0", "ajv-keywords": "3.2.0" } } @@ -17588,7 +16981,7 @@ "babel-register": "6.26.0", "babylon": "6.18.0", "colors": "1.1.2", - "flow-parser": "0.71.0", + "flow-parser": "0.70.0", "lodash": "4.17.10", "micromatch": "2.3.11", "node-dir": "0.1.8", @@ -17615,7 +17008,7 @@ "dev": true, "requires": { "ast-types": "0.10.1", - "core-js": "2.5.5", + "core-js": "2.5.6", "esprima": "4.0.0", "private": "0.1.8", "source-map": "0.6.1" @@ -17653,19 +17046,19 @@ } }, "webpack-cli": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.1.2.tgz", - "integrity": "sha512-2C6bs9gORlzCSgkNZTnj8hnXMxe3g2v+yqiUdB+1l/I3sI36ND4zZStV00yq0eGjE5CNu0eqOQr7YYe+42H2Yw==", + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.0.15.tgz", + "integrity": "sha512-bjNeIUO51D4OsmZ5ufzcpzVoacjxfWNfeBZKYL3jc+EMfCME3TyfdCPSUoKiOnebQChfupQuIRpAnx7L4l3Hew==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "cross-spawn": "6.0.5", "diff": "3.5.0", "enhanced-resolve": "4.0.0", "envinfo": "4.4.2", "glob-all": "3.1.0", "global-modules": "1.0.0", - "got": "8.3.1", + "got": "8.3.0", "import-local": "1.0.0", "inquirer": "5.2.0", "interpret": "1.1.0", @@ -17683,7 +17076,7 @@ "webpack-addons": "1.1.5", "yargs": "11.1.0", "yeoman-environment": "2.0.6", - "yeoman-generator": "2.0.5" + "yeoman-generator": "2.0.4" }, "dependencies": { "ansi-regex": { @@ -17708,9 +17101,9 @@ "dev": true }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -17749,7 +17142,7 @@ "dev": true, "requires": { "ansi-escapes": "3.1.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "cli-cursor": "2.1.0", "cli-width": "2.2.0", "external-editor": "2.2.0", @@ -18129,7 +17522,7 @@ "integrity": "sha512-jzHBTTy8EPI4ImV8dpUMt+Q5zELkSU5xvGpndHcHudQ4tqN6YgIWaCGmRFl+HDchwRUkcgyjQ+n6/w5zlJBCPg==", "dev": true, "requires": { - "chalk": "2.4.1", + "chalk": "2.4.0", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", @@ -18154,9 +17547,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -18210,15 +17603,15 @@ } }, "yeoman-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.5.tgz", - "integrity": "sha512-rV6tJ8oYzm4mmdF2T3wjY+Q42jKF2YiiD0VKfJ8/0ZYwmhCKC9Xs2346HVLPj/xE13i68psnFJv7iS6gWRkeAg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.4.tgz", + "integrity": "sha512-Sgvz3MAkOpEIobcpW3rjEl6bOTNnl8SkibP9z7hYKfIGIlw0QDC2k0MAeXvyE2pLqc2M0Duql+6R7/W9GrJojg==", "dev": true, "requires": { "async": "2.6.0", - "chalk": "2.4.1", + "chalk": "2.4.0", "cli-table": "0.3.1", - "cross-spawn": "6.0.5", + "cross-spawn": "5.1.0", "dargs": "5.1.0", "dateformat": "3.0.3", "debug": "3.1.0", @@ -18228,15 +17621,15 @@ "github-username": "4.1.0", "istextorbinary": "2.2.1", "lodash": "4.17.10", - "make-dir": "1.2.0", - "mem-fs-editor": "4.0.1", + "make-dir": "1.3.0", + "mem-fs-editor": "3.0.2", "minimist": "1.2.0", "pretty-bytes": "4.0.2", "read-chunk": "2.1.0", "read-pkg-up": "3.0.0", "rimraf": "2.6.2", "run-async": "2.3.0", - "shelljs": "0.8.1", + "shelljs": "0.8.2", "text-table": "0.2.0", "through2": "2.0.3", "yeoman-environment": "2.0.6" @@ -18261,9 +17654,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", + "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", "dev": true, "requires": { "ansi-styles": "3.2.1", @@ -18271,19 +17664,6 @@ "supports-color": "5.4.0" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "1.0.4", - "path-key": "2.0.1", - "semver": "5.5.0", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, "debug": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", @@ -18365,8 +17745,7 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "supports-color": { "version": "5.4.0", diff --git a/package.json b/package.json index c65e212c0..7ef1382a0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "homepage": "https://github.com/ghostery/ghostery-extension#readme", "dependencies": { "base64-js": "^1.2.1", - "browser-core": "https://s3.amazonaws.com/cdncliqz/update/edge/ghostery/v7.26/1.26.2.tgz?version=1", + "browser-core": "https://s3.amazonaws.com/cdncliqz/update/edge/ghostery/v7.26/1.26.3.tgz?v=1", "classnames": "^2.2.5", "d3": "^4.13.0", "d3-scale": "^1.0.6", @@ -54,6 +54,7 @@ "react-redux": "^5.0.6", "react-remarkable": "^1.1.3", "react-router-dom": "^4.2.2", + "react-shadow": "^16.2.0", "redux": "^3.7.2", "redux-thunk": "^2.2.0", "ua-parser-js": "^0.7.17", @@ -90,6 +91,7 @@ "node-sass": "^4.5.3", "oboe": "^2.1.3", "path": "^0.12.7", + "react-router": "^4.2.0", "react-test-renderer": "^16.2.0", "read-file": "^0.2.0", "redux-mock-store": "^1.5.1", diff --git a/src/background.js b/src/background.js index 89985f930..f4639cb34 100644 --- a/src/background.js +++ b/src/background.js @@ -20,13 +20,13 @@ */ import _ from 'underscore'; import moment from 'moment/min/moment-with-locales.min'; -import CLIQZ from 'browser-core'; +import cliqz from './classes/Cliqz'; // object classes -import Button from './classes/BrowserButton'; import Events from './classes/EventHandlers'; import PanelData from './classes/PanelData'; // static classes import bugDb from './classes/BugDb'; +import button from './classes/BrowserButton'; import c2pDb from './classes/Click2PlayDb'; import cmp from './classes/CMP'; import abtest from './classes/ABTest'; @@ -39,6 +39,7 @@ import globals from './classes/Globals'; import surrogatedb from './classes/SurrogateDb'; import tabInfo from './classes/TabInfo'; import metrics from './classes/Metrics'; +import rewards from './classes/Rewards'; // utilities import * as accounts from './utils/accounts'; import { allowAllwaysC2P } from './utils/click2play'; @@ -46,22 +47,20 @@ import * as common from './utils/common'; import * as utils from './utils/utils'; // class instantiation -const button = new Button(); const events = new Events(); - const panelData = new PanelData(); -const cliqz = new (CLIQZ.App)(); // function shortcuts const { log } = common; const { sendMessage } = utils; const { onMessage } = chrome.runtime; // simple consts const { - GHOSTERY_DOMAIN, CDN_SUB_DOMAIN, BROWSER_INFO, IS_CLIQZ + GHOSTERY_DOMAIN, CDN_SUB_DOMAIN, BROWSER_INFO, IS_CLIQZ, DEBUG } = globals; const IS_EDGE = (BROWSER_INFO.name === 'edge'); const VERSION_CHECK_URL = `https://${CDN_SUB_DOMAIN}.ghostery.com/update/version`; const OFFERS_HANDLER_ID = 'ghostery'; +const REAL_ESTATE_ID = 'ghostery'; const onBeforeRequest = events.onBeforeRequest.bind(events); const onHeadersReceived = events.onHeadersReceived.bind(events); @@ -87,7 +86,23 @@ function setCliqzModuleEnabled(module, enabled) { cliqz.disableModule(module.name); return Promise.resolve(); } +/** + * Register/unregister real estate with Offers core module. + * @memberOf Background + * @param {Object} offersModule offers module + * @param {Boolean} register true - register, false - unregister + */ +function registerWithOffers(offersModule, register) { + if (!offersModule.isEnabled) { + return Promise.resolve(); + } + log('REGISTER WITH OFFERS CALLED', register); + return offersModule.action(register ? 'registerRealEstate' : 'unregisterRealEstate', { realEstateID: REAL_ESTATE_ID }) + .catch((e) => { + log(`FAILED TO ${register ? 'REGISTER' : 'UNREGISTER'} REAL ESTATE WITH OFFERS CORE`); + }); +} /** * Check and fetch (if needed) a new tracker library every 12 hours * @memberOf Background @@ -374,7 +389,7 @@ function handleClick2Play(name, message, tab_id, callback) { * * @param {string} name message name * @param {Object} message message data - * @param {number} tab_id tab id + * @param {number} tab_id tab id * @param {function} callback function to call (at most once) when you have a response */ function handleBlockedRedirect(name, message, tab_id, callback) { @@ -397,7 +412,40 @@ function handleBlockedRedirect(name, message, tab_id, callback) { } /** - * Handle messages sent from dist/settings_redirect.js script. + * Handle messages sent from dist/rewards.js content script. + * @memberOf Background + * + * @param {string} name message name + * @param {Object} message message data + * @param {number} tab_id tab id + * @param {function} callback function to call (at most once) when you have a response + */ +function handleRewards(name, message, tab_id, callback) { + switch (name) { + case 'rewardSignal': + rewards.sendSignal(message); + break; + case 'rewardSeen': + rewards.markRewardRead(message.offerId); + button.update(); + break; + case 'deleteReward': + rewards.deleteReward(message.offerId); + button.update(); + break; + case 'rewardsDisabled': + conf.enable_offers = false; + break; + case 'rewardsPromptAccepted': + conf.rewards_accepted = true; + break; + default: + break; + } +} + +/** + * Handle messages sent from dist/settings_redirect.js script * of the settings_redirect.html local page. Used on @EDGE and Chrome. * @memberOf Background * @@ -532,6 +580,8 @@ function onMessageHandler(request, sender, callback) { return handleClick2Play(name, message, tab_id, callback); } else if (origin === 'blocked_redirect') { return handleBlockedRedirect(name, message, tab_id, callback); + } else if (origin === 'rewards' || origin === 'rewardsPanel') { + return handleRewards(name, message, tab_id, callback); } // HANDLE UNIVERSAL EVENTS HERE (NO ORIGIN LISTED ABOVE) @@ -780,18 +830,22 @@ function initializeDispatcher() { dispatcher.on('conf.save.enable_human_web', (enableHumanWeb) => { if (!IS_EDGE && !IS_CLIQZ) { setCliqzModuleEnabled(humanweb, enableHumanWeb).then((data) => { - // We don't want to affect Offers here - setupABTestAntitracking(); + setupABTest(); }); } else { setCliqzModuleEnabled(humanweb, false); } }); dispatcher.on('conf.save.enable_offers', (enableOffers) => { + button.update(); if (!IS_EDGE && !IS_CLIQZ) { + if (!enableOffers) { + registerWithOffers(offers, enableOffers); + } setCliqzModuleEnabled(offers, enableOffers); } else { setCliqzModuleEnabled(offers, false); + registerWithOffers(offers, false); } }); dispatcher.on('conf.save.enable_anti_tracking', (enableAntitracking) => { @@ -850,7 +904,7 @@ function getAntitrackingTestConfig() { * Adjust antitracking parameters based on the current state * of ABTest and availability of Human Web. */ -function setupABTestAntitracking() { +function setupABTest() { const antitrackingConfig = getAntitrackingTestConfig(); if (antitrackingConfig && conf.enable_anti_tracking) { if (!conf.enable_human_web) { @@ -864,22 +918,6 @@ function setupABTestAntitracking() { }); } } -/** - * Adjust offers based on the current state of ABTest. - */ -function setupABTestOffers() { - // enable offers ONLY if ABTest is true and user has left it enabled. - conf.enable_offers = (abtest.hasTest('offers') && conf.enable_offers); -} -/** - * Setup Antitracking and Offers based on the results - * returned from the abtest endpoint. - * @memberOf Background - */ -function setupABTests() { - setupABTestAntitracking(); - setupABTestOffers(); -} /** * WebRequest pipeline initialisation: find which Cliqz modules are enabled, @@ -1013,10 +1051,23 @@ adblocker.on('enabled', () => { offers.on('enabled', () => { offers.isReady().then(() => { log('IN OFFERS ON ENABLED', offers, messageCenter); - setCliqzModuleEnabled(messageCenter, true); + + if (DEBUG) { + offers.action('setConfiguration', { + config_location: 'de', + triggersBE: 'http://offers-api-stage.clyqz.com:81', + showConsoleLogs: true, + offersLogsEnabled: true, + offersDevFlag: true, + offersTelemetryFreq: '10' + }); + } + registerWithOffers(offers, true) + .then(() => { + setCliqzModuleEnabled(messageCenter, true); + }); }); -}); -/** +});/** * Set listener for 'enabled' event for Offers module. * It registers message handler for messages with the offers. * This handler adds incoming message data to the array of @@ -1049,27 +1100,22 @@ messageCenter.on('enabled', () => { * } * } */ - log('GOT OFFER', msg); // first check that the message is from core and is the one we expect if (msg.origin === 'offers-core' && msg.type === 'push-offer' && msg.data.offer_data) { - const { data } = msg; - const cmpMsg = { - id: data.offer_data.display_id, - Message: data.offer_data.ui_info.template_data.title, - Link: data.offer_data.ui_info.template_data.call_to_action.url, - LinkText: data.offer_data.ui_info.template_data.call_to_action.text, - type: 'offers', - origin: 'cliqz', - data: { - offer_info: { - offer_id: data.offer_data.offer_id, - offer_urls: data.offer_data.rule_info.url - } - } - }; - cmp.CMP_DATA.push(cmpMsg); + if (!rewards.storedOffers.hasOwnProperty(msg.data.offer_id)) { + rewards.storedOffers[msg.data.offer_id] = msg.data; + rewards.unreadOfferIds.push(msg.data.offer_id); + } + + log('RECEIVED OFFER', msg); + button.update(); + utils.getActiveTab((tab) => { + let tabId = 0; + if (tab) tabId = tab.id; + rewards.showHotDog(tabId, msg.data); + }); } }); }); @@ -1323,14 +1369,14 @@ function initializeGhosteryModules() { if (globals.JUST_UPGRADED_FROM_7) { conf.enable_ad_block = false; conf.enable_anti_tracking = false; + conf.enable_offers = false; conf.enable_human_web = (IS_EDGE || IS_CLIQZ) ? false : conf.enable_human_web; } else { conf.enable_ad_block = IS_CLIQZ ? false : !adblocker.isDisabled; conf.enable_anti_tracking = IS_CLIQZ ? false : !antitracking.isDisabled; conf.enable_human_web = (IS_EDGE || IS_CLIQZ) ? false : !humanweb.isDisabled; + conf.enable_offers = (IS_EDGE || IS_CLIQZ) ? false : !offers.isDisabled; } - // sync conf from module status - conf.enable_offers = (IS_EDGE || IS_CLIQZ) ? false : !offers.isDisabled; })).catch((e) => { log('cliqzStartup error', e); }); @@ -1347,7 +1393,7 @@ function initializeGhosteryModules() { if (!IS_EDGE && !IS_CLIQZ) { // auto-fetch human web offer abtest.fetch().then(() => { - setupABTests(); + setupABTest(); }).catch((err) => { log('Unable to reach abtest server'); }); @@ -1357,7 +1403,7 @@ function initializeGhosteryModules() { cliqzStartup.then(() => { if (!IS_EDGE && !IS_CLIQZ) { abtest.fetch().then(() => { - setupABTests(); + setupABTest(); }).catch((err) => { log('cliqzStartup abtest fetch error', err); }); diff --git a/src/classes/ABTest.js b/src/classes/ABTest.js index 6ee9f8115..ff57a6b86 100644 --- a/src/classes/ABTest.js +++ b/src/classes/ABTest.js @@ -30,7 +30,7 @@ class ABTest { this.tests = {}; } /** - * @class to determine if a test with specified name is present. + * Determine if a test with specified name is present. * @param {string} name test name */ hasTest(name) { diff --git a/src/classes/BrowserButton.js b/src/classes/BrowserButton.js index f7762d91d..30a59033d 100644 --- a/src/classes/BrowserButton.js +++ b/src/classes/BrowserButton.js @@ -15,6 +15,7 @@ import conf from './Conf'; import foundBugs from './FoundBugs'; +import rewards from './Rewards'; import Policy from './Policy'; import { getTab } from '../utils/utils'; import { log } from '../utils/common'; @@ -23,7 +24,6 @@ import globals from './Globals'; /** * @class for handling Ghostery button. * @memberof BackgroundClasses - * @todo make it a Singelton */ class BrowserButton { constructor() { @@ -77,10 +77,13 @@ class BrowserButton { if (globals.BROWSER_INFO.os === 'android') { return; } if (tabId <= 0) { return; } + const iconAlt = (!active) ? '_off' : + (conf.enable_offers && rewards.unreadOfferIds.length > 0) ? '_star' : ''; + chrome.browserAction.setIcon({ path: { - 19: `app/images/icon19${active ? '' : '_off'}.png`, - 38: `app/images/icon38${active ? '' : '_off'}.png` + 19: `app/images/icon19${iconAlt}.png`, + 38: `app/images/icon38${iconAlt}.png` }, tabId }, () => { @@ -101,11 +104,10 @@ class BrowserButton { // Only show the badge if the conf setting allows it if (conf.show_badge) { - // Add tracker count to the badgeText - chrome.browserAction.setBadgeText({ - text: trackerCount, - tabId - }); + // Don't show badgeText when there is a new reward and Ghostery is active + // Otherwise set the tracker count to the badgeText + const text = (conf.enable_offers && rewards.unreadOfferIds.length && active) ? '' : trackerCount; + chrome.browserAction.setBadgeText({ text, tabId }); // Set badge background color chrome.browserAction.setBadgeBackgroundColor({ @@ -153,4 +155,4 @@ class BrowserButton { } } -export default BrowserButton; +export default new BrowserButton(); diff --git a/src/classes/Cliqz.js b/src/classes/Cliqz.js new file mode 100644 index 000000000..a0112c0a3 --- /dev/null +++ b/src/classes/Cliqz.js @@ -0,0 +1,17 @@ +/** + * Cliqz Import Class + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +/* @memberOf BackgroundClasses */ +import CLIQZ from 'browser-core'; + +export default new (CLIQZ.App)(); diff --git a/src/classes/ConfData.js b/src/classes/ConfData.js index 246f68ed5..4ee1553d1 100644 --- a/src/classes/ConfData.js +++ b/src/classes/ConfData.js @@ -105,6 +105,7 @@ class ConfData { _initProperty('last_cmp_date', 0); _initProperty('notify_library_updates', false); _initProperty('notify_upgrade_updates', true); + _initProperty('rewards_accepted', false); _initProperty('settings_last_imported', 0); _initProperty('settings_last_exported', 0); _initProperty('show_alert', true); diff --git a/src/classes/EventHandlers.js b/src/classes/EventHandlers.js index c8a6903fa..41618927e 100644 --- a/src/classes/EventHandlers.js +++ b/src/classes/EventHandlers.js @@ -16,7 +16,7 @@ import _ from 'underscore'; import bugDb from './BugDb'; -import Button from './BrowserButton'; +import button from './BrowserButton'; import c2pDb from './Click2PlayDb'; import cmp from './CMP'; import conf from './Conf'; @@ -26,6 +26,7 @@ import latency from './Latency'; import Policy, { BLOCK_REASON_SS_UNBLOCKED, BLOCK_REASON_C2P_ALLOWED_THROUGH } from './Policy'; import PolicySmartBlock from './PolicySmartBlock'; import PurpleBox from './PurpleBox'; +import rewards from './Rewards'; import surrogatedb from './SurrogateDb'; import compDb from './CompatibilityDb'; import tabInfo from './TabInfo'; @@ -34,7 +35,6 @@ import { log } from '../utils/common'; import { isBug } from '../utils/matcher'; import * as utils from '../utils/utils'; -const button = new Button(); const RequestsMap = new Map(); /** * This class is a collection of handlers for @@ -43,7 +43,6 @@ const RequestsMap = new Map(); */ class EventHandlers { constructor() { - this.button = new Button(); this.policy = new Policy(); this.policySmartBlock = new PolicySmartBlock(); this.purplebox = new PurpleBox(); @@ -159,7 +158,7 @@ class EventHandlers { // we look for a cliqz offer which does not have urls specified (meaning good for any site) // All Cliqz offers have Dismiss === 1, so the found one is injected and removed. // Lastly we look for non-cliqz offers (classic CMPs) - if (cmp.CMP_DATA.length !== 0) { + if (!rewards.currentOffer && cmp.CMP_DATA.length !== 0) { const CMPS = cmp.CMP_DATA; const numOffers = CMPS.length; let cliqzOffer; diff --git a/src/classes/PanelData.js b/src/classes/PanelData.js index 5732171c2..84e80fe31 100644 --- a/src/classes/PanelData.js +++ b/src/classes/PanelData.js @@ -15,7 +15,7 @@ */ import _ from 'underscore'; -import Button from './BrowserButton'; +import button from './BrowserButton'; import conf from './Conf'; import foundBugs from './FoundBugs'; import bugDb from './BugDb'; @@ -23,11 +23,11 @@ import globals from './Globals'; import Policy from './Policy'; import tabInfo from './TabInfo'; import abtest from './ABTest'; +import rewards from './Rewards'; import { pushUserSettings, buildUserSettings } from '../utils/accounts'; import { getActiveTab, flushChromeMemoryCache } from '../utils/utils'; import { objectEntries, log } from '../utils/common'; -const button = new Button(); const SYNC_SET = new Set(globals.SYNC_ARRAY); const IS_EDGE = (globals.BROWSER_INFO.name === 'edge'); const { IS_CLIQZ } = globals; @@ -48,6 +48,7 @@ class PanelData { this._panelView = {}; this._summaryView = {}; this._blockingView = {}; + this._rewardsView = {}; this._settingsView = {}; } @@ -80,6 +81,8 @@ class PanelData { return this.summaryView; case 'blocking': return this.blockingView; + case 'rewards': + return this.rewardsView; default: return false; } @@ -171,6 +174,7 @@ class PanelData { enable_ad_block: this._confData.get('enable_ad_block'), enable_anti_tracking: this._confData.get('enable_anti_tracking'), enable_smart_block: this._confData.get('enable_smart_block'), + enable_offers: this._confData.get('enable_offers'), is_expanded: this._confData.get('is_expanded'), is_expert: this._confData.get('is_expert'), is_android: globals.BROWSER_INFO.os === 'android', @@ -183,9 +187,10 @@ class PanelData { needsReload: this._trackerData.get('needsReload'), smartBlock: this._trackerData.get('smartBlock'), tab_id: this._trackerData.get('tab_id'), + unread_offer_ids: rewards.unreadOfferIds, }, summary: this.summaryView, - blocking: this._confData.get('is_expert') ? this.blockingView : false + blocking: this._confData.get('is_expert') ? this.blockingView : false, }; return this._panelView; } @@ -232,6 +237,19 @@ class PanelData { return this._blockingView; } + /** + * Get rewards data for the Rewards View + * @return {Object} Rewards view data + */ + get rewardsView() { + this._rewardsView = { + enable_offers: this._confData.get('enable_offers'), + rewards: rewards.storedOffers, + unread_offer_ids: rewards.unreadOfferIds, + }; + return this._rewardsView; + } + /** * Get conf and tracker data for Settings View. Note we have overlapping properties * from blockView incase the user is in Simple Mode. @@ -250,7 +268,6 @@ class PanelData { enable_human_web: this._confData.get('enable_human_web'), enable_offers: this._confData.get('enable_offers'), enable_metrics: this._confData.get('enable_metrics'), - enable_offers_abtest: abtest.hasTest('offers'), first_name: this._confData.get('first_name'), last_name: this._confData.get('last_name'), hide_alert_trusted: this._confData.get('hide_alert_trusted'), diff --git a/src/classes/Rewards.js b/src/classes/Rewards.js new file mode 100644 index 000000000..e06932fd6 --- /dev/null +++ b/src/classes/Rewards.js @@ -0,0 +1,132 @@ +/** + * Rewards Class + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2018 Ghostery, Inc. All rights reserved. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0 + */ + +/* eslint consistent-return: 0 */ + +import button from './BrowserButton'; +import cliqz from './Cliqz'; +import conf from './Conf'; +import tabInfo from './TabInfo'; +import Policy from './Policy'; +import globals from './Globals'; +import { log } from '../utils/common'; +import { sendMessage, injectScript } from '../utils/utils'; +import * as accounts from '../utils/accounts'; + +const t = chrome.i18n.getMessage; +/** + * Class for handling Ghostery Rewards Box overlay. + * @memberOf BackgroundClasses + * @todo make it a Singelton + */ +class Rewards { + constructor() { + this.storedOffers = {}; + this.unreadOfferIds = []; + this.currentOffer = null; + this.ports = new Map(); + this.channelsSupported = (typeof chrome.runtime.onConnect === 'object'); + } + + deleteReward(offerId) { + this.markRewardRead(offerId); + delete this.storedOffers[offerId]; + // @TODO send signal? + } + + markRewardRead(offerId) { + const rewardIdx = this.unreadOfferIds.indexOf(offerId); + this.unreadOfferIds.splice(rewardIdx, 1); + } + + sendSignal(message) { + const { offerId, actionId } = message; + const signal = { + origin: 'ghostery', + type: 'action-signal', + data: { + action_id: actionId, + offer_id: offerId + } + }; + cliqz.modules['offers-v2'].background.actions.processRealEstateMessage(signal); + } + + showHotDog(tab_id, offer) { + this.currentOffer = offer; + const tab = tabInfo.getTabInfo(tab_id); + + // If the tab is prefetched, we can't add purplebox to it. + if (!conf.enable_offers || + !tab || tab.rewards) { + return Promise.resolve(false); + } + + // Inject script cannot handle errors properly, but we call createBox after verifying that the tab is OK + // So update hotdog status for this tab + // tabInfo.setTabInfo(tab_id, 'rewards', true); + if (this.channelsSupported) { + if (this.ports.has(tab_id)) { + this.ports.get(tab_id).disconnect(); + this.ports.delete(tab_id); + } + if (!this.connectListenerAdded) { + this.connectListenerAdded = true; + chrome.runtime.onConnect.addListener((port) => { + if (port && port.name === 'rewardsPort' && port.sender && port.sender.tab && port.sender.tab.id) { + const tabId = port.sender.tab.id; + if (!this.ports.has(tabId)) { + this.ports.set(tabId, port); + this.ports.get(tabId).onMessage.addListener((message) => { + switch (message.name) { + case 'rewardsLoaded': + this.ports.get(tabId).postMessage({ + name: 'showHotDog', + reward: this.currentOffer, + conf: { + rewardsPromptAccepted: conf.rewards_accepted + } + }); + break; + case 'rewardSignal': + this.sendSignal(message.message); + break; + case 'rewardsDisabled': + conf.enable_offers = false; + break; + case 'rewardsPromptAccepted': + // @TODO set conf disabled + conf.rewards_accepted = true; + break; + case 'rewardSeen': + this.markRewardRead(message.offerId); + button.update(); + break; + default: + break; + } + }); + } + } + }); + } + } + + return injectScript(tab_id, 'dist/rewards.js', 'dist/css/rewards_styles.css', 'document_start').catch((err) => { + log('rewards injectScript error', err); + return false; + }); + } +} + +export default new Rewards(); diff --git a/src/classes/TabInfo.js b/src/classes/TabInfo.js index df3423490..1a726702f 100644 --- a/src/classes/TabInfo.js +++ b/src/classes/TabInfo.js @@ -55,6 +55,7 @@ class TabInfo { partialScan: true, prefetched: false, purplebox: false, + rewards: false, timestamp: Date.now(), // assign only when smartBlock is enabled so avoid false positives // when enabling smartBlock is enabled for the first time diff --git a/webpack.config.js b/webpack.config.js index e5e855629..4f8ad8df7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,6 +27,7 @@ const SRC_DIR = path.resolve(__dirname, 'src'); const PANEL_DIR = path.resolve(__dirname, 'app/panel'); const SETUP_DIR = path.resolve(__dirname, 'app/setup'); const LICENSES_DIR = path.resolve(__dirname, 'app/licenses'); +const REWARDS_DIR = path.resolve(__dirname, 'app/rewards'); const SASS_DIR = path.resolve(__dirname, 'app/scss'); const CONTENT_SCRIPTS_DIR = path.resolve(__dirname, 'app/content-scripts'); const RM = (process.platform === 'win32') ? 'powershell remove-item' : 'rm'; @@ -46,7 +47,8 @@ const cleanTmpStyleFiles = new WebpackShellPlugin({ `${RM} ./dist/panel_android.js`, `${RM} ./dist/purplebox_styles.js`, `${RM} ./dist/setup.js`, - `${RM} ./dist/ghostery_dot_com_css.js` + `${RM} ./dist/ghostery_dot_com_css.js`, + `${RM} ./dist/rewards_styles.js` ] }); @@ -117,6 +119,7 @@ const config = { notifications: [CONTENT_SCRIPTS_DIR + '/notifications.js'], page_performance: [CONTENT_SCRIPTS_DIR + '/page_performance.js'], platform_pages: [CONTENT_SCRIPTS_DIR + '/platform_pages.js'], + rewards: [CONTENT_SCRIPTS_DIR + '/rewards'], purplebox: [CONTENT_SCRIPTS_DIR + '/purplebox.js'], content_script_bundle: [CLIQZ_DIR + '/core/content-script.bundle.js'], panel_react: [PANEL_DIR + '/index.jsx'], @@ -129,6 +132,7 @@ const config = { purplebox_styles: [SASS_DIR + '/purplebox.scss'], setup: [SASS_DIR + '/setup.scss'], licenses: [SASS_DIR + '/licenses.scss'], + rewards_styles: [SASS_DIR + '/rewards.scss'], }, devtool: 'none', performance: { hints: false }, @@ -149,8 +153,8 @@ const config = { loader: 'html-loader' } },{ - test : /\.jsx?/, - include : [PANEL_DIR, SETUP_DIR, LICENSES_DIR], + test : /\.(jsx|js)?/, + include : [PANEL_DIR, SETUP_DIR, LICENSES_DIR, CONTENT_SCRIPTS_DIR, REWARDS_DIR], use: { loader: 'babel-loader' } @@ -163,12 +167,10 @@ const config = { }, { loader: "sass-loader", options: { - sourceMap: false, - precision: 8, includePaths: [ path.resolve(__dirname, 'node_modules/foundation-sites/scss'), ] - }, + } }] },{ test: /\.svg$/,