diff --git a/gulp/tasks/build.js b/gulp/tasks/build.js
index 0c5c2647c..bcc575fe4 100644
--- a/gulp/tasks/build.js
+++ b/gulp/tasks/build.js
@@ -1,28 +1,48 @@
import gulp from 'gulp';
import gutil from 'gulp-util';
import webpack from 'webpack';
+import path from 'path';
import devConfig from '../../webpack/dev.config';
import stagingConfig from '../../webpack/staging.config';
import extConfig from '../../webpack/production.config';
-const build = (config, callback) => {
- let myConfig = Object.create(config);
- webpack(myConfig, (err, stats) => {
- if (err) {
- throw new gutil.PluginError('webpack:build', err);
- }
- gutil.log('[webpack:build]', stats.toString({ colors: true }));
+function compiler(config) {
+ return webpack(Object.create(config));
+}
+
+function staticCompiler(config) {
+ if (!staticCompiler.instance) {
+ staticCompiler.instance = compiler(config);
+ }
+ return staticCompiler.instance;
+}
+
+function build(compiler, callback) {
+ compiler.run((err, stats) => {
+ if (err) throw new gutil.PluginError('webpack:build', err);
+ gutil.log('[webpack:build]', stats.toString({
+ chunks: false,
+ colors: true,
+ }));
callback();
});
-};
+}
+gulp.task('webpack:watch', () => {
+ const globs = [
+ path.join(__dirname, '../../src/') + '**/*',
+ path.join(__dirname, '../../test/') + '**/*',
+ ];
+ return gulp.watch(globs, ['webpack:build:dev']);
+});
gulp.task('webpack:build:dev', (callback) => {
- build(devConfig, callback);
+ // Instance webpack compiler once over multiple times (watch)
+ build(staticCompiler(devConfig), callback);
});
gulp.task('webpack:build:staging', (callback) => {
- build(stagingConfig, callback);
+ build(compiler(stagingConfig), callback);
});
gulp.task('webpack:build:production', (callback) => {
- build(extConfig, callback);
+ build(compiler(extConfig), callback);
});
\ No newline at end of file
diff --git a/gulp/tasks/webpack.js b/gulp/tasks/webpack.js
deleted file mode 100644
index f943f5273..000000000
--- a/gulp/tasks/webpack.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import gulp from 'gulp';
-import fs from 'fs';
-
-gulp.task('replace-webpack-code', () => {
- const replaceTasks = [{
- from: './webpack/replace/JsonpMainTemplate.runtime.js',
- to: './node_modules/webpack/lib/JsonpMainTemplate.runtime.js'
- }, {
- from: './webpack/replace/log-apply-result.js',
- to: './node_modules/webpack/hot/log-apply-result.js'
- }];
- replaceTasks.forEach(task => fs.writeFileSync(task.to, fs.readFileSync(task.from)));
-});
diff --git a/gulpfile.babel.js b/gulpfile.babel.js
index 2aca0f6a9..345f5700a 100644
--- a/gulpfile.babel.js
+++ b/gulpfile.babel.js
@@ -4,7 +4,8 @@ requireDir('./gulp/tasks');
gulp.task('default',['build:dev']);
gulp.task('build:dev',
- ['webpack:build:dev', 'views:build:dev', 'copy:build:dev', 'copy:watch']);
+ ['webpack:build:dev', 'views:build:dev', 'copy:build:dev',
+ 'copy:watch', 'webpack:watch']);
gulp.task('build:staging',
['webpack:build:staging', 'views:build:staging', 'copy:build:staging']);
gulp.task('build:production',
diff --git a/package.json b/package.json
index 6a9604107..c9e980e4e 100644
--- a/package.json
+++ b/package.json
@@ -8,8 +8,8 @@
"build:staging": "gulp build:staging",
"build:dev": "gulp build:dev",
"clean": "rm -rf build",
- "test": "LMEM_BACKEND_ORIGIN='' NODE_ENV=test mocha --compilers js:babel-core/register test/app --recursive",
- "lint": "git ls-files -om --exclude-standard | grep \\.js$ | xargs eslint --fix",
+ "test": "LMEM_BACKEND_ORIGIN='' LMEM_SCRIPTS_ORIGIN='.' NODE_ENV=test mocha --compilers js:babel-core/register test/app --recursive",
+ "lint": "git diff-index --name-only --cached HEAD | grep \\.js$ | xargs eslint --fix",
"deploy:production": "gulp deploy:production",
"deploy:staging": "gulp deploy:staging"
},
diff --git a/src/app/actions/install.js b/src/app/actions/install.js
new file mode 100644
index 000000000..6c30c0bb9
--- /dev/null
+++ b/src/app/actions/install.js
@@ -0,0 +1,23 @@
+import { INSTALLED } from '../constants/ActionTypes';
+
+// Promise constructed when the module is first imported (very early)
+// in order to not miss the "install" event.
+const onInstalledPromise = new Promise(resolve => {
+ chrome.runtime.onInstalled.addListener(details => {
+ if (details.reason !== 'install') return;
+
+ resolve(Object.assign({}, details, {
+ datetime: new Date(),
+ version: chrome.runtime.getManifest().version,
+ }));
+ });
+});
+
+export default function () {
+ return dispatch => {
+ onInstalledPromise.then(onInstalledDetails => dispatch({
+ type: INSTALLED,
+ onInstalledDetails
+ }));
+ };
+}
diff --git a/src/app/components/AlternativeHeader.js b/src/app/components/AlternativeHeader.js
index 43b1b6298..c2a46abcf 100644
--- a/src/app/components/AlternativeHeader.js
+++ b/src/app/components/AlternativeHeader.js
@@ -41,11 +41,12 @@ class AlternativeHeader extends Component {
- onDeactivate({
- where: DEACTIVATE_EVERYWHERE,
- duration: SESSION_DEACTIVATE_DELAY
- }) }
- >
+ onDeactivate({
+ where: DEACTIVATE_EVERYWHERE,
+ duration: SESSION_DEACTIVATE_DELAY
+ })}>
Désactiver partout pour 30mins
- onDeactivate({
- where: window.location && location.hostname,
- duration: DEACTIVATE_WEBSITE_ALWAYS
- }) }
- >
+ onDeactivate({
+ where: window.location && location.hostname,
+ duration: DEACTIVATE_WEBSITE_ALWAYS
+ })}>
-
-
- Fermer l'écran des préférences
+ Fermer l’écran des préférences
) :
[(
-
{
if(reduced){
onExtend();
}
openPrefScreen(PREFERENCE_SCREEN_PANEL_ABOUT);
}}>
-
Préférences
@@ -113,12 +121,13 @@ class AlternativeHeader extends Component {
),
(
-
-
+
+
Désactiver
@@ -128,6 +137,21 @@ class AlternativeHeader extends Component {
{ deactivateMenu }
+ ),
+ (
+
+
+ {reduceButtonText}
+
+ {reduceButtonText}
+
+
)];
@@ -135,14 +159,13 @@ class AlternativeHeader extends Component {
const extendReduceButton = preferenceScreenPanel ? undefined :
(
-
+
+ className={ buttonButtonClassName } />
{reduceButtonText}
{reduceButtonText}
@@ -158,9 +181,9 @@ class AlternativeHeader extends Component {
return (
-
+
{ reduceButtonText + ' le panneau comparatif' }
@@ -170,7 +193,7 @@ class AlternativeHeader extends Component {
- {headerContent}
+ {headerContent}
@@ -179,21 +202,6 @@ class AlternativeHeader extends Component {
-
-
- {reduceButtonText}
-
- {reduceButtonText}
-
-
-
-
);
}
@@ -208,13 +216,13 @@ class AlternativeHeader extends Component {
componentWillUnmount() {
this.refs.deactivateMenu.ownerDocument
- .removeEventListener('click', this._closeMenuDocumentClickHandler);
+ .removeEventListener('click', this.closeMenuDocumentClickHandler);
}
watchForMenuExit() {
const menuElement = this.refs.deactivateMenu;
- this._closeMenuDocumentClickHandler = event => {
+ this.closeMenuDocumentClickHandler = event => {
if (!this.state.deactivateMenuOpen) return;
if (!event.target.matches('.menu-deactivate, .menu-deactivate *')) {
@@ -222,7 +230,7 @@ class AlternativeHeader extends Component {
}
};
- menuElement.ownerDocument.addEventListener('click', this._closeMenuDocumentClickHandler);
+ menuElement.ownerDocument.addEventListener('click', this.closeMenuDocumentClickHandler);
}
diff --git a/src/app/components/AlternativeMain.js b/src/app/components/AlternativeMain.js
index 7fe753940..c0adf44c6 100644
--- a/src/app/components/AlternativeMain.js
+++ b/src/app/components/AlternativeMain.js
@@ -39,7 +39,9 @@ const AlternativeMain = ({ imagesUrl, contributorUrl, recommendation }) => {
{recommendation.description}
-
+
{
recommendation.alternatives[0].url_to_redirect.replace(/^https?:\/\/(www.)?/, '')
}
@@ -58,7 +60,7 @@ const AlternativeMain = ({ imagesUrl, contributorUrl, recommendation }) => {
- )
+ );
};
AlternativeMain.propTypes = {
diff --git a/src/app/components/Alternatives.js b/src/app/components/Alternatives.js
index 436b9c9ce..0fc589e5f 100644
--- a/src/app/components/Alternatives.js
+++ b/src/app/components/Alternatives.js
@@ -17,15 +17,18 @@ class Alternatives extends Component {
const { props, state } = this;
const {
recommendation, imagesUrl, reduced, contributorUrl, preferenceScreenPanel, deactivatedWebsites,
- onExtend, onReduce, onDeactivate, togglePrefPanel, onReactivateWebsite, closePrefScreen, openPrefScreen
+ onExtend, onReduce, onDeactivate, togglePrefPanel, onReactivateWebsite, closePrefScreen, openPrefScreen,
+ onInstalledDetails
} = props;
-
+
const body = (preferenceScreenPanel ?
:
);
@@ -55,6 +58,7 @@ Alternatives.propTypes = {
reduced: PropTypes.bool.isRequired,
onExtend: PropTypes.func.isRequired,
onReduce: PropTypes.func.isRequired,
+ onInstalledDetails: PropTypes.object.isRequired,
};
export default Alternatives;
diff --git a/src/app/components/PreferenceAboutPanel.js b/src/app/components/PreferenceAboutPanel.js
index 32839f27f..06622e368 100644
--- a/src/app/components/PreferenceAboutPanel.js
+++ b/src/app/components/PreferenceAboutPanel.js
@@ -1,5 +1,54 @@
import React, { Component, PropTypes } from 'react';
+import { EXTENSION_VERSION } from '../constants/ui';
-export default function(){
- return Le Même En Mieux vous recommande des alternatives pertinentes, blablabla ;
-}
\ No newline at end of file
+function formatLocaleDate(strDate) {
+ const dateOfInstall = new Date(strDate);
+
+ if (Number.isNaN(dateOfInstall.getTime()))
+ return undefined;
+
+ return dateOfInstall.toLocaleDateString(navigator.language,
+ { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
+}
+
+export default function ({ onInstalledDetails }) {
+ const ISODateOfInstall = onInstalledDetails && onInstalledDetails.get('datetime');
+ const localeDateOfInstall = ISODateOfInstall && formatLocaleDate(ISODateOfInstall);
+
+ return (
+
+
+
+ Le Même en Mieux est un assistant d’achat
+ { localeDateOfInstall ? (
+ que vous avez installé le {localeDateOfInstall}
+ ) : ''}.
+
+
+ Quand vous consultez un produit sur Internet, il vous trouve des conseils d’achat,
+ des comparatifs et de meilleures alternatives, selon vos préférences.
+
+
+ {/* Localisation */}
+ {/* */}
+ {/* Si possible, l’extension filtre les recommandations pertinentes pour votre localité : */}
+ {/* 33 600 PESSAC (changer) .*/}
+ {/*
*/}
+
+
+
+ );
+}
diff --git a/src/app/components/PreferenceDeactivatedPanel.js b/src/app/components/PreferenceDeactivatedPanel.js
index 9c09f7b9e..d55a97dc2 100644
--- a/src/app/components/PreferenceDeactivatedPanel.js
+++ b/src/app/components/PreferenceDeactivatedPanel.js
@@ -15,7 +15,7 @@ class PreferenceDeactivatedPanel extends Component {
render() {
const { props, state } = this;
const {
- deactivatedWebsites, onReactivateWebsite
+ deactivatedWebsites, onReactivateWebsite, imagesUrl
} = props;
const { reactivatedWebsites } = state;
@@ -27,32 +27,59 @@ class PreferenceDeactivatedPanel extends Component {
const reactivatedWebsitesArray = [...reactivatedWebsites]
.map(w => ({ website: w, active: true }));
- console.log('d, r', websitesDisplayedAsDeactivatedArray, reactivatedWebsitesArray);
+ // console.log('d, r', websitesDisplayedAsDeactivatedArray, reactivatedWebsitesArray);
const displayedWebsites = websitesDisplayedAsDeactivatedArray.concat(reactivatedWebsitesArray);
displayedWebsites.sort(({ website: w1 }, { website: w2 }) => w1.localeCompare(w2));
const lis = displayedWebsites
- .map(({ website, active }) =>
- {website}
- {
- onReactivateWebsite(website);
- this.setState(Object.assign({}, state, {
- reactivatedWebsites: reactivatedWebsites.add(website)
- }));
- }
- }>{
- active ? '✓' : 'Réactiver'
- }
- );
-
- return ();
+ .map(({ website, active }) =>
+
+
+ {website.replace(/^www\./, '')}
+
+ {
+ onReactivateWebsite(website);
+ this.setState(Object.assign({}, state, {
+ reactivatedWebsites: reactivatedWebsites.add(website)
+ }));
+ }
+ }>{active ? : 'Réactiver'}
+ );
+
+ return lis.length > 0 ?
+ (
+
+
+
+ Aide
+
+ Voici la liste des sites Web pour lesquels l’assistant ne se déclenche pas,
+ c’est-à-dire ne cherche pas de guide, conseil ou alternative en rapport avec la page consultée.
+
+
+ Pour réactiver un site précédemment désactivé :
+ cliquez sur le bouton réactiver qui s’affiche à côté de chaque site de la liste.
+
+ Désactiver un site
+
+ Quand l’assistant s’affiche sur un site sur lequel vous ne souhaitez pas être
+ accompagné-e, cliquez sur le bouton
+ (en haut à droite
+ de l’assistant).
+
+
+
) :
+ (Aucun site n’est désactivé :
+ L’assistant vous accompagne partout pour vous trouver
+ des recommandations susceptibles de vous intéresser.
+
);
}
}
diff --git a/src/app/components/PreferenceScreen.js b/src/app/components/PreferenceScreen.js
index 6d51dfeaf..b42731c33 100644
--- a/src/app/components/PreferenceScreen.js
+++ b/src/app/components/PreferenceScreen.js
@@ -8,44 +8,77 @@ import {
import PreferenceAboutPanel from './PreferenceAboutPanel';
import PreferenceDeactivatedPanel from './PreferenceDeactivatedPanel';
-export default function(props) {
+function mainClassName(screenPanel) {
+ switch (screenPanel) {
+ case PREFERENCE_SCREEN_PANEL_ABOUT:
+ return 'preference-about';
+ case PREFERENCE_SCREEN_PANEL_DEACTIVATED_WEBSITES:
+ return 'preference-deactivated-websites';
+ default:
+ return '';
+ }
+}
+
+export default function (props) {
const {
preferenceScreenPanel, deactivatedWebsites,
- onReactivateWebsite, openPrefScreen
+ onReactivateWebsite, openPrefScreen, imagesUrl,
+ onInstalledDetails
} = props;
let mainContent;
switch (preferenceScreenPanel){
case PREFERENCE_SCREEN_PANEL_ABOUT:
- mainContent = ;
+ mainContent = ( );
break;
case PREFERENCE_SCREEN_PANEL_DEACTIVATED_WEBSITES:
- mainContent =
+ imagesUrl={imagesUrl}
+ />);
break;
default:
- console.error('Unknown content value', content);
+ console.error('Unknown content value', preferenceScreenPanel);
}
const changePanel = e => {
- const newContent = e.target.getAttribute('data-panel');
+ const newContent = e.currentTarget.getAttribute('data-panel');
openPrefScreen(newContent);
};
- return (
-
- A propos
-
- Sites désactivés
+ return (
+
+
+
+
+
+ À propos
-
-
- {mainContent}
-
+
+
+
+
+ Sites désactivés
+
+
+
+
+
+
+ {mainContent}
+
);
}
-
diff --git a/src/app/constants/ActionTypes.js b/src/app/constants/ActionTypes.js
index b6cec4849..de353a027 100644
--- a/src/app/constants/ActionTypes.js
+++ b/src/app/constants/ActionTypes.js
@@ -11,9 +11,13 @@ export const HEAP_EVENT_TRACKED = 'heap/EVENT_TRACKED';
export const UPDATE_DRAFT_RECOMMANDATIONS = 'UPDATE_DRAFT_RECOMMANDATIONS';
+export const INSTALLED = 'INSTALLED';
+
// content actions
export const ALTERNATIVE_FOUND = 'ALTERNATIVE_FOUND';
+export const INSTALLED_DETAILS = 'INSTALLED_DETAILS';
+
export const REDUCE_ALTERNATIVE_IFRAME = 'REDUCE_ALTERNATIVE_IFRAME';
export const EXTEND_ALTERNATIVE_IFRAME = 'EXTEND_ALTERNATIVE_IFRAME';
@@ -24,3 +28,4 @@ export const DEACTIVATED_WEBSITES = 'DEACTIVATED_WEBSITES';
export const DEACTIVATE = 'DEACTIVATE';
export const REACTIVATE_WEBSITE = 'REACTIVATE_WEBSITE';
+
diff --git a/src/app/constants/assetsUrls.js b/src/app/constants/assetsUrls.js
index 948366b54..e554c90ca 100644
--- a/src/app/constants/assetsUrls.js
+++ b/src/app/constants/assetsUrls.js
@@ -1,4 +1,3 @@
export const IMAGES_URL = chrome.extension.getURL('img/');
export const CONTRIBUTOR_IMAGES_URL = 'https://lmem-craft-backend.cleverapps.io/uploads/avatars/';
-export const FONTS_URL = chrome.extension.getURL('fonts/');
-export const STYLES_URL = chrome.extension.getURL('styles/');
+
diff --git a/src/app/constants/origins.js b/src/app/constants/origins.js
index 74667d6aa..1414b69e0 100644
--- a/src/app/constants/origins.js
+++ b/src/app/constants/origins.js
@@ -1,7 +1,10 @@
-const _LMEM_BACKEND_ORIGIN = process.env.LMEM_BACKEND_ORIGIN;
-
-if(typeof _LMEM_BACKEND_ORIGIN !== 'string'){
- throw new TypeError('Missing LMEM backend origin ' + _LMEM_BACKEND_ORIGIN);
+function originFromEnv(key) {
+ const origin = process.env[key];
+ if (typeof origin !== 'string') {
+ throw new TypeError(`Missing LMEM env '${key}': ${origin}`);
+ }
+ return origin;
}
-export const LMEM_BACKEND_ORIGIN = _LMEM_BACKEND_ORIGIN;
\ No newline at end of file
+export const LMEM_BACKEND_ORIGIN = originFromEnv('LMEM_BACKEND_ORIGIN');
+export const LMEM_SCRIPTS_ORIGIN = originFromEnv('LMEM_SCRIPTS_ORIGIN');
\ No newline at end of file
diff --git a/src/app/constants/ui.js b/src/app/constants/ui.js
index 63d428562..a5d1b4464 100644
--- a/src/app/constants/ui.js
+++ b/src/app/constants/ui.js
@@ -3,24 +3,28 @@ import React, { Component } from 'react';
export const PREFERENCE_SCREEN_PANEL_ABOUT = 'PREFERENCE_SCREEN_PANEL_ABOUT';
export const PREFERENCE_SCREEN_PANEL_DEACTIVATED_WEBSITES = 'PREFERENCE_SCREEN_PANEL_DEACTIVATED_WEBSITES';
+export const EXTENSION_VERSION = chrome.runtime.getManifest().version;
+
export const HEADER_CONTENT = {
[PREFERENCE_SCREEN_PANEL_ABOUT]: imagesUrl => (
-
+
- Préférences de l'extension - A propos
+ Préférences
+ À propos
),
[PREFERENCE_SCREEN_PANEL_DEACTIVATED_WEBSITES]: imagesUrl => (
-
+
- Préférences de l'extension - Sites désactivés
+ Préférences
+ Sites désactivés
),
diff --git a/src/app/containers/App.js b/src/app/containers/App.js
index 04cb6c9ca..067567241 100644
--- a/src/app/containers/App.js
+++ b/src/app/containers/App.js
@@ -4,7 +4,7 @@ import Alternative from '../components/Alternatives';
import uiActions from '../content/actions/ui.js';
import { IMAGES_URL, CONTRIBUTOR_IMAGES_URL } from '../constants/assetsUrls';
-import portCommunication from 'app/content/portCommunication';
+import portCommunication from '../content/portCommunication';
const {
reduce, extend, deactivate, closePrefScreen, openPrefScreen, reactivateWebsite
@@ -17,7 +17,8 @@ function mapStateToProps(state) {
contributorUrl: CONTRIBUTOR_IMAGES_URL,
reduced: state.get('reduced'),
preferenceScreenPanel: state.get('preferenceScreenPanel'),
- deactivatedWebsites: state.get('deactivatedWebsites')
+ deactivatedWebsites: state.get('deactivatedWebsites'),
+ onInstalledDetails: state.get('onInstalledDetails')
};
}
function mapDispatchToProps(dispatch) {
diff --git a/src/app/content/actions/preferences.js b/src/app/content/actions/preferences.js
index 0d6d3dbe7..6076962e4 100644
--- a/src/app/content/actions/preferences.js
+++ b/src/app/content/actions/preferences.js
@@ -1,8 +1,15 @@
-import { DEACTIVATED_WEBSITES } from '../../constants/ActionTypes';
+import { DEACTIVATED_WEBSITES, INSTALLED_DETAILS } from '../../constants/ActionTypes';
-export default function (deactivatedWebsites) {
+export function updateDeactivatedWebsites(deactivatedWebsites) {
return {
type: DEACTIVATED_WEBSITES,
deactivatedWebsites
};
}
+
+export function updateInstalledDetails(onInstalledDetails) {
+ return {
+ type: INSTALLED_DETAILS,
+ onInstalledDetails
+ };
+}
diff --git a/src/app/content/reducers/index.js b/src/app/content/reducers/index.js
index a8433bae1..056183749 100644
--- a/src/app/content/reducers/index.js
+++ b/src/app/content/reducers/index.js
@@ -6,7 +6,8 @@ import {
OPEN_PREFERENCE_PANEL,
CLOSE_PREFERENCE_PANEL,
DEACTIVATED_WEBSITES,
- REACTIVATE_WEBSITE
+ REACTIVATE_WEBSITE,
+ INSTALLED_DETAILS,
} from '../../constants/ActionTypes';
export default function (state = {}, action) {
@@ -15,7 +16,7 @@ export default function (state = {}, action) {
switch (type) {
case ALTERNATIVE_FOUND: {
const { alternative } = action;
- return state.set('alternative', alternative);
+ return state.set('alternative', alternative).set('reduced', false);
}
case REDUCE_ALTERNATIVE_IFRAME:
@@ -39,6 +40,10 @@ export default function (state = {}, action) {
const { deactivatedWebsites } = action;
return state.set('deactivatedWebsites', deactivatedWebsites);
+ case INSTALLED_DETAILS:
+ const { onInstalledDetails } = action;
+ return state.set('onInstalledDetails', onInstalledDetails);
+
case REACTIVATE_WEBSITE:
const { website } = action;
return state.set('deactivatedWebsites', state.get('deactivatedWebsites').delete(website));
diff --git a/src/app/events/trackEvents.js b/src/app/events/trackEvents.js
index 924ba1a64..977155d74 100644
--- a/src/app/events/trackEvents.js
+++ b/src/app/events/trackEvents.js
@@ -1,4 +1,4 @@
-import { trackHeapEvent } from '../actions/heap';
+// import { trackHeapEvent } from '../actions/heap';
// Arbitrary set max payload size
// @TODO find a nicer way to handle the error
@@ -24,7 +24,12 @@ const MAX_PAYLOAD_SIZE = 10000;
* Could be implemented in a much nicer way though.
*/
const events = store => next => action => {
- // Check payload size to avoid HTTP 414 Request-URI Too Large url
+ if (!window.heap) {
+ console.log(`Heap analytics disabled: ignore tracking of "${action.type}"`);
+ return next(action);
+ }
+
+ // Check payload size to avoid HTTP 414 Request-URI Too Large url
const payloadSize = JSON.stringify(action).length;
if (payloadSize > MAX_PAYLOAD_SIZE) {
console.log('Payload size too large', payloadSize);
@@ -32,8 +37,7 @@ const events = store => next => action => {
} else {
window.heap.track(action.type, action.payload);
}
- const result = next(action);
- return result;
+ return next(action);
};
export default events;
\ No newline at end of file
diff --git a/src/app/reducers/index.js b/src/app/reducers/index.js
index a3dd0e857..97520968d 100644
--- a/src/app/reducers/index.js
+++ b/src/app/reducers/index.js
@@ -3,7 +3,8 @@ import {
RECEIVED_MATCHING_CONTEXTS,
DEACTIVATE,
REACTIVATE_WEBSITE,
- UPDATE_DRAFT_RECOMMANDATIONS
+ UPDATE_DRAFT_RECOMMANDATIONS,
+ INSTALLED
} from '../constants/ActionTypes';
import { DEACTIVATE_EVERYWHERE, DEACTIVATE_WEBSITE_ALWAYS } from '../constants/preferences';
@@ -77,6 +78,11 @@ export default function (state = {}, action) {
return Object.assign({}, state, { draftRecommandations });
}
+ case INSTALLED: {
+ const { onInstalledDetails } = action;
+ return Object.assign({}, state, { onInstalledDetails });
+ }
+
default:
return state;
}
diff --git a/src/app/styles/_variables.scss b/src/app/styles/_variables.scss
index a7d25756a..001f6ff88 100644
--- a/src/app/styles/_variables.scss
+++ b/src/app/styles/_variables.scss
@@ -54,7 +54,8 @@ $pythagoras-const: 1.41421;
// Layout widths
$global-width: rem-size(940);
-$sideframe-width: $global-width * 1/3;
+//$sideframe-width: $global-width * 1/3;
+$sideframe-width: $block-size * 4;
$controls-inner-width: $block-size * 1.5;
// Media Query Thresholds
diff --git a/src/app/styles/elements.scss b/src/app/styles/elements.scss
index b56503c14..717884095 100644
--- a/src/app/styles/elements.scss
+++ b/src/app/styles/elements.scss
@@ -14,6 +14,7 @@
html, body{
margin: 0;
padding: 0;
+ height: 100%;
}
// Content elements
diff --git a/src/app/styles/main.scss b/src/app/styles/main.scss
index cf61721ad..383bdac51 100644
--- a/src/app/styles/main.scss
+++ b/src/app/styles/main.scss
@@ -102,12 +102,12 @@ main {
}
.menu-right {
- left: calc(100% + #{ rem-size(3) });
+ left: calc(100% + #{rem-size(3)});
top: 0;
}
.menu-left {
- right: calc(100% + #{ rem-size(3) });
+ right: calc(100% + #{rem-size(3)});
top: 0;
}
@@ -362,7 +362,7 @@ ul.summary-entry-content {
.pane-opened::after {
@include iso-rect-triangle('to top', $simple-line-height, $heavy-border-color);
bottom: -$midway-font-size;
- left: calc(50% - #{ $simple-line-height * $pythagoras-const / 2 });
+ left: calc(50% - #{$simple-line-height * $pythagoras-const / 2});
}
}
@@ -736,7 +736,7 @@ ul.summary-entry-content {
}
.fieldset-inner-wrapper {
- margin: #{ $margin-size * 1/3 } 0 0;
+ margin: #{$margin-size * 1/3} 0 0;
}
}
@@ -900,7 +900,7 @@ input[type=text] {
max-height: 2 * $double-line-height;
}
- @media(max-width: #{ $media-query-threshold-wide - $block-size }) {
+ @media(max-width: #{$media-query-threshold-wide - $block-size}) {
label small {
@include visually-hidden;
}
@@ -1085,6 +1085,19 @@ input[type=text] {
strong {
color: $highlight-color;
}
+
+ .lmem-topbar-preferences {
+ color: inherit;
+
+ > img {
+ margin-right: $half-adjusted-margin;
+ }
+
+ > span:not(:last-child)::after {
+ content: '-';
+ margin: 0 $half-adjusted-margin;
+ }
+ }
}
@@ -1127,9 +1140,12 @@ input[type=text] {
}
-.lmem-controls-list > li {
- display: inline-block;
- margin-right: $margin-size;
+.lmem-controls-list {
+ display: flex;
+
+ > li {
+ margin-left: $margin-size;
+ }
}
.lmem-controls-picto {
diff --git a/src/app/styles/preference-screen.scss b/src/app/styles/preference-screen.scss
index 2eafd82b8..7d238fb0f 100644
--- a/src/app/styles/preference-screen.scss
+++ b/src/app/styles/preference-screen.scss
@@ -2,34 +2,182 @@
display: flex;
flex-direction: row;
+ margin-top: $half-adjusted-margin;
+
box-sizing: border-box;
* {
box-sizing: border-box;
}
nav {
- width: 10%;
+ flex: 0 0 auto;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- justify-content: flex-start;
+ ul {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ justify-content: flex-start;
+ }
+
+ li {
+ margin-bottom: $half-adjusted-margin;
+ }
button {
- background: transparent;
- border: 0;
- padding: 1em;
+ color: inherit;
+ font-size: $simple-line-height;
+ line-height: $double-line-height;
- &:hover{
- background: hsla(0, 0%, 50%, 0.3);
- }
+ align-items: center;
+
+ padding: 0 $margin-size 0 $half-adjusted-margin;
+ width: 100%;
+ }
+
+ img {
+ margin-right: 1ex;
+ margin-left: initial;
+ opacity: .9;
}
}
main {
- flex: 1;
+ flex: 1 1 auto;
+ }
+}
+
+.preference-about {
+
+ aside {
+ margin-top: $margin-size;
+ font-size: $tiny-font-size;
+
+ ul {
+ display: flex;
+ }
+ li:not(:last-child)::after {
+ content: '-';
+ margin: 0 1ex;
+ }
+
+ h1,
+ h2 {
+ font-size: $simple-line-height;
+ margin: $half-adjusted-margin 0 #{$margin-size - $half-adjusted-margin};
+ }
+ }
+
+ time {
+ white-space: nowrap;
+ }
+}
+
+.preference-deactivated-websites {
+ p {
+ margin-top: $half-adjusted-margin;
+ }
+
+ > div {
+ display: flex;
+ }
+ aside {
+ flex: 1 1 50%;
+ color: $font-quiet-color;
+
+ h1,
+ h2 {
+ font-weight: 600;
+ margin: $half-adjusted-margin 0 #{$margin-size - $half-adjusted-margin};
+ }
+
+ img {
+ vertical-align: bottom;
+ }
+ }
+ ul {
+ flex: 1 0 auto;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ overflow-y: auto;
+ max-height: $block-size * 3;
+ }
+ li {
+ flex: 0 0 auto;
+ display: flex;
+ line-height: $double-line-height;
+ margin-bottom: $half-adjusted-margin;
+
+ &.reactivated {
+ > button,
+ > .deactivated-website-title {
+ opacity: .5;
+ }
- padding: 1em;
+ > button {
+ background: none;
+
+ width: $block-size / 2;
+ margin-left: $block-size / 2;
+ margin-right: $block-size / 2;
+ padding: 0;
+
+ border-radius: 100%;
+
+ > img {
+ width: $double-line-height;
+ opacity: .8;
+ }
+ }
+
+ &:hover {
+ > button,
+ > span { opacity: initial }
+ }
+ }
+
+ > button {
+ align-self: center;
+ height: $double-line-height;
+ width: $block-size * 3 / 2;
+
+ font-size: $simple-line-height;
+ line-height: $simple-line-height;
+ font-weight: inherit;
+
+ opacity: 0;
+ transition: opacity, border-radius .2s ease-out 50ms;
+ }
+
+ &:hover > button,
+ > button:focus,
+ > button:active {
+ opacity: initial;
+ }
+
+ transition: background-color .2s ease-out;
+ &:hover {
+ background-color: $background-light-color;
+
+ img {
+ filter: initial;
+ -webkit-filter: initial;
+ opacity: initial;
+ }
+ }
}
+ .deactivated-website-title {
+ font-size: $midway-font-size;
+ margin: 0 $margin-size 0 $half-adjusted-margin;
+
+ img {
+ margin-right: $half-adjusted-margin;
+ vertical-align: middle;
+
+ filter: grayscale(100%);
+ -webkit-filter: grayscale(100%);
+ opacity: .7;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/app/styles/reco-header.scss b/src/app/styles/reco-header.scss
index 35fe154d2..52ef457a6 100644
--- a/src/app/styles/reco-header.scss
+++ b/src/app/styles/reco-header.scss
@@ -4,6 +4,10 @@
> header{
background: $background-em-color;
+ color: $font-em-color;
+
+ flex: 0 0 100%;
+
display: flex;
flex-direction: row;
@@ -11,10 +15,13 @@
justify-content: flex-start;
padding: $margin-size;
+ max-height: $block-size;
+ line-height: $double-line-height;
> .logo{
position: relative; // so that the tooltip with position: absolute works
+ max-height: $block-size - $margin-size;
background: inherit;
box-shadow: none;
border: 0;
@@ -38,6 +45,7 @@
button.reduce{
align-items: center;
line-height: $double-line-height;
+ display: flex;
}
.separation-bar{
@@ -46,14 +54,14 @@
// Win some space hiding secondary UI elements.
- @media(max-width: #{ $media-query-threshold-small-edge - rem-size(1) }) {
+ @media(max-width: #{$media-query-threshold-small-edge - rem-size(1)}) {
z-index: 20;
.button-label {
@include visually-hidden;
}
.button-compact.with-image img {
- margin-left: .5ex;
+ margin-left: initial;
}
}
@@ -72,8 +80,5 @@
min-width: $controls-inner-width;
}
}
-
}
-
-
-}
\ No newline at end of file
+}
diff --git a/src/app/styles/reco-main.scss b/src/app/styles/reco-main.scss
index 322cb0812..688285ef3 100644
--- a/src/app/styles/reco-main.scss
+++ b/src/app/styles/reco-main.scss
@@ -5,13 +5,11 @@
& > main{
display: flex;
justify-content: flex-end;
- margin-top: 0.539em;
+ margin-top: $half-adjusted-margin;
a.mainframe{
flex: 20 1 0;
-
- margin-left: $adjusted-margin;
- padding-left: $margin-size;
+ align-self: flex-start;
border: solid $border-width $background-light-color;
display: block;
@@ -27,6 +25,5 @@
}
}
}
-
}
\ No newline at end of file
diff --git a/src/app/styles/reco.scss b/src/app/styles/reco.scss
index 3c2f9958c..2fe4fa317 100644
--- a/src/app/styles/reco.scss
+++ b/src/app/styles/reco.scss
@@ -10,7 +10,7 @@
.reco-summary-header {
display: flex;
justify-content: space-between;
- align-items: baseline;
+ align-items: flex-start;
}
.reco-summary-title {
@@ -79,7 +79,7 @@
}
img {
- height: rem-size(20);
+ height: rem-size(16);
margin-right: 1ex;
}
}
diff --git a/src/app/styles/top-level.scss b/src/app/styles/top-level.scss
index ada5b8fdd..848ba7d73 100644
--- a/src/app/styles/top-level.scss
+++ b/src/app/styles/top-level.scss
@@ -15,12 +15,15 @@ body {
}
.lmem-top-level {
+ height: inherit;
display: flex;
flex-direction: column;
- > *{
- padding: 0.539em $margin-size;
- }
+ > * {
+ padding: $half-adjusted-margin $margin-size;
+ }
}
+
+
diff --git a/src/app/tabs/index.js b/src/app/tabs/index.js
index 482f2db54..a23d12951 100644
--- a/src/app/tabs/index.js
+++ b/src/app/tabs/index.js
@@ -1,6 +1,6 @@
export default function (
tabs,
- { findMatchingOffers, dispatch, contentCode, contentStyle, getDeactivatedWebsites }
+ { findMatchingOffers, dispatch, contentCode, contentStyle, getDeactivatedWebsites, getOnInstalledDetails }
) {
const matchingTabIdToPortP = new Map();
@@ -27,7 +27,8 @@ export default function (
tabPort.postMessage({
type: 'init',
style: contentStyle,
- deactivatedWebsites: [...getDeactivatedWebsites()]
+ deactivatedWebsites: [...getDeactivatedWebsites()],
+ onInstalledDetails: getOnInstalledDetails()
});
resolve(tabPort);
diff --git a/src/assets/img/ball.svg b/src/assets/img/ball.svg
index fd73a0cc3..fc5c82bb1 100644
--- a/src/assets/img/ball.svg
+++ b/src/assets/img/ball.svg
@@ -1,1046 +1,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ?
-
-
- ?
-
-
-
-
-
- i
-
-
-
-
- i
-
-
- i
-
-
- i
-
-
- i
-
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/src/assets/img/close.svg b/src/assets/img/close.svg
index 9bcac290f..9130abd24 100644
--- a/src/assets/img/close.svg
+++ b/src/assets/img/close.svg
@@ -1,145 +1,6 @@
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ?
-
-
- ?
-
-
-
-
-
- i
-
-
- i
-
-
- i
-
-
- i
-
-
- i
-
-
- i
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/src/assets/img/valid.svg b/src/assets/img/valid.svg
new file mode 100644
index 000000000..ad954e964
--- /dev/null
+++ b/src/assets/img/valid.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/src/browser/extension/background/index.js b/src/browser/extension/background/index.js
index b04dce897..e9862c92c 100644
--- a/src/browser/extension/background/index.js
+++ b/src/browser/extension/background/index.js
@@ -1,7 +1,10 @@
/* eslint global-require: "off" */
+// Early imports with high priority stuff involved, such as event listeners creation
+import onInstalled from '../../../app/actions/install';
+import loadHeap from '../../../lib/heap';
+
import configureStore from './../../../app/store/configureStore';
-import initBadge from './badge';
import findMatchingOffersAccordingToPreferences
from '../../../app/lmem/findMatchingOffersAccordingToPreferences';
@@ -11,7 +14,8 @@ import prepareDraftPreview from '../../../app/lmem/draft-preview/main.js';
import { dispatchInitialStateFromBackend } from '../../../app/actions/kraftBackend';
import updateDraftRecommandations from '../../../app/actions/updateDraftRecommandations';
-import heap from './../../../lib/heap';
+import {LMEM_BACKEND_ORIGIN, LMEM_SCRIPTS_ORIGIN} from '../../../app/constants/origins';
+
/**
* FIXME import styles from components instead and let Webpack taking care of them...
*
@@ -28,12 +32,21 @@ import mainStyles from './../../../app/styles/main.scss';
if(process.env.NODE_ENV !== 'production'){
console.info('NODE_ENV', process.env.NODE_ENV);
}
-console.info('LMEM_BACKEND_ORIGIN', process.env.LMEM_BACKEND_ORIGIN);
+console.info(`LMEM_BACKEND_ORIGIN "${LMEM_BACKEND_ORIGIN}"`);
+console.info(`LMEM_SCRIPTS_ORIGIN "${LMEM_SCRIPTS_ORIGIN}"`);
+const heapAppId = process.env.HEAP_APPID;
+if (typeof heapAppId === 'string') {
+ console.info(`Heap loading with appId "${heapAppId}"`);
+ loadHeap(heapAppId);
+}
+else {
+ console.warn('Heap analytics disabled: assuming "process.env.HEAP_APPID" is deliberately not defined.');
+}
// Load content code when the extension is loaded
-const contentCodeP = fetch('./js/content.bundle.js').then(resp => resp.text());
-const draftRecoContentCodeP = fetch('./js/grabDraftRecommandations.js').then(resp => resp.text());
+const contentCodeP = fetch(LMEM_SCRIPTS_ORIGIN + '/js/content.bundle.js').then(resp => resp.text());
+const draftRecoContentCodeP = fetch(LMEM_SCRIPTS_ORIGIN + '/js/grabDraftRecommandations.js').then(resp => resp.text());
configureStore(store => {
window.store = store;
@@ -71,11 +84,14 @@ configureStore(store => {
const deactivated = prefs.deactivated || {};
return deactivated.deactivatedWebsites || new Set();
},
+ getOnInstalledDetails: () => {
+ const state = store.getState();
+ return state.onInstalledDetails || {};
+ },
dispatch: store.dispatch,
contentCode,
contentStyle: mainStyles
- }
- );
+ });
});
draftRecoContentCodeP
@@ -86,6 +102,10 @@ configureStore(store => {
)
);
+ if (!store.getState().onInstalledDetails) {
+ store.dispatch(onInstalled());
+ }
+
store.dispatch(dispatchInitialStateFromBackend()); // store initialization from the kraft server
if (process.env.NODE_ENV !== 'production') {
diff --git a/src/browser/extension/content/index.js b/src/browser/extension/content/index.js
index e003993b8..40e65902a 100644
--- a/src/browser/extension/content/index.js
+++ b/src/browser/extension/content/index.js
@@ -1,40 +1,128 @@
-import { Record, Set as ImmutableSet } from 'immutable';
+import { Record, Set as ImmutableSet, Map as ImmutableMap, fromJS as immutableFromJS } from 'immutable';
import React from 'react';
import { render } from 'react-dom';
import Root from '../../../app/containers/Root';
-import configureStore from '../../../app/store/configureStore';
-
-import Alternative from '../../../app/components/Alternatives';
-import { STYLES_URL, IMAGES_URL } from '../../../app/constants/assetsUrls';
import { createStore } from 'redux';
import rootReducer from '../../../app/content/reducers';
import alternativeFound from '../../../app/content/actions/alternatives';
-import updateDeactivatedWebsites from '../../../app/content/actions/preferences';
+import { updateDeactivatedWebsites, updateInstalledDetails } from '../../../app/content/actions/preferences';
import portCommunication from '../../../app/content/portCommunication';
-import {
- REDUCE_ALTERNATIVE_IFRAME,
- EXTEND_ALTERNATIVE_IFRAME
-} from '../../../app/constants/ActionTypes.js';
-
const IFRAME_EXTENDED_HEIGHT = '255px';
const IFRAME_REDUCED_HEIGHT = '60px';
+const EXTENSION_STATE_SHOW_LOADING = 'EXTENSION_STATE_SHOW_LOADING';
+const EXTENSION_STATE_SHOW_ALTERNATIVE = 'EXTENSION_STATE_SHOW_ALTERNATIVE';
+
+const AFTER_DOMCOMPLETE_DELAY = 5000;
+const AFTER_LOADEND_DELAY = 1000;
+const LOADING_SCREEN_DELAY = 4000;
+
+/*
+ LIB
+*/
+function createExtensionIframe(reduced, style, onLoad){
+ const iframe = document.createElement('iframe');
+ iframe.id = 'lmemFrame';
+ iframe.width = '100%';
+ iframe.height = reduced ? IFRAME_REDUCED_HEIGHT : IFRAME_EXTENDED_HEIGHT;
+ iframe.style.position = 'fixed';
+ iframe.style.bottom = 0;
+ iframe.style.left = 0;
+ iframe.style.right = 0;
+ iframe.style.zIndex = 2147483647; // Max z-index value (signed 32bits integer)
+ iframe.style.background = '#FDF6E3'; // UI bg color (avoid having a transparent iframe after injection)
+ iframe.style.border = 'none';
+ iframe.style.transition = 'height .1s';
+ iframe.style.boxShadow = '0 0 15px #888';
+ iframe.srcdoc = `
+
+
+
+
+
+
+
+ `;
+
+ iframe.onload = onLoad;
+
+ return iframe;
+}
+
+
+/*
+ SETUP
+*/
+
+const DOMCompleteP = Promise.resolve(); // because the content script is loaded at "document_end"
+
+const DOMCompletePlusDelayP = DOMCompleteP.then(() => {
+ return new Promise(resolve => {
+ const {navigationStart, domContentLoadedEventStart} = performance.timing;
+
+ const diff = domContentLoadedEventStart - navigationStart;
+
+ if(diff >= AFTER_DOMCOMPLETE_DELAY)
+ resolve();
+ else
+ setTimeout(resolve, AFTER_DOMCOMPLETE_DELAY - diff);
+ });
+});
+
+
+const LoadEndP = new Promise(resolve => {
+ document.addEventListener('load', resolve);
+});
+
+const LoadEndPlusDelayP = LoadEndP.then(() => {
+ return new Promise(resolve => {
+ const {navigationStart, loadEventStart} = performance.timing;
+
+ const diff = loadEventStart - navigationStart;
+
+ if(diff >= AFTER_LOADEND_DELAY)
+ resolve();
+ else
+ setTimeout(resolve, AFTER_LOADEND_DELAY - diff);
+ });
+});
+
+// Wait for some time before showing the extension to the user in loading mode
+const CanShowIframeLoadingP = Promise.race([DOMCompletePlusDelayP, LoadEndPlusDelayP]);
+
+// User research showed that the LMEM loading screen is important so people don't
+// think the LMEM iframe is an ad.
+// Wait for some time loading before showing an alternative.
+const CanShowAlternativeIfAvailableP = process.env.NODE_ENV === 'development' ?
+ Promise.resolve() : // otherwise the delay is annoying when developing
+ CanShowIframeLoadingP.then(() => {
+ return new Promise(resolve => {
+ setTimeout(resolve, LOADING_SCREEN_DELAY);
+ });
+ });
+
+
+
// create redux store
const store = createStore(
rootReducer,
new Record({
open: true,
- reduced: false,
+ reduced: true,
preferenceScreenPanel: undefined, // preference screen close
alternative: undefined,
- deactivatedWebsites: new ImmutableSet()
+ deactivatedWebsites: new ImmutableSet(),
+ onInstalledDetails: new ImmutableMap(),
})()
);
+
+
+
// reach back to background script
chrome.runtime.onConnect.addListener(function listener(portToBackground) {
portCommunication.port = portToBackground;
@@ -46,60 +134,54 @@ chrome.runtime.onConnect.addListener(function listener(portToBackground) {
switch (type) {
case 'init':
- const { style, deactivatedWebsites } = msg;
- const reduced = store.getState().get('reduced');
- const lmemContentContainerP = new Promise(resolve => {
- const iframe = document.createElement('iframe');
- iframe.id = 'lmemFrame';
- iframe.width = '100%';
- iframe.height = reduced ? IFRAME_REDUCED_HEIGHT : IFRAME_EXTENDED_HEIGHT;
- iframe.style.position = 'fixed';
- iframe.style.bottom = '0px';
- iframe.style.left = '0px';
- iframe.style.right = '0px';
- iframe.style.zIndex = '999999999';
- iframe.srcdoc = `
-
-
-
-
-
-
-
- `;
-
- iframe.onload = function () {
- resolve(iframe.contentDocument.body);
- };
- document.body.appendChild(iframe);
-
- store.subscribe(() => {
- const state = store.getState();
-
- if (!state.get('open')) {
- iframe.remove();
- }
- else {
- iframe.height = state.get('reduced') ? IFRAME_REDUCED_HEIGHT : IFRAME_EXTENDED_HEIGHT;
- }
-
- });
- });
+ const { style, deactivatedWebsites, onInstalledDetails } = msg;
store.dispatch(updateDeactivatedWebsites(new ImmutableSet(deactivatedWebsites)));
+ store.dispatch(updateInstalledDetails(immutableFromJS(onInstalledDetails)));
+
+ // Let the page load a bit before showing the iframe in loading mode
+ CanShowIframeLoadingP
+ .then(() => {
+
+ return new Promise(resolve => {
+ const iframe = createExtensionIframe(
+ store.getState().get('reduced'),
+ style,
+ () => { resolve(iframe.contentDocument.body); }
+ );
+
+ document.body.appendChild(iframe);
+
+ store.subscribe(() => {
+ const state = store.getState();
+
+ if (!state.get('open')) {
+ iframe.remove();
+ }
+ else {
+ iframe.height = state.get('reduced') ? IFRAME_REDUCED_HEIGHT : IFRAME_EXTENDED_HEIGHT;
+ }
+
+ });
+ })
+ .then(lmemContainer => {
+ render( , lmemContainer);
+ });
- lmemContentContainerP.then(lmemContentContainer => {
- render(
- ,
- lmemContentContainer
- );
});
+
break;
case 'alternative':
const { alternative } = msg;
-
// console.log('alternative in content', alternative);
- store.dispatch(alternativeFound(alternative));
+
+ // Even if the alternative arrived early, let the page load a bit before
+ // showing the iframe in loading mode
+ CanShowAlternativeIfAvailableP
+ .then(() => {
+ store.dispatch(alternativeFound(alternative));
+ });
+
break;
default:
console.error('Content script: unrecognized message type from background', type, msg);
diff --git a/src/browser/extension/heap/index.js b/src/browser/extension/heap/index.js
index bad70f298..c0f55c7a7 100644
--- a/src/browser/extension/heap/index.js
+++ b/src/browser/extension/heap/index.js
@@ -1,14 +1,15 @@
import React from 'react';
import { render } from 'react-dom';
-import Root from 'app/containers/Root';
-import configureStore from 'app/store/configureStore';
+import Root from '../../../app/containers/Root';
+import configureStore from '../../../app/store/configureStore';
+import { LMEM_SCRIPTS_ORIGIN } from '../../../app/constants/origins';
configureStore(store => {
window.addEventListener('load', () => {
console.log('Injecting heap analytics');
- let injectScript = document.createElement('script');
- injectScript.src('https://ui.lmem.net/js/heap.js');
+ const injectScript = document.createElement('script');
+ injectScript.src(LMEM_SCRIPTS_ORIGIN + '/js/heap.js');
document.getElementsByTagName('head')[0].appendChild(injectScript);
render(
diff --git a/src/browser/extension/manifest/dev.js b/src/browser/extension/manifest/dev.js
index 166748c5d..43a8cfde9 100644
--- a/src/browser/extension/manifest/dev.js
+++ b/src/browser/extension/manifest/dev.js
@@ -1,5 +1,5 @@
import base from './base.js';
-import csp from "content-security-policy-builder";
+import csp from 'content-security-policy-builder';
export default Object.assign(
{},
diff --git a/src/browser/extension/manifest/prod.js b/src/browser/extension/manifest/prod.js
index 9e39d278d..58137e063 100644
--- a/src/browser/extension/manifest/prod.js
+++ b/src/browser/extension/manifest/prod.js
@@ -1,5 +1,5 @@
import base from './base.js';
-import csp from "content-security-policy-builder";
+import csp from 'content-security-policy-builder';
export default Object.assign(
{},
@@ -9,7 +9,8 @@ export default Object.assign(
'content_security_policy': csp({
'directives': {
'default-src': [
- 'https://lmem-craft-backend.cleverapps.io'
+ 'https://lmem-craft-backend.cleverapps.io',
+ 'https://ui.lmem.net',
],
'script-src': [
'https://ui.lmem.net',
diff --git a/src/browser/extension/manifest/staging.js b/src/browser/extension/manifest/staging.js
index 5547cd254..cb602d0f6 100644
--- a/src/browser/extension/manifest/staging.js
+++ b/src/browser/extension/manifest/staging.js
@@ -1,5 +1,5 @@
import base from './base.js';
-import csp from "content-security-policy-builder";
+import csp from 'content-security-policy-builder';
export default Object.assign(
{},
@@ -9,7 +9,8 @@ export default Object.assign(
'content_security_policy': csp({
'directives': {
'default-src': [
- 'https://preprod-lmem-craft-backend.cleverapps.io'
+ 'https://preprod-lmem-craft-backend.cleverapps.io',
+ 'https://testing.ui.lmem.net'
],
'script-src': [
'https://testing.ui.lmem.net',
diff --git a/src/lib/heap.js b/src/lib/heap.js
index 6b2bb0cfe..963b78cfd 100644
--- a/src/lib/heap.js
+++ b/src/lib/heap.js
@@ -1,2 +1,4 @@
-window.heap=window.heap||[],heap.load=function(e,t){window.heap.appid=e,window.heap.config=t=t||{};var r=t.forceSSL||"https:"===document.location.protocol,a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=(r?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+e+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(e){return function(){heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","removeEventProperty","setEventProperties","track","unsetEventProperty"],c=0;c 0) {
- console.warn("[HMR] The following modules couldn't be hot updated: (They would need a full reload!)");
- unacceptedModules.forEach(function(moduleId) {
- console.warn("[HMR] - " + moduleId);
- });
-
- if(chrome && chrome.runtime && chrome.runtime.reload) {
- console.warn("[HMR] extension reload");
- chrome.runtime.reload();
- } else {
- console.warn("[HMR] Can't extension reload. not found chrome.runtime.reload.");
- }
- }
-
- if(!renewedModules || renewedModules.length === 0) {
- console.log("[HMR] Nothing hot updated.");
- } else {
- console.log("[HMR] Updated modules:");
- renewedModules.forEach(function(moduleId) {
- console.log("[HMR] - " + moduleId);
- });
- }
-};
diff --git a/webpack/staging.config.js b/webpack/staging.config.js
index 35c985e26..4f991a6f2 100644
--- a/webpack/staging.config.js
+++ b/webpack/staging.config.js
@@ -5,7 +5,7 @@ const srcPath = path.join(__dirname, '../src/browser/');
export default baseConfig({
input: {
background: [`${srcPath}extension/background/`],
- window: [`${srcPath}window/`],
+ // window: [`${srcPath}window/`],
//popup: [`${srcPath}extension/popup/`],
content: [`${srcPath}extension/content/`]
},
@@ -22,7 +22,9 @@ export default baseConfig({
globals: {
'process.env': {
NODE_ENV: '"staging"',
- LMEM_BACKEND_ORIGIN: '"https://preprod-lmem-craft-backend.cleverapps.io"'
+ LMEM_BACKEND_ORIGIN: '"https://preprod-lmem-craft-backend.cleverapps.io"',
+ LMEM_SCRIPTS_ORIGIN: "'https://testing.ui.lmem.net'",
+ HEAP_APPID: '"234457910"', // testing
}
}
});